提交 63b953d4 编写于 作者: A austry 提交者: Oleg Nenashev

[JENKINS-11888] - Add stop-job CLI command (#3686)

* JENKINS-11888 Add stop-job CLI command

* JENKINS-11888 Add license headers

* JENKINS-11888 Review fix

* JENKINS-11888 Fix review

* JENKINS-11888 Remove moved files from cli package

* JENKINS-11888 Fix logging

* JENKINS-11888 Collect jobs list before stopping any builds

* JENKINS-11888 Refactor isBuilding check

* JENKINS-11888 Add Restricted annotation for StopBuildsCommand

* JENKINS-11888 Make cli output more responsive

* JENKINS-11888 Remove single quote for job name

* JENKINS-11888 Add semicolon

* JENKINS-11888 Handle exception for executor.doStop

* JENKINS-11888 Rework test to use JenkinsRule

* JENKINS-11888 Remove semicolons from CLI output

* JENKINS-11888 Fix tests for windows platform

* JENKINS-11888 Write message about exception in one line

* JENKINS-11888 Fix output on not founded job

* JENKINS-11888 Improve tests

* JENKINS-11888 Use fullDisplayName for job name

* JENKINS-11888 Use single quotes for build and job names

* JENKINS-11888 Cleanup exceptions throwing

* JENKINS-11888 Add test checking second job will be stopped if first stop failed
上级 f6cd994b
/*
* The MIT License
*
* Copyright (c) 2018, Ilia Zasimov
*
* 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 jenkins.cli;
import hudson.Extension;
import hudson.cli.CLICommand;
import hudson.model.Executor;
import hudson.model.Item;
import hudson.model.Job;
import hudson.model.Run;
import jenkins.model.Jenkins;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.args4j.Argument;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@Extension
@Restricted(NoExternalUse.class)
public class StopBuildsCommand extends CLICommand {
@Argument(usage = "Name of the job(s) to stop", required = true, multiValued = true)
private List<String> jobNames;
private boolean isAnyBuildStopped;
@Override
public String getShortDescription() {
return "Stop all running builds for job(s)";
}
@Override
protected int run() throws Exception {
Jenkins jenkins = Jenkins.get();
final HashSet<String> names = new HashSet<>();
names.addAll(jobNames);
final List<Job> jobsToStop = new ArrayList<>();
for (final String jobName : names) {
Item item = jenkins.getItemByFullName(jobName);
if (item instanceof Job) {
jobsToStop.add((Job) item);
} else {
throw new IllegalArgumentException("Job not found: '" + jobName + "'");
}
}
for (final Job job : jobsToStop) {
stopJobBuilds(job);
}
if (!isAnyBuildStopped) {
stdout.println("No builds stopped");
}
return 0;
}
private void stopJobBuilds(final Job job) {
final Run lastBuild = job.getLastBuild();
final String jobName = job.getFullDisplayName();
if (lastBuild != null && lastBuild.isBuilding()) {
stopBuild(lastBuild, jobName);
checkAndStopPreviousBuilds(lastBuild, jobName);
}
}
private void stopBuild(final Run build,
final String jobName) {
final String buildName = build.getDisplayName();
Executor executor = build.getExecutor();
if (executor != null) {
try {
executor.doStop();
isAnyBuildStopped = true;
stdout.println(String.format("Build '%s' stopped for job '%s'", buildName, jobName));
} catch (final Exception e) {
stdout.print(String.format("Exception occurred while trying to stop build '%s' for job '%s'. ", buildName, jobName));
stdout.println(String.format("Exception class: %s, message: %s", e.getClass().getSimpleName(), e.getMessage()));
}
} else {
stdout.println(String.format("Build '%s' in job '%s' not stopped", buildName, jobName));
}
}
private void checkAndStopPreviousBuilds(final Run lastBuild,
final String jobName) {
Run build = lastBuild.getPreviousBuildInProgress();
while (build != null) {
stopBuild(build, jobName);
build = build.getPreviousBuildInProgress();
}
}
}
/*
* The MIT License
*
* Copyright (c) 2018, Ilia Zasimov
*
* 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 jenkins.cli;
import hudson.Functions;
import hudson.cli.CLICommand;
import hudson.cli.CLICommandInvoker;
import hudson.model.FreeStyleProject;
import hudson.model.Item;
import hudson.slaves.DumbSlave;
import hudson.tasks.BatchFile;
import hudson.tasks.Builder;
import hudson.tasks.Shell;
import jenkins.model.Jenkins;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.MockAuthorizationStrategy;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.List;
import static java.util.Arrays.asList;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.IsEqual.equalTo;
public class StopBuildsCommandTest {
@Rule
public JenkinsRule j = new JenkinsRule();
private static final String TEST_JOB_NAME = "jobName";
private static final String TEST_JOB_NAME_2 = "jobName2";
private static final String LN = System.lineSeparator();
@Test
public void shouldStopLastBuild() throws Exception {
final FreeStyleProject project = createLongRunningProject(TEST_JOB_NAME);
project.scheduleBuild2(0).waitForStart();
final String stdout = runWith(Collections.singletonList(TEST_JOB_NAME)).stdout();
assertThat(stdout, equalTo("Build '#1' stopped for job 'jobName'" + LN));
waitForLastBuildToStop(project);
}
@Test
public void shouldNotStopEndedBuild() throws Exception {
final FreeStyleProject project = j.createFreeStyleProject(TEST_JOB_NAME);
project.getBuildersList().add(createScriptBuilder("echo 1"));
project.scheduleBuild2(0).waitForStart();
waitForLastBuildToStop(project);
final String out = runWith(Collections.singletonList(TEST_JOB_NAME)).stdout();
assertThat(out, equalTo("No builds stopped" + LN));
}
@Test
public void shouldStopSeveralWorkingBuilds() throws Exception {
final FreeStyleProject project = createLongRunningProject(TEST_JOB_NAME);
project.setConcurrentBuild(true);
setupSlaveWithTwoExecutors();
project.scheduleBuild2(0).waitForStart();
project.scheduleBuild2(0).waitForStart();
final String stdout = runWith(Collections.singletonList(TEST_JOB_NAME)).stdout();
assertThat(stdout, equalTo("Build '#2' stopped for job 'jobName'" + LN +
"Build '#1' stopped for job 'jobName'" + LN));
waitForLastBuildToStop(project);
}
@Test
public void shouldReportNotSupportedType() throws Exception {
final String testFolderName = "folder";
j.createFolder(testFolderName);
final String stderr = runWith(Collections.singletonList(testFolderName)).stderr();
assertThat(stderr, equalTo(LN + "ERROR: Job not found: 'folder'" + LN));
}
@Test
public void shouldDoNothingIfJobNotFound() throws Exception {
final String stderr = runWith(Collections.singletonList(TEST_JOB_NAME)).stderr();
assertThat(stderr, equalTo(LN + "ERROR: Job not found: 'jobName'" + LN));
}
@Test
public void shouldStopWorkingBuildsInSeveralJobs() throws Exception {
final List<String> inputJobNames = asList(TEST_JOB_NAME, TEST_JOB_NAME_2);
setupAndAssertTwoBuildsStop(inputJobNames);
}
@Test
public void shouldFilterJobDuplicatesInInput() throws Exception {
final List<String> inputNames = asList(TEST_JOB_NAME, TEST_JOB_NAME, TEST_JOB_NAME_2);
setupAndAssertTwoBuildsStop(inputNames);
}
@Test
public void shouldReportBuildStopError() throws Exception {
final FreeStyleProject project = createLongRunningProject(TEST_JOB_NAME);
j.jenkins.setSecurityRealm(j.createDummySecurityRealm());
j.jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy().
grant(Jenkins.READ).everywhere().toEveryone().
grant(Item.READ).onItems(project).toEveryone().
grant(Item.CANCEL).onItems(project).toAuthenticated());
project.scheduleBuild2(0).waitForStart();
final String stdout = runWith(Collections.singletonList(TEST_JOB_NAME)).stdout();
assertThat(stdout,
equalTo("Exception occurred while trying to stop build '#1' for job 'jobName'. " +
"Exception class: AccessDeniedException2, message: anonymous is missing the Job/Cancel permission" + LN +
"No builds stopped" + LN));
}
@Test
public void shouldStopSecondJobEvenIfFirstStopFailed() throws Exception {
final FreeStyleProject project = createLongRunningProject(TEST_JOB_NAME_2);
final FreeStyleProject restrictedProject = createLongRunningProject(TEST_JOB_NAME);
j.jenkins.setSecurityRealm(j.createDummySecurityRealm());
j.jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy().
grant(Jenkins.READ).everywhere().toEveryone().
grant(Item.READ).onItems(restrictedProject, project).toEveryone().
grant(Item.CANCEL).onItems(restrictedProject).toAuthenticated().
grant(Item.CANCEL).onItems(project).toEveryone());
restrictedProject.scheduleBuild2(0).waitForStart();
project.scheduleBuild2(0).waitForStart();
final String stdout = runWith(asList(TEST_JOB_NAME, TEST_JOB_NAME_2)).stdout();
assertThat(stdout,
equalTo("Exception occurred while trying to stop build '#1' for job 'jobName'. " +
"Exception class: AccessDeniedException2, message: anonymous is missing the Job/Cancel permission" + LN +
"Build '#1' stopped for job 'jobName2'" + LN));
}
private CLICommandInvoker.Result runWith(final List<String> jobNames) throws Exception {
CLICommand cmd = new StopBuildsCommand();
CLICommandInvoker invoker = new CLICommandInvoker(j, cmd);
return invoker.invokeWithArgs(jobNames.toArray(new String[jobNames.size()]));
}
private void setupAndAssertTwoBuildsStop(final List<String> inputNames) throws Exception {
setupSlaveWithTwoExecutors();
final FreeStyleProject project = createLongRunningProject(TEST_JOB_NAME);
final FreeStyleProject project2 = createLongRunningProject(TEST_JOB_NAME_2);
project.scheduleBuild2(0).waitForStart();
project2.scheduleBuild2(0).waitForStart();
final String stdout = runWith(inputNames).stdout();
assertThat(stdout, equalTo("Build '#1' stopped for job 'jobName'" + LN +
"Build '#1' stopped for job 'jobName2'" + LN));
waitForLastBuildToStop(project);
waitForLastBuildToStop(project2);
}
private FreeStyleProject createLongRunningProject(final String jobName) throws IOException {
final FreeStyleProject project = j.createFreeStyleProject(jobName);
project.getBuildersList().add(createScriptBuilder("sleep 50000"));
return project;
}
private void setupSlaveWithTwoExecutors() throws hudson.model.Descriptor.FormException, IOException, URISyntaxException {
DumbSlave slave = new DumbSlave("slave", j.jenkins.getRootDir().getAbsolutePath(), j.createComputerLauncher(null));
slave.setNumExecutors(2);
j.jenkins.addNode(slave);
}
private Builder createScriptBuilder(String script) {
return Functions.isWindows() ? new BatchFile(script) : new Shell(script);
}
private void waitForLastBuildToStop(final FreeStyleProject project) throws InterruptedException {
while (project.getLastBuild().isBuilding()) {
Thread.sleep(500);
}
assertThat(project.getLastBuild().isBuilding(), equalTo(false));
}
}
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册