Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[FIXED JENKINS-24824] Collect all asynchronously deleted directories
  • Loading branch information
olivergondza committed Oct 10, 2016
1 parent 452372c commit ccd9071
Show file tree
Hide file tree
Showing 4 changed files with 195 additions and 35 deletions.
35 changes: 25 additions & 10 deletions pom.xml
Expand Up @@ -43,6 +43,12 @@
<artifactId>matrix-project</artifactId>
<version>1.7.1</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>resource-disposer</artifactId>
<version>0.1</version>
</dependency>

<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
Expand Down Expand Up @@ -71,14 +77,25 @@
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-basic-steps</artifactId>
<version>${workflow.version}</version>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>1.6.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>1.6.2</version>
<scope>test</scope>
</dependency>
</dependencies>

<repositories>
<repository>
<id>repo.jenkins-ci.org</id>
<url>http://repo.jenkins-ci.org/public/</url>
<id>repo.jenkins-ci.org</id>
<url>http://repo.jenkins-ci.org/public/</url>
</repository>
</repositories>

Expand All @@ -89,12 +106,10 @@
</repository>
</distributionManagement>


<pluginRepositories>
<pluginRepository>
<id>repo.jenkins-ci.org</id>
<url>http://repo.jenkins-ci.org/public/</url>
</pluginRepository>
</pluginRepositories>
<pluginRepositories>
<pluginRepository>
<id>repo.jenkins-ci.org</id>
<url>http://repo.jenkins-ci.org/public/</url>
</pluginRepository>
</pluginRepositories>
</project>

71 changes: 47 additions & 24 deletions src/main/java/hudson/plugins/ws_cleanup/Wipeout.java
Expand Up @@ -23,17 +23,19 @@
*/
package hudson.plugins.ws_cleanup;

import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

import hudson.FilePath;
import hudson.FilePath.FileCallable;
import hudson.Util;
import hudson.model.Computer;
import hudson.remoting.VirtualChannel;
import jenkins.security.Roles;
import org.jenkinsci.remoting.RoleChecker;
import jenkins.model.Jenkins;
import org.jenkinsci.plugins.resourcedisposer.AsyncResourceDisposer;
import org.jenkinsci.plugins.resourcedisposer.Disposable;

import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;

/**
* Cleanup workspace wiping it out completely.
Expand All @@ -43,48 +45,69 @@
*
* @author ogondza
*/
/*package*/ final class Wipeout extends RemoteCleaner {
/*package final*/ class Wipeout extends RemoteCleaner {

private final static Wipeout INSTANCE = new Wipeout();
/*private final*/ static Wipeout INSTANCE = new Wipeout();

/*package*/ static Wipeout getInstance() {
return INSTANCE;
}

@Override
protected void perform(FilePath workspace) throws IOException, InterruptedException {
final FilePath deleteMe = workspace.withSuffix("_ws-cleanup_" + System.currentTimeMillis());
workspace.renameTo(deleteMe);
final FilePath deleteMe = getWipeoutWorkspace(workspace);
Computer computer = workspace.toComputer();
if (computer == null) {
workspace.deleteRecursive();
return;
}

workspace.renameTo(deleteMe);
if (!deleteMe.exists()) {
LOGGER.log(
Level.WARNING,
"Cleaning workspace synchronously. Failed to rename {0} to {1}.",
new Object[] { workspace.getRemote(), deleteMe.getName() }
);
workspace.deleteRecursive();
return;
}

deleteMe.actAsync(COMMAND);
AsyncResourceDisposer.get().dispose(new DisposableImpl(deleteMe));
}

private final static Command COMMAND = new Command();
private final static class Command implements FileCallable<Void> {
public Void invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
try {
Util.deleteRecursive(f);
} catch (IOException ex) {
LOGGER.log(Level.SEVERE, "Unable to delete workspace", ex);
}
if (f.exists()) {
LOGGER.log(Level.SEVERE, "Workspace not deleted successfully: " + f.getAbsolutePath());
/*package for testing*/ FilePath getWipeoutWorkspace(FilePath workspace) {
return workspace.withSuffix("_ws-cleanup_" + System.currentTimeMillis());
}

private final static class DisposableImpl implements Disposable {
private final String node;
private final String path;
private transient FilePath ws;

private DisposableImpl(FilePath ws) {
this.ws = ws;
this.node = ws.toComputer().getName();
this.path = ws.getRemote();
}

@Nonnull public State dispose() throws Exception {
Jenkins j = Jenkins.getInstance();
if (j == null) return State.TO_DISPOSE; // Going down?

if (ws == null) {
ws = new FilePath(j.getComputer(node).getChannel(), path);
}
return null;
ws.deleteRecursive();

return ws.exists()
? State.TO_DISPOSE // Failed to delete silently
: State.PURGED
;
}

@Override
public void checkRoles(RoleChecker checker) throws SecurityException {
checker.check(this, Roles.SLAVE);
@Nonnull public String getDisplayName() {
return "Workspace " + (node.isEmpty() ? "master" : node) + ':' + path;
}
}

Expand Down
114 changes: 114 additions & 0 deletions src/test/java/hudson/plugins/ws_cleanup/CleanupPowermockTest.java
@@ -0,0 +1,114 @@
/*
* The MIT License
*
* Copyright (c) Red Hat, Inc.
*
* 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 hudson.plugins.ws_cleanup;

import hudson.FilePath;
import hudson.Util;
import hudson.model.FreeStyleProject;
import org.hamcrest.Matchers;
import org.jenkinsci.plugins.resourcedisposer.AsyncResourceDisposer;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.jvnet.hudson.test.JenkinsRule;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

import java.io.File;
import java.io.IOException;
import java.util.Set;

import static org.hamcrest.Matchers.emptyCollectionOf;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;

@RunWith(PowerMockRunner.class)
@PowerMockIgnore({"javax.crypto.*"}) // http://stackoverflow.com/questions/12914814/java-security-class-cast-exception
@PrepareForTest(FilePath.class)
public class CleanupPowermockTest {

@Rule public JenkinsRule j = new JenkinsRule();

@Test
public void retryAsyncDirDeletion() throws Exception {
FreeStyleProject p = j.jenkins.createProject(FreeStyleProject.class, "sut");

WsCleanup wipeout = CleanupTest.wipeoutPublisher();
p.getPublishersList().add(wipeout);

final Wipeout spy = spy(Wipeout.INSTANCE);
Wipeout.INSTANCE = spy;


final FilePath tempWs = spy(j.jenkins.getWorkspaceFor(p).withSuffix("_ws-cleanup_" + System.currentTimeMillis()));
doReturn(tempWs).when(spy).getWipeoutWorkspace(any(FilePath.class));

final Answer<Void>[] answer = new Answer[1];
PowerMockito.doAnswer(new Answer<Void>() { // Plug actual answers dynamically
@Override public Void answer(InvocationOnMock invocation) throws Throwable {
answer[0].answer(invocation);
return null;
}
}).when(tempWs).deleteRecursive();

answer[0] = new Answer<Void>() { // Throw exception
@Override public Void answer(InvocationOnMock invocation) throws Throwable {
throw new IOException("BOOM!");
}
};

j.buildAndAssertSuccess(p);

Thread.sleep(100);

AsyncResourceDisposer disposer = AsyncResourceDisposer.get();
Set<AsyncResourceDisposer.WorkItem> backlog = disposer.getBacklog();
assertThat(backlog, Matchers.<AsyncResourceDisposer.WorkItem>iterableWithSize(1));
AsyncResourceDisposer.WorkItem entry = backlog.iterator().next();
assertThat(entry.getDisposable().getDisplayName(), startsWith("Workspace master:"));
assertEquals("BOOM!", entry.getLastState().getDisplayName());

answer[0] = new Answer<Void>() { // Correct deletion
@Override public Void answer(InvocationOnMock invocation) throws Throwable {
Util.deleteRecursive(new File(tempWs.getRemote()));
return null;
}
};

//noinspection deprecation
disposer.reschedule();

Thread.sleep(100);

assertThat(disposer.getBacklog(), emptyCollectionOf(AsyncResourceDisposer.WorkItem.class));
}
}
10 changes: 9 additions & 1 deletion src/test/java/hudson/plugins/ws_cleanup/CleanupTest.java
Expand Up @@ -23,11 +23,13 @@
*/
package hudson.plugins.ws_cleanup;

import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
import static org.hamcrest.Matchers.*;

import hudson.FilePath;
import hudson.Functions;
import hudson.Launcher;
Expand All @@ -37,7 +39,11 @@
import hudson.matrix.MatrixProject;
import hudson.matrix.TextAxis;
import hudson.model.*;
import hudson.tasks.BuildStepMonitor;
import hudson.tasks.BuildWrapper;
import hudson.tasks.Notifier;
import hudson.tasks.Publisher;
import hudson.tasks.Recorder;
import hudson.tasks.Shell;

import java.io.File;
Expand All @@ -47,6 +53,7 @@
import java.util.List;
import java.util.concurrent.Future;

import hudson.util.DescribableList;
import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition;
import org.jenkinsci.plugins.workflow.job.WorkflowJob;
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
Expand All @@ -55,6 +62,7 @@
import org.junit.rules.TemporaryFolder;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.TestExtension;

public class CleanupTest {

Expand Down Expand Up @@ -264,7 +272,7 @@ public void pipelineWorkspaceCleanupUnlessBuildFails() throws Exception {
assertThat(files[0].getName(), is("foo.txt"));
}

private WsCleanup wipeoutPublisher() {
public static WsCleanup wipeoutPublisher() {
WsCleanup wsCleanup = new WsCleanup();
wsCleanup.setNotFailBuild(true);
wsCleanup.setCleanupMatrixParent(true);
Expand Down

0 comments on commit ccd9071

Please sign in to comment.