Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[FIXED JENKINS-20056] Delete workspace asynchronously
  • Loading branch information
olivergondza committed Sep 8, 2014
1 parent 0af6bfb commit 8e31d6a
Show file tree
Hide file tree
Showing 6 changed files with 187 additions and 23 deletions.
11 changes: 10 additions & 1 deletion src/main/java/hudson/plugins/ws_cleanup/Cleanup.java
@@ -1,6 +1,7 @@
package hudson.plugins.ws_cleanup;

import hudson.FilePath.FileCallable;
import hudson.FilePath;
import hudson.Util;
import hudson.model.BuildListener;
import hudson.plugins.ws_cleanup.Pattern.PatternType;
Expand All @@ -16,7 +17,10 @@

import org.apache.tools.ant.DirectoryScanner;

class Cleanup implements FileCallable<Object> {
/**
* Perform configured cleanup on remote directory.
*/
class Cleanup extends RemoteCleaner implements FileCallable<Object> {

private List<Pattern> patterns;
private final boolean deleteDirs;
Expand Down Expand Up @@ -145,4 +149,9 @@ private void doDelete(List<String> cmdList) throws IOException, InterruptedExcep
Process deletProc = new ProcessBuilder(cmdList).start();
deletProc.waitFor();
}

@Override
protected void perform(FilePath workspace) throws IOException, InterruptedException {
workspace.act(this);
}
}
15 changes: 6 additions & 9 deletions src/main/java/hudson/plugins/ws_cleanup/PreBuildCleanup.java
Expand Up @@ -81,20 +81,16 @@ public void preCheckout(AbstractBuild build, Launcher launcher,
FilePath ws = build.getWorkspace();

if (ws != null) {
RemoteCleaner cleaner = RemoteCleaner.get(patterns, deleteDirs, externalDelete, listener, build);
try {
// Retry the operation a couple of times,
int retry = 3;
while (true) {
try {
if (!ws.exists())
return;
if ((patterns == null || patterns.isEmpty()) && (externalDelete == null || externalDelete.isEmpty())) {
ws.deleteRecursive();
} else {
ws.act(new Cleanup(patterns,deleteDirs, build.getBuiltOn().getNodeProperties().get(
EnvironmentVariablesNodeProperty.class), externalDelete, listener));
}

cleaner.perform(ws);
listener.getLogger().append("done\n\n");
break;
} catch (IOException e) {
Expand All @@ -103,10 +99,11 @@ public void preCheckout(AbstractBuild build, Launcher launcher,
// Swallow the I/O exception and retry in a few seconds.
Thread.sleep(3000);
} else {
listener.getLogger().append("Cannot delete workspace: " + e.getCause() + "\n");
Logger.getLogger(PreBuildCleanup.class.getName()).log(Level.SEVERE, null, e);
listener.getLogger().append("Cannot delete workspace: " + e.getMessage() + "\n");
Logger.getLogger(PreBuildCleanup.class.getName()).log(Level.SEVERE, "Cannot delete workspace", e);
e.printStackTrace();
throw new AbortException("Cannot delete workspace: " + e.getCause());

throw new AbortException("Cannot delete workspace: " + e.getMessage());
}
}
}
Expand Down
67 changes: 67 additions & 0 deletions src/main/java/hudson/plugins/ws_cleanup/RemoteCleaner.java
@@ -0,0 +1,67 @@
/*
* The MIT License
*
* Copyright (c) 2014 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.model.BuildListener;
import hudson.model.AbstractBuild;
import hudson.slaves.EnvironmentVariablesNodeProperty;

import java.io.IOException;
import java.util.List;

/**
* Local abstraction to orchestrate remote cleaning.
*
* @author ogondza
*/
/*package*/ abstract class RemoteCleaner {

/*package*/ static RemoteCleaner get(
List<Pattern> patterns,
boolean deleteDirs,
String externalDelete,
BuildListener listener,
AbstractBuild<?, ?> build
) {
boolean wipeout = (patterns == null || patterns.isEmpty())
&& (externalDelete == null || externalDelete.isEmpty())
;

if (wipeout) return Wipeout.getInstance();

final EnvironmentVariablesNodeProperty properties = build.getBuiltOn()
.getNodeProperties().get(EnvironmentVariablesNodeProperty.class)
;

return new Cleanup(patterns, deleteDirs, properties, externalDelete, listener);
}

/**
* Perform the cleanup.
*
* Once this method completes, workspace is ready to be used.
*/
protected abstract void perform(FilePath workspace) throws IOException, InterruptedException;
}
64 changes: 64 additions & 0 deletions src/main/java/hudson/plugins/ws_cleanup/Wipeout.java
@@ -0,0 +1,64 @@
/*
* The MIT License
*
* Copyright (c) 2014 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 java.io.File;
import java.io.IOException;

import hudson.FilePath;
import hudson.FilePath.FileCallable;
import hudson.Util;
import hudson.remoting.VirtualChannel;

/**
* Cleanup workspace wiping it out completely.
*
* This implementation renames workspace directory and let Jenkins to create
* new one. Old workspace is removed asynchronously.
*
* @author ogondza
*/
/*package*/ final class Wipeout extends RemoteCleaner {

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);
deleteMe.actAsync(COMMAND);
}

private final static Command COMMAND = new Command();
private final static class Command implements FileCallable<Object> {
public Object invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
Util.deleteRecursive(f);
return null;
}
}
}
11 changes: 2 additions & 9 deletions src/main/java/hudson/plugins/ws_cleanup/WsCleanup.java
Expand Up @@ -159,15 +159,8 @@ public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListen
listener.getLogger().append("Skipped based on build state " + build.getResult() + "\n\n");
return true;
}
if ((patterns == null || patterns.isEmpty()) && (externalDelete == null || externalDelete.isEmpty())) {
workspace.deleteRecursive();
} else {
workspace.act(
new Cleanup(
patterns,
deleteDirs, build.getBuiltOn().getNodeProperties().get(
EnvironmentVariablesNodeProperty.class), externalDelete, listener));
}
RemoteCleaner cleaner = RemoteCleaner.get(patterns, deleteDirs, externalDelete, listener, build);
cleaner.perform(workspace);
listener.getLogger().append("done\n\n");
} catch (Exception ex) {
Logger.getLogger(WsCleanup.class.getName()).log(Level.SEVERE, null, ex);
Expand Down
42 changes: 38 additions & 4 deletions src/test/java/hudson/plugins/ws_cleanup/CleanupTest.java
Expand Up @@ -36,12 +36,20 @@
import hudson.model.BuildListener;
import hudson.model.FreeStyleBuild;
import hudson.model.FreeStyleProject;
import hudson.model.ParametersAction;
import hudson.model.ParametersDefinitionProperty;
import hudson.model.StringParameterDefinition;
import hudson.model.StringParameterValue;
import hudson.slaves.DumbSlave;
import hudson.tasks.BuildWrapper;
import hudson.tasks.Shell;

import java.beans.FeatureDescriptor;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Future;

import org.junit.Rule;
import org.junit.Test;
Expand Down Expand Up @@ -102,6 +110,36 @@ public void wipeOutWholeWorkspaceAfterBuildMatrix() throws Exception {
assertWorkspaceCleanedUp(p.getItem("name=b").getLastBuild());
}

@Test
public void workspaceShouldNotBeManipulated() throws Exception {
final int ITERATIONS = 50;

FreeStyleProject p = j.jenkins.createProject(FreeStyleProject.class, "sut");
p.addProperty(new ParametersDefinitionProperty(new StringParameterDefinition("RAND", "")));
p.setConcurrentBuild(true);
p.getBuildWrappersList().add(new PreBuildCleanup(Collections.<Pattern>emptyList(), false, null, null));
p.getPublishersList().add(wipeoutPublisher());
p.getBuildersList().add(new Shell(
"echo =$BUILD_NUMBER= > marker;" +
// Something hopefully expensive to delete
"mkdir -p a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/a/b/c/d/e/f/g/h/j/k/l/m/n/o/p/q/r/s//u/v/w/x/y/z/" +
"sleep $(($BUILD_NUMBER%5));" +
"grep =$BUILD_NUMBER= marker"
));

final List<Future<FreeStyleBuild>> futureBuilds = new ArrayList<Future<FreeStyleBuild>>(ITERATIONS);

for (int i = 0; i < ITERATIONS; i++) {
futureBuilds.add(p.scheduleBuild2(0, null, new ParametersAction(
new StringParameterValue("RAND", Integer.toString(i))
)));
}

for (Future<FreeStyleBuild> fb: futureBuilds) {
j.assertBuildStatusSuccess(fb.get());
}
}

private WsCleanup wipeoutPublisher() {
return new WsCleanup(Collections.<Pattern>emptyList(), false,
true, true, true, true, true, true, true, // run always
Expand Down Expand Up @@ -134,13 +172,9 @@ private static final class MatrixWsPopulator extends BuildWrapper {
@Override
public Environment setUp(AbstractBuild build, Launcher launcher, BuildListener listener) throws IOException, InterruptedException {
build.getWorkspace().child("content.txt").touch(0);
listener.error(build.getWorkspace().toURI().toString());
listener.error(build.getWorkspace().list().toString());
if (build instanceof MatrixRun) {
MatrixBuild mb = ((MatrixRun) build).getParentBuild();
mb.getWorkspace().child("content.txt").touch(0);
listener.error(mb.getWorkspace().toURI().toString());
listener.error(mb.getWorkspace().list().toString());
}

return new Environment() {};
Expand Down

0 comments on commit 8e31d6a

Please sign in to comment.