Skip to content

Commit

Permalink
Add nodes by label step
Browse files Browse the repository at this point in the history
  • Loading branch information
jetersen committed Feb 12, 2018
1 parent c16423e commit a9191d0
Show file tree
Hide file tree
Showing 4 changed files with 306 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/*
* The MIT License
*
* Copyright (c) 2018, Joseph Petersen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

package org.jenkinsci.plugins.pipeline.utility.steps.jenkins;

import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Set;

import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Extension;
import hudson.model.Computer;
import hudson.model.Label;
import hudson.model.Node;
import hudson.model.TaskListener;
import hudson.util.ComboBoxModel;
import jenkins.model.Jenkins;
import org.jenkinsci.plugins.workflow.steps.Step;
import org.jenkinsci.plugins.workflow.steps.StepContext;
import org.jenkinsci.plugins.workflow.steps.StepDescriptor;
import org.jenkinsci.plugins.workflow.steps.StepExecution;
import org.jenkinsci.plugins.workflow.steps.SynchronousStepExecution;
import org.kohsuke.stapler.DataBoundConstructor;

/**
* Obtains a list of node names by their label
*/
public class NodesByLabelStep extends Step {

private final String label;

@DataBoundConstructor
public NodesByLabelStep(String label) {
this.label = label;
}

public String getLabel() {
return label;
}

@Override
public StepExecution start(StepContext context) throws Exception {
return new Execution(label, context);
}

@Extension
public static class DescriptorImpl extends StepDescriptor {

@Override
public String getFunctionName() {
return "nodesByLabel";
}

@Override
@NonNull
public String getDisplayName() {
return "List of node names by their label";
}

@SuppressWarnings("unused") // used by stapler
public ComboBoxModel doFillLabelItems() {
ComboBoxModel cbm = new ComboBoxModel();
Set<Label> labels = Jenkins.getInstance().getLabels();
for (Label label : labels) {
cbm.add(label.getDisplayName());
}
return cbm;
}

@Override
public Set<? extends Class<?>> getRequiredContext() {
return Collections.singleton(TaskListener.class);
}
}

public static class Execution extends SynchronousStepExecution<ArrayList<String>> {

private static final long serialVersionUID = 1L;
private transient final String label;

Execution(String label, StepContext context) {
super(context);
this.label = label;
}

@Override
protected ArrayList<String> run() throws Exception {
Label aLabel = Label.get(this.label);
Set<Node> nodeSet = aLabel.getNodes();
TaskListener listener = getContext().get(TaskListener.class);
assert listener != null;
PrintStream logger = listener.getLogger();
ArrayList<String> nodes = new ArrayList<>();
for (Node node : nodeSet) {
Computer computer = node.toComputer();
if (!(computer == null || computer.isOffline())) nodes.add(node.getNodeName());
}
if (nodes.isEmpty()) {
logger.println("Could not find any nodes with '" + label + "' label");
} else {
logger.println("Found a total of " + nodes.size() + " nodes with the '" + label + "' label");
}
return nodes;
}

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
* The MIT License
*
* Copyright (c) 2018, Joseph Petersen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
-->

<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:f="/lib/form">
<f:entry field="label" title="Label">
<f:combobox/>
</f:entry>
</j:jelly>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<div>
Returns an array of <code>node</code> names with the given label.
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/*
* The MIT License
*
* Copyright (c) 2018, Joseph Petersen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

package org.jenkinsci.plugins.pipeline.utility.steps.jenkins;

import static hudson.cli.CLICommandInvoker.Matcher.succeededSilently;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;

import hudson.EnvVars;
import hudson.cli.CLICommandInvoker;
import hudson.model.Computer;
import hudson.model.Result;
import hudson.slaves.DumbSlave;
import jenkins.model.Jenkins;
import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition;
import org.jenkinsci.plugins.workflow.job.WorkflowJob;
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.JenkinsRule;

public class NodesByLabelStepTest {
@Rule
public JenkinsRule r = new JenkinsRule();
private WorkflowJob job;
private WorkflowRun run;

@Before
public void setup() throws Exception {
job = r.jenkins.createProject(WorkflowJob.class, "workflow");
r.createSlave("dummy1", "a", new EnvVars());
r.createSlave("dummy2", "a b", new EnvVars());
r.createSlave("dummy3", "a b c", new EnvVars());
DumbSlave slave = r.createSlave("dummy4", "a b c d", new EnvVars());
slave.toComputer().waitUntilOnline();
CLICommandInvoker command = new CLICommandInvoker(r, "disconnect-node");
CLICommandInvoker.Result result = command
.authorizedTo(Computer.DISCONNECT, Jenkins.READ)
.invokeWithArgs("dummy4");
assertThat(result, succeededSilently());
assertThat(slave.toComputer().isOffline(), equalTo(true));
}

@Test
public void test_nodes_by_label_count_a() throws Exception {
// leave out the subject
job.setDefinition(new CpsFlowDefinition("nodesByLabel('a')", true));

run = r.assertBuildStatus(Result.SUCCESS, job.scheduleBuild2(0).get());
r.assertLogContains("Found a total of 3 nodes with the 'a' label", run);
}

@Test
public void test_nodes_by_label_count_b() throws Exception {
job.setDefinition(new CpsFlowDefinition("nodesByLabel('b')", true));

run = r.assertBuildStatus(Result.SUCCESS, job.scheduleBuild2(0).get());
r.assertLogContains("Found a total of 2 nodes with the 'b' label", run);
}

@Test
public void test_nodes_by_label_count_c() throws Exception {
job.setDefinition(new CpsFlowDefinition("nodesByLabel('c')", true));

run = r.assertBuildStatus(Result.SUCCESS, job.scheduleBuild2(0).get());
r.assertLogContains("Found a total of 1 nodes with the 'c' label", run);
}

@Test
public void test_nodes_by_label_count_d_offline() throws Exception {
job.setDefinition(new CpsFlowDefinition("nodesByLabel('d')", true));

run = r.assertBuildStatus(Result.SUCCESS, job.scheduleBuild2(0).get());
r.assertLogContains("Could not find any nodes with 'd' label", run);
}

@Test
public void test_nodes_by_label_for_loop() throws Exception {
job.setDefinition(new CpsFlowDefinition("def nodes = nodesByLabel('a')\n" +
" for (int i = 0; i < nodes.size(); i++) {\n" +
" def n = nodes[i]\n" +
" node(n) {\n" +
" echo \"Hello ${n}\"\n" +
" }\n" +
" }", true));
assertBuildLoop();
}
@Test
public void test_nodes_by_label_each_loop() throws Exception {
job.setDefinition(new CpsFlowDefinition("def nodes = nodesByLabel('a')\n" +
" nodes.each { n ->\n" +
" node(n) {\n" +
" echo \"Hello ${n}\"\n" +
" }\n" +
" }", true));
assertBuildLoop();
}

private void assertBuildLoop() throws Exception {
run = r.assertBuildStatus(Result.SUCCESS, job.scheduleBuild2(0).get());
r.assertLogContains("Hello dummy1", run);
r.assertLogContains("Hello dummy2", run);
r.assertLogContains("Hello dummy3", run);
}

@Test
public void test_nodes_by_label_non_existing_label() throws Exception {
job.setDefinition(new CpsFlowDefinition("def nodes = nodesByLabel('F')\n", true));
run = r.assertBuildStatus(Result.SUCCESS, job.scheduleBuild2(0).get());
r.assertLogContains("Could not find any nodes with 'F' label", run);
}

@Test
public void test_nodes_by_label_get_label() {
NodesByLabelStep step1 = new NodesByLabelStep("a");
Assert.assertEquals(step1.getLabel(), "a");
}
}

0 comments on commit a9191d0

Please sign in to comment.