From e990e75b8793d3b016dc711bb55bfe3e25e53486 Mon Sep 17 00:00:00 2001 From: Kanstantsin Shautsou Date: Thu, 5 Jan 2017 22:41:43 +0300 Subject: [PATCH 01/17] WIP Signed-off-by: Kanstantsin Shautsou --- pom.xml | 4 +- .../com/github/kostyasha/yad/DockerCloud.java | 6 +- .../github/kostyasha/yad/DockerConnector.java | 28 +++--- .../kostyasha/yad/DockerJobProperty.java | 50 ++++++++++ .../yad/DockerQueueDecisionHandler.java | 18 ++++ .../kostyasha/yad/DockerQueueListener.java | 67 +++++++++++++ .../yad/DockerQueueTaskDispatcher.java | 13 +++ .../com/github/kostyasha/yad/DockerSlave.java | 8 ++ .../action/DockerLabelAssignmentAction.java | 43 +++++++++ .../kostyasha/yad/commons/AbstractCloud.java | 93 ++++++++++++++++++- .../yad/connector/DockerCloudConnector.java | 16 ++++ .../yad/connector/DockerCloudConnectorId.java | 89 ++++++++++++++++++ .../yad/connector/YADockerConnector.java | 30 ++++++ .../yad/jobconfig/DockerCloudJobConfig.java | 51 ++++++++++ .../yad/jobconfig/SlaveJobConfig.java | 25 +++++ .../DockerJobProperty/config-details.groovy | 37 ++++++++ .../yad/DockerSlaveTemplate/config.groovy | 4 +- .../DockerCloudConnectorId/config.groovy | 10 ++ .../config.groovy | 7 ++ .../config.groovy | 7 ++ .../DockerCloudJobConfig/config.groovy | 26 ++++++ 21 files changed, 605 insertions(+), 27 deletions(-) create mode 100644 yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerJobProperty.java create mode 100644 yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerQueueDecisionHandler.java create mode 100644 yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerQueueListener.java create mode 100644 yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerQueueTaskDispatcher.java create mode 100644 yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/action/DockerLabelAssignmentAction.java create mode 100644 yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/connector/DockerCloudConnector.java create mode 100644 yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/connector/DockerCloudConnectorId.java create mode 100644 yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/connector/YADockerConnector.java create mode 100644 yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/jobconfig/DockerCloudJobConfig.java create mode 100644 yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/jobconfig/SlaveJobConfig.java create mode 100644 yet-another-docker-plugin/src/main/resources/com/github/kostyasha/yad/DockerJobProperty/config-details.groovy create mode 100644 yet-another-docker-plugin/src/main/resources/com/github/kostyasha/yad/connector/DockerCloudConnectorId/config.groovy create mode 100644 yet-another-docker-plugin/src/main/resources/com/github/kostyasha/yad/connector/DockerCloudConnectorTemplateId/config.groovy create mode 100644 yet-another-docker-plugin/src/main/resources/com/github/kostyasha/yad/connector/DockerCloudConnectorTemplateRaw/config.groovy create mode 100644 yet-another-docker-plugin/src/main/resources/com/github/kostyasha/yad/jobconfig/DockerCloudJobConfig/config.groovy diff --git a/pom.xml b/pom.xml index 0c2633de..2eb86873 100644 --- a/pom.xml +++ b/pom.xml @@ -43,13 +43,13 @@ - 1.625.3 + 1.642.4 UTF-8 2.5.3 3.0.3 3.3 2.10.3 - 1.119 + 1.102 1.8 8 diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerCloud.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerCloud.java index 2aef31ee..bbdc2f39 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerCloud.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerCloud.java @@ -96,7 +96,7 @@ public synchronized Collection provision(@CheckForNull Label label, LOG.info("Asked to provision load: '{}', for: '{}' label", excessWorkload, label); List r = new ArrayList<>(excessWorkload); - final List tryTemplates = getTemplates(label); + final List tryTemplates = getAllTemplates(label); while (excessWorkload > 0 && !tryTemplates.isEmpty()) { final DockerSlaveTemplate t = tryTemplates.get(0); // get first @@ -113,6 +113,7 @@ public synchronized Collection provision(@CheckForNull Label label, LOG.warn("Bad template '{}' in cloud '{}': '{}'. Trying next template...", t.getDockerContainerLifecycle().getImage(), getDisplayName(), e.getMessage(), e); tryTemplates.remove(t); + continue; } @@ -361,7 +362,7 @@ public DescriptorImpl getDescriptor() { } @Extension - public static class DescriptorImpl extends Descriptor { + public static class DescriptorImpl extends AbstractCloudDescriptor { public FormValidation doCheckName(@QueryParameter String name) { if (StringUtils.isEmpty(name)) { @@ -371,6 +372,7 @@ public FormValidation doCheckName(@QueryParameter String name) { return FormValidation.ok(); } + @Nonnull @Override public String getDisplayName() { return "Yet Another Docker"; diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerConnector.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerConnector.java index d31fdb2e..0cbc8508 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerConnector.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerConnector.java @@ -3,6 +3,9 @@ import com.cloudbees.plugins.credentials.CredentialsMatchers; import com.cloudbees.plugins.credentials.CredentialsProvider; import com.cloudbees.plugins.credentials.common.StandardCredentials; +import com.github.kostyasha.yad.connector.YADockerConnector; +import com.github.kostyasha.yad.other.ConnectorType; +import com.github.kostyasha.yad.utils.CredentialsListBoxModel; import com.github.kostyasha.yad_docker_java.com.github.dockerjava.api.DockerClient; import com.github.kostyasha.yad_docker_java.com.github.dockerjava.api.exception.DockerException; import com.github.kostyasha.yad_docker_java.com.github.dockerjava.api.model.Version; @@ -10,18 +13,13 @@ import com.github.kostyasha.yad_docker_java.com.github.dockerjava.core.RemoteApiVersion; import com.github.kostyasha.yad_docker_java.com.google.common.base.Preconditions; import com.github.kostyasha.yad_docker_java.org.apache.commons.lang.StringUtils; -import com.github.kostyasha.yad.other.ConnectorType; -import com.github.kostyasha.yad.utils.CredentialsListBoxModel; import com.google.common.base.Throwables; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.Extension; -import hudson.model.Describable; -import hudson.model.Descriptor; import hudson.model.ItemGroup; import hudson.security.ACL; import hudson.util.FormValidation; import hudson.util.ListBoxModel; -import jenkins.model.Jenkins; import org.apache.commons.lang.builder.EqualsBuilder; import org.apache.commons.lang.builder.HashCodeBuilder; import org.kohsuke.stapler.AncestorInPath; @@ -30,6 +28,7 @@ import org.kohsuke.stapler.QueryParameter; import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; import javax.servlet.ServletException; import java.io.IOException; import java.security.GeneralSecurityException; @@ -37,19 +36,19 @@ import java.util.List; import static com.github.kostyasha.yad.client.ClientBuilderForConnector.newClientBuilderForConnector; -import static com.github.kostyasha.yad_docker_java.com.github.dockerjava.core.RemoteApiVersion.parseConfig; import static com.github.kostyasha.yad.other.ConnectorType.NETTY; +import static com.github.kostyasha.yad_docker_java.com.github.dockerjava.core.RemoteApiVersion.parseConfig; import static hudson.util.FormValidation.ok; import static hudson.util.FormValidation.warning; import static org.apache.commons.lang.builder.ToStringBuilder.reflectionToString; import static org.apache.commons.lang.builder.ToStringStyle.MULTI_LINE_STYLE; /** - * Settings for connecting to docker. + * Settings for connecting to docker via docker-java configured connection. * * @author Kanstantsin Shautsou */ -public class DockerConnector implements Describable { +public class DockerConnector extends YADockerConnector { @CheckForNull private String serverUrl; @@ -187,14 +186,8 @@ public int hashCode() { .toHashCode(); } - @Override - public Descriptor getDescriptor() { - return (DescriptorImpl) Jenkins.getActiveInstance().getDescriptor(DockerConnector.class); - } - - - @Extension - public static class DescriptorImpl extends Descriptor { + @Extension(ordinal = 100) + public static class DescriptorImpl extends YADockerConnectorDescriptor { public ListBoxModel doFillCredentialsIdItems(@AncestorInPath ItemGroup context) { List credentials = @@ -253,9 +246,10 @@ public FormValidation doCheckApiVersion(@QueryParameter String apiVersion) { return ok(); } + @Nonnull @Override public String getDisplayName() { - return "Docker Connector"; + return "Direct Docker Connector"; } } } diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerJobProperty.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerJobProperty.java new file mode 100644 index 00000000..8e942db7 --- /dev/null +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerJobProperty.java @@ -0,0 +1,50 @@ +package com.github.kostyasha.yad; + +import com.github.kostyasha.yad.jobconfig.SlaveJobConfig; +import hudson.Extension; +import hudson.model.Job; +import jenkins.model.OptionalJobProperty; +import org.kohsuke.stapler.DataBoundConstructor; +import org.kohsuke.stapler.DataBoundSetter; + +import javax.annotation.Nonnull; + +/** + * Property for storing docker specific job settings. + * + * @author Kanstantsin Shautsou + */ +public class DockerJobProperty extends OptionalJobProperty> { + + /** + * Any type of configuration for running job in single container. + */ + private SlaveJobConfig slaveJobConfig; + + @DataBoundConstructor + public DockerJobProperty() { + } + + public SlaveJobConfig getSlaveJobConfig() { + return slaveJobConfig; + } + + @DataBoundSetter + public void setSlaveJobConfig(SlaveJobConfig slaveJobConfig) { + this.slaveJobConfig = slaveJobConfig; + } + + @Extension + public static class DescriptorImpl extends OptionalJobPropertyDescriptor { + @Override + public boolean isApplicable(Class jobType) { + return Job.class.isAssignableFrom(jobType); + } + + @Nonnull + @Override + public String getDisplayName() { + return "Docker Job Property"; + } + } +} diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerQueueDecisionHandler.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerQueueDecisionHandler.java new file mode 100644 index 00000000..9a7fdc10 --- /dev/null +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerQueueDecisionHandler.java @@ -0,0 +1,18 @@ +package com.github.kostyasha.yad; + +import hudson.Extension; +import hudson.model.Action; +import hudson.model.Queue; + +import java.util.List; + +/** + * @author Kanstantsin Shautsou + */ +@Extension +public class DockerQueueDecisionHandler extends Queue.QueueDecisionHandler { + @Override + public boolean shouldSchedule(Queue.Task p, List actions) { + return true; + } +} diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerQueueListener.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerQueueListener.java new file mode 100644 index 00000000..e3794db5 --- /dev/null +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerQueueListener.java @@ -0,0 +1,67 @@ +package com.github.kostyasha.yad; + +import com.github.kostyasha.yad.action.DockerLabelAssignmentAction; +import com.github.kostyasha.yad.jobconfig.DockerCloudJobConfig; +import com.github.kostyasha.yad.jobconfig.SlaveJobConfig; +import hudson.Extension; +import hudson.model.Job; +import hudson.model.Queue; +import hudson.model.labels.LabelAtom; +import hudson.model.queue.QueueListener; +import hudson.slaves.Cloud; +import jenkins.model.Jenkins; + +import java.util.UUID; + +import static java.util.Objects.isNull; + +/** + * @author Kanstantsin Shautsou + */ +@Extension(ordinal = 100) +public class DockerQueueListener extends QueueListener { + + /** + * 1. Assign unique label + * 2. If DockerCloud, then add template. + */ + @Override + public void onEnterWaiting(Queue.WaitingItem wi) { + if (!(wi.task instanceof Job)) { + // looks like not correct Job type check + return; + } + + final Job job = (Job) wi.task; + final DockerJobProperty dockerProp = (DockerJobProperty) job.getProperty(DockerJobProperty.class); + if (isNull(dockerProp)) { + return; + } + + final UUID uuid = UUID.randomUUID(); + final LabelAtom label = new LabelAtom(uuid.toString()); + + final DockerLabelAssignmentAction labelAssignmentAction = new DockerLabelAssignmentAction(label); + wi.addAction(labelAssignmentAction); + + + final SlaveJobConfig slaveJobConfig = dockerProp.getSlaveJobConfig(); + if (slaveJobConfig instanceof DockerCloudJobConfig) { + final DockerCloudJobConfig cloudJobConfig = (DockerCloudJobConfig) slaveJobConfig; + final DockerSlaveTemplate template = cloudJobConfig.getTemplate(); + template.setLabelString(label.toString()); + + final DockerCloud cloud = (DockerCloud) Jenkins.getInstance().getCloud(cloudJobConfig.getConnector().getCloudId()); + cloud.addTransientTemplate(template); + } + + label.nodeProvisioner.suggestReviewNow(); + } + + /** + * Remove template it it was cloud. + */ + @Override + public void onLeft(Queue.LeftItem li) { + } +} diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerQueueTaskDispatcher.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerQueueTaskDispatcher.java new file mode 100644 index 00000000..f5877656 --- /dev/null +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerQueueTaskDispatcher.java @@ -0,0 +1,13 @@ +package com.github.kostyasha.yad; + +import hudson.Extension; +import hudson.model.queue.QueueTaskDispatcher; + +/** + * @author Kanstantsin Shautsou + */ +@Extension +public class DockerQueueTaskDispatcher extends QueueTaskDispatcher { + + +} diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlave.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlave.java index 2d26b8b7..3cf0e543 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlave.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlave.java @@ -8,6 +8,7 @@ import hudson.Extension; import hudson.model.Computer; import hudson.model.Descriptor; +import hudson.model.Node; import hudson.model.Queue; import hudson.model.TaskListener; import hudson.model.queue.CauseOfBlockage; @@ -15,9 +16,11 @@ import hudson.slaves.Cloud; import hudson.slaves.ComputerLauncher; import jenkins.model.Jenkins; +import net.sf.json.JSONObject; import org.apache.commons.lang.StringUtils; import org.jenkinsci.plugins.cloudstats.ProvisioningActivity; import org.jenkinsci.plugins.cloudstats.TrackedItem; +import org.kohsuke.stapler.StaplerRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -137,6 +140,11 @@ public boolean containerExistsInCloud() { } } + @Override + public Node reconfigure(StaplerRequest req, JSONObject form) throws Descriptor.FormException { + return null; + } + @Override protected void _terminate(TaskListener listener) throws IOException, InterruptedException { final DockerContainerLifecycle dockerContainerLifecycle = dockerSlaveTemplate.getDockerContainerLifecycle(); diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/action/DockerLabelAssignmentAction.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/action/DockerLabelAssignmentAction.java new file mode 100644 index 00000000..e26ec319 --- /dev/null +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/action/DockerLabelAssignmentAction.java @@ -0,0 +1,43 @@ +package com.github.kostyasha.yad.action; + +import hudson.model.Label; +import hudson.model.labels.LabelAssignmentAction; +import hudson.model.queue.SubTask; + +import javax.annotation.Nonnull; + +/** + * @author Kanstantsin Shautsou + */ +public class DockerLabelAssignmentAction implements LabelAssignmentAction { + + private Label label; + + public DockerLabelAssignmentAction(Label label) { + this.label = label; + } + + public Label getLabel() { + return label; + } + + @Override + public Label getAssignedLabel(@Nonnull SubTask task) { + return label; + } + + @Override + public String getIconFileName() { + return null; + } + + @Override + public String getDisplayName() { + return null; + } + + @Override + public String getUrlName() { + return null; + } +} diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/commons/AbstractCloud.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/commons/AbstractCloud.java index 90bfdcf3..b3f47a1d 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/commons/AbstractCloud.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/commons/AbstractCloud.java @@ -1,6 +1,7 @@ package com.github.kostyasha.yad.commons; import com.github.kostyasha.yad.DockerSlaveTemplate; +import hudson.model.Descriptor; import hudson.model.Label; import hudson.model.Node; import hudson.slaves.Cloud; @@ -10,7 +11,12 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Set; + +import static java.util.Objects.isNull; +import static java.util.Objects.nonNull; /** * (Very) Pure abstraction to clean up docker specific implementation. @@ -27,7 +33,7 @@ public abstract class AbstractCloud extends Cloud { protected final HashMap provisionedImages = new HashMap<>(); @Nonnull - protected List templates = Collections.emptyList(); + protected List templates = new ArrayList<>(0); /** * Total max allowed number of containers @@ -48,7 +54,23 @@ public void setContainerCap(int containerCap) { @Override public boolean canProvision(Label label) { - return getTemplate(label) != null; + return nonNull(getAllTemplates(label)); + } + + @CheckForNull + public DockerSlaveTemplate getFirstTemplate(String template) { + final DockerSlaveTemplate transientTemplate = getTransientTemplate(template); + return nonNull(transientTemplate) ? transientTemplate : getTemplate(template); + } + + @CheckForNull + public DockerSlaveTemplate getTransientTemplate(String template) { + for (DockerSlaveTemplate t : getTransientTemplates()) { + if (t.getDockerContainerLifecycle().getImage().equals(template)) { + return t; + } + } + return null; } @CheckForNull @@ -81,10 +103,34 @@ public synchronized void addTemplate(DockerSlaveTemplate t) { templates.add(t); } + public synchronized void addTransientTemplate(DockerSlaveTemplate t) { + getTransientTemplates().add(t); + } + + @Nonnull + public List getAllTemplates() { + List temp = new ArrayList<>(getTemplates()); + temp.addAll(getTransientTemplates()); + return temp; + } + public List getTemplates() { return templates; } + @Nonnull + public Set getTransientTemplates() { + return getDescriptor().getTransientTemplates(); + } + + // + @Nonnull + public List getAllTemplates(Label label) { + final ArrayList labelTemplates = new ArrayList<>(getTemplates(label)); + labelTemplates.addAll(getTransientTemplates(label)); + return labelTemplates; + } + /** * Multiple templates may have the same label. * @@ -95,11 +141,11 @@ public List getTemplates(Label label) { List dockerSlaveTemplates = new ArrayList<>(); for (DockerSlaveTemplate t : templates) { - if (label == null && t.getMode() == Node.Mode.NORMAL) { + if (isNull(label) && t.getMode() == Node.Mode.NORMAL) { dockerSlaveTemplates.add(t); } - if (label != null && label.matches(t.getLabelSet())) { + if (nonNull(label) && label.matches(t.getLabelSet())) { dockerSlaveTemplates.add(t); } } @@ -107,6 +153,25 @@ public List getTemplates(Label label) { return dockerSlaveTemplates; } + @Nonnull + public List getTransientTemplates(Label label) { + List dockerSlaveTemplates = new ArrayList<>(); + + for (DockerSlaveTemplate t : getTransientTemplates()) { + if (isNull(label) && t.getMode() == Node.Mode.NORMAL) { + dockerSlaveTemplates.add(t); + } + + if (nonNull(label) && label.matches(t.getLabelSet())) { + dockerSlaveTemplates.add(t); + } + } + + return dockerSlaveTemplates; + } + + // + /** * Set list of available templates */ @@ -125,6 +190,10 @@ public synchronized void removeTemplate(DockerSlaveTemplate t) { templates.remove(t); } + public synchronized void removeTransientTemplate(DockerSlaveTemplate t) { + getTransientTemplates().remove(t); + } + /** * Decrease the count of slaves being "provisioned". */ @@ -138,4 +207,20 @@ protected void decrementAmiSlaveProvision(DockerSlaveTemplate container) { } } + @Override + public AbstractCloudDescriptor getDescriptor() { + return (AbstractCloudDescriptor) super.getDescriptor(); + } + + public static abstract class AbstractCloudDescriptor extends Descriptor { + public transient Set transientTemplates = new HashSet<>(0); + @Nonnull + public Set getTransientTemplates() { + if (isNull(transientTemplates)) { + transientTemplates = new HashSet<>(); + } + return transientTemplates; + } + } + } diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/connector/DockerCloudConnector.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/connector/DockerCloudConnector.java new file mode 100644 index 00000000..39bd570b --- /dev/null +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/connector/DockerCloudConnector.java @@ -0,0 +1,16 @@ +package com.github.kostyasha.yad.connector; + +import com.github.kostyasha.yad.connector.YADockerConnector; +import hudson.DescriptorExtensionList; +import hudson.model.AbstractDescribableImpl; +import hudson.model.Descriptor; +import jenkins.model.Jenkins; + +/** + * Will use Cloud connector. + * + * @author Kanstantsin Shautsou + */ +public abstract class DockerCloudConnector extends YADockerConnector { + +} diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/connector/DockerCloudConnectorId.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/connector/DockerCloudConnectorId.java new file mode 100644 index 00000000..013842cd --- /dev/null +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/connector/DockerCloudConnectorId.java @@ -0,0 +1,89 @@ +package com.github.kostyasha.yad.connector; + +import com.github.kostyasha.yad.DockerCloud; +import com.github.kostyasha.yad_docker_java.com.github.dockerjava.api.DockerClient; +import com.github.kostyasha.yad_docker_java.com.github.dockerjava.api.model.Version; +import hudson.Extension; +import hudson.slaves.Cloud; +import hudson.util.FormValidation; +import hudson.util.ListBoxModel; +import jenkins.model.Jenkins; +import org.kohsuke.stapler.DataBoundConstructor; +import org.kohsuke.stapler.DataBoundSetter; +import org.kohsuke.stapler.QueryParameter; + +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; + +import static hudson.util.FormValidation.error; +import static hudson.util.FormValidation.ok; +import static org.apache.commons.lang.builder.ToStringBuilder.reflectionToString; +import static org.apache.commons.lang.builder.ToStringStyle.MULTI_LINE_STYLE; + +/** + * Should be under `com.github.kostyasha.yad.connector` package, but it was first class and can't move. + * + * @author Kanstantsin Shautsou + */ +public class DockerCloudConnectorId extends YADockerConnector { + + private String cloudId; + + @DataBoundConstructor + public DockerCloudConnectorId() { + } + + public String getCloudId() { + return cloudId; + } + + @DataBoundSetter + public void setCloudId(String cloudId) { + this.cloudId = cloudId; + } + + @CheckForNull + @Override + public DockerClient getClient() { + final Cloud cloud = Jenkins.getInstance().getCloud(cloudId); + if (cloud instanceof DockerCloud) { + final DockerCloud dockerCloud = (DockerCloud) cloud; + return dockerCloud.getClient(); + } + return null; + } + + @Extension + public static class DescriptorImpl extends YADockerConnectorDescriptor { + public FormValidation doCheckCloudId(@QueryParameter String cloudId) { + try { + final Cloud cloud = Jenkins.getInstance().getCloud(cloudId); + if (cloud instanceof DockerCloud) { + final DockerCloud dockerCloud = (DockerCloud) cloud; + Version verResult = dockerCloud.getConnector().getClient().versionCmd().exec(); + + return ok(reflectionToString(verResult, MULTI_LINE_STYLE)); + } else { + return FormValidation.error("cloudId '" + cloudId + "' isn't DockerCloud"); + } + } catch (Throwable t) { + return error(t, "error"); + } + } + + public ListBoxModel doFillCloudIdItems() { + ListBoxModel items = new ListBoxModel(); + Jenkins.getInstance().clouds.getAll(DockerCloud.class) + .forEach(dockerCloud -> + items.add(dockerCloud.getDisplayName()) + ); + return items; + } + + @Nonnull + @Override + public String getDisplayName() { + return "Docker Cloud by Name"; + } + } +} diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/connector/YADockerConnector.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/connector/YADockerConnector.java new file mode 100644 index 00000000..d9f8db5e --- /dev/null +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/connector/YADockerConnector.java @@ -0,0 +1,30 @@ +package com.github.kostyasha.yad.connector; + +import com.github.kostyasha.yad_docker_java.com.github.dockerjava.api.DockerClient; +import hudson.DescriptorExtensionList; +import hudson.ExtensionPoint; +import hudson.model.AbstractDescribableImpl; +import hudson.model.Descriptor; +import jenkins.model.Jenkins; + +/** + * Different connectors to docker. + * DockerConnector appeared first so can't rename and YAD become parent. + * + * @author Kanstantsin Shautsou + */ +public abstract class YADockerConnector extends AbstractDescribableImpl implements ExtensionPoint { + + public abstract DockerClient getClient(); + + @Override + public YADockerConnectorDescriptor getDescriptor() { + return (YADockerConnectorDescriptor) super.getDescriptor(); + } + + public abstract static class YADockerConnectorDescriptor extends Descriptor { + public static DescriptorExtensionList allDockerConnectorDescriptors() { + return Jenkins.getInstance().getDescriptorList(YADockerConnector.class); + } + } +} diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/jobconfig/DockerCloudJobConfig.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/jobconfig/DockerCloudJobConfig.java new file mode 100644 index 00000000..d4ec1eb9 --- /dev/null +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/jobconfig/DockerCloudJobConfig.java @@ -0,0 +1,51 @@ +package com.github.kostyasha.yad.jobconfig; + +import com.github.kostyasha.yad.DockerSlaveTemplate; +import com.github.kostyasha.yad.connector.DockerCloudConnectorId; +import hudson.Extension; +import org.kohsuke.stapler.DataBoundConstructor; +import org.kohsuke.stapler.DataBoundSetter; + +import javax.annotation.Nonnull; + + +/** + * @author Kanstantsin Shautsou + */ +public class DockerCloudJobConfig extends SlaveJobConfig { + + private DockerCloudConnectorId connector; + + private DockerSlaveTemplate template; + + @DataBoundConstructor + public DockerCloudJobConfig() { + } + + public DockerCloudConnectorId getConnector() { + return connector; + } + + @DataBoundSetter + public void setConnector(DockerCloudConnectorId connector) { + this.connector = connector; + } + + public DockerSlaveTemplate getTemplate() { + return template; + } + + @DataBoundSetter + public void setTemplate(DockerSlaveTemplate template) { + this.template = template; + } + + @Extension + public static final class DescriptorImpl extends SlaveJobConfigDescriptor { + @Nonnull + @Override + public String getDisplayName() { + return "Existing cloud"; + } + } +} diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/jobconfig/SlaveJobConfig.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/jobconfig/SlaveJobConfig.java new file mode 100644 index 00000000..5c19e120 --- /dev/null +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/jobconfig/SlaveJobConfig.java @@ -0,0 +1,25 @@ +package com.github.kostyasha.yad.jobconfig; + +import hudson.DescriptorExtensionList; +import hudson.model.AbstractDescribableImpl; +import hudson.model.Descriptor; +import jenkins.model.Jenkins; + +/** + * Different container template settings for job. + * + * @author Kanstantsin Shautsou + */ +public abstract class SlaveJobConfig extends AbstractDescribableImpl { + @Override + public SlaveJobConfigDescriptor getDescriptor() { + return (SlaveJobConfigDescriptor) super.getDescriptor(); + } + + public abstract static class SlaveJobConfigDescriptor extends Descriptor { + public static DescriptorExtensionList getAllSlaveJobConfigurationDescriptors() { + return Jenkins.getInstance().getDescriptorList(SlaveJobConfig.class); + } + } +} + diff --git a/yet-another-docker-plugin/src/main/resources/com/github/kostyasha/yad/DockerJobProperty/config-details.groovy b/yet-another-docker-plugin/src/main/resources/com/github/kostyasha/yad/DockerJobProperty/config-details.groovy new file mode 100644 index 00000000..1d56898b --- /dev/null +++ b/yet-another-docker-plugin/src/main/resources/com/github/kostyasha/yad/DockerJobProperty/config-details.groovy @@ -0,0 +1,37 @@ +package com.github.kostyasha.yad.DockerJobProperty + +import com.github.kostyasha.yad.DockerJobProperty +import lib.FormTagLib + +import static com.github.kostyasha.yad.connector.YADockerConnector.YADockerConnectorDescriptor.allDockerConnectorDescriptors +import static com.github.kostyasha.yad.jobconfig.SlaveJobConfig.SlaveJobConfigDescriptor.allSlaveJobConfigurationDescriptors + +def f = namespace(FormTagLib); +def st = namespace("jelly:stapler") + +if (instance == null) { + instance = new DockerJobProperty() +} + +f.section() { + f.dropdownList(name: "slaveJobConfig", title: _("Docker agent configuration")) { + // all possible descriptors. + getAllSlaveJobConfigurationDescriptors().each { sd -> + if (sd != null) { + f.dropdownListBlock(value: sd.clazz.name, name: sd.displayName, + selected: instance.slaveJobConfig == null ? + "false" : instance.slaveJobConfig.descriptor.equals(sd), + title: sd.displayName) { + descriptor = sd + if (instance.slaveJobConfig != null && instance.slaveJobConfig.descriptor.equals(sd)) { + instance = instance.slaveJobConfig + } + f.invisibleEntry() { + input(type: "hidden", name: "stapler-class", value: sd.clazz.name) + } + st.include(from: sd, page: sd.configPage, optional: "true") + } + } + } + } +} diff --git a/yet-another-docker-plugin/src/main/resources/com/github/kostyasha/yad/DockerSlaveTemplate/config.groovy b/yet-another-docker-plugin/src/main/resources/com/github/kostyasha/yad/DockerSlaveTemplate/config.groovy index 5b83c36b..57879c48 100644 --- a/yet-another-docker-plugin/src/main/resources/com/github/kostyasha/yad/DockerSlaveTemplate/config.groovy +++ b/yet-another-docker-plugin/src/main/resources/com/github/kostyasha/yad/DockerSlaveTemplate/config.groovy @@ -15,8 +15,8 @@ if (instance == null) { instance = new DockerSlaveTemplate(); } -f.invisibleEntry() { - f.textbox(field: "id") +f.entry(title: "Internal template ID") { + f.textbox(field: "id", readonly: "true") } f.entry(title: _("Max Instances"), field: "maxCapacity") { diff --git a/yet-another-docker-plugin/src/main/resources/com/github/kostyasha/yad/connector/DockerCloudConnectorId/config.groovy b/yet-another-docker-plugin/src/main/resources/com/github/kostyasha/yad/connector/DockerCloudConnectorId/config.groovy new file mode 100644 index 00000000..ccba92d2 --- /dev/null +++ b/yet-another-docker-plugin/src/main/resources/com/github/kostyasha/yad/connector/DockerCloudConnectorId/config.groovy @@ -0,0 +1,10 @@ +package com.github.kostyasha.yad.connector.DockerCloudConnectorId + +import lib.FormTagLib + +def f = namespace(FormTagLib) + + +f.entry(field: "cloudId", title: "YADocker Connector") { + f.select(name: "cloudId") +} \ No newline at end of file diff --git a/yet-another-docker-plugin/src/main/resources/com/github/kostyasha/yad/connector/DockerCloudConnectorTemplateId/config.groovy b/yet-another-docker-plugin/src/main/resources/com/github/kostyasha/yad/connector/DockerCloudConnectorTemplateId/config.groovy new file mode 100644 index 00000000..9907408d --- /dev/null +++ b/yet-another-docker-plugin/src/main/resources/com/github/kostyasha/yad/connector/DockerCloudConnectorTemplateId/config.groovy @@ -0,0 +1,7 @@ +package com.github.kostyasha.yad.connector.DockerCloudConnectorTemplateId + +import lib.FormTagLib + +def f = namespace(FormTagLib) + +f.property() \ No newline at end of file diff --git a/yet-another-docker-plugin/src/main/resources/com/github/kostyasha/yad/connector/DockerCloudConnectorTemplateRaw/config.groovy b/yet-another-docker-plugin/src/main/resources/com/github/kostyasha/yad/connector/DockerCloudConnectorTemplateRaw/config.groovy new file mode 100644 index 00000000..47437221 --- /dev/null +++ b/yet-another-docker-plugin/src/main/resources/com/github/kostyasha/yad/connector/DockerCloudConnectorTemplateRaw/config.groovy @@ -0,0 +1,7 @@ +package com.github.kostyasha.yad.connector.DockerCloudConnectorTemplateRaw + +import lib.FormTagLib + +def f = namespace(FormTagLib) + +f.property(field: "slaveTemplate") diff --git a/yet-another-docker-plugin/src/main/resources/com/github/kostyasha/yad/jobconfig/DockerCloudJobConfig/config.groovy b/yet-another-docker-plugin/src/main/resources/com/github/kostyasha/yad/jobconfig/DockerCloudJobConfig/config.groovy new file mode 100644 index 00000000..e5b956c0 --- /dev/null +++ b/yet-another-docker-plugin/src/main/resources/com/github/kostyasha/yad/jobconfig/DockerCloudJobConfig/config.groovy @@ -0,0 +1,26 @@ +package com.github.kostyasha.yad.jobconfig.DockerCloudJobConfig + +import com.github.kostyasha.yad.DockerSlaveTemplate +import com.github.kostyasha.yad.jobconfig.DockerCloudJobConfig +import lib.FormTagLib + +def f = namespace(FormTagLib) + +if (instance == null) { + instance = new DockerCloudJobConfig() +} + +if (instance.template == null) { + instance.template = new DockerSlaveTemplate() + instance.template.setMaxCapacity(1) + instance.template.setMode(hudson.model.Node.Mode.EXCLUSIVE) + instance.template.setLabelString("automatically-changed") +} + +f.entry { + f.property(field: "connector") +} + +f.entry { + f.property(field: "template") +} From 3243f10e7b0a8768a946d30b7362c38d118bfe04 Mon Sep 17 00:00:00 2001 From: Kanstantsin Shautsou Date: Tue, 10 Jan 2017 19:08:55 +0300 Subject: [PATCH 02/17] Fix label hashes. --- .../main/java/com/github/kostyasha/yad/DockerQueueListener.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerQueueListener.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerQueueListener.java index e3794db5..cd65a5e6 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerQueueListener.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerQueueListener.java @@ -39,7 +39,7 @@ public void onEnterWaiting(Queue.WaitingItem wi) { } final UUID uuid = UUID.randomUUID(); - final LabelAtom label = new LabelAtom(uuid.toString()); + final LabelAtom label = Jenkins.getInstance().getLabelAtom(uuid.toString()); final DockerLabelAssignmentAction labelAssignmentAction = new DockerLabelAssignmentAction(label); wi.addAction(labelAssignmentAction); From defc0b60f31b681795001ec374f9b16b8882506c Mon Sep 17 00:00:00 2001 From: Kanstantsin Shautsou Date: Tue, 10 Jan 2017 19:10:58 +0300 Subject: [PATCH 03/17] Test new core --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2eb86873..2c1742bf 100644 --- a/pom.xml +++ b/pom.xml @@ -43,7 +43,7 @@ - 1.642.4 + 2.40-SNAPSHOT UTF-8 2.5.3 3.0.3 From 79d041244db1d4f2609ce771e2cc7be6138f0e53 Mon Sep 17 00:00:00 2001 From: Kanstantsin Shautsou Date: Sun, 22 Jan 2017 01:01:37 +0300 Subject: [PATCH 04/17] WIP Signed-off-by: Kanstantsin Shautsou --- pom.xml | 2 +- .../kostyasha/yad/DockerQueueListener.java | 36 +++++-- .../action/DockerLabelAssignmentAction.java | 37 +++++-- .../kostyasha/yad/commons/AbstractCloud.java | 21 ++-- .../yad/connector/DockerCloudConnector.java | 6 -- .../launcher/NoOpComputerLauncherFilter.java | 14 +++ .../DockerJobProperty/config-details.groovy | 26 +++-- .../github/kostyasha/yad/step/DockerTask.java | 100 ++++++++++++++++++ .../kostyasha/yad/step/MyExecutable.java | 49 +++++++++ .../kostyasha/yad/step/TaskBuildStep.java | 55 ++++++++++ .../kostyasha/yad/step/TaskStepTest.java | 33 ++++++ .../yad/step/MyExecutable/executorCell.groovy | 11 ++ 12 files changed, 350 insertions(+), 40 deletions(-) create mode 100644 yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/launcher/NoOpComputerLauncherFilter.java create mode 100644 yet-another-docker-plugin/src/test/java/com/github/kostyasha/yad/step/DockerTask.java create mode 100644 yet-another-docker-plugin/src/test/java/com/github/kostyasha/yad/step/MyExecutable.java create mode 100644 yet-another-docker-plugin/src/test/java/com/github/kostyasha/yad/step/TaskBuildStep.java create mode 100644 yet-another-docker-plugin/src/test/java/com/github/kostyasha/yad/step/TaskStepTest.java create mode 100644 yet-another-docker-plugin/src/test/resources/com/github/kostyasha/yad/step/MyExecutable/executorCell.groovy diff --git a/pom.xml b/pom.xml index 2c1742bf..d2472516 100644 --- a/pom.xml +++ b/pom.xml @@ -43,7 +43,7 @@ - 2.40-SNAPSHOT + 2.19.4 UTF-8 2.5.3 3.0.3 diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerQueueListener.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerQueueListener.java index cd65a5e6..9bba8b03 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerQueueListener.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerQueueListener.java @@ -14,34 +14,43 @@ import java.util.UUID; import static java.util.Objects.isNull; +import static java.util.Objects.nonNull; /** * @author Kanstantsin Shautsou */ @Extension(ordinal = 100) public class DockerQueueListener extends QueueListener { - /** * 1. Assign unique label * 2. If DockerCloud, then add template. */ @Override public void onEnterWaiting(Queue.WaitingItem wi) { + if (!processItemWithAction(wi)) { + processJobWithProperty(wi); + } + } + + /** + * If it Job and has JobProperty, then spin with DockerCloud + */ + private boolean processJobWithProperty(Queue.WaitingItem wi) { if (!(wi.task instanceof Job)) { // looks like not correct Job type check - return; + return false; } final Job job = (Job) wi.task; final DockerJobProperty dockerProp = (DockerJobProperty) job.getProperty(DockerJobProperty.class); if (isNull(dockerProp)) { - return; + return false; } final UUID uuid = UUID.randomUUID(); - final LabelAtom label = Jenkins.getInstance().getLabelAtom(uuid.toString()); +// final LabelAtom label = Jenkins.getInstance().getLabelAtom(uuid.toString()); - final DockerLabelAssignmentAction labelAssignmentAction = new DockerLabelAssignmentAction(label); + final DockerLabelAssignmentAction labelAssignmentAction = new DockerLabelAssignmentAction(uuid.toString()); wi.addAction(labelAssignmentAction); @@ -49,13 +58,26 @@ public void onEnterWaiting(Queue.WaitingItem wi) { if (slaveJobConfig instanceof DockerCloudJobConfig) { final DockerCloudJobConfig cloudJobConfig = (DockerCloudJobConfig) slaveJobConfig; final DockerSlaveTemplate template = cloudJobConfig.getTemplate(); - template.setLabelString(label.toString()); + template.setLabelString(uuid.toString()); final DockerCloud cloud = (DockerCloud) Jenkins.getInstance().getCloud(cloudJobConfig.getConnector().getCloudId()); cloud.addTransientTemplate(template); } - label.nodeProvisioner.suggestReviewNow(); + labelAssignmentAction.getAssignedLabel(null).nodeProvisioner.suggestReviewNow(); + + return true; + } + + /** + * Process with special Action + */ + private boolean processItemWithAction(Queue.WaitingItem wi) { + final DockerLabelAssignmentAction action = wi.getAction(DockerLabelAssignmentAction.class); + if (nonNull(action)) { + + } + return false; } /** diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/action/DockerLabelAssignmentAction.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/action/DockerLabelAssignmentAction.java index e26ec319..cdc7954e 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/action/DockerLabelAssignmentAction.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/action/DockerLabelAssignmentAction.java @@ -1,29 +1,52 @@ package com.github.kostyasha.yad.action; +import com.github.kostyasha.yad.DockerConnector; +import com.github.kostyasha.yad.DockerSlaveTemplate; import hudson.model.Label; import hudson.model.labels.LabelAssignmentAction; import hudson.model.queue.SubTask; +import jenkins.model.Jenkins; import javax.annotation.Nonnull; +import javax.annotation.Nullable; /** * @author Kanstantsin Shautsou */ public class DockerLabelAssignmentAction implements LabelAssignmentAction { - private Label label; + private final String assignedLabel; - public DockerLabelAssignmentAction(Label label) { - this.label = label; + private DockerConnector connector = null; + private DockerSlaveTemplate slaveTemplate = null; + + public DockerLabelAssignmentAction(String assignedLabel) { + this.assignedLabel = assignedLabel; + } + + public String getAssignedLabel() { + return assignedLabel; + } + + public DockerConnector getConnector() { + return connector; + } + + public void setConnector(DockerConnector connector) { + this.connector = connector; + } + + public DockerSlaveTemplate getSlaveTemplate() { + return slaveTemplate; } - public Label getLabel() { - return label; + public void setSlaveTemplate(DockerSlaveTemplate slaveTemplate) { + this.slaveTemplate = slaveTemplate; } @Override - public Label getAssignedLabel(@Nonnull SubTask task) { - return label; + public Label getAssignedLabel(@Nullable SubTask task) { + return Jenkins.getInstance().getLabelAtom(assignedLabel); } @Override diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/commons/AbstractCloud.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/commons/AbstractCloud.java index b3f47a1d..1447ed1d 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/commons/AbstractCloud.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/commons/AbstractCloud.java @@ -13,7 +13,10 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; import static java.util.Objects.isNull; import static java.util.Objects.nonNull; @@ -30,7 +33,7 @@ public abstract class AbstractCloud extends Cloud { * Track the count per image name for images currently being * provisioned, but not necessarily reported yet by docker. */ - protected final HashMap provisionedImages = new HashMap<>(); + protected final ConcurrentHashMap provisionedImages = new ConcurrentHashMap<>(); @Nonnull protected List templates = new ArrayList<>(0); @@ -120,7 +123,7 @@ public List getTemplates() { @Nonnull public Set getTransientTemplates() { - return getDescriptor().getTransientTemplates(); + return getDescriptor().getTransientTemplates(name); } // @@ -213,14 +216,14 @@ public AbstractCloudDescriptor getDescriptor() { } public static abstract class AbstractCloudDescriptor extends Descriptor { - public transient Set transientTemplates = new HashSet<>(0); + // docker cloud id <> set + public final transient ConcurrentHashMap> transientTemplates = new ConcurrentHashMap<>(); + public final transient ConcurrentHashMap> provisionedImages = new ConcurrentHashMap<>(); + @Nonnull - public Set getTransientTemplates() { - if (isNull(transientTemplates)) { - transientTemplates = new HashSet<>(); - } - return transientTemplates; + public Set getTransientTemplates(String cloudId) { + transientTemplates.putIfAbsent(cloudId, ConcurrentHashMap.newKeySet()); + return transientTemplates.get(cloudId); } } - } diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/connector/DockerCloudConnector.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/connector/DockerCloudConnector.java index 39bd570b..b23c9805 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/connector/DockerCloudConnector.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/connector/DockerCloudConnector.java @@ -1,11 +1,5 @@ package com.github.kostyasha.yad.connector; -import com.github.kostyasha.yad.connector.YADockerConnector; -import hudson.DescriptorExtensionList; -import hudson.model.AbstractDescribableImpl; -import hudson.model.Descriptor; -import jenkins.model.Jenkins; - /** * Will use Cloud connector. * diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/launcher/NoOpComputerLauncherFilter.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/launcher/NoOpComputerLauncherFilter.java new file mode 100644 index 00000000..81528fa9 --- /dev/null +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/launcher/NoOpComputerLauncherFilter.java @@ -0,0 +1,14 @@ +package com.github.kostyasha.yad.launcher; + +import hudson.slaves.ComputerLauncher; +import hudson.slaves.ComputerLauncherFilter; + +/** + * @author Kanstantsin Shautsou + */ +public class NoOpComputerLauncherFilter extends ComputerLauncherFilter { + public NoOpComputerLauncherFilter(ComputerLauncher core) { + super(core); + } + +} diff --git a/yet-another-docker-plugin/src/main/resources/com/github/kostyasha/yad/DockerJobProperty/config-details.groovy b/yet-another-docker-plugin/src/main/resources/com/github/kostyasha/yad/DockerJobProperty/config-details.groovy index 1d56898b..10f59d82 100644 --- a/yet-another-docker-plugin/src/main/resources/com/github/kostyasha/yad/DockerJobProperty/config-details.groovy +++ b/yet-another-docker-plugin/src/main/resources/com/github/kostyasha/yad/DockerJobProperty/config-details.groovy @@ -14,23 +14,29 @@ if (instance == null) { } f.section() { + curSlaveJobConfig = instance.slaveJobConfig f.dropdownList(name: "slaveJobConfig", title: _("Docker agent configuration")) { // all possible descriptors. - getAllSlaveJobConfigurationDescriptors().each { sd -> - if (sd != null) { - f.dropdownListBlock(value: sd.clazz.name, name: sd.displayName, + getAllSlaveJobConfigurationDescriptors().each { desc -> + if (desc != null) { + f.dropdownListBlock( + value: desc.clazz.name, + name: desc.displayName, selected: instance.slaveJobConfig == null ? - "false" : instance.slaveJobConfig.descriptor.equals(sd), - title: sd.displayName) { - descriptor = sd - if (instance.slaveJobConfig != null && instance.slaveJobConfig.descriptor.equals(sd)) { + "false" : instance.slaveJobConfig.descriptor.equals(desc), + title: desc.displayName + ) { + descriptor = desc +// if (instance.slaveJobConfig != null && instance.slaveJobConfig.descriptor.equals(desc)) { instance = instance.slaveJobConfig - } +// } f.invisibleEntry() { - input(type: "hidden", name: "stapler-class", value: sd.clazz.name) + input(type: "hidden", name: "stapler-class", value: desc.clazz.name) } - st.include(from: sd, page: sd.configPage, optional: "true") + st.include(from: desc, page: desc.configPage, optional: "true") } + } else { + println("descriptor is null") } } } diff --git a/yet-another-docker-plugin/src/test/java/com/github/kostyasha/yad/step/DockerTask.java b/yet-another-docker-plugin/src/test/java/com/github/kostyasha/yad/step/DockerTask.java new file mode 100644 index 00000000..ced3e062 --- /dev/null +++ b/yet-another-docker-plugin/src/test/java/com/github/kostyasha/yad/step/DockerTask.java @@ -0,0 +1,100 @@ +package com.github.kostyasha.yad.step; + +import hudson.model.Label; +import hudson.model.Node; +import hudson.model.Queue; +import hudson.model.ResourceList; +import hudson.model.queue.AbstractQueueTask; +import hudson.security.ACL; +import hudson.security.AccessControlled; +import hudson.security.Permission; +import jenkins.model.Jenkins; +import org.acegisecurity.AccessDeniedException; + +import javax.annotation.Nonnull; +import java.io.IOException; + +/** + * @author Kanstantsin Shautsou + */ +public class DockerTask extends AbstractQueueTask implements Queue.TransientTask, AccessControlled { + @Override + public boolean isBuildBlocked() { + return false; + } + + @Override + public String getWhyBlocked() { + return null; + } + + @Override + public String getName() { + return "Some name"; + } + + @Override + public String getFullDisplayName() { + return "Full display name"; + } + + @Override + public void checkAbortPermission() { + } + + @Override + public boolean hasAbortPermission() { + return true; + } + + @Override + public String getUrl() { + return null; + } + + @Override + public ResourceList getResourceList() { + return ResourceList.EMPTY; + } + + @Override + public String getDisplayName() { + return "Display name for task"; + } + + @Override + public Label getAssignedLabel() { + return null; + } + + @Override + public Node getLastBuiltOn() { + return null; + } + + @Override + public long getEstimatedDuration() { + return -1; + } + + @Override + public Queue.Executable createExecutable() throws IOException { + return new MyExecutable(this); + } + + @Nonnull + @Override + public ACL getACL() { + return Jenkins.getInstance().getAuthorizationStrategy().getRootACL(); + } + + @Override + public void checkPermission(@Nonnull Permission permission) throws AccessDeniedException { + getACL().checkPermission(permission); + } + + @Override + public boolean hasPermission(@Nonnull Permission permission) { + return getACL().hasPermission(permission); + } +} diff --git a/yet-another-docker-plugin/src/test/java/com/github/kostyasha/yad/step/MyExecutable.java b/yet-another-docker-plugin/src/test/java/com/github/kostyasha/yad/step/MyExecutable.java new file mode 100644 index 00000000..47a6146c --- /dev/null +++ b/yet-another-docker-plugin/src/test/java/com/github/kostyasha/yad/step/MyExecutable.java @@ -0,0 +1,49 @@ +package com.github.kostyasha.yad.step; + +import hudson.model.Queue; +import hudson.model.queue.SubTask; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.Nonnull; + +import static java.util.concurrent.TimeUnit.MINUTES; + +/** + * @author Kanstantsin Shautsou + */ +public class MyExecutable implements Queue.Executable { + private static final Logger LOG = LoggerFactory.getLogger(MyExecutable.class); + + private DockerTask dockerTask; + + public MyExecutable(DockerTask dockerTask) { + this.dockerTask = dockerTask; + } + + @Nonnull + @Override + public SubTask getParent() { + return dockerTask; + } + + @Override + public void run() { + LOG.info(" In run!"); + try { + Thread.sleep(MINUTES.toMillis(5)); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + @Override + public long getEstimatedDuration() { + return -1; + } + + @Override + public String toString() { + return "This is my super executable"; + } +} diff --git a/yet-another-docker-plugin/src/test/java/com/github/kostyasha/yad/step/TaskBuildStep.java b/yet-another-docker-plugin/src/test/java/com/github/kostyasha/yad/step/TaskBuildStep.java new file mode 100644 index 00000000..4df7638c --- /dev/null +++ b/yet-another-docker-plugin/src/test/java/com/github/kostyasha/yad/step/TaskBuildStep.java @@ -0,0 +1,55 @@ +package com.github.kostyasha.yad.step; + +import hudson.Extension; +import hudson.FilePath; +import hudson.Launcher; +import hudson.model.AbstractProject; +import hudson.model.Queue; +import hudson.model.Run; +import hudson.model.TaskListener; +import hudson.model.queue.SubTask; +import hudson.tasks.BuildStepDescriptor; +import hudson.tasks.Builder; +import jenkins.tasks.SimpleBuildStep; +import org.kohsuke.stapler.DataBoundConstructor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.Nonnull; +import java.io.IOException; + +import static java.util.concurrent.TimeUnit.MINUTES; + +/** + * @author Kanstantsin Shautsou + */ +public class TaskBuildStep extends Builder implements SimpleBuildStep { + + @DataBoundConstructor + public TaskBuildStep() { + } + + @Override + public void perform(@Nonnull Run run, @Nonnull FilePath filePath, @Nonnull Launcher launcher, + @Nonnull TaskListener taskListener) throws InterruptedException, IOException { + taskListener.getLogger().println("Entering task producer"); + Queue.getInstance().schedule(new DockerTask(), 0); + taskListener.getLogger().println("Task scheduled? sleep"); + Thread.sleep(MINUTES.toMillis(10)); + taskListener.getLogger().println("end of sleep"); + } + + @Extension + public static final class DescriptorImpl extends BuildStepDescriptor { + @Nonnull + @Override + public String getDisplayName() { + return "Test Task Producer"; + } + + @Override + public boolean isApplicable(Class aClass) { + return true; + } + } +} diff --git a/yet-another-docker-plugin/src/test/java/com/github/kostyasha/yad/step/TaskStepTest.java b/yet-another-docker-plugin/src/test/java/com/github/kostyasha/yad/step/TaskStepTest.java new file mode 100644 index 00000000..f0d7559a --- /dev/null +++ b/yet-another-docker-plugin/src/test/java/com/github/kostyasha/yad/step/TaskStepTest.java @@ -0,0 +1,33 @@ +package com.github.kostyasha.yad.step; + +import com.github.kostyasha.yad.DockerCloud; +import com.github.kostyasha.yad.DockerSlaveTemplate; +import com.github.kostyasha.yad.action.DockerLabelAssignmentAction; +import hudson.model.Queue; +import jenkins.model.Jenkins; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.JenkinsRule; + +/** + * @author Kanstantsin Shautsou + */ +public class TaskStepTest { + @Rule + public JenkinsRule jRule = new JenkinsRule(); + + @Test + public void actionTask() throws Exception { + final Jenkins jenkins = jRule.getInstance(); + + final DockerSlaveTemplate slaveTemplate = new DockerSlaveTemplate(); + + slaveTemplate.setLabelString(); + +// final DockerCloud dockerCloud = new DockerCloud("localTestCloud"); + + new DockerLabelAssignmentAction() + final Queue.WaitingItem waitingItem = jenkins.getQueue().schedule(new DockerTask(), 0, ); + jRule.waitUntilNoActivity(); + } +} diff --git a/yet-another-docker-plugin/src/test/resources/com/github/kostyasha/yad/step/MyExecutable/executorCell.groovy b/yet-another-docker-plugin/src/test/resources/com/github/kostyasha/yad/step/MyExecutable/executorCell.groovy new file mode 100644 index 00000000..bc1eb55d --- /dev/null +++ b/yet-another-docker-plugin/src/test/resources/com/github/kostyasha/yad/step/MyExecutable/executorCell.groovy @@ -0,0 +1,11 @@ +package com.github.kostyasha.yad.step.MyExecutable + +import lib.FormTagLib + +def f = namespace(FormTagLib); + +td(class: "pane") { + div(style: "white-space: normal") { + text("Building") + } +} From 24fc3ad20e8cfbfb5986466674aaa3e46fb4790b Mon Sep 17 00:00:00 2001 From: Kanstantsin Shautsou Date: Wed, 25 Jan 2017 02:40:07 +0300 Subject: [PATCH 05/17] WIP Signed-off-by: Kanstantsin Shautsou --- yet-another-docker-its/pom.xml | 2 +- yet-another-docker-plugin/pom.xml | 2 +- .../kostyasha/yad/DockerComputerSingle.java | 82 +++++ .../com/github/kostyasha/yad/DockerSlave.java | 12 +- .../kostyasha/yad/DockerSlaveSingle.java | 72 +++++ .../action/DockerLabelAssignmentAction.java | 10 +- .../launcher/DockerComputerJNLPLauncher.java | 4 +- .../DockerComputerSingleJNLPLauncher.java | 298 ++++++++++++++++++ .../{ => listener}/DockerQueueListener.java | 44 ++- .../yad/listener/DockerRunListener.java | 84 +++++ .../github/kostyasha/yad/TaskStepTest.java | 47 +++ .../kostyasha/yad/step/TaskStepTest.java | 33 -- .../test/DockerLabelAssignmentActionTest.java | 100 ++++++ 13 files changed, 745 insertions(+), 45 deletions(-) create mode 100644 yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerComputerSingle.java create mode 100644 yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlaveSingle.java create mode 100644 yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/launcher/DockerComputerSingleJNLPLauncher.java rename yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/{ => listener}/DockerQueueListener.java (58%) create mode 100644 yet-another-docker-plugin/src/test/java/com/github/kostyasha/yad/TaskStepTest.java delete mode 100644 yet-another-docker-plugin/src/test/java/com/github/kostyasha/yad/step/TaskStepTest.java create mode 100644 yet-another-docker-plugin/src/test/java/org/jvnet/hudson/test/DockerLabelAssignmentActionTest.java diff --git a/yet-another-docker-its/pom.xml b/yet-another-docker-its/pom.xml index 46a1efc5..e41a3350 100644 --- a/yet-another-docker-its/pom.xml +++ b/yet-another-docker-its/pom.xml @@ -247,7 +247,7 @@ org.jenkins-ci.plugins cloud-stats - 0.3 + 0.7 org.jenkins-ci.plugins diff --git a/yet-another-docker-plugin/pom.xml b/yet-another-docker-plugin/pom.xml index ddec7ec5..c62e9015 100644 --- a/yet-another-docker-plugin/pom.xml +++ b/yet-another-docker-plugin/pom.xml @@ -31,7 +31,7 @@ org.jenkins-ci.plugins cloud-stats - 0.5 + 0.7 org.jenkins-ci.plugins diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerComputerSingle.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerComputerSingle.java new file mode 100644 index 00000000..7a4cf2c1 --- /dev/null +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerComputerSingle.java @@ -0,0 +1,82 @@ +package com.github.kostyasha.yad; + +import com.github.kostyasha.yad.launcher.DockerComputerJNLPLauncher; +import hudson.model.Node; +import hudson.model.Run; +import hudson.model.Slave; +import hudson.model.TaskListener; +import hudson.remoting.Channel; +import hudson.slaves.AbstractCloudComputer; +import hudson.slaves.ComputerLauncher; +import org.jenkinsci.plugins.cloudstats.ProvisioningActivity; +import org.jenkinsci.plugins.cloudstats.TrackedItem; + +import javax.annotation.Nullable; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.Charset; + +import static java.util.Objects.nonNull; + +/** + * @author Kanstantsin Shautsou + */ +public class DockerComputerSingle extends AbstractCloudComputer implements TrackedItem { + private final ProvisioningActivity.Id activityId; + private transient TaskListener listener; + private transient Run run; + + public DockerComputerSingle(DockerSlaveSingle slave, ProvisioningActivity.Id activityId) { + super(slave); + this.activityId = activityId; + } + + @Override + public TaskListener getListener() { + return nonNull(listener) ? listener : super.getListener(); + } + + public void setListener(TaskListener listener) { + this.listener = listener; + } + + public Run getRun() { + return run; + } + + public void setRun(Run run) { + this.run = run; + } + + @Override + public void setChannel(Channel channel, OutputStream launchLog, Channel.Listener listener) throws IOException, InterruptedException { + super.setChannel(channel, launchLog, listener); + } + + public boolean isReallyOffline() { + return super.isOffline(); + } + + @Override + public boolean isOffline() { + // create executors to pick tasks + return false; + } + + @Override + public Charset getDefaultCharset() { + // either fails + // java.lang.NullPointerException + // at hudson.model.Run.execute(Run.java:1702) + // at hudson.model.FreeStyleBuild.run(FreeStyleBuild.java:43) + // at hudson.model.ResourceController.execute(ResourceController.java:98) + // at hudson.model.Executor.run(Executor.java:404) + return Charset.forName("UTF-8"); + } + + @Nullable + @Override + public ProvisioningActivity.Id getId() { + return activityId; + } +} diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlave.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlave.java index 3cf0e543..c076f619 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlave.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlave.java @@ -18,6 +18,7 @@ import jenkins.model.Jenkins; import net.sf.json.JSONObject; import org.apache.commons.lang.StringUtils; +import org.jenkinsci.plugins.cloudstats.CloudStatistics; import org.jenkinsci.plugins.cloudstats.ProvisioningActivity; import org.jenkinsci.plugins.cloudstats.TrackedItem; import org.kohsuke.stapler.StaplerRequest; @@ -29,6 +30,7 @@ import javax.annotation.Nullable; import java.io.IOException; +import static java.util.Objects.nonNull; /** * Jenkins Slave with yad specific configuration @@ -98,7 +100,7 @@ public void setDockerSlaveTemplate(DockerSlaveTemplate dockerSlaveTemplate) { @Nonnull public DockerCloud getCloud() { - final Cloud cloud = Jenkins.getActiveInstance().getCloud(getCloudId()); + final Cloud cloud = Jenkins.getInstance().getCloud(getCloudId()); if (cloud == null) { throw new RuntimeException("Docker template " + dockerSlaveTemplate + " has no assigned Cloud."); @@ -189,9 +191,11 @@ protected void _terminate(TaskListener listener) throws IOException, Interrupted } else { LOG.error("ContainerId is absent, no way to remove/stop container"); } - // after it node will be finally removed from jenkins -// CloudStatistics.ProvisioningListener.get().onComplete() no completion method?! - // https://issues.jenkins-ci.org/browse/JENKINS-33780 + + ProvisioningActivity activity = CloudStatistics.get().getActivityFor(this); + if (nonNull(activity)) { + activity.enterIfNotAlready(ProvisioningActivity.Phase.COMPLETED); + } } @Nullable diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlaveSingle.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlaveSingle.java new file mode 100644 index 00000000..021d97ae --- /dev/null +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlaveSingle.java @@ -0,0 +1,72 @@ +package com.github.kostyasha.yad; + +import hudson.model.Descriptor; +import hudson.model.TaskListener; +import hudson.slaves.AbstractCloudComputer; +import hudson.slaves.AbstractCloudSlave; +import hudson.slaves.ComputerLauncher; +import hudson.slaves.NodeProperty; +import hudson.slaves.RetentionStrategy; +import org.jenkinsci.plugins.cloudstats.CloudStatistics; +import org.jenkinsci.plugins.cloudstats.ProvisioningActivity; +import org.jenkinsci.plugins.cloudstats.TrackedItem; + +import javax.annotation.Nullable; +import java.io.IOException; +import java.util.List; + +/** + * @author Kanstantsin Shautsou + */ +public class DockerSlaveSingle extends AbstractCloudSlave implements TrackedItem { + + private ProvisioningActivity.Id activityId; + + public DockerSlaveSingle(String name, + String nodeDescription, + String remoteFS, + String numExecutors, + Mode mode, + String labelString, + ComputerLauncher launcher, + RetentionStrategy retentionStrategy, + List> nodeProperties) + throws Descriptor.FormException, IOException { + super(name, nodeDescription, remoteFS, numExecutors, mode, labelString, launcher, retentionStrategy, nodeProperties); + } + + public DockerSlaveSingle(String name, + String nodeDescription, + String remoteFS, + int numExecutors, + Mode mode, + String labelString, + ComputerLauncher launcher, + RetentionStrategy retentionStrategy, + List> nodeProperties, + ProvisioningActivity.Id activityId) + throws Descriptor.FormException, IOException { + super(name, nodeDescription, remoteFS, numExecutors, mode, labelString, launcher, retentionStrategy, nodeProperties); + this.activityId = activityId; + } + + @Override + public AbstractCloudComputer createComputer() { + return new DockerComputerSingle(this, activityId); + } + + @Override + protected void _terminate(TaskListener listener) throws IOException, InterruptedException { + CloudStatistics statistics = CloudStatistics.get(); + ProvisioningActivity activity = statistics.getActivityFor(this); + if (activity != null) { + activity.enterIfNotAlready(ProvisioningActivity.Phase.COMPLETED); + } + } + + @Nullable + @Override + public ProvisioningActivity.Id getId() { + return null; + } +} \ No newline at end of file diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/action/DockerLabelAssignmentAction.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/action/DockerLabelAssignmentAction.java index cdc7954e..7f1d0f70 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/action/DockerLabelAssignmentAction.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/action/DockerLabelAssignmentAction.java @@ -2,11 +2,13 @@ import com.github.kostyasha.yad.DockerConnector; import com.github.kostyasha.yad.DockerSlaveTemplate; +import com.github.kostyasha.yad.connector.YADockerConnector; import hudson.model.Label; import hudson.model.labels.LabelAssignmentAction; import hudson.model.queue.SubTask; import jenkins.model.Jenkins; +import javax.annotation.CheckForNull; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -17,7 +19,7 @@ public class DockerLabelAssignmentAction implements LabelAssignmentAction { private final String assignedLabel; - private DockerConnector connector = null; + private YADockerConnector connector = null; private DockerSlaveTemplate slaveTemplate = null; public DockerLabelAssignmentAction(String assignedLabel) { @@ -28,14 +30,16 @@ public String getAssignedLabel() { return assignedLabel; } - public DockerConnector getConnector() { + @CheckForNull + public YADockerConnector getConnector() { return connector; } - public void setConnector(DockerConnector connector) { + public void setConnector(YADockerConnector connector) { this.connector = connector; } + @CheckForNull public DockerSlaveTemplate getSlaveTemplate() { return slaveTemplate; } diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/launcher/DockerComputerJNLPLauncher.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/launcher/DockerComputerJNLPLauncher.java index eef528bb..d789bf31 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/launcher/DockerComputerJNLPLauncher.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/launcher/DockerComputerJNLPLauncher.java @@ -146,7 +146,8 @@ public void launch(@Nonnull SlaveComputer computer, TaskListener listener) throw if (computer instanceof DockerComputer) { dockerComputer = (DockerComputer) computer; } else { - throw new IllegalArgumentException("Docker JNLP Launcher accepts only DockerComputer"); + listener.error("Docker JNLP Launcher accepts only DockerComputer.class"); + throw new IllegalArgumentException("Docker JNLP Launcher accepts only DockerComputer.class"); } Objects.requireNonNull(dockerComputer); @@ -154,6 +155,7 @@ public void launch(@Nonnull SlaveComputer computer, TaskListener listener) throw final DockerCloud dockerCloud = dockerComputer.getCloud(); // Objects.requireNonNull(dockerCloud, "Cloud not found for computer " + computer.getName()); if (isNull(dockerCloud)) { + listener.error("Cloud not found for computer " + computer.getName()); throw new NullPointerException("Cloud not found for computer " + computer.getName()); } final DockerClient connect = dockerCloud.getClient(); diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/launcher/DockerComputerSingleJNLPLauncher.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/launcher/DockerComputerSingleJNLPLauncher.java new file mode 100644 index 00000000..46eb07b4 --- /dev/null +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/launcher/DockerComputerSingleJNLPLauncher.java @@ -0,0 +1,298 @@ +package com.github.kostyasha.yad.launcher; + +import com.github.kostyasha.yad.DockerComputerSingle; +import com.github.kostyasha.yad.DockerContainerLifecycle; +import com.github.kostyasha.yad.DockerSlaveSingle; +import com.github.kostyasha.yad.action.DockerLabelAssignmentAction; +import com.github.kostyasha.yad.commons.DockerCreateContainer; +import com.github.kostyasha.yad_docker_java.com.github.dockerjava.api.DockerClient; +import com.github.kostyasha.yad_docker_java.com.github.dockerjava.api.command.CreateContainerCmd; +import com.github.kostyasha.yad_docker_java.com.github.dockerjava.api.command.CreateContainerResponse; +import com.github.kostyasha.yad_docker_java.com.github.dockerjava.api.command.ExecCreateCmdResponse; +import com.github.kostyasha.yad_docker_java.com.github.dockerjava.api.command.InspectContainerResponse; +import com.github.kostyasha.yad_docker_java.com.github.dockerjava.api.command.StartContainerCmd; +import com.github.kostyasha.yad_docker_java.com.github.dockerjava.api.exception.NotFoundException; +import com.github.kostyasha.yad_docker_java.com.github.dockerjava.core.command.ExecStartResultCallback; +import com.github.kostyasha.yad_docker_java.javax.ws.rs.ProcessingException; +import hudson.model.Run; +import hudson.model.TaskListener; +import hudson.slaves.JNLPLauncher; +import hudson.slaves.SlaveComputer; +import hudson.util.TimeUnit2; +import jenkins.model.Jenkins; +import org.apache.commons.io.Charsets; +import org.apache.commons.io.IOUtils; +import org.kohsuke.stapler.DataBoundSetter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.Nonnull; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; +import java.util.concurrent.TimeUnit; + +import static com.github.kostyasha.yad_docker_java.org.apache.commons.lang.BooleanUtils.isFalse; +import static com.github.kostyasha.yad_docker_java.org.apache.commons.lang.StringUtils.isNotEmpty; +import static com.github.kostyasha.yad_docker_java.org.apache.commons.lang.StringUtils.trimToEmpty; +import static java.util.Objects.isNull; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.apache.commons.lang.BooleanUtils.isTrue; + +/** + * @author Kanstantsin Shautsou + */ +public class DockerComputerSingleJNLPLauncher extends JNLPLauncher { + private static final Logger LOG = LoggerFactory.getLogger(DockerComputerSingleJNLPLauncher.class); + + private static final String NL = "\"\n"; + public static final long DEFAULT_TIMEOUT = 120L; + public static final String DEFAULT_USER = "jenkins"; + + protected long launchTimeout = DEFAULT_TIMEOUT; //seconds + + protected String user = "jenkins"; + + protected String jvmOpts = ""; + + protected String slaveOpts = ""; + + protected String jenkinsUrl = ""; + + protected boolean noCertificateCheck = false; + + @DataBoundSetter + public void setSlaveOpts(String slaveOpts) { + this.slaveOpts = trimToEmpty(slaveOpts); + } + + @Nonnull + public String getSlaveOpts() { + return trimToEmpty(slaveOpts); + } + + @DataBoundSetter + public void setJvmOpts(String jvmOpts) { + this.jvmOpts = trimToEmpty(jvmOpts); + } + + @Nonnull + public String getJvmOpts() { + return trimToEmpty(jvmOpts); + } + + @DataBoundSetter + public void setNoCertificateCheck(boolean noCertificateCheck) { + this.noCertificateCheck = noCertificateCheck; + } + + public boolean isNoCertificateCheck() { + return noCertificateCheck; + } + + @DataBoundSetter + public void setUser(String user) { + this.user = trimToEmpty(user); + } + + public String getUser() { + return trimToEmpty(user); + } + + public long getLaunchTimeout() { + return launchTimeout; + } + + @DataBoundSetter + public void setLaunchTimeout(long launchTimeout) { + this.launchTimeout = launchTimeout; + } + + @Nonnull + public String getJenkinsUrl(String rootUrl) { + return isNotEmpty(jenkinsUrl) ? jenkinsUrl : trimToEmpty(rootUrl); + } + + public void setJenkinsUrl(String jenkinsUrl) { + this.jenkinsUrl = trimToEmpty(jenkinsUrl); + } + + + @Override + public void launch(SlaveComputer computer, TaskListener listener) { + listener.getLogger().println("Launching " + computer.getDisplayName()); + try { + provisionWithWait((DockerComputerSingle) computer, listener); + } catch (IOException | InterruptedException e) { + e.printStackTrace(); + listener.error(e.toString()); + } + } + + /** + * Provision slave container and wait for it's availability. + */ + private void provisionWithWait(DockerComputerSingle computer, TaskListener listener) + throws IOException, InterruptedException { + final PrintStream logger = listener.getLogger(); + + final Run run = computer.getRun(); + final DockerLabelAssignmentAction action = run.getAction(DockerLabelAssignmentAction.class); + final DockerClient client = action.getConnector().getClient(); + // final DockerContainerLifecycle dockerContainerLifecycle = template.getDockerContainerLifecycle(); + final DockerContainerLifecycle containerLifecycle = new DockerContainerLifecycle(); + containerLifecycle.setImage("java:8-jdk-alpine"); + + final String imageId = containerLifecycle.getImage(); + + //pull image + logger.println("Pulling image " + imageId + "..."); + containerLifecycle.getPullImage().exec(client, imageId); + + logger.println("Trying to run container for " + imageId); + LOG.info("Trying to run container for {}", imageId); + final DockerCreateContainer createContainer = containerLifecycle.getCreateContainer(); + CreateContainerCmd containerConfig = client.createContainerCmd(imageId); + // template specific options + createContainer.fillContainerConfig(containerConfig); + + // cloud specific options + appendContainerConfig(containerConfig); + + // create + CreateContainerResponse createResp = containerConfig.exec(); + String containerId = createResp.getId(); + logger.println("Created container " + containerId + ", for " + run.getDisplayName()); + LOG.debug("Created container {}, for {}", containerId, run.getDisplayName()); + // start + StartContainerCmd startCommand = client.startContainerCmd(containerId); + startCommand.exec(); + logger.println("Started container " + containerId); + LOG.debug("Start container {}, for {}", containerId, run.getDisplayName()); + + boolean running = false; + long launchTime = System.currentTimeMillis(); + while (!running && + TimeUnit2.SECONDS.toMillis(launchTimeout) > System.currentTimeMillis() - launchTime) { + try { + InspectContainerResponse inspectResp = client.inspectContainerCmd(containerId).exec(); + if (isTrue(inspectResp.getState().getRunning())) { + logger.println("Container is running!"); + LOG.debug("Container {} is running", containerId); + running = true; + } else { + logger.println("Container is not running..."); + } + } catch (ProcessingException ignore) { + } + Thread.sleep(1000); + } + + if (!running) { + listener.error("Failed to run container for %s, clean-up container", imageId); + LOG.error("Failed to run container for {}, clean-up container", imageId); + containerLifecycle.getRemoveContainer().exec(client, containerId); + } + + // now real launch + final String rootUrl = getJenkinsUrl(Jenkins.getInstance().getRootUrl()); +// Objects.requireNonNull(rootUrl, "Jenkins root url is not specified!"); + if (isNull(rootUrl)) { + listener.fatalError("Jenkins root url is not specified!"); + containerLifecycle.getRemoveContainer().exec(client, containerId); + throw new IllegalStateException("Jenkins root url is not specified!"); + } + + // exec jnlp connection in running container + // TODO implement PID 1 replacement + String startCmd = + "cat << EOF > /tmp/config.sh.tmp && cd /tmp && mv config.sh.tmp config.sh\n" + + "JENKINS_URL=\"" + rootUrl + NL + + "JENKINS_USER=\"" + getUser() + NL + + "JENKINS_HOME=\"" + computer.getNode().getRemoteFS() + NL + + "COMPUTER_URL=\"" + computer.getUrl() + NL + + "COMPUTER_SECRET=\"" + computer.getJnlpMac() + NL + + "JAVA_OPTS=\"" + getJvmOpts() + NL + + "SLAVE_OPTS=\"" + getSlaveOpts() + NL + + "NO_CERTIFICATE_CHECK=\"" + isNoCertificateCheck() + NL + + "EOF" + "\n"; + + try { + final ExecCreateCmdResponse createCmdResponse = client.execCreateCmd(containerId) + .withTty(true) + .withAttachStdin(false) + .withAttachStderr(true) + .withAttachStdout(true) + .withCmd("/bin/sh", "-cxe", startCmd.replace("$", "\\$")) + .exec(); + + logger.println("Starting connection command for " + containerId); + LOG.info("Starting connection command for {}", containerId); + + try (ExecStartResultCallback exec = client.execStartCmd(createCmdResponse.getId()) + .withDetach(true) + .withTty(true) + .exec(new ExecStartResultCallback()) + ) { + exec.awaitCompletion(10 , SECONDS); + } catch (NotFoundException ex) { + listener.error("Can't execute command: " + ex.getMessage().trim()); + LOG.error("Can't execute jnlp connection command: '{}'", ex.getMessage().trim()); + containerLifecycle.getRemoveContainer().exec(client, containerId); + computer.getNode().terminate(); + throw ex; + } + } catch (Exception ex) { + listener.error("Can't execute command: " + ex.getMessage().trim()); + LOG.error("Can't execute jnlp connection command: '{}'", ex.getMessage().trim()); + containerLifecycle.getRemoveContainer().exec(client, containerId); + computer.getNode().terminate(); + throw ex; + } + + LOG.info("Successfully executed jnlp connection for '{}'", containerId); + logger.println("Successfully executed jnlp connection for " + containerId); + + // TODO better strategy + launchTime = System.currentTimeMillis(); + while (computer.isReallyOffline() && + TimeUnit2.SECONDS.toMillis(launchTimeout) > System.currentTimeMillis() - launchTime) { + logger.println("Waiting slave connection..."); + Thread.sleep(1000); + } + + if (computer.isReallyOffline()) { + LOG.info("Launch timeout, termintaing slave based on '{}'", containerId); + logger.println("Launch timeout, termintaing slave."); + containerLifecycle.getRemoveContainer().exec(client, containerId); + computer.getNode().terminate(); + throw new IOException("Can't connect slave to jenkins"); + } + + LOG.info("Launched slave '{}' '{}' based on '{}'", + computer.getSlaveVersion(), computer.getName(), containerId); + logger.println("Launched slave for " + containerId); + } + + + public void appendContainerConfig(CreateContainerCmd createContainerCmd) + throws IOException { + try (InputStream instream = DockerComputerJNLPLauncher.class.getResourceAsStream("DockerComputerJNLPLauncher/init.sh")) { + final String initCmd = IOUtils.toString(instream, Charsets.UTF_8); + if (initCmd == null) { + throw new IllegalStateException("Resource file 'init.sh' not found"); + } + + // wait for params + createContainerCmd.withCmd("/bin/sh", + "-cxe", + "cat << EOF >> /tmp/init.sh && chmod +x /tmp/init.sh && exec /tmp/init.sh\n" + + initCmd.replace("$", "\\$") + "\n" + + "EOF" + "\n" + ); + } + + createContainerCmd.withTty(true); + createContainerCmd.withStdinOpen(true); + } +} diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerQueueListener.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/listener/DockerQueueListener.java similarity index 58% rename from yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerQueueListener.java rename to yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/listener/DockerQueueListener.java index 9bba8b03..73d6988f 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerQueueListener.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/listener/DockerQueueListener.java @@ -1,20 +1,35 @@ -package com.github.kostyasha.yad; +package com.github.kostyasha.yad.listener; +import com.github.kostyasha.yad.DockerCloud; +import com.github.kostyasha.yad.DockerJobProperty; +import com.github.kostyasha.yad.DockerSlaveSingle; +import com.github.kostyasha.yad.DockerSlaveTemplate; import com.github.kostyasha.yad.action.DockerLabelAssignmentAction; import com.github.kostyasha.yad.jobconfig.DockerCloudJobConfig; import com.github.kostyasha.yad.jobconfig.SlaveJobConfig; +import com.github.kostyasha.yad.launcher.DockerComputerSingleJNLPLauncher; +import com.github.kostyasha.yad.strategy.DockerOnceRetentionStrategy; import hudson.Extension; +import hudson.model.Descriptor; import hudson.model.Job; +import hudson.model.Node; import hudson.model.Queue; +import hudson.model.TaskListener; import hudson.model.labels.LabelAtom; import hudson.model.queue.QueueListener; import hudson.slaves.Cloud; +import hudson.slaves.DelegatingComputerLauncher; +import hudson.slaves.SlaveComputer; import jenkins.model.Jenkins; +import org.jenkinsci.plugins.cloudstats.ProvisioningActivity; +import java.io.IOException; import java.util.UUID; +import static java.util.Collections.emptyList; import static java.util.Objects.isNull; import static java.util.Objects.nonNull; +import static org.jenkinsci.plugins.cloudstats.CloudStatistics.ProvisioningListener.get; /** * @author Kanstantsin Shautsou @@ -75,7 +90,32 @@ private boolean processJobWithProperty(Queue.WaitingItem wi) { private boolean processItemWithAction(Queue.WaitingItem wi) { final DockerLabelAssignmentAction action = wi.getAction(DockerLabelAssignmentAction.class); if (nonNull(action)) { - + try { + final ProvisioningActivity.Id activityId = new ProvisioningActivity.Id(wi.getDisplayName(), + "fake-image"); + get().onStarted(activityId); + + final DockerSlaveSingle slave = new DockerSlaveSingle("docker-slave", + "description", + "/home/jenkins", + 1, + Node.Mode.EXCLUSIVE, + "", // label string + new DelegatingComputerLauncher(new DockerComputerSingleJNLPLauncher()) { + @Override + public void launch(SlaveComputer computer, TaskListener listener) throws IOException, InterruptedException { + // no launch + } + }, + new DockerOnceRetentionStrategy(10), + emptyList(), + activityId + ); + Jenkins.getInstance().addNode(slave); + return true; + } catch (Descriptor.FormException | IOException e) { + e.printStackTrace(); + } } return false; } diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/listener/DockerRunListener.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/listener/DockerRunListener.java index cbc1422a..34eb2739 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/listener/DockerRunListener.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/listener/DockerRunListener.java @@ -1,20 +1,40 @@ package com.github.kostyasha.yad.listener; import com.github.kostyasha.yad.DockerComputer; +import com.github.kostyasha.yad.DockerComputerSingle; +import com.github.kostyasha.yad.DockerSlave; +import com.github.kostyasha.yad.DockerSlaveSingle; +import com.github.kostyasha.yad.action.DockerLabelAssignmentAction; +import com.github.kostyasha.yad_docker_java.com.github.dockerjava.api.DockerClient; import hudson.Extension; +import hudson.console.ConsoleLogFilter; +import hudson.model.AbstractBuild; +import hudson.model.BuildableItemWithBuildWrappers; import hudson.model.Computer; import hudson.model.Executor; +import hudson.model.Job; +import hudson.model.Node; import hudson.model.Run; +import hudson.model.StreamBuildListener; import hudson.model.TaskListener; import hudson.model.listeners.RunListener; +import hudson.slaves.DelegatingComputerLauncher; +import hudson.tasks.BuildWrapper; +import jenkins.model.Jenkins; import org.jenkinsci.plugins.docker.commons.fingerprint.DockerFingerprints; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.annotation.Nonnull; +import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.Charset; import java.text.ParseException; import static com.github.kostyasha.yad.utils.ContainerRecordUtils.createRecordFor; +import static java.util.Objects.isNull; /** * @author Kanstantsin Shautsou @@ -23,8 +43,48 @@ public class DockerRunListener extends RunListener> { private static final Logger LOG = LoggerFactory.getLogger(DockerRunListener.class); + +// @Override +// public void onInitialize(Run run) { +// final DockerLabelAssignmentAction assignmentAction = run.getAction(DockerLabelAssignmentAction.class); +// if (isNull(assignmentAction)) { +// return; +// } +// try { +// final StreamBuildListener listener = createBuildListener(run); +// final DockerClient client = assignmentAction.getConnector().getClient(); +// final Node node = Jenkins.getInstance().getNode(assignmentAction.getAssignedLabel()); +// final DockerSlaveSingle dockerSlave = (DockerSlaveSingle) node; +// final DockerComputerSingle computer = (DockerComputerSingle) dockerSlave.toComputer(); +// +// computer.getLauncher().launch(computer, (TaskListener) listener); +// } catch (IOException | InterruptedException e) { +// e.printStackTrace(); +// } +// +// } + @Override public void onStarted(Run run, TaskListener listener) { + final DockerLabelAssignmentAction assignmentAction = run.getAction(DockerLabelAssignmentAction.class); + if (isNull(assignmentAction)) { + return; + } + try { + final Node node = Jenkins.getInstance().getNode(assignmentAction.getAssignedLabel()); + final DockerSlaveSingle dockerSlave = (DockerSlaveSingle) node; + final DockerComputerSingle computer = (DockerComputerSingle) dockerSlave.toComputer(); + computer.setRun(run); + computer.setListener(listener); + ((DelegatingComputerLauncher) computer.getLauncher()).getLauncher().launch(computer, listener); + } catch (IOException | InterruptedException e) { + LOG.error("fd", e); + } + + attachFacet(run, listener); + } + + private void attachFacet(Run run, TaskListener listener) { final Executor executor = run.getExecutor(); if (executor == null) { return; @@ -44,7 +104,31 @@ public void onStarted(Run run, TaskListener listener) { run ); } catch (IOException | ParseException e) { + listener.error("Can't add Docker fingerprint to run."); LOG.error("Can't add fingerprint to run {}", run, e); } } + + private StreamBuildListener createBuildListener(@Nonnull Run run) throws IOException, InterruptedException { + // don't do buffering so that what's written to the listener + // gets reflected to the file immediately, which can then be + // served to the browser immediately + OutputStream logger = new FileOutputStream(run.getLogFile(), true); + Job job = run.getParent(); + + // Global log filters + for (ConsoleLogFilter filter : ConsoleLogFilter.all()) { + logger = filter.decorateLogger(run, logger); + } + + // Project specific log filters + if (job instanceof BuildableItemWithBuildWrappers && run instanceof AbstractBuild) { + BuildableItemWithBuildWrappers biwbw = (BuildableItemWithBuildWrappers) job; + for (BuildWrapper bw : biwbw.getBuildWrappersList()) { + logger = bw.decorateLogger((AbstractBuild) run, logger); + } + } + + return new StreamBuildListener(logger, run.getCharset()); + } } diff --git a/yet-another-docker-plugin/src/test/java/com/github/kostyasha/yad/TaskStepTest.java b/yet-another-docker-plugin/src/test/java/com/github/kostyasha/yad/TaskStepTest.java new file mode 100644 index 00000000..d8e2f36b --- /dev/null +++ b/yet-another-docker-plugin/src/test/java/com/github/kostyasha/yad/TaskStepTest.java @@ -0,0 +1,47 @@ +package com.github.kostyasha.yad; + +import com.github.kostyasha.yad.DockerCloud; +import com.github.kostyasha.yad.DockerSlaveTemplate; +import com.github.kostyasha.yad.action.DockerLabelAssignmentAction; +import com.github.kostyasha.yad.launcher.DockerComputerJNLPLauncher; +import com.github.kostyasha.yad.other.ConnectorType; +import com.github.kostyasha.yad.step.DockerTask; +import com.github.kostyasha.yad.strategy.DockerOnceRetentionStrategy; +import hudson.model.Node; +import hudson.model.Queue; +import jenkins.model.Jenkins; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.JenkinsRule; + +import static com.github.kostyasha.yad.other.ConnectorType.NETTY; +import static java.util.Collections.emptyList; + +/** + * @author Kanstantsin Shautsou + */ +public class TaskStepTest { + @Rule + public JenkinsRule jRule = new JenkinsRule(); + + @Test + public void actionTask() throws Exception { + final Jenkins jenkins = jRule.getInstance(); + + final DockerSlaveTemplate slaveTemplate = new DockerSlaveTemplate(); + + slaveTemplate.setLabelString("docker-slave"); + +// final DockerCloud dockerCloud = new DockerCloud("localTestCloud"); + + final DockerLabelAssignmentAction assignmentAction = new DockerLabelAssignmentAction("docker-slave"); + final DockerConnector dockerConnector = new DockerConnector("tcp://192.168.1.3:2376/"); + dockerConnector.setConnectorType(NETTY); + + assignmentAction.setConnector(dockerConnector); + + final Queue.WaitingItem waitingItem = jenkins.getQueue().schedule(new DockerTask(), 0, assignmentAction); + + jRule.waitUntilNoActivity(); + } +} diff --git a/yet-another-docker-plugin/src/test/java/com/github/kostyasha/yad/step/TaskStepTest.java b/yet-another-docker-plugin/src/test/java/com/github/kostyasha/yad/step/TaskStepTest.java deleted file mode 100644 index f0d7559a..00000000 --- a/yet-another-docker-plugin/src/test/java/com/github/kostyasha/yad/step/TaskStepTest.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.github.kostyasha.yad.step; - -import com.github.kostyasha.yad.DockerCloud; -import com.github.kostyasha.yad.DockerSlaveTemplate; -import com.github.kostyasha.yad.action.DockerLabelAssignmentAction; -import hudson.model.Queue; -import jenkins.model.Jenkins; -import org.junit.Rule; -import org.junit.Test; -import org.jvnet.hudson.test.JenkinsRule; - -/** - * @author Kanstantsin Shautsou - */ -public class TaskStepTest { - @Rule - public JenkinsRule jRule = new JenkinsRule(); - - @Test - public void actionTask() throws Exception { - final Jenkins jenkins = jRule.getInstance(); - - final DockerSlaveTemplate slaveTemplate = new DockerSlaveTemplate(); - - slaveTemplate.setLabelString(); - -// final DockerCloud dockerCloud = new DockerCloud("localTestCloud"); - - new DockerLabelAssignmentAction() - final Queue.WaitingItem waitingItem = jenkins.getQueue().schedule(new DockerTask(), 0, ); - jRule.waitUntilNoActivity(); - } -} diff --git a/yet-another-docker-plugin/src/test/java/org/jvnet/hudson/test/DockerLabelAssignmentActionTest.java b/yet-another-docker-plugin/src/test/java/org/jvnet/hudson/test/DockerLabelAssignmentActionTest.java new file mode 100644 index 00000000..ae5d754b --- /dev/null +++ b/yet-another-docker-plugin/src/test/java/org/jvnet/hudson/test/DockerLabelAssignmentActionTest.java @@ -0,0 +1,100 @@ +package org.jvnet.hudson.test; + +import com.github.kostyasha.yad.DockerConnector; +import com.github.kostyasha.yad.DockerSlaveTemplate; +import com.github.kostyasha.yad.action.DockerLabelAssignmentAction; +import hudson.model.FreeStyleProject; +import hudson.model.queue.QueueTaskFuture; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.HttpConnectionFactory; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.webapp.Configuration; +import org.eclipse.jetty.webapp.WebAppContext; +import org.eclipse.jetty.webapp.WebXmlConfiguration; +import org.junit.Rule; +import org.junit.Test; +import org.slf4j.Logger; + +import javax.servlet.ServletContext; +import java.io.IOException; +import java.net.URL; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import static com.github.kostyasha.yad.other.ConnectorType.JERSEY; +import static com.github.kostyasha.yad.other.ConnectorType.NETTY; + +/** + * @author Kanstantsin Shautsou + */ +public class DockerLabelAssignmentActionTest { + private static final Logger LOGGER = org.slf4j.LoggerFactory.getLogger(DockerLabelAssignmentActionTest.class); + + private static final String ADDRESS = "192.168.1.3"; + + @Rule + public JenkinsRule jRule = new JenkinsRule() { + @Override + public URL getURL() throws IOException { + return new URL("http://" + ADDRESS + ":" + localPort + contextPath + "/"); + } + + @Override + protected ServletContext createWebServer() throws Exception { + server = new Server(new ThreadPoolImpl(new ThreadPoolExecutor(10, 10, 10L, TimeUnit.SECONDS, new LinkedBlockingQueue(), new ThreadFactory() { + public Thread newThread(Runnable r) { + Thread t = new Thread(r); + t.setName("Jetty Thread Pool"); + return t; + } + }))); + + WebAppContext context = new WebAppContext(WarExploder.getExplodedDir().getPath(), contextPath); + context.setClassLoader(getClass().getClassLoader()); + context.setConfigurations(new Configuration[]{new WebXmlConfiguration()}); + context.addBean(new NoListenerConfiguration(context)); + server.setHandler(context); + context.setMimeTypes(MIME_TYPES); + context.getSecurityHandler().setLoginService(configureUserRealm()); + context.setResourceBase(WarExploder.getExplodedDir().getPath()); + + ServerConnector connector = new ServerConnector(server); + HttpConfiguration config = connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration(); + // use a bigger buffer as Stapler traces can get pretty large on deeply nested URL + config.setRequestHeaderSize(12 * 1024); + connector.setHost(ADDRESS); + if (System.getProperty("port") != null) + connector.setPort(Integer.parseInt(System.getProperty("port"))); + + server.addConnector(connector); + server.start(); + + localPort = connector.getLocalPort(); + LOGGER.info("Running on {}", getURL()); + + return context.getServletContext(); + } + }; + + @Test + public void actionTask() throws Exception { + + final FreeStyleProject project = jRule.createProject(FreeStyleProject.class, "test"); + + final DockerSlaveTemplate slaveTemplate = new DockerSlaveTemplate(); + + final DockerLabelAssignmentAction assignmentAction = new DockerLabelAssignmentAction("docker-slave"); + final DockerConnector dockerConnector = new DockerConnector("tcp://192.168.1.3:2376/"); + dockerConnector.setConnectorType(JERSEY); + + assignmentAction.setConnector(dockerConnector); + + final QueueTaskFuture build2 = project.scheduleBuild2(0, assignmentAction); + + jRule.pause(); + jRule.waitUntilNoActivity(); + } +} \ No newline at end of file From 4f3634c93a30635103b856d1f6dc6aa1db046f5a Mon Sep 17 00:00:00 2001 From: Kanstantsin Shautsou Date: Wed, 25 Jan 2017 02:47:03 +0300 Subject: [PATCH 06/17] fix Signed-off-by: Kanstantsin Shautsou --- .../main/java/com/github/kostyasha/yad/DockerSlaveSingle.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlaveSingle.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlaveSingle.java index 021d97ae..8dcf872e 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlaveSingle.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlaveSingle.java @@ -67,6 +67,6 @@ protected void _terminate(TaskListener listener) throws IOException, Interrupted @Nullable @Override public ProvisioningActivity.Id getId() { - return null; + return activityId; } } \ No newline at end of file From 1c2937c6acde1d48ffd1000c530e9d04d07c4bf7 Mon Sep 17 00:00:00 2001 From: Kanstantsin Shautsou Date: Sun, 29 Jan 2017 23:32:15 +0300 Subject: [PATCH 07/17] Rm unused Signed-off-by: Kanstantsin Shautsou --- .../yad/listener/DockerRunListener.java | 44 ------------------- 1 file changed, 44 deletions(-) diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/listener/DockerRunListener.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/listener/DockerRunListener.java index 34eb2739..8913ea10 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/listener/DockerRunListener.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/listener/DockerRunListener.java @@ -43,27 +43,6 @@ public class DockerRunListener extends RunListener> { private static final Logger LOG = LoggerFactory.getLogger(DockerRunListener.class); - -// @Override -// public void onInitialize(Run run) { -// final DockerLabelAssignmentAction assignmentAction = run.getAction(DockerLabelAssignmentAction.class); -// if (isNull(assignmentAction)) { -// return; -// } -// try { -// final StreamBuildListener listener = createBuildListener(run); -// final DockerClient client = assignmentAction.getConnector().getClient(); -// final Node node = Jenkins.getInstance().getNode(assignmentAction.getAssignedLabel()); -// final DockerSlaveSingle dockerSlave = (DockerSlaveSingle) node; -// final DockerComputerSingle computer = (DockerComputerSingle) dockerSlave.toComputer(); -// -// computer.getLauncher().launch(computer, (TaskListener) listener); -// } catch (IOException | InterruptedException e) { -// e.printStackTrace(); -// } -// -// } - @Override public void onStarted(Run run, TaskListener listener) { final DockerLabelAssignmentAction assignmentAction = run.getAction(DockerLabelAssignmentAction.class); @@ -108,27 +87,4 @@ private void attachFacet(Run run, TaskListener listener) { LOG.error("Can't add fingerprint to run {}", run, e); } } - - private StreamBuildListener createBuildListener(@Nonnull Run run) throws IOException, InterruptedException { - // don't do buffering so that what's written to the listener - // gets reflected to the file immediately, which can then be - // served to the browser immediately - OutputStream logger = new FileOutputStream(run.getLogFile(), true); - Job job = run.getParent(); - - // Global log filters - for (ConsoleLogFilter filter : ConsoleLogFilter.all()) { - logger = filter.decorateLogger(run, logger); - } - - // Project specific log filters - if (job instanceof BuildableItemWithBuildWrappers && run instanceof AbstractBuild) { - BuildableItemWithBuildWrappers biwbw = (BuildableItemWithBuildWrappers) job; - for (BuildWrapper bw : biwbw.getBuildWrappersList()) { - logger = bw.decorateLogger((AbstractBuild) run, logger); - } - } - - return new StreamBuildListener(logger, run.getCharset()); - } } From 785161d6bff0fb8024a5d1eb5a5ce1439eead741 Mon Sep 17 00:00:00 2001 From: Kanstantsin Shautsou Date: Mon, 30 Jan 2017 03:20:11 +0300 Subject: [PATCH 08/17] WIP Signed-off-by: Kanstantsin Shautsou --- .../com/github/kostyasha/yad/DockerCloud.java | 58 ++--- .../github/kostyasha/yad/DockerConnector.java | 2 +- .../yad/DockerQueueTaskDispatcher.java | 13 -- .../com/github/kostyasha/yad/DockerSlave.java | 1 + .../kostyasha/yad/DockerSlaveConfig.java | 210 ++++++++++++++++++ .../kostyasha/yad/DockerSlaveTemplate.java | 159 ++----------- .../action/DockerLabelAssignmentAction.java | 40 ++-- .../yad/client/ClientBuilderForConnector.java | 46 ++-- .../CredentialsYADockerConnector.java | 145 ++++++++++++ .../yad/connector/DockerCloudConnector.java | 10 - .../yad/connector/DockerCloudConnectorId.java | 4 +- .../yad/connector/YADockerConnector.java | 2 +- .../launcher/NoOpComputerLauncherFilter.java | 14 -- .../NoOpDelegatingComputerLauncher.java | 36 +++ .../yad/listener/DockerQueueListener.java | 121 +++++----- .../DockerQueueDecisionHandler.java | 2 +- .../yad/queue/DockerQueueTaskDispatcher.java | 26 +++ .../{ => queue}/FlyweightCauseOfBlockage.java | 2 +- .../yad/queue/SingleNodeCauseOfBlockage.java | 23 ++ .../config.groovy | 2 +- .../github/kostyasha/yad/TaskStepTest.java | 8 +- .../test/DockerLabelAssignmentActionTest.java | 33 +-- 22 files changed, 623 insertions(+), 334 deletions(-) delete mode 100644 yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerQueueTaskDispatcher.java create mode 100644 yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlaveConfig.java create mode 100644 yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/connector/CredentialsYADockerConnector.java delete mode 100644 yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/connector/DockerCloudConnector.java delete mode 100644 yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/launcher/NoOpComputerLauncherFilter.java create mode 100644 yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/launcher/NoOpDelegatingComputerLauncher.java rename yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/{ => queue}/DockerQueueDecisionHandler.java (89%) create mode 100644 yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/queue/DockerQueueTaskDispatcher.java rename yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/{ => queue}/FlyweightCauseOfBlockage.java (87%) create mode 100644 yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/queue/SingleNodeCauseOfBlockage.java diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerCloud.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerCloud.java index bbdc2f39..d897d34e 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerCloud.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerCloud.java @@ -2,6 +2,7 @@ import com.github.kostyasha.yad.commons.AbstractCloud; import com.github.kostyasha.yad.commons.DockerCreateContainer; +import com.github.kostyasha.yad.launcher.DockerComputerLauncher; import com.github.kostyasha.yad_docker_java.com.github.dockerjava.api.DockerClient; import com.github.kostyasha.yad_docker_java.com.github.dockerjava.api.command.CreateContainerCmd; import com.github.kostyasha.yad_docker_java.com.github.dockerjava.api.command.CreateContainerResponse; @@ -102,7 +103,7 @@ public synchronized Collection provision(@CheckForNull Label label, final DockerSlaveTemplate t = tryTemplates.get(0); // get first LOG.info("Will provision '{}', for label: '{}', in cloud: '{}'", - t.getDockerContainerLifecycle().getImage(), label, getDisplayName()); + t.getDockerContainerLifecycle().getImage(), label, getDisplayName()); try { if (!addProvisionedSlave(t)) { @@ -111,34 +112,34 @@ public synchronized Collection provision(@CheckForNull Label label, } } catch (Exception e) { LOG.warn("Bad template '{}' in cloud '{}': '{}'. Trying next template...", - t.getDockerContainerLifecycle().getImage(), getDisplayName(), e.getMessage(), e); + t.getDockerContainerLifecycle().getImage(), getDisplayName(), e.getMessage(), e); tryTemplates.remove(t); continue; } final ProvisioningActivity.Id id = new ProvisioningActivity.Id(getDisplayName(), - t.getDockerContainerLifecycle().getImage()); + t.getDockerContainerLifecycle().getImage()); r.add(new TrackedPlannedNode( - id, - t.getNumExecutors(), - Computer.threadPoolForRemoting.submit(() -> { - get().onStarted(id); - try { - final DockerSlave dockerSlave = provisionWithWait(t, id); - get().onComplete(id, dockerSlave); //rename - return dockerSlave; - } catch (Exception ex) { - LOG.error("Error in provisioning; template='{}' for cloud='{}'", - t, getDisplayName(), ex); - get().onFailure(id, ex); - throw Throwables.propagate(ex); - } finally { - decrementAmiSlaveProvision(t); - } - }) - ) + id, + t.getNumExecutors(), + Computer.threadPoolForRemoting.submit(() -> { + get().onStarted(id); + try { + final DockerSlave dockerSlave = provisionWithWait(t, id); + get().onComplete(id, dockerSlave); //rename + return dockerSlave; + } catch (Exception ex) { + LOG.error("Error in provisioning; template='{}' for cloud='{}'", + t, getDisplayName(), ex); + get().onFailure(id, ex); + throw Throwables.propagate(ex); + } finally { + decrementAmiSlaveProvision(t); + } + }) + ) ); excessWorkload -= t.getNumExecutors(); } @@ -198,9 +199,10 @@ private void appendContainerConfig(DockerSlaveTemplate slaveTemplate, CreateCont * Provision slave container and wait for it's availability. */ private DockerSlave provisionWithWait(DockerSlaveTemplate template, ProvisioningActivity.Id id) - throws IOException, Descriptor.FormException { + throws IOException, Descriptor.FormException { final DockerContainerLifecycle dockerContainerLifecycle = template.getDockerContainerLifecycle(); final String imageId = dockerContainerLifecycle.getImage(); + final DockerComputerLauncher computerLauncher = template.getLauncher(); //pull image dockerContainerLifecycle.getPullImage().exec(getClient(), imageId); @@ -228,13 +230,13 @@ private DockerSlave provisionWithWait(DockerSlaveTemplate template, Provisioning String slaveName = String.format("%s-%s", getDisplayName(), containerId.substring(0, 12)); - if (template.getLauncher().waitUp(getDisplayName(), template, ir)) { + if (computerLauncher.waitUp(getDisplayName(), template, ir)) { LOG.debug("Container {} is ready for ssh slave connection", containerId); } else { LOG.error("Container {} is not ready for ssh slave connection.", containerId); } - final ComputerLauncher launcher = template.getLauncher().getPreparedLauncher(getDisplayName(), template, ir); + final ComputerLauncher launcher = computerLauncher.getPreparedLauncher(getDisplayName(), template, ir); return new DockerSlave(slaveName, nodeDescription, launcher, containerId, template, getDisplayName(), id); } @@ -255,7 +257,7 @@ public int countCurrentDockerSlaves(final DockerSlaveTemplate template) throws E // count only total cloud capacity count++; } else if (labels.containsKey(DOCKER_TEMPLATE_LABEL) && - labels.get(DOCKER_TEMPLATE_LABEL).equals(template.getId())) { + labels.get(DOCKER_TEMPLATE_LABEL).equals(template.getId())) { count++; } } @@ -289,18 +291,18 @@ private synchronized boolean addProvisionedSlave(DockerSlaveTemplate template) t if (estimatedTotalSlaves >= getContainerCap()) { LOG.info("Not Provisioning '{}'; Server '{}' full with '{}' container(s)", - dockerImageName, name, getContainerCap()); + dockerImageName, name, getContainerCap()); return false; // maxed out } if (templateCapacity != 0 && estimatedAmiSlaves >= templateCapacity) { LOG.info("Not Provisioning '{}'. Instance limit of '{}' reached on server '{}'", - dockerImageName, templateCapacity, name); + dockerImageName, templateCapacity, name); return false; // maxed out } LOG.info("Provisioning '{}' number '{}' on '{}'; Total containers: '{}'", - dockerImageName, estimatedAmiSlaves, name, estimatedTotalSlaves); + dockerImageName, estimatedAmiSlaves, name, estimatedTotalSlaves); provisionedImages.put(template, currentProvisioning + 1); return true; diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerConnector.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerConnector.java index 0cbc8508..257e7645 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerConnector.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerConnector.java @@ -215,7 +215,7 @@ public FormValidation doTestConnection( final DockerClient testClient = newClientBuilderForConnector() .withConfigBuilder(configBuilder) .withConnectorType(connectorType) - .withCredentials(credentialsId) + .withCredentialsId(credentialsId) .withConnectTimeout(connectTimeout) .build(); diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerQueueTaskDispatcher.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerQueueTaskDispatcher.java deleted file mode 100644 index f5877656..00000000 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerQueueTaskDispatcher.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.github.kostyasha.yad; - -import hudson.Extension; -import hudson.model.queue.QueueTaskDispatcher; - -/** - * @author Kanstantsin Shautsou - */ -@Extension -public class DockerQueueTaskDispatcher extends QueueTaskDispatcher { - - -} diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlave.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlave.java index c076f619..a1b07f34 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlave.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlave.java @@ -1,6 +1,7 @@ package com.github.kostyasha.yad; import com.github.kostyasha.yad.action.DockerTerminateCmdAction; +import com.github.kostyasha.yad.queue.FlyweightCauseOfBlockage; import com.github.kostyasha.yad_docker_java.com.github.dockerjava.api.DockerClient; import com.github.kostyasha.yad_docker_java.com.github.dockerjava.api.exception.NotModifiedException; import com.github.kostyasha.yad_docker_java.com.google.common.base.MoreObjects; diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlaveConfig.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlaveConfig.java new file mode 100644 index 00000000..631883d4 --- /dev/null +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlaveConfig.java @@ -0,0 +1,210 @@ +package com.github.kostyasha.yad; + +import com.github.kostyasha.yad.launcher.DockerComputerJNLPLauncher; +import com.github.kostyasha.yad.strategy.DockerOnceRetentionStrategy; +import com.github.kostyasha.yad_docker_java.com.google.common.base.Strings; +import hudson.Extension; +import hudson.Util; +import hudson.model.AbstractDescribableImpl; +import hudson.model.Descriptor; +import hudson.model.Node; +import hudson.slaves.ComputerLauncher; +import hudson.slaves.NodeProperty; +import hudson.slaves.NodePropertyDescriptor; +import hudson.slaves.RetentionStrategy; +import hudson.util.DescribableList; +import hudson.util.FormValidation; +import jenkins.model.Jenkins; +import org.apache.commons.lang.builder.EqualsBuilder; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; +import org.kohsuke.stapler.DataBoundSetter; +import org.kohsuke.stapler.QueryParameter; + +import javax.annotation.Nonnull; +import java.io.IOException; +import java.util.List; + +import static java.util.Collections.emptyList; +import static java.util.Collections.unmodifiableList; +import static java.util.Objects.isNull; +import static java.util.Objects.nonNull; + +/** + * @author Kanstantsin Shautsou + */ +public class DockerSlaveConfig extends AbstractDescribableImpl { + /** + * Unique id of this template configuration. Required for: + * - hashcode, + * - cloud counting + */ + @Nonnull + protected final String id; + + private String labelString = "docker"; + + protected ComputerLauncher launcher = new DockerComputerJNLPLauncher(); + + private String remoteFs = "/home/jenkins"; + + protected Node.Mode mode = Node.Mode.EXCLUSIVE; + + protected RetentionStrategy retentionStrategy = new DockerOnceRetentionStrategy(10); + + protected int numExecutors = 1; + + /** + * Bundle class that contains all docker related actions/configs + */ + protected DockerContainerLifecycle dockerContainerLifecycle = new DockerContainerLifecycle(); + + private List> nodeProperties = null; + + public DockerSlaveConfig(@Nonnull String id) { + this.id = id; + } + + public DockerContainerLifecycle getDockerContainerLifecycle() { + return dockerContainerLifecycle; + } + + @DataBoundSetter + public void setDockerContainerLifecycle(DockerContainerLifecycle dockerContainerLifecycle) { + this.dockerContainerLifecycle = dockerContainerLifecycle; + } + + public String getLabelString() { + return labelString; + } + + @DataBoundSetter + public void setLabelString(String labelString) { + this.labelString = Util.fixNull(labelString); + } + + @DataBoundSetter + public void setMode(Node.Mode mode) { + this.mode = mode; + } + + public Node.Mode getMode() { + return mode; + } + + /** + * Experimental option allows set number of executors + */ + @DataBoundSetter + public void setNumExecutors(int numExecutors) { + this.numExecutors = numExecutors; + } + + public int getNumExecutors() { + return numExecutors; + } + + @DataBoundSetter + public void setRetentionStrategy(RetentionStrategy retentionStrategy) { + this.retentionStrategy = retentionStrategy; + } + + public RetentionStrategy getRetentionStrategy() { + return retentionStrategy; + } + + @DataBoundSetter + public void setLauncher(ComputerLauncher launcher) { + this.launcher = launcher; + } + + public ComputerLauncher getLauncher() { + return launcher; + } + + @Nonnull + public String getRemoteFs() { + return Strings.isNullOrEmpty(remoteFs) ? "/home/jenkins" : remoteFs; + } + + @DataBoundSetter + public void setRemoteFs(String remoteFs) { + this.remoteFs = remoteFs; + } + + @Nonnull + @Restricted(value = NoExternalUse.class) // ancient UI jelly form + public DescribableList, NodePropertyDescriptor> getNodePropertiesUI() throws IOException { + return new DescribableList<>(Jenkins.getActiveInstance().getNodesObject(), getNodeProperties()); + } + + @Restricted(value = NoExternalUse.class) // ancient UI jelly form + public void setNodePropertiesUI(DescribableList, NodePropertyDescriptor> nodePropertiesUI) { + setNodeProperties(nodePropertiesUI); + } + + @Nonnull + public List> getNodeProperties() { + return nonNull(nodeProperties) ? unmodifiableList(nodeProperties) : emptyList(); + } + + public void setNodeProperties(List> nodeProperties) { + this.nodeProperties = nodeProperties; + } + + /** + * Id used for counting running slaves + */ + @Nonnull + public String getId() { + return id; + } + + protected Object readResolve() { + + return this; + } + + @Override + public int hashCode() { + return id.hashCode(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (!(o instanceof DockerSlaveConfig)) return false; + + DockerSlaveConfig that = (DockerSlaveConfig) o; + + return new EqualsBuilder() + .append(numExecutors, that.numExecutors) + .append(id, that.id) + .append(labelString, that.labelString) + .append(launcher, that.launcher) + .append(remoteFs, that.remoteFs) + .append(mode, that.mode) + .append(retentionStrategy, that.retentionStrategy) + .append(dockerContainerLifecycle, that.dockerContainerLifecycle) +// .append(nodeProperties, that.nodeProperties) + .isEquals(); + } + + @Extension + public static class DescriptorImpl extends Descriptor { + public FormValidation doCheckLabelString(@QueryParameter String labelString) { + if (isNull(labelString)) { + return FormValidation.warning("Please specify some label"); + } + + return FormValidation.ok(); + } + + @Nonnull + @Override + public String getDisplayName() { + return "Docker Slave Configuration"; + } + } +} diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlaveTemplate.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlaveTemplate.java index 53106517..720b8cb8 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlaveTemplate.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlaveTemplate.java @@ -8,12 +8,14 @@ import com.github.kostyasha.yad.strategy.DockerOnceRetentionStrategy; import hudson.Extension; import hudson.Util; +import hudson.model.AbstractDescribableImpl; import hudson.model.Describable; import hudson.model.Descriptor; import hudson.model.Descriptor.FormException; import hudson.model.Label; import hudson.model.Node; import hudson.model.labels.LabelAtom; +import hudson.slaves.ComputerLauncher; import hudson.slaves.NodeProperty; import hudson.slaves.NodePropertyDescriptor; import hudson.slaves.RetentionStrategy; @@ -46,57 +48,28 @@ /** * All configuration (jenkins and docker specific) required for launching slave instances. */ -public class DockerSlaveTemplate implements Describable { +public class DockerSlaveTemplate extends DockerSlaveConfig { private static final Logger LOG = LoggerFactory.getLogger(DockerSlaveTemplate.class); - /** - * Unique id of this template configuration. Required for: - * - hashcode, - * - cloud counting - */ - @Nonnull - private final String id; - - private String labelString = "docker"; - - private transient String remoteFsMapping; - - private DockerComputerLauncher launcher = new DockerComputerJNLPLauncher(); - - private String remoteFs = "/home/jenkins"; - private int maxCapacity = 10; - private Node.Mode mode = Node.Mode.EXCLUSIVE; - - private RetentionStrategy retentionStrategy = new DockerOnceRetentionStrategy(10); - - private int numExecutors = 1; - - /** - * Bundle class that contains all docker related actions/configs - */ - private DockerContainerLifecycle dockerContainerLifecycle = new DockerContainerLifecycle(); - - private List> nodeProperties = null; - private transient /*almost final*/ Set labelSet; /** * Generates new unique ID for new instances. */ public DockerSlaveTemplate() { - this.id = UUID.randomUUID().toString(); + super(UUID.randomUUID().toString()); } /** * Custom specified ID. When editing existed UI entry, UI sends it back. */ public DockerSlaveTemplate(@Nonnull String id) throws FormException { - if (id == null) { + super(id); + if (isNull(id)) { throw new FormException("Hidden id must not be null", "id"); } - this.id = id; } /** @@ -104,47 +77,18 @@ public DockerSlaveTemplate(@Nonnull String id) throws FormException { */ @DataBoundConstructor public DockerSlaveTemplate(@Nonnull String id, List> nodePropertiesUI) - throws FormException { + throws FormException { this(id); setNodeProperties(nodePropertiesUI); } - public DockerContainerLifecycle getDockerContainerLifecycle() { - return dockerContainerLifecycle; - } - - @DataBoundSetter - public void setDockerContainerLifecycle(DockerContainerLifecycle dockerContainerLifecycle) { - this.dockerContainerLifecycle = dockerContainerLifecycle; - } - - public String getLabelString() { - return labelString; - } @DataBoundSetter public void setLabelString(String labelString) { - this.labelString = Util.fixNull(labelString); + super.setLabelString(labelString); this.labelSet = Label.parse(labelString); } - @DataBoundSetter - public void setMode(Node.Mode mode) { - this.mode = mode; - } - - public Node.Mode getMode() { - return mode; - } - - /** - * Experimental option allows set number of executors - */ - @DataBoundSetter - public void setNumExecutors(int numExecutors) { - this.numExecutors = numExecutors; - } - public int getNumExecutors() { if (getRetentionStrategy() instanceof DockerOnceRetentionStrategy) { return 1; // works only with one executor! @@ -153,15 +97,6 @@ public int getNumExecutors() { return numExecutors; } - @DataBoundSetter - public void setRetentionStrategy(RetentionStrategy retentionStrategy) { - this.retentionStrategy = retentionStrategy; - } - - public RetentionStrategy getRetentionStrategy() { - return retentionStrategy; - } - /** * tmp fix for terminating boolean caching */ @@ -173,25 +108,6 @@ public RetentionStrategy getRetentionStrategyCopy() { return retentionStrategy; } - @DataBoundSetter - public void setLauncher(DockerComputerLauncher launcher) { - this.launcher = launcher; - } - - public DockerComputerLauncher getLauncher() { - return launcher; - } - - @Nonnull - public String getRemoteFs() { - return Strings.isNullOrEmpty(remoteFs) ? "/home/jenkins" : remoteFs; - } - - @DataBoundSetter - public void setRemoteFs(String remoteFs) { - this.remoteFs = remoteFs; - } - public int getMaxCapacity() { return maxCapacity; } @@ -203,27 +119,12 @@ public void setMaxCapacity(int maxCapacity) { @Nonnull public Set getLabelSet() { - return labelSet != null ? labelSet : Collections.emptySet(); + return labelSet != null ? labelSet : Collections.emptySet(); } - @Nonnull - @Restricted(value = NoExternalUse.class) // ancient UI jelly form - public DescribableList, NodePropertyDescriptor> getNodePropertiesUI() throws IOException { - return new DescribableList<>(Jenkins.getActiveInstance().getNodesObject(), getNodeProperties()); - } - - @Restricted(value = NoExternalUse.class) // ancient UI jelly form - public void setNodePropertiesUI(DescribableList, NodePropertyDescriptor> nodePropertiesUI) { - setNodeProperties(nodePropertiesUI); - } - - @Nonnull - public List> getNodeProperties() { - return nonNull(nodeProperties) ? unmodifiableList(nodeProperties) : emptyList(); - } - - public void setNodeProperties(List> nodeProperties) { - this.nodeProperties = nodeProperties; + @Override + public DockerComputerLauncher getLauncher() { + return (DockerComputerLauncher) super.getLauncher(); } /** @@ -240,7 +141,7 @@ public Object readResolve() { } try { - labelSet = Label.parse(labelString); // fails sometimes under debugger + labelSet = Label.parse(getLabelString()); // fails sometimes under debugger } catch (Throwable t) { LOG.error("Can't parse labels: {}", t); } @@ -251,6 +152,7 @@ public Object readResolve() { /** * Id used for counting running slaves */ + @Nonnull public String getId() { return id; } @@ -269,17 +171,9 @@ public boolean equals(Object o) { DockerSlaveTemplate that = (DockerSlaveTemplate) o; return new EqualsBuilder() - .append(maxCapacity, that.maxCapacity) - .append(numExecutors, that.numExecutors) - .append(id, that.id) - .append(labelString, that.labelString) - .append(launcher, that.launcher) - .append(remoteFs, that.remoteFs) - .append(mode, that.mode) - .append(retentionStrategy, that.retentionStrategy) - .append(dockerContainerLifecycle, that.dockerContainerLifecycle) -// .append(nodeProperties, that.nodeProperties) - .isEquals(); + .appendSuper(true) + .append(maxCapacity, that.maxCapacity) + .isEquals(); } @Override @@ -289,23 +183,17 @@ public String toString() { public String getShortDescription() { return MoreObjects.toStringHelper(this) - .add("image", dockerContainerLifecycle.getImage()) - .toString(); + .add("image", dockerContainerLifecycle.getImage()) + .toString(); } - public Descriptor getDescriptor() { - return (DescriptorImpl) Jenkins.getActiveInstance().getDescriptor(getClass()); + @Override + public DescriptorImpl getDescriptor() { + return (DescriptorImpl) super.getDescriptor(); } @Extension - public static final class DescriptorImpl extends Descriptor { - public FormValidation doCheckLabelString(@QueryParameter String labelString) { - if (isNull(labelString)) { - return FormValidation.warning("Please specify some label"); - } - - return FormValidation.ok(); - } + public static final class DescriptorImpl extends DockerSlaveConfig.DescriptorImpl { public FormValidation doCheckNumExecutors(@QueryParameter int numExecutors) { if (numExecutors > 1) { @@ -316,6 +204,7 @@ public FormValidation doCheckNumExecutors(@QueryParameter int numExecutors) { return FormValidation.ok(); } + @Nonnull @Override public String getDisplayName() { return "Docker Template"; diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/action/DockerLabelAssignmentAction.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/action/DockerLabelAssignmentAction.java index 7f1d0f70..027d336b 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/action/DockerLabelAssignmentAction.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/action/DockerLabelAssignmentAction.java @@ -1,7 +1,6 @@ package com.github.kostyasha.yad.action; -import com.github.kostyasha.yad.DockerConnector; -import com.github.kostyasha.yad.DockerSlaveTemplate; +import com.github.kostyasha.yad.DockerSlaveConfig; import com.github.kostyasha.yad.connector.YADockerConnector; import hudson.model.Label; import hudson.model.labels.LabelAssignmentAction; @@ -13,44 +12,41 @@ import javax.annotation.Nullable; /** + * Configuration required to provision slave without Cloud. + * * @author Kanstantsin Shautsou */ public class DockerLabelAssignmentAction implements LabelAssignmentAction { + // plugabble connector + @Nonnull + private final YADockerConnector connector; - private final String assignedLabel; + @Nonnull + private final DockerSlaveConfig slaveConfig; - private YADockerConnector connector = null; - private DockerSlaveTemplate slaveTemplate = null; - - public DockerLabelAssignmentAction(String assignedLabel) { - this.assignedLabel = assignedLabel; + public DockerLabelAssignmentAction(@Nonnull DockerSlaveConfig slaveConfig, + @Nonnull YADockerConnector connector) { + this.slaveConfig = slaveConfig; + this.connector = connector; } public String getAssignedLabel() { - return assignedLabel; + return slaveConfig.getLabelString(); } - @CheckForNull + @Nonnull public YADockerConnector getConnector() { return connector; } - public void setConnector(YADockerConnector connector) { - this.connector = connector; - } - - @CheckForNull - public DockerSlaveTemplate getSlaveTemplate() { - return slaveTemplate; - } - - public void setSlaveTemplate(DockerSlaveTemplate slaveTemplate) { - this.slaveTemplate = slaveTemplate; + @Nonnull + public DockerSlaveConfig getSlaveConfig() { + return slaveConfig; } @Override public Label getAssignedLabel(@Nullable SubTask task) { - return Jenkins.getInstance().getLabelAtom(assignedLabel); + return Jenkins.getInstance().getLabelAtom(getAssignedLabel()); } @Override diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/client/ClientBuilderForConnector.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/client/ClientBuilderForConnector.java index e9a402ef..8a148629 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/client/ClientBuilderForConnector.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/client/ClientBuilderForConnector.java @@ -85,7 +85,7 @@ public ClientBuilderForConnector withSslConfig(SSLConfig sslConfig) public ClientBuilderForConnector forConnector(DockerConnector connector) throws UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { LOG.debug("Building connection to docker host '{}'", connector.getServerUrl()); - withCredentials(connector.getCredentialsId()); + withCredentialsId(connector.getCredentialsId()); withConnectorType(connector.getConnectorType()); withConnectTimeout(connector.getConnectTimeout()); @@ -121,17 +121,24 @@ public ClientBuilderForConnector forServer(String uri, @Nullable String version) * @param credentialsId credentials to find in jenkins * @return docker-java client */ - public ClientBuilderForConnector withCredentials(String credentialsId) + public ClientBuilderForConnector withCredentialsId(String credentialsId) throws UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { if (isNotBlank(credentialsId)) { - Credentials credentials = lookupSystemCredentials(credentialsId); - - if (credentials instanceof CertificateCredentials) { - CertificateCredentials certificateCredentials = (CertificateCredentials) credentials; - withSslConfig(new KeystoreSSLConfig( - certificateCredentials.getKeyStore(), - certificateCredentials.getPassword().getPlainText() - )); + withCredentials(lookupSystemCredentials(credentialsId)); + } else { + withSslConfig(null); + } + + return this; + } + + public ClientBuilderForConnector withCredentials(Credentials credentials) throws UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { + if (credentials instanceof CertificateCredentials) { + CertificateCredentials certificateCredentials = (CertificateCredentials) credentials; + withSslConfig(new KeystoreSSLConfig( + certificateCredentials.getKeyStore(), + certificateCredentials.getPassword().getPlainText() + )); // } else if (credentials instanceof StandardUsernamePasswordCredentials) { // StandardUsernamePasswordCredentials usernamePasswordCredentials = // ((StandardUsernamePasswordCredentials) credentials); @@ -139,17 +146,14 @@ public ClientBuilderForConnector withCredentials(String credentialsId) // dockerClientConfigBuilder.withRegistryUsername(usernamePasswordCredentials.getUsername()); // dockerClientConfigBuilder.withRegistryPassword(usernamePasswordCredentials.getPassword().getPlainText()); // - } else if (credentials instanceof DockerServerCredentials) { - final DockerServerCredentials dockerCreds = (DockerServerCredentials) credentials; - - withSslConfig(new VariableSSLConfig( - dockerCreds.getClientKey(), - dockerCreds.getClientCertificate(), - dockerCreds.getServerCaCertificate() - )); - } - } else { - withSslConfig(null); + } else if (credentials instanceof DockerServerCredentials) { + final DockerServerCredentials dockerCreds = (DockerServerCredentials) credentials; + + withSslConfig(new VariableSSLConfig( + dockerCreds.getClientKey(), + dockerCreds.getClientCertificate(), + dockerCreds.getServerCaCertificate() + )); } return this; diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/connector/CredentialsYADockerConnector.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/connector/CredentialsYADockerConnector.java new file mode 100644 index 00000000..1bbe1e81 --- /dev/null +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/connector/CredentialsYADockerConnector.java @@ -0,0 +1,145 @@ +package com.github.kostyasha.yad.connector; + +import com.cloudbees.plugins.credentials.Credentials; +import com.github.kostyasha.yad.other.ConnectorType; +import com.github.kostyasha.yad_docker_java.com.github.dockerjava.api.DockerClient; +import com.github.kostyasha.yad_docker_java.com.github.dockerjava.core.DefaultDockerClientConfig; + +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; + +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; + +import static com.github.kostyasha.yad.client.ClientBuilderForConnector.newClientBuilderForConnector; +import static com.github.kostyasha.yad.other.ConnectorType.NETTY; +import static java.util.Objects.isNull; + +/** + * Connector from Credentials. + * + * @author Kanstantsin Shautsou + */ +public class CredentialsYADockerConnector extends YADockerConnector { + + @CheckForNull + private String serverUrl; + + @CheckForNull + private String apiVersion; + + private transient Boolean tlsVerify; + + @CheckForNull + private Credentials credentials = null; + + @CheckForNull + private transient DockerClient client = null; + + private ConnectorType connectorType = NETTY; + + @CheckForNull + private Integer connectTimeout; + + public CredentialsYADockerConnector() { + } + + @CheckForNull + public ConnectorType getConnectorType() { + return connectorType; + } + + public CredentialsYADockerConnector withConnectorType(ConnectorType connectorType) { + this.connectorType = connectorType; + return this; + } + + @CheckForNull + public String getServerUrl() { + return serverUrl; + } + + public CredentialsYADockerConnector withServerUrl(String serverUrl) { + this.serverUrl = serverUrl; + return this; + } + + @CheckForNull + public String getApiVersion() { + return apiVersion; + } + + public CredentialsYADockerConnector withApiVersion(String apiVersion) { + this.apiVersion = apiVersion; + return this; + } + + @CheckForNull + public Boolean getTlsVerify() { + return tlsVerify; + } + + public CredentialsYADockerConnector withTlsVerify(Boolean tlsVerify) { + this.tlsVerify = tlsVerify; + return this; + } + + @CheckForNull + public Credentials getCredentials() { + return credentials; + } + + public CredentialsYADockerConnector withCredentials(Credentials credentials) { + this.credentials = credentials; + return this; + } + + public CredentialsYADockerConnector withClient(DockerClient client) { + this.client = client; + return this; + } + + @CheckForNull + public Integer getConnectTimeout() { + return connectTimeout; + } + + public CredentialsYADockerConnector withConnectTimeout(Integer connectTimeout) { + this.connectTimeout = connectTimeout; + return this; + } + + @Nonnull + @Override + public DockerClient getClient() throws UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException, + KeyManagementException { + + if (isNull(client)) { + DefaultDockerClientConfig.Builder configBuilder = new DefaultDockerClientConfig.Builder() + .withApiVersion(apiVersion) + .withDockerHost(serverUrl); + + final DockerClient client = newClientBuilderForConnector() + .withConfigBuilder(configBuilder) + .withConnectorType(connectorType) + .withCredentials(credentials) + .withConnectTimeout(connectTimeout) + .build(); + + client.versionCmd().exec(); + } + + return client; + } + + + public static class DescriptorImpl extends YADockerConnectorDescriptor { + @Nonnull + @Override + public String getDisplayName() { + return "Connector from Credentials"; + } + } +} diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/connector/DockerCloudConnector.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/connector/DockerCloudConnector.java deleted file mode 100644 index b23c9805..00000000 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/connector/DockerCloudConnector.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.github.kostyasha.yad.connector; - -/** - * Will use Cloud connector. - * - * @author Kanstantsin Shautsou - */ -public abstract class DockerCloudConnector extends YADockerConnector { - -} diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/connector/DockerCloudConnectorId.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/connector/DockerCloudConnectorId.java index 013842cd..338bcf27 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/connector/DockerCloudConnectorId.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/connector/DockerCloudConnectorId.java @@ -21,7 +21,9 @@ import static org.apache.commons.lang.builder.ToStringStyle.MULTI_LINE_STYLE; /** - * Should be under `com.github.kostyasha.yad.connector` package, but it was first class and can't move. + * Should be under {@link com.github.kostyasha.yad.connector} package, + * but it was first class and can't move. + * Get {@link DockerClient} from existing {@link DockerCloud} * * @author Kanstantsin Shautsou */ diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/connector/YADockerConnector.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/connector/YADockerConnector.java index d9f8db5e..819e3bed 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/connector/YADockerConnector.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/connector/YADockerConnector.java @@ -15,7 +15,7 @@ */ public abstract class YADockerConnector extends AbstractDescribableImpl implements ExtensionPoint { - public abstract DockerClient getClient(); + public abstract DockerClient getClient() throws Exception; @Override public YADockerConnectorDescriptor getDescriptor() { diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/launcher/NoOpComputerLauncherFilter.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/launcher/NoOpComputerLauncherFilter.java deleted file mode 100644 index 81528fa9..00000000 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/launcher/NoOpComputerLauncherFilter.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.github.kostyasha.yad.launcher; - -import hudson.slaves.ComputerLauncher; -import hudson.slaves.ComputerLauncherFilter; - -/** - * @author Kanstantsin Shautsou - */ -public class NoOpComputerLauncherFilter extends ComputerLauncherFilter { - public NoOpComputerLauncherFilter(ComputerLauncher core) { - super(core); - } - -} diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/launcher/NoOpDelegatingComputerLauncher.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/launcher/NoOpDelegatingComputerLauncher.java new file mode 100644 index 00000000..8d658dc9 --- /dev/null +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/launcher/NoOpDelegatingComputerLauncher.java @@ -0,0 +1,36 @@ +package com.github.kostyasha.yad.launcher; + +import hudson.Extension; +import hudson.model.TaskListener; +import hudson.slaves.ComputerLauncher; +import hudson.slaves.ComputerLauncherFilter; +import hudson.slaves.DelegatingComputerLauncher; +import hudson.slaves.SlaveComputer; + +import javax.annotation.Nonnull; +import java.io.IOException; + +/** + * Delegating ComputerLauncher without launch. + * + * @author Kanstantsin Shautsou + */ +public class NoOpDelegatingComputerLauncher extends DelegatingComputerLauncher { + public NoOpDelegatingComputerLauncher(ComputerLauncher core) { + super(core); + } + + @Override + public void launch(SlaveComputer computer, TaskListener listener) throws IOException, InterruptedException { + // noop + } + + @Extension + public static final class DescriptorImpl extends DelegatingComputerLauncher.DescriptorImpl { + @Nonnull + @Override + public String getDisplayName() { + return "No Launching Delegating Computer Launcher"; + } + } +} diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/listener/DockerQueueListener.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/listener/DockerQueueListener.java index 73d6988f..ebb5bd9e 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/listener/DockerQueueListener.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/listener/DockerQueueListener.java @@ -1,33 +1,19 @@ package com.github.kostyasha.yad.listener; -import com.github.kostyasha.yad.DockerCloud; -import com.github.kostyasha.yad.DockerJobProperty; +import com.github.kostyasha.yad.DockerContainerLifecycle; import com.github.kostyasha.yad.DockerSlaveSingle; import com.github.kostyasha.yad.DockerSlaveTemplate; import com.github.kostyasha.yad.action.DockerLabelAssignmentAction; -import com.github.kostyasha.yad.jobconfig.DockerCloudJobConfig; -import com.github.kostyasha.yad.jobconfig.SlaveJobConfig; -import com.github.kostyasha.yad.launcher.DockerComputerSingleJNLPLauncher; -import com.github.kostyasha.yad.strategy.DockerOnceRetentionStrategy; import hudson.Extension; import hudson.model.Descriptor; -import hudson.model.Job; -import hudson.model.Node; import hudson.model.Queue; -import hudson.model.TaskListener; -import hudson.model.labels.LabelAtom; import hudson.model.queue.QueueListener; -import hudson.slaves.Cloud; -import hudson.slaves.DelegatingComputerLauncher; -import hudson.slaves.SlaveComputer; import jenkins.model.Jenkins; import org.jenkinsci.plugins.cloudstats.ProvisioningActivity; import java.io.IOException; -import java.util.UUID; import static java.util.Collections.emptyList; -import static java.util.Objects.isNull; import static java.util.Objects.nonNull; import static org.jenkinsci.plugins.cloudstats.CloudStatistics.ProvisioningListener.get; @@ -43,46 +29,48 @@ public class DockerQueueListener extends QueueListener { @Override public void onEnterWaiting(Queue.WaitingItem wi) { if (!processItemWithAction(wi)) { - processJobWithProperty(wi); } } - /** - * If it Job and has JobProperty, then spin with DockerCloud - */ - private boolean processJobWithProperty(Queue.WaitingItem wi) { - if (!(wi.task instanceof Job)) { - // looks like not correct Job type check - return false; - } - - final Job job = (Job) wi.task; - final DockerJobProperty dockerProp = (DockerJobProperty) job.getProperty(DockerJobProperty.class); - if (isNull(dockerProp)) { - return false; - } - - final UUID uuid = UUID.randomUUID(); -// final LabelAtom label = Jenkins.getInstance().getLabelAtom(uuid.toString()); - - final DockerLabelAssignmentAction labelAssignmentAction = new DockerLabelAssignmentAction(uuid.toString()); - wi.addAction(labelAssignmentAction); - - - final SlaveJobConfig slaveJobConfig = dockerProp.getSlaveJobConfig(); - if (slaveJobConfig instanceof DockerCloudJobConfig) { - final DockerCloudJobConfig cloudJobConfig = (DockerCloudJobConfig) slaveJobConfig; - final DockerSlaveTemplate template = cloudJobConfig.getTemplate(); - template.setLabelString(uuid.toString()); - - final DockerCloud cloud = (DockerCloud) Jenkins.getInstance().getCloud(cloudJobConfig.getConnector().getCloudId()); - cloud.addTransientTemplate(template); - } - - labelAssignmentAction.getAssignedLabel(null).nodeProvisioner.suggestReviewNow(); - - return true; - } +// /** +// * If it Job and has JobProperty, then spin with DockerCloud +// */ +// private boolean processJobWithProperty(Queue.WaitingItem wi) throws Descriptor.FormException { +// if (!(wi.task instanceof Job)) { +// // looks like not correct Job type check +// return false; +// } +// +// final Job job = (Job) wi.task; +// final DockerJobProperty dockerProp = (DockerJobProperty) job.getProperty(DockerJobProperty.class); +// if (isNull(dockerProp)) { +// return false; +// } +// +// final UUID uuid = UUID.randomUUID(); +//// final LabelAtom label = Jenkins.getInstance().getLabelAtom(uuid.toString()); +// +// final DockerSlaveTemplate template = new DockerSlaveTemplate(Long.toString(wi.getId())); +// template.setLabelString(uuid.toString()); +// +// final DockerLabelAssignmentAction labelAssignmentAction = new DockerLabelAssignmentAction(template, ); +// wi.addAction(labelAssignmentAction); +// +// +// final SlaveJobConfig slaveJobConfig = dockerProp.getSlaveJobConfig(); +// if (slaveJobConfig instanceof DockerCloudJobConfig) { +// final DockerCloudJobConfig cloudJobConfig = (DockerCloudJobConfig) slaveJobConfig; +// final DockerSlaveTemplate template = cloudJobConfig.getTemplate(); +// template.setLabelString(uuid.toString()); +// +// final DockerCloud cloud = (DockerCloud) Jenkins.getInstance().getCloud(cloudJobConfig.getConnector().getCloudId()); +// cloud.addTransientTemplate(template); +// } +// +// labelAssignmentAction.getAssignedLabel(null).nodeProvisioner.suggestReviewNow(); +// +// return true; +// } /** * Process with special Action @@ -91,23 +79,24 @@ private boolean processItemWithAction(Queue.WaitingItem wi) { final DockerLabelAssignmentAction action = wi.getAction(DockerLabelAssignmentAction.class); if (nonNull(action)) { try { - final ProvisioningActivity.Id activityId = new ProvisioningActivity.Id(wi.getDisplayName(), - "fake-image"); + final DockerSlaveTemplate template = action.getSlaveConfig(); + final DockerContainerLifecycle lifecycle = template.getDockerContainerLifecycle(); + + final ProvisioningActivity.Id activityId = new ProvisioningActivity.Id( + wi.getDisplayName(), + lifecycle.getImage() + ); + get().onStarted(activityId); final DockerSlaveSingle slave = new DockerSlaveSingle("docker-slave", - "description", - "/home/jenkins", - 1, - Node.Mode.EXCLUSIVE, - "", // label string - new DelegatingComputerLauncher(new DockerComputerSingleJNLPLauncher()) { - @Override - public void launch(SlaveComputer computer, TaskListener listener) throws IOException, InterruptedException { - // no launch - } - }, - new DockerOnceRetentionStrategy(10), + "Slave for " + wi.getDisplayName(), + template.getRemoteFs(), + template.getNumExecutors(), + template.getMode(), + "", // label string + template.getLauncher(), + template.getRetentionStrategy(), emptyList(), activityId ); diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerQueueDecisionHandler.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/queue/DockerQueueDecisionHandler.java similarity index 89% rename from yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerQueueDecisionHandler.java rename to yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/queue/DockerQueueDecisionHandler.java index 9a7fdc10..d893b887 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerQueueDecisionHandler.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/queue/DockerQueueDecisionHandler.java @@ -1,4 +1,4 @@ -package com.github.kostyasha.yad; +package com.github.kostyasha.yad.queue; import hudson.Extension; import hudson.model.Action; diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/queue/DockerQueueTaskDispatcher.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/queue/DockerQueueTaskDispatcher.java new file mode 100644 index 00000000..acfccb5d --- /dev/null +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/queue/DockerQueueTaskDispatcher.java @@ -0,0 +1,26 @@ +package com.github.kostyasha.yad.queue; + +import com.github.kostyasha.yad.DockerSlaveSingle; +import hudson.Extension; +import hudson.model.Node; +import hudson.model.Queue; +import hudson.model.queue.CauseOfBlockage; +import hudson.model.queue.QueueTaskDispatcher; + +/** + * @author Kanstantsin Shautsou + */ +@Extension +public class DockerQueueTaskDispatcher extends QueueTaskDispatcher { + + @Override + public CauseOfBlockage canTake(Node node, Queue.BuildableItem item) { + if (node instanceof DockerSlaveSingle) { + if (!node.getNodeName().equals(Long.toString(item.getId()))) { + return new SingleNodeCauseOfBlockage(item.getDisplayName()); + } + } + + return null; + } +} diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/FlyweightCauseOfBlockage.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/queue/FlyweightCauseOfBlockage.java similarity index 87% rename from yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/FlyweightCauseOfBlockage.java rename to yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/queue/FlyweightCauseOfBlockage.java index 28e5c8af..7a7069c3 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/FlyweightCauseOfBlockage.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/queue/FlyweightCauseOfBlockage.java @@ -1,4 +1,4 @@ -package com.github.kostyasha.yad; +package com.github.kostyasha.yad.queue; import hudson.model.queue.CauseOfBlockage; diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/queue/SingleNodeCauseOfBlockage.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/queue/SingleNodeCauseOfBlockage.java new file mode 100644 index 00000000..ee8f0065 --- /dev/null +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/queue/SingleNodeCauseOfBlockage.java @@ -0,0 +1,23 @@ +package com.github.kostyasha.yad.queue; + +import hudson.model.queue.CauseOfBlockage; + +/** + * @author Kanstantsin Shautsou + */ +public class SingleNodeCauseOfBlockage extends CauseOfBlockage { + private String displayName; + + public SingleNodeCauseOfBlockage(String displayName) { + this.displayName = displayName; + } + + public String getDisplayName() { + return displayName; + } + + @Override + public String getShortDescription() { + return "Slave tied to " + getDisplayName(); + } +} diff --git a/yet-another-docker-plugin/src/main/resources/com/github/kostyasha/yad/connector/DockerCloudConnectorTemplateRaw/config.groovy b/yet-another-docker-plugin/src/main/resources/com/github/kostyasha/yad/connector/DockerCloudConnectorTemplateRaw/config.groovy index 47437221..5d8d39fb 100644 --- a/yet-another-docker-plugin/src/main/resources/com/github/kostyasha/yad/connector/DockerCloudConnectorTemplateRaw/config.groovy +++ b/yet-another-docker-plugin/src/main/resources/com/github/kostyasha/yad/connector/DockerCloudConnectorTemplateRaw/config.groovy @@ -4,4 +4,4 @@ import lib.FormTagLib def f = namespace(FormTagLib) -f.property(field: "slaveTemplate") +f.property(field: "slaveConfig") diff --git a/yet-another-docker-plugin/src/test/java/com/github/kostyasha/yad/TaskStepTest.java b/yet-another-docker-plugin/src/test/java/com/github/kostyasha/yad/TaskStepTest.java index d8e2f36b..da416db6 100644 --- a/yet-another-docker-plugin/src/test/java/com/github/kostyasha/yad/TaskStepTest.java +++ b/yet-another-docker-plugin/src/test/java/com/github/kostyasha/yad/TaskStepTest.java @@ -10,6 +10,7 @@ import hudson.model.Node; import hudson.model.Queue; import jenkins.model.Jenkins; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.jvnet.hudson.test.JenkinsRule; @@ -24,6 +25,7 @@ public class TaskStepTest { @Rule public JenkinsRule jRule = new JenkinsRule(); + @Ignore @Test public void actionTask() throws Exception { final Jenkins jenkins = jRule.getInstance(); @@ -34,13 +36,13 @@ public void actionTask() throws Exception { // final DockerCloud dockerCloud = new DockerCloud("localTestCloud"); - final DockerLabelAssignmentAction assignmentAction = new DockerLabelAssignmentAction("docker-slave"); +// final DockerLabelAssignmentAction assignmentAction = new DockerLabelAssignmentAction("docker-slave"); final DockerConnector dockerConnector = new DockerConnector("tcp://192.168.1.3:2376/"); dockerConnector.setConnectorType(NETTY); - assignmentAction.setConnector(dockerConnector); +// assignmentAction.setConnector(dockerConnector); - final Queue.WaitingItem waitingItem = jenkins.getQueue().schedule(new DockerTask(), 0, assignmentAction); +// final Queue.WaitingItem waitingItem = jenkins.getQueue().schedule(new DockerTask(), 0, assignmentAction); jRule.waitUntilNoActivity(); } diff --git a/yet-another-docker-plugin/src/test/java/org/jvnet/hudson/test/DockerLabelAssignmentActionTest.java b/yet-another-docker-plugin/src/test/java/org/jvnet/hudson/test/DockerLabelAssignmentActionTest.java index ae5d754b..bd787eac 100644 --- a/yet-another-docker-plugin/src/test/java/org/jvnet/hudson/test/DockerLabelAssignmentActionTest.java +++ b/yet-another-docker-plugin/src/test/java/org/jvnet/hudson/test/DockerLabelAssignmentActionTest.java @@ -1,8 +1,12 @@ package org.jvnet.hudson.test; +// until https://github.com/jenkinsci/jenkins-test-harness/pull/46 import com.github.kostyasha.yad.DockerConnector; import com.github.kostyasha.yad.DockerSlaveTemplate; import com.github.kostyasha.yad.action.DockerLabelAssignmentAction; +import com.github.kostyasha.yad.launcher.DockerComputerSingleJNLPLauncher; +import com.github.kostyasha.yad.launcher.NoOpDelegatingComputerLauncher; +import com.github.kostyasha.yad.strategy.DockerOnceRetentionStrategy; import hudson.model.FreeStyleProject; import hudson.model.queue.QueueTaskFuture; import org.eclipse.jetty.server.HttpConfiguration; @@ -15,6 +19,7 @@ import org.junit.Rule; import org.junit.Test; import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.servlet.ServletContext; import java.io.IOException; @@ -25,13 +30,12 @@ import java.util.concurrent.TimeUnit; import static com.github.kostyasha.yad.other.ConnectorType.JERSEY; -import static com.github.kostyasha.yad.other.ConnectorType.NETTY; /** * @author Kanstantsin Shautsou */ public class DockerLabelAssignmentActionTest { - private static final Logger LOGGER = org.slf4j.LoggerFactory.getLogger(DockerLabelAssignmentActionTest.class); + private static final Logger LOGGER = LoggerFactory.getLogger(DockerLabelAssignmentActionTest.class); private static final String ADDRESS = "192.168.1.3"; @@ -44,12 +48,10 @@ public URL getURL() throws IOException { @Override protected ServletContext createWebServer() throws Exception { - server = new Server(new ThreadPoolImpl(new ThreadPoolExecutor(10, 10, 10L, TimeUnit.SECONDS, new LinkedBlockingQueue(), new ThreadFactory() { - public Thread newThread(Runnable r) { - Thread t = new Thread(r); - t.setName("Jetty Thread Pool"); - return t; - } + server = new Server(new ThreadPoolImpl(new ThreadPoolExecutor(10, 10, 10L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), r -> { + Thread t = new Thread(r); + t.setName("Jetty Thread Pool"); + return t; }))); WebAppContext context = new WebAppContext(WarExploder.getExplodedDir().getPath(), contextPath); @@ -81,20 +83,19 @@ public Thread newThread(Runnable r) { @Test public void actionTask() throws Exception { - final FreeStyleProject project = jRule.createProject(FreeStyleProject.class, "test"); - final DockerSlaveTemplate slaveTemplate = new DockerSlaveTemplate(); - - final DockerLabelAssignmentAction assignmentAction = new DockerLabelAssignmentAction("docker-slave"); - final DockerConnector dockerConnector = new DockerConnector("tcp://192.168.1.3:2376/"); - dockerConnector.setConnectorType(JERSEY); + final DockerConnector connector = new DockerConnector("tcp://192.168.1.3:2376/"); + connector.setConnectorType(JERSEY); - assignmentAction.setConnector(dockerConnector); + final DockerSlaveTemplate template = new DockerSlaveTemplate(); + template.setLabelString("docker-slave"); + template.setLauncher(new NoOpDelegatingComputerLauncher(new DockerComputerSingleJNLPLauncher())); + template.setRetentionStrategy(new DockerOnceRetentionStrategy(10)); + final DockerLabelAssignmentAction assignmentAction = new DockerLabelAssignmentAction(template, connector); final QueueTaskFuture build2 = project.scheduleBuild2(0, assignmentAction); - jRule.pause(); jRule.waitUntilNoActivity(); } } \ No newline at end of file From 81e676e106c2e43fe1b85adab0eddfad6010f596 Mon Sep 17 00:00:00 2001 From: Kanstantsin Shautsou Date: Mon, 30 Jan 2017 03:29:36 +0300 Subject: [PATCH 09/17] WIP Signed-off-by: Kanstantsin Shautsou --- .../kostyasha/yad/DockerComputerSingle.java | 4 --- .../kostyasha/yad/DockerSlaveConfig.java | 5 --- .../kostyasha/yad/DockerSlaveSingle.java | 2 +- .../kostyasha/yad/DockerSlaveTemplate.java | 31 +------------------ .../yad/client/ClientBuilderForConnector.java | 3 +- .../DockerComputerSingleJNLPLauncher.java | 13 +++----- .../yad/listener/DockerRunListener.java | 13 -------- 7 files changed, 9 insertions(+), 62 deletions(-) diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerComputerSingle.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerComputerSingle.java index 7a4cf2c1..828bf198 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerComputerSingle.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerComputerSingle.java @@ -1,13 +1,9 @@ package com.github.kostyasha.yad; -import com.github.kostyasha.yad.launcher.DockerComputerJNLPLauncher; -import hudson.model.Node; import hudson.model.Run; -import hudson.model.Slave; import hudson.model.TaskListener; import hudson.remoting.Channel; import hudson.slaves.AbstractCloudComputer; -import hudson.slaves.ComputerLauncher; import org.jenkinsci.plugins.cloudstats.ProvisioningActivity; import org.jenkinsci.plugins.cloudstats.TrackedItem; diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlaveConfig.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlaveConfig.java index 631883d4..0c3d3985 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlaveConfig.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlaveConfig.java @@ -160,11 +160,6 @@ public String getId() { return id; } - protected Object readResolve() { - - return this; - } - @Override public int hashCode() { return id.hashCode(); diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlaveSingle.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlaveSingle.java index 8dcf872e..23b48b22 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlaveSingle.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlaveSingle.java @@ -69,4 +69,4 @@ protected void _terminate(TaskListener listener) throws IOException, Interrupted public ProvisioningActivity.Id getId() { return activityId; } -} \ No newline at end of file +} diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlaveTemplate.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlaveTemplate.java index 720b8cb8..922e84ff 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlaveTemplate.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlaveTemplate.java @@ -1,32 +1,20 @@ package com.github.kostyasha.yad; import com.github.kostyasha.yad.commons.DockerCreateContainer; -import com.github.kostyasha.yad_docker_java.com.google.common.base.MoreObjects; -import com.github.kostyasha.yad_docker_java.com.google.common.base.Strings; -import com.github.kostyasha.yad.launcher.DockerComputerJNLPLauncher; import com.github.kostyasha.yad.launcher.DockerComputerLauncher; import com.github.kostyasha.yad.strategy.DockerOnceRetentionStrategy; +import com.github.kostyasha.yad_docker_java.com.google.common.base.MoreObjects; import hudson.Extension; -import hudson.Util; -import hudson.model.AbstractDescribableImpl; -import hudson.model.Describable; -import hudson.model.Descriptor; import hudson.model.Descriptor.FormException; import hudson.model.Label; import hudson.model.Node; import hudson.model.labels.LabelAtom; -import hudson.slaves.ComputerLauncher; import hudson.slaves.NodeProperty; -import hudson.slaves.NodePropertyDescriptor; import hudson.slaves.RetentionStrategy; -import hudson.util.DescribableList; import hudson.util.FormValidation; -import jenkins.model.Jenkins; import org.apache.commons.lang.builder.EqualsBuilder; import org.apache.commons.lang.builder.ToStringBuilder; import org.apache.commons.lang.builder.ToStringStyle; -import org.kohsuke.accmod.Restricted; -import org.kohsuke.accmod.restrictions.NoExternalUse; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.DataBoundSetter; import org.kohsuke.stapler.QueryParameter; @@ -34,16 +22,12 @@ import org.slf4j.LoggerFactory; import javax.annotation.Nonnull; -import java.io.IOException; import java.util.Collections; import java.util.List; import java.util.Set; import java.util.UUID; -import static java.util.Collections.emptyList; -import static java.util.Collections.unmodifiableList; import static java.util.Objects.isNull; -import static java.util.Objects.nonNull; /** * All configuration (jenkins and docker specific) required for launching slave instances. @@ -149,19 +133,6 @@ public Object readResolve() { return this; } - /** - * Id used for counting running slaves - */ - @Nonnull - public String getId() { - return id; - } - - @Override - public int hashCode() { - return id.hashCode(); - } - @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/client/ClientBuilderForConnector.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/client/ClientBuilderForConnector.java index 8a148629..b6b70fff 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/client/ClientBuilderForConnector.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/client/ClientBuilderForConnector.java @@ -132,7 +132,8 @@ public ClientBuilderForConnector withCredentialsId(String credentialsId) return this; } - public ClientBuilderForConnector withCredentials(Credentials credentials) throws UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { + public ClientBuilderForConnector withCredentials(Credentials credentials) throws UnrecoverableKeyException, + NoSuchAlgorithmException, KeyStoreException, KeyManagementException { if (credentials instanceof CertificateCredentials) { CertificateCredentials certificateCredentials = (CertificateCredentials) credentials; withSslConfig(new KeystoreSSLConfig( diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/launcher/DockerComputerSingleJNLPLauncher.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/launcher/DockerComputerSingleJNLPLauncher.java index 46eb07b4..35877f22 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/launcher/DockerComputerSingleJNLPLauncher.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/launcher/DockerComputerSingleJNLPLauncher.java @@ -2,7 +2,6 @@ import com.github.kostyasha.yad.DockerComputerSingle; import com.github.kostyasha.yad.DockerContainerLifecycle; -import com.github.kostyasha.yad.DockerSlaveSingle; import com.github.kostyasha.yad.action.DockerLabelAssignmentAction; import com.github.kostyasha.yad.commons.DockerCreateContainer; import com.github.kostyasha.yad_docker_java.com.github.dockerjava.api.DockerClient; @@ -30,9 +29,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; -import java.util.concurrent.TimeUnit; -import static com.github.kostyasha.yad_docker_java.org.apache.commons.lang.BooleanUtils.isFalse; import static com.github.kostyasha.yad_docker_java.org.apache.commons.lang.StringUtils.isNotEmpty; import static com.github.kostyasha.yad_docker_java.org.apache.commons.lang.StringUtils.trimToEmpty; import static java.util.Objects.isNull; @@ -51,7 +48,7 @@ public class DockerComputerSingleJNLPLauncher extends JNLPLauncher { protected long launchTimeout = DEFAULT_TIMEOUT; //seconds - protected String user = "jenkins"; + protected String user = DEFAULT_USER; protected String jvmOpts = ""; @@ -123,8 +120,8 @@ public void launch(SlaveComputer computer, TaskListener listener) { listener.getLogger().println("Launching " + computer.getDisplayName()); try { provisionWithWait((DockerComputerSingle) computer, listener); - } catch (IOException | InterruptedException e) { - e.printStackTrace(); + } catch (Exception e) { + LOG.error("Can't launch", e); listener.error(e.toString()); } } @@ -133,7 +130,7 @@ public void launch(SlaveComputer computer, TaskListener listener) { * Provision slave container and wait for it's availability. */ private void provisionWithWait(DockerComputerSingle computer, TaskListener listener) - throws IOException, InterruptedException { + throws Exception { final PrintStream logger = listener.getLogger(); final Run run = computer.getRun(); @@ -234,7 +231,7 @@ private void provisionWithWait(DockerComputerSingle computer, TaskListener liste .withTty(true) .exec(new ExecStartResultCallback()) ) { - exec.awaitCompletion(10 , SECONDS); + exec.awaitCompletion(10, SECONDS); } catch (NotFoundException ex) { listener.error("Can't execute command: " + ex.getMessage().trim()); LOG.error("Can't execute jnlp connection command: '{}'", ex.getMessage().trim()); diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/listener/DockerRunListener.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/listener/DockerRunListener.java index 8913ea10..dd63b81a 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/listener/DockerRunListener.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/listener/DockerRunListener.java @@ -2,35 +2,22 @@ import com.github.kostyasha.yad.DockerComputer; import com.github.kostyasha.yad.DockerComputerSingle; -import com.github.kostyasha.yad.DockerSlave; import com.github.kostyasha.yad.DockerSlaveSingle; import com.github.kostyasha.yad.action.DockerLabelAssignmentAction; -import com.github.kostyasha.yad_docker_java.com.github.dockerjava.api.DockerClient; import hudson.Extension; -import hudson.console.ConsoleLogFilter; -import hudson.model.AbstractBuild; -import hudson.model.BuildableItemWithBuildWrappers; import hudson.model.Computer; import hudson.model.Executor; -import hudson.model.Job; import hudson.model.Node; import hudson.model.Run; -import hudson.model.StreamBuildListener; import hudson.model.TaskListener; import hudson.model.listeners.RunListener; import hudson.slaves.DelegatingComputerLauncher; -import hudson.tasks.BuildWrapper; import jenkins.model.Jenkins; import org.jenkinsci.plugins.docker.commons.fingerprint.DockerFingerprints; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.annotation.Nonnull; -import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.charset.Charset; import java.text.ParseException; import static com.github.kostyasha.yad.utils.ContainerRecordUtils.createRecordFor; From 34822e8308807a6f511870991d9f9cbc086bdd68 Mon Sep 17 00:00:00 2001 From: Kanstantsin Shautsou Date: Mon, 30 Jan 2017 03:45:41 +0300 Subject: [PATCH 10/17] Wrapper? Signed-off-by: Kanstantsin Shautsou --- .../yad/DockerSimpleBuildWrapper.java | 34 +++++++++++++++++++ .../yad/listener/DockerRunListener.java | 1 + 2 files changed, 35 insertions(+) create mode 100644 yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSimpleBuildWrapper.java diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSimpleBuildWrapper.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSimpleBuildWrapper.java new file mode 100644 index 00000000..593c8368 --- /dev/null +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSimpleBuildWrapper.java @@ -0,0 +1,34 @@ +package com.github.kostyasha.yad; + +import hudson.EnvVars; +import hudson.FilePath; +import hudson.Launcher; +import hudson.model.Run; +import hudson.model.TaskListener; +import hudson.model.queue.AbstractQueueTask; +import jenkins.tasks.SimpleBuildWrapper; + +import java.io.IOException; + +/** + * @author Kanstantsin Shautsou + */ +public class DockerSimpleBuildWrapper extends SimpleBuildWrapper { + final private AbstractQueueTask task; //? + + public DockerSimpleBuildWrapper(AbstractQueueTask task) { + this.task = task; + } + + @Override + public void setUp(Context context, + Run run, + FilePath workspace, + Launcher launcher, + TaskListener listener, + EnvVars initialEnvironment) throws IOException, InterruptedException { + // launch external docker Slave + // get task future after scheduling task + // wait for task end? + } +} diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/listener/DockerRunListener.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/listener/DockerRunListener.java index dd63b81a..571c2fb8 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/listener/DockerRunListener.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/listener/DockerRunListener.java @@ -36,6 +36,7 @@ public void onStarted(Run run, TaskListener listener) { if (isNull(assignmentAction)) { return; } + try { final Node node = Jenkins.getInstance().getNode(assignmentAction.getAssignedLabel()); final DockerSlaveSingle dockerSlave = (DockerSlaveSingle) node; From d8ce2e2589d8ccf8aa6c06d90afdce14e2d128af Mon Sep 17 00:00:00 2001 From: Kanstantsin Shautsou Date: Fri, 3 Feb 2017 20:30:47 +0300 Subject: [PATCH 11/17] SimpleWrapper Signed-off-by: Kanstantsin Shautsou --- .../kostyasha/yad/DockerComputerSingle.java | 6 +- .../kostyasha/yad/DockerOfflineCause.java | 8 +- .../yad/DockerSimpleBuildWrapper.java | 121 ++++++++++++- .../com/github/kostyasha/yad/DockerSlave.java | 3 +- .../kostyasha/yad/DockerSlaveConfig.java | 26 +-- .../kostyasha/yad/DockerSlaveSingle.java | 170 ++++++++++++++---- .../kostyasha/yad/commons/AbstractCloud.java | 5 +- .../DockerComputerSingleJNLPLauncher.java | 26 ++- .../yad/listener/DockerQueueListener.java | 29 ++- .../yad/listener/DockerRunListener.java | 32 +--- .../strategy/DockerOnceRetentionStrategy.java | 4 +- .../yad/utils/ContainerRecordUtils.java | 34 ++++ .../test/DockerSimpleBuildWrapperTest.java | 110 ++++++++++++ 13 files changed, 463 insertions(+), 111 deletions(-) create mode 100644 yet-another-docker-plugin/src/test/java/org/jvnet/hudson/test/DockerSimpleBuildWrapperTest.java diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerComputerSingle.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerComputerSingle.java index 828bf198..64646828 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerComputerSingle.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerComputerSingle.java @@ -7,6 +7,7 @@ import org.jenkinsci.plugins.cloudstats.ProvisioningActivity; import org.jenkinsci.plugins.cloudstats.TrackedItem; +import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.io.IOException; import java.io.OutputStream; @@ -22,7 +23,7 @@ public class DockerComputerSingle extends AbstractCloudComputer item) { + return true; + } + } + + public static class DisposerImpl extends Disposer { + private static final long serialVersionUID = 1; + private final String slaveName; + + public DisposerImpl(@Nonnull final String slaveName) { + this.slaveName = slaveName; + } + + @Override + public void tearDown(Run run, FilePath workspace, Launcher launcher, TaskListener listener) + throws IOException, InterruptedException { + LOG.info("Shutting down slave"); + listener.getLogger().println("Shutting down slave '" + slaveName + "'."); + + final DockerSlaveSingle node = (DockerSlaveSingle) Jenkins.getInstance().getNode(slaveName); + try { + node.terminate(); + } catch (IOException | InterruptedException e) { + LOG.error("fd", e); + CloudStatistics.ProvisioningListener.get().onFailure(node.getId(), e); + } + + } } } diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlave.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlave.java index a1b07f34..04a20b60 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlave.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlave.java @@ -155,10 +155,11 @@ protected void _terminate(TaskListener listener) throws IOException, Interrupted LOG.info("Requesting disconnect for computer: '{}'", name); final Computer toComputer = toComputer(); if (toComputer != null) { - toComputer.disconnect(new DockerOfflineCause()); + toComputer.disconnect(new DockerOfflineCause("Terminating from _terminate.")); } } catch (Exception e) { LOG.error("Can't disconnect computer: '{}'", name, e); + listener.error("Can't disconnect computer: " + name); } if (StringUtils.isNotBlank(containerId)) { diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlaveConfig.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlaveConfig.java index 0c3d3985..f4325093 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlaveConfig.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlaveConfig.java @@ -31,6 +31,9 @@ import static java.util.Objects.nonNull; /** + * Some generic config with everything required for container-slave operaition. + * Without docker connector because one connector may have multiple configs i.e. {@link DockerCloud} + * * @author Kanstantsin Shautsou */ public class DockerSlaveConfig extends AbstractDescribableImpl { @@ -59,8 +62,11 @@ public class DockerSlaveConfig extends AbstractDescribableImpl> nodeProperties = null; + private List> nodeProperties = emptyList(); + /** + * @param id some unique id to identify this configuration. Use case - count running computers based on this config. + */ public DockerSlaveConfig(@Nonnull String id) { this.id = id; } @@ -174,16 +180,16 @@ public boolean equals(Object o) { DockerSlaveConfig that = (DockerSlaveConfig) o; return new EqualsBuilder() - .append(numExecutors, that.numExecutors) - .append(id, that.id) - .append(labelString, that.labelString) - .append(launcher, that.launcher) - .append(remoteFs, that.remoteFs) - .append(mode, that.mode) - .append(retentionStrategy, that.retentionStrategy) - .append(dockerContainerLifecycle, that.dockerContainerLifecycle) + .append(numExecutors, that.numExecutors) + .append(id, that.id) + .append(labelString, that.labelString) + .append(launcher, that.launcher) + .append(remoteFs, that.remoteFs) + .append(mode, that.mode) + .append(retentionStrategy, that.retentionStrategy) + .append(dockerContainerLifecycle, that.dockerContainerLifecycle) // .append(nodeProperties, that.nodeProperties) - .isEquals(); + .isEquals(); } @Extension diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlaveSingle.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlaveSingle.java index 23b48b22..5c9870e3 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlaveSingle.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlaveSingle.java @@ -1,53 +1,85 @@ package com.github.kostyasha.yad; +import com.github.kostyasha.yad.action.DockerTerminateCmdAction; +import com.github.kostyasha.yad.connector.YADockerConnector; +import com.github.kostyasha.yad.launcher.DockerComputerSingleJNLPLauncher; +import com.github.kostyasha.yad_docker_java.com.github.dockerjava.api.exception.NotModifiedException; +import hudson.model.Computer; import hudson.model.Descriptor; import hudson.model.TaskListener; import hudson.slaves.AbstractCloudComputer; import hudson.slaves.AbstractCloudSlave; -import hudson.slaves.ComputerLauncher; -import hudson.slaves.NodeProperty; -import hudson.slaves.RetentionStrategy; +import hudson.slaves.DelegatingComputerLauncher; +import hudson.util.StreamTaskListener; +import jenkins.model.Jenkins; +import org.apache.commons.lang.StringUtils; import org.jenkinsci.plugins.cloudstats.CloudStatistics; import org.jenkinsci.plugins.cloudstats.ProvisioningActivity; import org.jenkinsci.plugins.cloudstats.TrackedItem; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -import javax.annotation.Nullable; +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; import java.io.IOException; -import java.util.List; +import java.nio.charset.Charset; +import java.util.logging.Level; + +import static java.nio.charset.Charset.defaultCharset; +import static java.util.Objects.nonNull; /** * @author Kanstantsin Shautsou */ public class DockerSlaveSingle extends AbstractCloudSlave implements TrackedItem { + private static final Logger LOG = LoggerFactory.getLogger(DockerSlaveSingle.class); - private ProvisioningActivity.Id activityId; - - public DockerSlaveSingle(String name, - String nodeDescription, - String remoteFS, - String numExecutors, - Mode mode, - String labelString, - ComputerLauncher launcher, - RetentionStrategy retentionStrategy, - List> nodeProperties) - throws Descriptor.FormException, IOException { - super(name, nodeDescription, remoteFS, numExecutors, mode, labelString, launcher, retentionStrategy, nodeProperties); - } + private final YADockerConnector connector; + private final ProvisioningActivity.Id activityId; + private final DockerSlaveConfig config; - public DockerSlaveSingle(String name, - String nodeDescription, - String remoteFS, - int numExecutors, - Mode mode, - String labelString, - ComputerLauncher launcher, - RetentionStrategy retentionStrategy, - List> nodeProperties, - ProvisioningActivity.Id activityId) - throws Descriptor.FormException, IOException { - super(name, nodeDescription, remoteFS, numExecutors, mode, labelString, launcher, retentionStrategy, nodeProperties); + private transient TaskListener listener; + + public DockerSlaveSingle(@Nonnull String name, + @Nonnull String nodeDescription, + @Nonnull DockerSlaveConfig config, + @Nonnull YADockerConnector connector, + @Nonnull ProvisioningActivity.Id activityId) + throws IOException, Descriptor.FormException { + super(name, nodeDescription, + config.getRemoteFs(), config.getNumExecutors(), config.getMode(), + "", + config.getLauncher(), config.getRetentionStrategy(), config.getNodeProperties()); + this.connector = connector; this.activityId = activityId; + this.config = config; + + } + + public YADockerConnector getConnector() { + return connector; + } + + public DockerSlaveConfig getConfig() { + return config; + } + + @Override + public DelegatingComputerLauncher getLauncher() { + return (DelegatingComputerLauncher) super.getLauncher(); + } + + private String getContainerId() { + return ((DockerComputerSingleJNLPLauncher) getLauncher().getLauncher()).getContainerId(); + } + + @Nonnull + public TaskListener getListener() { + return nonNull(listener) ? listener : new StreamTaskListener(System.out, Charset.forName("UTF-8")); + } + + public void setListener(TaskListener listener) { + this.listener = listener; } @Override @@ -55,16 +87,84 @@ public AbstractCloudComputer createComputer() { return new DockerComputerSingle(this, activityId); } + + @Override + public void terminate() throws InterruptedException, IOException { + try { + _terminate(getListener()); + } finally { + try { + Jenkins.getInstance().removeNode(this); + } catch (IOException e) { + LOG.warn("Failed to remove {}", name, e); + getListener().error("Failed to remove " + name); + } + } + } + @Override protected void _terminate(TaskListener listener) throws IOException, InterruptedException { - CloudStatistics statistics = CloudStatistics.get(); - ProvisioningActivity activity = statistics.getActivityFor(this); - if (activity != null) { + final DockerContainerLifecycle dockerContainerLifecycle = config.getDockerContainerLifecycle(); + try { + LOG.info("Requesting disconnect for computer: '{}'", name); + final Computer toComputer = toComputer(); + if (toComputer != null) { + toComputer.disconnect(new DockerOfflineCause("Terminating from _terminate.")); + } + } catch (Exception e) { + LOG.error("Can't disconnect computer: '{}'", name, e); + listener.error("Can't disconnect computer: " + name); + } + + if (StringUtils.isNotBlank(getContainerId())) { + try { + dockerContainerLifecycle.getStopContainer().exec(connector.getClient(), getContainerId()); + LOG.info("Stopped container {}", getContainerId()); + listener.getLogger().println("Stopped container " + getContainerId()); + } catch (NotModifiedException ex) { + LOG.info("Container '{}' is already stopped.", getContainerId()); + } catch (Exception ex) { + LOG.error("Failed to stop instance '{}' for slave '{}' due to exception: {}", + getContainerId(), name, ex.getMessage()); + } + + final Computer computer = toComputer(); + if (computer instanceof DockerComputerSingle) { + final DockerComputerSingle dockerComputer = (DockerComputerSingle) computer; + for (DockerTerminateCmdAction a : dockerComputer.getActions(DockerTerminateCmdAction.class)) { + try { + a.exec(connector.getClient(), getContainerId()); + } catch (Exception e) { + LOG.error("Failed execute action {}", a, e); + listener.error("Failed execute " + a.getDisplayName()); + } + } + } else { + LOG.error("Computer '{}' is not DockerComputerSingle", computer); + listener.error("Computer ' " + computer + "' is not DockerComputerSingle", computer); + } + + try { + dockerContainerLifecycle.getRemoveContainer().exec(connector.getClient(), getContainerId()); + LOG.info("Removed container {}", getContainerId()); + listener.getLogger().println("Removed container " + getContainerId()); + } catch (Exception ex) { + LOG.error("Failed to remove instance '{}' for slave '{}' due to exception: {}", + getContainerId(), name, ex.getMessage()); + listener.error("failed to remove " + getContainerId()); + } + } else { + LOG.error("ContainerId is absent, no way to remove/stop container"); + listener.error("ContainerId is absent, no way to remove/stop container"); + } + + ProvisioningActivity activity = CloudStatistics.get().getActivityFor(this); + if (nonNull(activity)) { activity.enterIfNotAlready(ProvisioningActivity.Phase.COMPLETED); } } - @Nullable + @Nonnull @Override public ProvisioningActivity.Id getId() { return activityId; diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/commons/AbstractCloud.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/commons/AbstractCloud.java index 1447ed1d..f394ad1a 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/commons/AbstractCloud.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/commons/AbstractCloud.java @@ -10,10 +10,7 @@ import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; @@ -215,7 +212,7 @@ public AbstractCloudDescriptor getDescriptor() { return (AbstractCloudDescriptor) super.getDescriptor(); } - public static abstract class AbstractCloudDescriptor extends Descriptor { + public abstract static class AbstractCloudDescriptor extends Descriptor { // docker cloud id <> set public final transient ConcurrentHashMap> transientTemplates = new ConcurrentHashMap<>(); public final transient ConcurrentHashMap> provisionedImages = new ConcurrentHashMap<>(); diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/launcher/DockerComputerSingleJNLPLauncher.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/launcher/DockerComputerSingleJNLPLauncher.java index 35877f22..2fa77708 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/launcher/DockerComputerSingleJNLPLauncher.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/launcher/DockerComputerSingleJNLPLauncher.java @@ -2,7 +2,7 @@ import com.github.kostyasha.yad.DockerComputerSingle; import com.github.kostyasha.yad.DockerContainerLifecycle; -import com.github.kostyasha.yad.action.DockerLabelAssignmentAction; +import com.github.kostyasha.yad.DockerSlaveSingle; import com.github.kostyasha.yad.commons.DockerCreateContainer; import com.github.kostyasha.yad_docker_java.com.github.dockerjava.api.DockerClient; import com.github.kostyasha.yad_docker_java.com.github.dockerjava.api.command.CreateContainerCmd; @@ -25,6 +25,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.annotation.CheckForNull; import javax.annotation.Nonnull; import java.io.IOException; import java.io.InputStream; @@ -58,6 +59,8 @@ public class DockerComputerSingleJNLPLauncher extends JNLPLauncher { protected boolean noCertificateCheck = false; + private String containerId; + @DataBoundSetter public void setSlaveOpts(String slaveOpts) { this.slaveOpts = trimToEmpty(slaveOpts); @@ -114,6 +117,17 @@ public void setJenkinsUrl(String jenkinsUrl) { this.jenkinsUrl = trimToEmpty(jenkinsUrl); } + private void setContainerId(String containerId) { + this.containerId = containerId; + } + + /** + * @return not null when container launched. + */ + @CheckForNull + public String getContainerId() { + return containerId; + } @Override public void launch(SlaveComputer computer, TaskListener listener) { @@ -134,11 +148,9 @@ private void provisionWithWait(DockerComputerSingle computer, TaskListener liste final PrintStream logger = listener.getLogger(); final Run run = computer.getRun(); - final DockerLabelAssignmentAction action = run.getAction(DockerLabelAssignmentAction.class); - final DockerClient client = action.getConnector().getClient(); - // final DockerContainerLifecycle dockerContainerLifecycle = template.getDockerContainerLifecycle(); - final DockerContainerLifecycle containerLifecycle = new DockerContainerLifecycle(); - containerLifecycle.setImage("java:8-jdk-alpine"); + final DockerSlaveSingle slave = computer.getNode(); + final DockerClient client = slave.getConnector().getClient(); + final DockerContainerLifecycle containerLifecycle = slave.getConfig().getDockerContainerLifecycle(); final String imageId = containerLifecycle.getImage(); @@ -159,6 +171,7 @@ private void provisionWithWait(DockerComputerSingle computer, TaskListener liste // create CreateContainerResponse createResp = containerConfig.exec(); String containerId = createResp.getId(); + setContainerId(containerId); logger.println("Created container " + containerId + ", for " + run.getDisplayName()); LOG.debug("Created container {}, for {}", containerId, run.getDisplayName()); // start @@ -271,7 +284,6 @@ private void provisionWithWait(DockerComputerSingle computer, TaskListener liste logger.println("Launched slave for " + containerId); } - public void appendContainerConfig(CreateContainerCmd createContainerCmd) throws IOException { try (InputStream instream = DockerComputerJNLPLauncher.class.getResourceAsStream("DockerComputerJNLPLauncher/init.sh")) { diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/listener/DockerQueueListener.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/listener/DockerQueueListener.java index ebb5bd9e..99a0fefc 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/listener/DockerQueueListener.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/listener/DockerQueueListener.java @@ -1,6 +1,7 @@ package com.github.kostyasha.yad.listener; import com.github.kostyasha.yad.DockerContainerLifecycle; +import com.github.kostyasha.yad.DockerSlaveConfig; import com.github.kostyasha.yad.DockerSlaveSingle; import com.github.kostyasha.yad.DockerSlaveTemplate; import com.github.kostyasha.yad.action.DockerLabelAssignmentAction; @@ -9,6 +10,7 @@ import hudson.model.Queue; import hudson.model.queue.QueueListener; import jenkins.model.Jenkins; +import org.jenkinsci.plugins.cloudstats.CloudStatistics; import org.jenkinsci.plugins.cloudstats.ProvisioningActivity; import java.io.IOException; @@ -60,7 +62,7 @@ public void onEnterWaiting(Queue.WaitingItem wi) { // final SlaveJobConfig slaveJobConfig = dockerProp.getSlaveJobConfig(); // if (slaveJobConfig instanceof DockerCloudJobConfig) { // final DockerCloudJobConfig cloudJobConfig = (DockerCloudJobConfig) slaveJobConfig; -// final DockerSlaveTemplate template = cloudJobConfig.getTemplate(); +// final DockerSlaveTemplate template = cloudJobConfig.getConfig(); // template.setLabelString(uuid.toString()); // // final DockerCloud cloud = (DockerCloud) Jenkins.getInstance().getCloud(cloudJobConfig.getConnector().getCloudId()); @@ -78,32 +80,27 @@ public void onEnterWaiting(Queue.WaitingItem wi) { private boolean processItemWithAction(Queue.WaitingItem wi) { final DockerLabelAssignmentAction action = wi.getAction(DockerLabelAssignmentAction.class); if (nonNull(action)) { - try { - final DockerSlaveTemplate template = action.getSlaveConfig(); - final DockerContainerLifecycle lifecycle = template.getDockerContainerLifecycle(); + final DockerSlaveConfig template = action.getSlaveConfig(); + final DockerContainerLifecycle lifecycle = template.getDockerContainerLifecycle(); - final ProvisioningActivity.Id activityId = new ProvisioningActivity.Id( + final ProvisioningActivity.Id activityId = new ProvisioningActivity.Id( wi.getDisplayName(), lifecycle.getImage() - ); + ); + try { get().onStarted(activityId); final DockerSlaveSingle slave = new DockerSlaveSingle("docker-slave", - "Slave for " + wi.getDisplayName(), - template.getRemoteFs(), - template.getNumExecutors(), - template.getMode(), - "", // label string - template.getLauncher(), - template.getRetentionStrategy(), - emptyList(), - activityId + "Slave for " + wi.getDisplayName(), + action.getSlaveConfig(), + action.getConnector(), + activityId ); Jenkins.getInstance().addNode(slave); return true; } catch (Descriptor.FormException | IOException e) { - e.printStackTrace(); + CloudStatistics.ProvisioningListener.get().onFailure(activityId, e); } } return false; diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/listener/DockerRunListener.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/listener/DockerRunListener.java index 571c2fb8..1e1d57dd 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/listener/DockerRunListener.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/listener/DockerRunListener.java @@ -11,8 +11,10 @@ import hudson.model.Run; import hudson.model.TaskListener; import hudson.model.listeners.RunListener; +import hudson.slaves.ComputerLauncher; import hudson.slaves.DelegatingComputerLauncher; import jenkins.model.Jenkins; +import org.jenkinsci.plugins.cloudstats.CloudStatistics; import org.jenkinsci.plugins.docker.commons.fingerprint.DockerFingerprints; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -20,6 +22,7 @@ import java.io.IOException; import java.text.ParseException; +import static com.github.kostyasha.yad.utils.ContainerRecordUtils.attachFacet; import static com.github.kostyasha.yad.utils.ContainerRecordUtils.createRecordFor; import static java.util.Objects.isNull; @@ -37,42 +40,19 @@ public void onStarted(Run run, TaskListener listener) { return; } + final DockerSlaveSingle node = (DockerSlaveSingle) Jenkins.getInstance().getNode(assignmentAction.getAssignedLabel()); try { - final Node node = Jenkins.getInstance().getNode(assignmentAction.getAssignedLabel()); - final DockerSlaveSingle dockerSlave = (DockerSlaveSingle) node; - final DockerComputerSingle computer = (DockerComputerSingle) dockerSlave.toComputer(); + final DockerComputerSingle computer = (DockerComputerSingle) node.toComputer(); computer.setRun(run); computer.setListener(listener); ((DelegatingComputerLauncher) computer.getLauncher()).getLauncher().launch(computer, listener); } catch (IOException | InterruptedException e) { LOG.error("fd", e); + CloudStatistics.ProvisioningListener.get().onFailure(node.getId(), e); } attachFacet(run, listener); } - private void attachFacet(Run run, TaskListener listener) { - final Executor executor = run.getExecutor(); - if (executor == null) { - return; - } - - final Computer owner = executor.getOwner(); - DockerComputer dockerComputer; - if (owner instanceof DockerComputer) { - dockerComputer = (DockerComputer) owner; - } else { - return; - } - try { - DockerFingerprints.addRunFacet( - createRecordFor(dockerComputer), - run - ); - } catch (IOException | ParseException e) { - listener.error("Can't add Docker fingerprint to run."); - LOG.error("Can't add fingerprint to run {}", run, e); - } - } } diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/strategy/DockerOnceRetentionStrategy.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/strategy/DockerOnceRetentionStrategy.java index 781c8934..dd77c784 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/strategy/DockerOnceRetentionStrategy.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/strategy/DockerOnceRetentionStrategy.java @@ -93,7 +93,7 @@ public void taskCompletedWithProblems(Executor executor, Queue.Task task, long d done(executor); } - private void done(Executor executor) { + protected void done(Executor executor) { final AbstractCloudComputer c = (AbstractCloudComputer) executor.getOwner(); Queue.Executable exec = executor.getCurrentExecutable(); if (executor instanceof OneOffExecutor) { @@ -110,7 +110,7 @@ private void done(Executor executor) { done(c); } - private void done(final AbstractCloudComputer c) { + protected void done(final AbstractCloudComputer c) { c.setAcceptingTasks(false); // just in case synchronized (this) { if (terminating) { diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/utils/ContainerRecordUtils.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/utils/ContainerRecordUtils.java index b3f41271..e4b68343 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/utils/ContainerRecordUtils.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/utils/ContainerRecordUtils.java @@ -5,7 +5,14 @@ import com.github.kostyasha.yad_docker_java.com.fasterxml.jackson.databind.util.StdDateFormat; import com.github.kostyasha.yad_docker_java.com.github.dockerjava.api.DockerClient; import com.github.kostyasha.yad_docker_java.com.github.dockerjava.api.command.InspectContainerResponse; +import hudson.model.Computer; +import hudson.model.Executor; +import hudson.model.Run; +import hudson.model.TaskListener; import org.jenkinsci.plugins.docker.commons.fingerprint.ContainerRecord; +import org.jenkinsci.plugins.docker.commons.fingerprint.DockerFingerprints; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.IOException; import java.text.ParseException; @@ -19,6 +26,8 @@ * @author Kanstantsin Shautsou */ public class ContainerRecordUtils { + private static final Logger LOG = LoggerFactory.getLogger(ContainerRecordUtils.class); + private ContainerRecordUtils() { } @@ -46,4 +55,29 @@ public static ContainerRecord createRecordFor(DockerComputer computer) throws Pa return new ContainerRecord(host, containerId, imageId, containerName, created, tags); } + + public static void attachFacet(Run run, TaskListener listener) { + final Executor executor = run.getExecutor(); + if (executor == null) { + return; + } + + final Computer owner = executor.getOwner(); + DockerComputer dockerComputer; + if (owner instanceof DockerComputer) { + dockerComputer = (DockerComputer) owner; + } else { + return; + } + + try { + DockerFingerprints.addRunFacet( + createRecordFor(dockerComputer), + run + ); + } catch (IOException | ParseException e) { + listener.error("Can't add Docker fingerprint to run."); + LOG.error("Can't add fingerprint to run {}", run, e); + } + } } diff --git a/yet-another-docker-plugin/src/test/java/org/jvnet/hudson/test/DockerSimpleBuildWrapperTest.java b/yet-another-docker-plugin/src/test/java/org/jvnet/hudson/test/DockerSimpleBuildWrapperTest.java new file mode 100644 index 00000000..7e8ffdf6 --- /dev/null +++ b/yet-another-docker-plugin/src/test/java/org/jvnet/hudson/test/DockerSimpleBuildWrapperTest.java @@ -0,0 +1,110 @@ +package org.jvnet.hudson.test; + +import com.github.kostyasha.yad.DockerConnector; +import com.github.kostyasha.yad.DockerSimpleBuildWrapper; +import com.github.kostyasha.yad.DockerSlaveConfig; +import com.github.kostyasha.yad.launcher.DockerComputerSingleJNLPLauncher; +import com.github.kostyasha.yad.launcher.NoOpDelegatingComputerLauncher; +import com.github.kostyasha.yad.strategy.DockerOnceRetentionStrategy; +import hudson.model.FreeStyleBuild; +import hudson.model.FreeStyleProject; +import hudson.model.queue.QueueTaskFuture; +import hudson.tasks.Shell; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.HttpConnectionFactory; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.webapp.Configuration; +import org.eclipse.jetty.webapp.WebAppContext; +import org.eclipse.jetty.webapp.WebXmlConfiguration; +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.ServletContext; +import java.io.IOException; +import java.net.URL; +import java.util.UUID; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import static com.github.kostyasha.yad.other.ConnectorType.JERSEY; + +/** + * @author Kanstantsin Shautsou + */ +public class DockerSimpleBuildWrapperTest { + private static final Logger LOG = LoggerFactory.getLogger(DockerSimpleBuildWrapperTest.class); + + // switch to Inet4Address? + private static final String ADDRESS = "192.168.1.3"; + + @Rule + public JenkinsRule jRule = new JenkinsRule() { + @Override + public URL getURL() throws IOException { + return new URL("http://" + ADDRESS + ":" + localPort + contextPath + "/"); + } + + @Override + protected ServletContext createWebServer() throws Exception { + server = new Server(new ThreadPoolImpl(new ThreadPoolExecutor(10, 10, 10L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), r -> { + Thread t = new Thread(r); + t.setName("Jetty Thread Pool"); + return t; + }))); + + WebAppContext context = new WebAppContext(WarExploder.getExplodedDir().getPath(), contextPath); + context.setClassLoader(getClass().getClassLoader()); + context.setConfigurations(new Configuration[]{new WebXmlConfiguration()}); + context.addBean(new NoListenerConfiguration(context)); + server.setHandler(context); + context.setMimeTypes(MIME_TYPES); + context.getSecurityHandler().setLoginService(configureUserRealm()); + context.setResourceBase(WarExploder.getExplodedDir().getPath()); + + ServerConnector connector = new ServerConnector(server); + HttpConfiguration config = connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration(); + // use a bigger buffer as Stapler traces can get pretty large on deeply nested URL + config.setRequestHeaderSize(12 * 1024); + connector.setHost(ADDRESS); + if (System.getProperty("port") != null) + connector.setPort(Integer.parseInt(System.getProperty("port"))); + + server.addConnector(connector); + server.start(); + + localPort = connector.getLocalPort(); + LOG.info("Running on {}", getURL()); + + return context.getServletContext(); + } + }; + + @Ignore("For local experiments") + @Test + public void testWrapper() throws Exception { + final FreeStyleProject project = jRule.createProject(FreeStyleProject.class, "freestyle"); + + final DockerConnector connector = new DockerConnector("tcp://" + ADDRESS + ":2376/"); + connector.setConnectorType(JERSEY); + + final DockerSlaveConfig config = new DockerSlaveConfig(UUID.randomUUID().toString()); + config.getDockerContainerLifecycle().setImage("java:8-jdk-alpine"); + config.setLauncher(new NoOpDelegatingComputerLauncher(new DockerComputerSingleJNLPLauncher())); + config.setRetentionStrategy(new DockerOnceRetentionStrategy(10)); + + final DockerSimpleBuildWrapper dockerSimpleBuildWrapper = new DockerSimpleBuildWrapper(connector, config); + project.getBuildWrappersList().add(dockerSimpleBuildWrapper); + project.getBuildersList().add(new Shell("sleep 30")); + + final QueueTaskFuture taskFuture = project.scheduleBuild2(0); + + jRule.waitUntilNoActivity(); + jRule.pause(); + } + +} \ No newline at end of file From 370b1fc1ef14b4d4fe5cad3c19fe21dc16173e4a Mon Sep 17 00:00:00 2001 From: Kanstantsin Shautsou Date: Fri, 3 Feb 2017 21:10:31 +0300 Subject: [PATCH 12/17] Cleanups Signed-off-by: Kanstantsin Shautsou --- .../yad/DockerSimpleBuildWrapper.java | 41 ++++++++++++------- .../kostyasha/yad/DockerSlaveConfig.java | 6 +++ .../kostyasha/yad/DockerSlaveTemplate.java | 2 +- .../yad/utils/ContainerRecordUtils.java | 24 ++++++----- .../test/DockerSimpleBuildWrapperTest.java | 2 +- 5 files changed, 47 insertions(+), 28 deletions(-) diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSimpleBuildWrapper.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSimpleBuildWrapper.java index f94004f5..d7c803d4 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSimpleBuildWrapper.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSimpleBuildWrapper.java @@ -16,9 +16,11 @@ import org.jenkinsci.Symbol; import org.jenkinsci.plugins.cloudstats.CloudStatistics; import org.jenkinsci.plugins.cloudstats.ProvisioningActivity; +import org.kohsuke.stapler.DataBoundConstructor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.annotation.CheckForNull; import javax.annotation.Nonnull; import java.io.IOException; @@ -26,15 +28,21 @@ import static org.jenkinsci.plugins.cloudstats.CloudStatistics.ProvisioningListener.get; /** + * Wrapper that starts node and allows body execute anything in created {@link #getSlaveName()} slave. + * Body may assign tasks by {@link hudson.model.Label} using Actions or do anything directly on it. + * By defauld {@link com.github.kostyasha.yad.strategy.DockerOnceRetentionStrategy} is used and terminates + * slave after first execution, set other or override it with custom logic. + * * @author Kanstantsin Shautsou */ public class DockerSimpleBuildWrapper extends SimpleBuildWrapper { private static final Logger LOG = LoggerFactory.getLogger(DockerSimpleBuildWrapper.class); + private DockerConnector connector; private DockerSlaveConfig config; - private String slaveName; + @DataBoundConstructor public DockerSimpleBuildWrapper(@Nonnull DockerConnector connector, @Nonnull DockerSlaveConfig config) { this.connector = connector; this.config = config; @@ -48,11 +56,12 @@ public DockerSlaveConfig getConfig() { return config; } + @CheckForNull public String getSlaveName() { return slaveName; } - public void setSlaveName(String slaveName) { + protected void setSlaveName(String slaveName) { this.slaveName = slaveName; } @@ -80,8 +89,6 @@ public void setUp(Context context, activityId ); - attachFacet(run, listener); - Jenkins.getInstance().addNode(slave); final DockerSlaveSingle node = (DockerSlaveSingle) Jenkins.getInstance().getNode(futureName); @@ -97,23 +104,18 @@ public void setUp(Context context, CloudStatistics.ProvisioningListener.get().onFailure(node.getId(), e); } setSlaveName(futureName); + context.setDisposer(new DisposerImpl(futureName)); + } catch (Descriptor.FormException | IOException e) { get().onFailure(activityId, e); throw new AbortException("failed to run slave"); } - context.setDisposer(new DisposerImpl(getSlaveName())); - } - - @Symbol("dockerCustomWrapper") - @Extension - public static final class DescriptorImpl extends BuildWrapperDescriptor { - @Override - public boolean isApplicable(AbstractProject item) { - return true; - } } + /** + * Terminates slave for specified slaveName. Works only with {@link DockerSlaveSingle}. + */ public static class DisposerImpl extends Disposer { private static final long serialVersionUID = 1; private final String slaveName; @@ -132,10 +134,19 @@ public void tearDown(Run run, FilePath workspace, Launcher launcher, TaskL try { node.terminate(); } catch (IOException | InterruptedException e) { - LOG.error("fd", e); + LOG.error("Can't terminate node", e); CloudStatistics.ProvisioningListener.get().onFailure(node.getId(), e); } } } + + @Symbol("dockerCustomWrapper") + @Extension + public static final class DescriptorImpl extends BuildWrapperDescriptor { + @Override + public boolean isApplicable(AbstractProject item) { + return true; + } + } } diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlaveConfig.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlaveConfig.java index f4325093..9c6f8223 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlaveConfig.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlaveConfig.java @@ -24,11 +24,13 @@ import javax.annotation.Nonnull; import java.io.IOException; import java.util.List; +import java.util.UUID; import static java.util.Collections.emptyList; import static java.util.Collections.unmodifiableList; import static java.util.Objects.isNull; import static java.util.Objects.nonNull; +import static java.util.UUID.randomUUID; /** * Some generic config with everything required for container-slave operaition. @@ -64,6 +66,10 @@ public class DockerSlaveConfig extends AbstractDescribableImpl> nodeProperties = emptyList(); + public DockerSlaveConfig() { + this.id = randomUUID().toString(); + } + /** * @param id some unique id to identify this configuration. Use case - count running computers based on this config. */ diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlaveTemplate.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlaveTemplate.java index 922e84ff..e9af0a29 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlaveTemplate.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlaveTemplate.java @@ -43,7 +43,7 @@ public class DockerSlaveTemplate extends DockerSlaveConfig { * Generates new unique ID for new instances. */ public DockerSlaveTemplate() { - super(UUID.randomUUID().toString()); + super(); } /** diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/utils/ContainerRecordUtils.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/utils/ContainerRecordUtils.java index e4b68343..b7b9b6bc 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/utils/ContainerRecordUtils.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/utils/ContainerRecordUtils.java @@ -2,6 +2,7 @@ import com.github.kostyasha.yad.DockerCloud; import com.github.kostyasha.yad.DockerComputer; +import com.github.kostyasha.yad.DockerComputerSingle; import com.github.kostyasha.yad_docker_java.com.fasterxml.jackson.databind.util.StdDateFormat; import com.github.kostyasha.yad_docker_java.com.github.dockerjava.api.DockerClient; import com.github.kostyasha.yad_docker_java.com.github.dockerjava.api.command.InspectContainerResponse; @@ -66,18 +67,19 @@ public static void attachFacet(Run run, TaskListener listener) { DockerComputer dockerComputer; if (owner instanceof DockerComputer) { dockerComputer = (DockerComputer) owner; - } else { - return; - } + try { + DockerFingerprints.addRunFacet( + createRecordFor(dockerComputer), + run + ); + } catch (IOException | ParseException e) { + listener.error("Can't add Docker fingerprint to run."); + LOG.error("Can't add fingerprint to run {}", run, e); + } + } else if (owner instanceof DockerComputerSingle) { - try { - DockerFingerprints.addRunFacet( - createRecordFor(dockerComputer), - run - ); - } catch (IOException | ParseException e) { - listener.error("Can't add Docker fingerprint to run."); - LOG.error("Can't add fingerprint to run {}", run, e); } + + } } diff --git a/yet-another-docker-plugin/src/test/java/org/jvnet/hudson/test/DockerSimpleBuildWrapperTest.java b/yet-another-docker-plugin/src/test/java/org/jvnet/hudson/test/DockerSimpleBuildWrapperTest.java index 7e8ffdf6..616977ff 100644 --- a/yet-another-docker-plugin/src/test/java/org/jvnet/hudson/test/DockerSimpleBuildWrapperTest.java +++ b/yet-another-docker-plugin/src/test/java/org/jvnet/hudson/test/DockerSimpleBuildWrapperTest.java @@ -92,7 +92,7 @@ public void testWrapper() throws Exception { final DockerConnector connector = new DockerConnector("tcp://" + ADDRESS + ":2376/"); connector.setConnectorType(JERSEY); - final DockerSlaveConfig config = new DockerSlaveConfig(UUID.randomUUID().toString()); + final DockerSlaveConfig config = new DockerSlaveConfig(); config.getDockerContainerLifecycle().setImage("java:8-jdk-alpine"); config.setLauncher(new NoOpDelegatingComputerLauncher(new DockerComputerSingleJNLPLauncher())); config.setRetentionStrategy(new DockerOnceRetentionStrategy(10)); From 671b53c9539c5ed85269b132d2649e58b61af028 Mon Sep 17 00:00:00 2001 From: Kanstantsin Shautsou Date: Fri, 3 Feb 2017 21:11:03 +0300 Subject: [PATCH 13/17] undo intend Signed-off-by: Kanstantsin Shautsou --- .../com/github/kostyasha/yad/DockerCloud.java | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerCloud.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerCloud.java index d897d34e..88a2bbdc 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerCloud.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerCloud.java @@ -103,7 +103,7 @@ public synchronized Collection provision(@CheckForNull Label label, final DockerSlaveTemplate t = tryTemplates.get(0); // get first LOG.info("Will provision '{}', for label: '{}', in cloud: '{}'", - t.getDockerContainerLifecycle().getImage(), label, getDisplayName()); + t.getDockerContainerLifecycle().getImage(), label, getDisplayName()); try { if (!addProvisionedSlave(t)) { @@ -112,34 +112,34 @@ public synchronized Collection provision(@CheckForNull Label label, } } catch (Exception e) { LOG.warn("Bad template '{}' in cloud '{}': '{}'. Trying next template...", - t.getDockerContainerLifecycle().getImage(), getDisplayName(), e.getMessage(), e); + t.getDockerContainerLifecycle().getImage(), getDisplayName(), e.getMessage(), e); tryTemplates.remove(t); continue; } final ProvisioningActivity.Id id = new ProvisioningActivity.Id(getDisplayName(), - t.getDockerContainerLifecycle().getImage()); + t.getDockerContainerLifecycle().getImage()); r.add(new TrackedPlannedNode( - id, - t.getNumExecutors(), - Computer.threadPoolForRemoting.submit(() -> { - get().onStarted(id); - try { - final DockerSlave dockerSlave = provisionWithWait(t, id); - get().onComplete(id, dockerSlave); //rename - return dockerSlave; - } catch (Exception ex) { - LOG.error("Error in provisioning; template='{}' for cloud='{}'", - t, getDisplayName(), ex); - get().onFailure(id, ex); - throw Throwables.propagate(ex); - } finally { - decrementAmiSlaveProvision(t); - } - }) - ) + id, + t.getNumExecutors(), + Computer.threadPoolForRemoting.submit(() -> { + get().onStarted(id); + try { + final DockerSlave dockerSlave = provisionWithWait(t, id); + get().onComplete(id, dockerSlave); //rename + return dockerSlave; + } catch (Exception ex) { + LOG.error("Error in provisioning; template='{}' for cloud='{}'", + t, getDisplayName(), ex); + get().onFailure(id, ex); + throw Throwables.propagate(ex); + } finally { + decrementAmiSlaveProvision(t); + } + }) + ) ); excessWorkload -= t.getNumExecutors(); } @@ -199,7 +199,7 @@ private void appendContainerConfig(DockerSlaveTemplate slaveTemplate, CreateCont * Provision slave container and wait for it's availability. */ private DockerSlave provisionWithWait(DockerSlaveTemplate template, ProvisioningActivity.Id id) - throws IOException, Descriptor.FormException { + throws IOException, Descriptor.FormException { final DockerContainerLifecycle dockerContainerLifecycle = template.getDockerContainerLifecycle(); final String imageId = dockerContainerLifecycle.getImage(); final DockerComputerLauncher computerLauncher = template.getLauncher(); @@ -257,7 +257,7 @@ public int countCurrentDockerSlaves(final DockerSlaveTemplate template) throws E // count only total cloud capacity count++; } else if (labels.containsKey(DOCKER_TEMPLATE_LABEL) && - labels.get(DOCKER_TEMPLATE_LABEL).equals(template.getId())) { + labels.get(DOCKER_TEMPLATE_LABEL).equals(template.getId())) { count++; } } @@ -291,18 +291,18 @@ private synchronized boolean addProvisionedSlave(DockerSlaveTemplate template) t if (estimatedTotalSlaves >= getContainerCap()) { LOG.info("Not Provisioning '{}'; Server '{}' full with '{}' container(s)", - dockerImageName, name, getContainerCap()); + dockerImageName, name, getContainerCap()); return false; // maxed out } if (templateCapacity != 0 && estimatedAmiSlaves >= templateCapacity) { LOG.info("Not Provisioning '{}'. Instance limit of '{}' reached on server '{}'", - dockerImageName, templateCapacity, name); + dockerImageName, templateCapacity, name); return false; // maxed out } LOG.info("Provisioning '{}' number '{}' on '{}'; Total containers: '{}'", - dockerImageName, estimatedAmiSlaves, name, estimatedTotalSlaves); + dockerImageName, estimatedAmiSlaves, name, estimatedTotalSlaves); provisionedImages.put(template, currentProvisioning + 1); return true; From a5bc377346c1e1aa70c9f526678fbedc3b6489a0 Mon Sep 17 00:00:00 2001 From: Kanstantsin Shautsou Date: Fri, 3 Feb 2017 21:12:31 +0300 Subject: [PATCH 14/17] Undo format Signed-off-by: Kanstantsin Shautsou --- .../github/kostyasha/yad/DockerSlaveTemplate.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlaveTemplate.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlaveTemplate.java index e9af0a29..ca9c6d9f 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlaveTemplate.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlaveTemplate.java @@ -61,7 +61,7 @@ public DockerSlaveTemplate(@Nonnull String id) throws FormException { */ @DataBoundConstructor public DockerSlaveTemplate(@Nonnull String id, List> nodePropertiesUI) - throws FormException { + throws FormException { this(id); setNodeProperties(nodePropertiesUI); } @@ -142,9 +142,9 @@ public boolean equals(Object o) { DockerSlaveTemplate that = (DockerSlaveTemplate) o; return new EqualsBuilder() - .appendSuper(true) - .append(maxCapacity, that.maxCapacity) - .isEquals(); + .appendSuper(true) + .append(maxCapacity, that.maxCapacity) + .isEquals(); } @Override @@ -154,8 +154,8 @@ public String toString() { public String getShortDescription() { return MoreObjects.toStringHelper(this) - .add("image", dockerContainerLifecycle.getImage()) - .toString(); + .add("image", dockerContainerLifecycle.getImage()) + .toString(); } @Override From a6d988ed01622b6c7dcc3443cccfee2b581939c3 Mon Sep 17 00:00:00 2001 From: Kanstantsin Shautsou Date: Fri, 3 Feb 2017 21:32:19 +0300 Subject: [PATCH 15/17] checkstyle clenaups Signed-off-by: Kanstantsin Shautsou --- .../kostyasha/yad/DockerSlaveConfig.java | 1 - .../kostyasha/yad/DockerSlaveSingle.java | 6 +-- .../action/DockerLabelAssignmentAction.java | 1 - .../CredentialsYADockerConnector.java | 6 +-- .../DockerComputerSingleJNLPLauncher.java | 44 +++++++++---------- .../NoOpDelegatingComputerLauncher.java | 1 - .../yad/listener/DockerRunListener.java | 8 ---- .../yad/utils/ContainerRecordUtils.java | 3 -- .../test/DockerSimpleBuildWrapperTest.java | 1 - 9 files changed, 28 insertions(+), 43 deletions(-) diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlaveConfig.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlaveConfig.java index 9c6f8223..b4a6f2bc 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlaveConfig.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlaveConfig.java @@ -24,7 +24,6 @@ import javax.annotation.Nonnull; import java.io.IOException; import java.util.List; -import java.util.UUID; import static java.util.Collections.emptyList; import static java.util.Collections.unmodifiableList; diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlaveSingle.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlaveSingle.java index 5c9870e3..0c1c6844 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlaveSingle.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlaveSingle.java @@ -19,13 +19,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.annotation.CheckForNull; import javax.annotation.Nonnull; import java.io.IOException; import java.nio.charset.Charset; -import java.util.logging.Level; -import static java.nio.charset.Charset.defaultCharset; import static java.util.Objects.nonNull; /** @@ -78,6 +75,9 @@ public TaskListener getListener() { return nonNull(listener) ? listener : new StreamTaskListener(System.out, Charset.forName("UTF-8")); } + /** + * Set listener that will be used for printing out messages instead default listener. + */ public void setListener(TaskListener listener) { this.listener = listener; } diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/action/DockerLabelAssignmentAction.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/action/DockerLabelAssignmentAction.java index 027d336b..cb273818 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/action/DockerLabelAssignmentAction.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/action/DockerLabelAssignmentAction.java @@ -7,7 +7,6 @@ import hudson.model.queue.SubTask; import jenkins.model.Jenkins; -import javax.annotation.CheckForNull; import javax.annotation.Nonnull; import javax.annotation.Nullable; diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/connector/CredentialsYADockerConnector.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/connector/CredentialsYADockerConnector.java index 1bbe1e81..549d5c49 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/connector/CredentialsYADockerConnector.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/connector/CredentialsYADockerConnector.java @@ -7,7 +7,6 @@ import javax.annotation.CheckForNull; import javax.annotation.Nonnull; - import java.security.KeyManagementException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; @@ -121,14 +120,15 @@ public DockerClient getClient() throws UnrecoverableKeyException, NoSuchAlgorith .withApiVersion(apiVersion) .withDockerHost(serverUrl); - final DockerClient client = newClientBuilderForConnector() + final DockerClient newClient = newClientBuilderForConnector() .withConfigBuilder(configBuilder) .withConnectorType(connectorType) .withCredentials(credentials) .withConnectTimeout(connectTimeout) .build(); - client.versionCmd().exec(); + newClient.versionCmd().exec(); + client = newClient; } return client; diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/launcher/DockerComputerSingleJNLPLauncher.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/launcher/DockerComputerSingleJNLPLauncher.java index 2fa77708..ed2f8fc0 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/launcher/DockerComputerSingleJNLPLauncher.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/launcher/DockerComputerSingleJNLPLauncher.java @@ -170,25 +170,25 @@ private void provisionWithWait(DockerComputerSingle computer, TaskListener liste // create CreateContainerResponse createResp = containerConfig.exec(); - String containerId = createResp.getId(); - setContainerId(containerId); - logger.println("Created container " + containerId + ", for " + run.getDisplayName()); - LOG.debug("Created container {}, for {}", containerId, run.getDisplayName()); + String cId = createResp.getId(); + setContainerId(cId); + logger.println("Created container " + cId + ", for " + run.getDisplayName()); + LOG.debug("Created container {}, for {}", cId, run.getDisplayName()); // start - StartContainerCmd startCommand = client.startContainerCmd(containerId); + StartContainerCmd startCommand = client.startContainerCmd(cId); startCommand.exec(); - logger.println("Started container " + containerId); - LOG.debug("Start container {}, for {}", containerId, run.getDisplayName()); + logger.println("Started container " + cId); + LOG.debug("Start container {}, for {}", cId, run.getDisplayName()); boolean running = false; long launchTime = System.currentTimeMillis(); while (!running && TimeUnit2.SECONDS.toMillis(launchTimeout) > System.currentTimeMillis() - launchTime) { try { - InspectContainerResponse inspectResp = client.inspectContainerCmd(containerId).exec(); + InspectContainerResponse inspectResp = client.inspectContainerCmd(cId).exec(); if (isTrue(inspectResp.getState().getRunning())) { logger.println("Container is running!"); - LOG.debug("Container {} is running", containerId); + LOG.debug("Container {} is running", cId); running = true; } else { logger.println("Container is not running..."); @@ -201,7 +201,7 @@ private void provisionWithWait(DockerComputerSingle computer, TaskListener liste if (!running) { listener.error("Failed to run container for %s, clean-up container", imageId); LOG.error("Failed to run container for {}, clean-up container", imageId); - containerLifecycle.getRemoveContainer().exec(client, containerId); + containerLifecycle.getRemoveContainer().exec(client, cId); } // now real launch @@ -209,7 +209,7 @@ private void provisionWithWait(DockerComputerSingle computer, TaskListener liste // Objects.requireNonNull(rootUrl, "Jenkins root url is not specified!"); if (isNull(rootUrl)) { listener.fatalError("Jenkins root url is not specified!"); - containerLifecycle.getRemoveContainer().exec(client, containerId); + containerLifecycle.getRemoveContainer().exec(client, cId); throw new IllegalStateException("Jenkins root url is not specified!"); } @@ -228,7 +228,7 @@ private void provisionWithWait(DockerComputerSingle computer, TaskListener liste "EOF" + "\n"; try { - final ExecCreateCmdResponse createCmdResponse = client.execCreateCmd(containerId) + final ExecCreateCmdResponse createCmdResponse = client.execCreateCmd(cId) .withTty(true) .withAttachStdin(false) .withAttachStderr(true) @@ -236,8 +236,8 @@ private void provisionWithWait(DockerComputerSingle computer, TaskListener liste .withCmd("/bin/sh", "-cxe", startCmd.replace("$", "\\$")) .exec(); - logger.println("Starting connection command for " + containerId); - LOG.info("Starting connection command for {}", containerId); + logger.println("Starting connection command for " + cId); + LOG.info("Starting connection command for {}", cId); try (ExecStartResultCallback exec = client.execStartCmd(createCmdResponse.getId()) .withDetach(true) @@ -248,20 +248,20 @@ private void provisionWithWait(DockerComputerSingle computer, TaskListener liste } catch (NotFoundException ex) { listener.error("Can't execute command: " + ex.getMessage().trim()); LOG.error("Can't execute jnlp connection command: '{}'", ex.getMessage().trim()); - containerLifecycle.getRemoveContainer().exec(client, containerId); + containerLifecycle.getRemoveContainer().exec(client, cId); computer.getNode().terminate(); throw ex; } } catch (Exception ex) { listener.error("Can't execute command: " + ex.getMessage().trim()); LOG.error("Can't execute jnlp connection command: '{}'", ex.getMessage().trim()); - containerLifecycle.getRemoveContainer().exec(client, containerId); + containerLifecycle.getRemoveContainer().exec(client, cId); computer.getNode().terminate(); throw ex; } - LOG.info("Successfully executed jnlp connection for '{}'", containerId); - logger.println("Successfully executed jnlp connection for " + containerId); + LOG.info("Successfully executed jnlp connection for '{}'", cId); + logger.println("Successfully executed jnlp connection for " + cId); // TODO better strategy launchTime = System.currentTimeMillis(); @@ -272,16 +272,16 @@ private void provisionWithWait(DockerComputerSingle computer, TaskListener liste } if (computer.isReallyOffline()) { - LOG.info("Launch timeout, termintaing slave based on '{}'", containerId); + LOG.info("Launch timeout, termintaing slave based on '{}'", cId); logger.println("Launch timeout, termintaing slave."); - containerLifecycle.getRemoveContainer().exec(client, containerId); + containerLifecycle.getRemoveContainer().exec(client, cId); computer.getNode().terminate(); throw new IOException("Can't connect slave to jenkins"); } LOG.info("Launched slave '{}' '{}' based on '{}'", - computer.getSlaveVersion(), computer.getName(), containerId); - logger.println("Launched slave for " + containerId); + computer.getSlaveVersion(), computer.getName(), cId); + logger.println("Launched slave for " + cId); } public void appendContainerConfig(CreateContainerCmd createContainerCmd) diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/launcher/NoOpDelegatingComputerLauncher.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/launcher/NoOpDelegatingComputerLauncher.java index 8d658dc9..322b0f0f 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/launcher/NoOpDelegatingComputerLauncher.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/launcher/NoOpDelegatingComputerLauncher.java @@ -3,7 +3,6 @@ import hudson.Extension; import hudson.model.TaskListener; import hudson.slaves.ComputerLauncher; -import hudson.slaves.ComputerLauncherFilter; import hudson.slaves.DelegatingComputerLauncher; import hudson.slaves.SlaveComputer; diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/listener/DockerRunListener.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/listener/DockerRunListener.java index 1e1d57dd..4c056113 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/listener/DockerRunListener.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/listener/DockerRunListener.java @@ -1,29 +1,21 @@ package com.github.kostyasha.yad.listener; -import com.github.kostyasha.yad.DockerComputer; import com.github.kostyasha.yad.DockerComputerSingle; import com.github.kostyasha.yad.DockerSlaveSingle; import com.github.kostyasha.yad.action.DockerLabelAssignmentAction; import hudson.Extension; -import hudson.model.Computer; -import hudson.model.Executor; -import hudson.model.Node; import hudson.model.Run; import hudson.model.TaskListener; import hudson.model.listeners.RunListener; -import hudson.slaves.ComputerLauncher; import hudson.slaves.DelegatingComputerLauncher; import jenkins.model.Jenkins; import org.jenkinsci.plugins.cloudstats.CloudStatistics; -import org.jenkinsci.plugins.docker.commons.fingerprint.DockerFingerprints; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; -import java.text.ParseException; import static com.github.kostyasha.yad.utils.ContainerRecordUtils.attachFacet; -import static com.github.kostyasha.yad.utils.ContainerRecordUtils.createRecordFor; import static java.util.Objects.isNull; /** diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/utils/ContainerRecordUtils.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/utils/ContainerRecordUtils.java index b7b9b6bc..03926505 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/utils/ContainerRecordUtils.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/utils/ContainerRecordUtils.java @@ -2,7 +2,6 @@ import com.github.kostyasha.yad.DockerCloud; import com.github.kostyasha.yad.DockerComputer; -import com.github.kostyasha.yad.DockerComputerSingle; import com.github.kostyasha.yad_docker_java.com.fasterxml.jackson.databind.util.StdDateFormat; import com.github.kostyasha.yad_docker_java.com.github.dockerjava.api.DockerClient; import com.github.kostyasha.yad_docker_java.com.github.dockerjava.api.command.InspectContainerResponse; @@ -76,8 +75,6 @@ public static void attachFacet(Run run, TaskListener listener) { listener.error("Can't add Docker fingerprint to run."); LOG.error("Can't add fingerprint to run {}", run, e); } - } else if (owner instanceof DockerComputerSingle) { - } diff --git a/yet-another-docker-plugin/src/test/java/org/jvnet/hudson/test/DockerSimpleBuildWrapperTest.java b/yet-another-docker-plugin/src/test/java/org/jvnet/hudson/test/DockerSimpleBuildWrapperTest.java index 616977ff..37f050f9 100644 --- a/yet-another-docker-plugin/src/test/java/org/jvnet/hudson/test/DockerSimpleBuildWrapperTest.java +++ b/yet-another-docker-plugin/src/test/java/org/jvnet/hudson/test/DockerSimpleBuildWrapperTest.java @@ -26,7 +26,6 @@ import javax.servlet.ServletContext; import java.io.IOException; import java.net.URL; -import java.util.UUID; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; From 79cfd5baace4443377ed08935500f2476c872e9d Mon Sep 17 00:00:00 2001 From: Kanstantsin Shautsou Date: Fri, 3 Feb 2017 21:38:57 +0300 Subject: [PATCH 16/17] checkstle Signed-off-by: Kanstantsin Shautsou --- .../com/github/kostyasha/yad/DockerSimpleBuildWrapper.java | 1 - .../java/com/github/kostyasha/yad/DockerSlaveTemplate.java | 6 +++++- .../github/kostyasha/yad/listener/DockerQueueListener.java | 2 -- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSimpleBuildWrapper.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSimpleBuildWrapper.java index d7c803d4..86e42a6b 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSimpleBuildWrapper.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSimpleBuildWrapper.java @@ -24,7 +24,6 @@ import javax.annotation.Nonnull; import java.io.IOException; -import static com.github.kostyasha.yad.utils.ContainerRecordUtils.attachFacet; import static org.jenkinsci.plugins.cloudstats.CloudStatistics.ProvisioningListener.get; /** diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlaveTemplate.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlaveTemplate.java index ca9c6d9f..b83c3f22 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlaveTemplate.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSlaveTemplate.java @@ -25,7 +25,6 @@ import java.util.Collections; import java.util.List; import java.util.Set; -import java.util.UUID; import static java.util.Objects.isNull; @@ -147,6 +146,11 @@ public boolean equals(Object o) { .isEquals(); } + @Override + public int hashCode() { + return super.hashCode(); + } + @Override public String toString() { return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE); diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/listener/DockerQueueListener.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/listener/DockerQueueListener.java index 99a0fefc..117706a7 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/listener/DockerQueueListener.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/listener/DockerQueueListener.java @@ -3,7 +3,6 @@ import com.github.kostyasha.yad.DockerContainerLifecycle; import com.github.kostyasha.yad.DockerSlaveConfig; import com.github.kostyasha.yad.DockerSlaveSingle; -import com.github.kostyasha.yad.DockerSlaveTemplate; import com.github.kostyasha.yad.action.DockerLabelAssignmentAction; import hudson.Extension; import hudson.model.Descriptor; @@ -15,7 +14,6 @@ import java.io.IOException; -import static java.util.Collections.emptyList; import static java.util.Objects.nonNull; import static org.jenkinsci.plugins.cloudstats.CloudStatistics.ProvisioningListener.get; From f4761662ab3550840cf643c3e74f420d180113d2 Mon Sep 17 00:00:00 2001 From: Kanstantsin Shautsou Date: Fri, 3 Mar 2017 19:39:38 +0300 Subject: [PATCH 17/17] Don't start name from "-". Too much shell escaping needed. --- .../java/com/github/kostyasha/yad/DockerSimpleBuildWrapper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSimpleBuildWrapper.java b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSimpleBuildWrapper.java index 86e42a6b..6d6fe13c 100644 --- a/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSimpleBuildWrapper.java +++ b/yet-another-docker-plugin/src/main/java/com/github/kostyasha/yad/DockerSimpleBuildWrapper.java @@ -79,7 +79,7 @@ public void setUp(Context context, try { get().onStarted(activityId); - final String futureName = Integer.toString(activityId.getFingerprint()); + final String futureName = "yadp" + Integer.toString(activityId.getFingerprint()); final DockerSlaveSingle slave = new DockerSlaveSingle(futureName, "Slave for " + run.getFullDisplayName(),