diff --git a/pom.xml b/pom.xml index 173995eaf..d76a2f591 100644 --- a/pom.xml +++ b/pom.xml @@ -84,6 +84,10 @@ org.jenkins-ci.plugins bouncycastle-api + + org.jenkins-ci.plugins + cloud-stats + diff --git a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/KubernetesComputer.java b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/KubernetesComputer.java index a679b02a7..d6461ac75 100644 --- a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/KubernetesComputer.java +++ b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/KubernetesComputer.java @@ -1,5 +1,6 @@ package org.csanchez.jenkins.plugins.kubernetes; +import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; import hudson.model.Computer; import hudson.model.Executor; @@ -30,6 +31,8 @@ import jenkins.model.Jenkins; import org.acegisecurity.Authentication; import org.apache.commons.lang.StringUtils; +import org.jenkinsci.plugins.cloudstats.ProvisioningActivity; +import org.jenkinsci.plugins.cloudstats.TrackedItem; import org.jenkinsci.plugins.kubernetes.auth.KubernetesAuthException; import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.StaplerRequest2; @@ -41,7 +44,7 @@ /** * @author Carlos Sanchez carlos@apache.org */ -public class KubernetesComputer extends AbstractCloudComputer { +public class KubernetesComputer extends AbstractCloudComputer implements TrackedItem { private static final Logger LOGGER = Logger.getLogger(KubernetesComputer.class.getName()); private boolean launching; @@ -244,4 +247,15 @@ public void setAcceptingTasks(boolean acceptingTasks) { launching = false; } } + + @CheckForNull + @Override + public ProvisioningActivity.Id getId() { + KubernetesSlave slave = getNode(); + if (slave != null) { + return slave.getId(); + } + + return null; + } } diff --git a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/KubernetesSlave.java b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/KubernetesSlave.java index 601514b73..7d6266105 100644 --- a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/KubernetesSlave.java +++ b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/KubernetesSlave.java @@ -49,6 +49,8 @@ import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.Validate; import org.csanchez.jenkins.plugins.kubernetes.pod.retention.PodRetention; +import org.jenkinsci.plugins.cloudstats.ProvisioningActivity; +import org.jenkinsci.plugins.cloudstats.TrackedItem; import org.jenkinsci.plugins.durabletask.executors.OnceRetentionStrategy; import org.jenkinsci.plugins.kubernetes.auth.KubernetesAuthException; import org.jenkinsci.plugins.workflow.flow.FlowExecutionOwner; @@ -58,7 +60,7 @@ /** * @author Carlos Sanchez carlos@apache.org */ -public class KubernetesSlave extends AbstractCloudSlave { +public class KubernetesSlave extends AbstractCloudSlave implements TrackedItem { private static final Logger LOGGER = Logger.getLogger(KubernetesSlave.class.getName()); @@ -85,6 +87,9 @@ public class KubernetesSlave extends AbstractCloudSlave { @CheckForNull private transient Pod pod; + @NonNull + private final ProvisioningActivity.Id id; + @NonNull public PodTemplate getTemplate() throws IllegalStateException { // Look up updated pod template after a restart @@ -210,6 +215,7 @@ protected KubernetesSlave( this.cloudName = cloudName; this.template = template; this.podTemplateId = template.getId(); + this.id = new ProvisioningActivity.Id(cloudName, template.getName(), name); } public String getCloudName() { @@ -478,6 +484,12 @@ private void deleteSlavePod(TaskListener listener, KubernetesClient client) { listener.getLogger().println(msg); } + @CheckForNull + @Override + public ProvisioningActivity.Id getId() { + return id; + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/StandardPlannedNodeBuilder.java b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/StandardPlannedNodeBuilder.java index f27952153..e719b8b61 100644 --- a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/StandardPlannedNodeBuilder.java +++ b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/StandardPlannedNodeBuilder.java @@ -1,10 +1,12 @@ package org.csanchez.jenkins.plugins.kubernetes; -import hudson.Util; import hudson.model.Descriptor; +import hudson.model.Node; import hudson.slaves.NodeProvisioner; import java.io.IOException; import java.util.concurrent.CompletableFuture; +import org.jenkinsci.plugins.cloudstats.ProvisioningActivity; +import org.jenkinsci.plugins.cloudstats.TrackedPlannedNode; /** * The default {@link PlannedNodeBuilder} implementation, in case there is other registered. @@ -14,20 +16,24 @@ public class StandardPlannedNodeBuilder extends PlannedNodeBuilder { public NodeProvisioner.PlannedNode build() { KubernetesCloud cloud = getCloud(); PodTemplate t = getTemplate(); - CompletableFuture f; - String displayName; + CompletableFuture f; + ProvisioningActivity.Id id = null; try { KubernetesSlave agent = KubernetesSlave.builder() .podTemplate(t.isUnwrapped() ? t : cloud.getUnwrappedTemplate(t)) .cloud(cloud) .build(); - displayName = agent.getDisplayName(); + // always use one sourced from the slave we are provisioning so the identity is maintained + id = agent.getId(); f = CompletableFuture.completedFuture(agent); } catch (IOException | Descriptor.FormException e) { - displayName = null; - f = new CompletableFuture(); - f.completeExceptionally(e); + f = CompletableFuture.failedFuture(e); } - return new NodeProvisioner.PlannedNode(Util.fixNull(displayName), f, getNumExecutors()); + + if (id == null) { + id = new ProvisioningActivity.Id(cloud.name, t.getName()); + } + + return new TrackedPlannedNode(id, getNumExecutors(), f); } } diff --git a/src/test/java/org/csanchez/jenkins/plugins/kubernetes/KubernetesSlaveTest.java b/src/test/java/org/csanchez/jenkins/plugins/kubernetes/KubernetesSlaveTest.java index 1d1f40645..75ac64da4 100644 --- a/src/test/java/org/csanchez/jenkins/plugins/kubernetes/KubernetesSlaveTest.java +++ b/src/test/java/org/csanchez/jenkins/plugins/kubernetes/KubernetesSlaveTest.java @@ -45,6 +45,7 @@ import org.csanchez.jenkins.plugins.kubernetes.pod.retention.OnFailure; import org.csanchez.jenkins.plugins.kubernetes.pod.retention.PodRetention; import org.csanchez.jenkins.plugins.kubernetes.volumes.PodVolume; +import org.jenkinsci.plugins.cloudstats.ProvisioningActivity; import org.jenkinsci.plugins.kubernetes.auth.KubernetesAuthException; import org.junit.Rule; import org.junit.Test; @@ -164,6 +165,18 @@ private interface GetPodTestCase { void test(KubernetesCloud cloud, KubernetesSlave slave, PodResource podResource) throws Exception; } + @Test + public void testProvisioningActivityId() throws Descriptor.FormException, IOException { + PodTemplate pt = new PodTemplate("x"); + pt.setName("Template"); + KubernetesSlave slave = new KubernetesSlave("Node", pt, "bar", "Cloud", "", null, null); + ProvisioningActivity.Id id = slave.getId(); + assertNotNull(id); + assertEquals(id.getCloudName(), "Cloud"); + assertEquals(id.getTemplateName(), "Template"); + assertEquals(id.getNodeName(), "Node"); + } + @Test public void testGetPodRetention() { try { diff --git a/src/test/java/org/csanchez/jenkins/plugins/kubernetes/StandardPlannedNodeBuilderTest.java b/src/test/java/org/csanchez/jenkins/plugins/kubernetes/StandardPlannedNodeBuilderTest.java new file mode 100644 index 000000000..4e2a8bdb2 --- /dev/null +++ b/src/test/java/org/csanchez/jenkins/plugins/kubernetes/StandardPlannedNodeBuilderTest.java @@ -0,0 +1,36 @@ +package org.csanchez.jenkins.plugins.kubernetes; + +import static org.csanchez.jenkins.plugins.kubernetes.KubernetesTestUtil.assertRegex; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import hudson.slaves.NodeProvisioner; +import org.jenkinsci.plugins.cloudstats.ProvisioningActivity; +import org.jenkinsci.plugins.cloudstats.TrackedPlannedNode; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.JenkinsRule; + +public class StandardPlannedNodeBuilderTest { + + @Rule + public JenkinsRule r = new JenkinsRule(); + + @Test + public void testBuild() { + KubernetesCloud cloud = new KubernetesCloud("Cloud"); + PodTemplate template = new PodTemplate("t"); + template.setName("Template"); + StandardPlannedNodeBuilder builder = new StandardPlannedNodeBuilder(); + builder.cloud(cloud); + builder.template(template); + builder.numExecutors(1); + + NodeProvisioner.PlannedNode plannedNode = builder.build(); + assertTrue(plannedNode instanceof TrackedPlannedNode); + ProvisioningActivity.Id id = ((TrackedPlannedNode) plannedNode).getId(); + assertEquals(id.getCloudName(), "Cloud"); + assertEquals(id.getTemplateName(), "Template"); + assertRegex(id.getNodeName(), "template-\\w{5}"); + } +}