Skip to content

Commit

Permalink
JENKINS-18607 Xvfb plugin doesn't work on matrix jobs with slaves on the
Browse files Browse the repository at this point in the history
same machine
  • Loading branch information
Zoran Regvart committed Jul 7, 2013
1 parent e188d62 commit 61ca30d
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 49 deletions.
111 changes: 72 additions & 39 deletions src/main/java/org/jenkinsci/plugins/xvfb/XvfbBuildWrapper.java
Expand Up @@ -50,8 +50,10 @@
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
import java.util.Map;

import jenkins.model.Jenkins;
import net.sf.json.JSONObject;

import org.kohsuke.stapler.DataBoundConstructor;
Expand All @@ -60,25 +62,6 @@

public class XvfbBuildWrapper extends BuildWrapper {

@SuppressWarnings("rawtypes")
@Extension
public static final RunListener<Run> xvfbShutdownListener = new RunListener<Run>() {
@Override
public void onCompleted(Run r, TaskListener listener) {
XvfbEnvironment xvfbEnvironment = r.getAction(XvfbEnvironment.class);

if (xvfbEnvironment != null && xvfbEnvironment.isShutdownWithBuild()) {
try {
shutdownAndCleanup(xvfbEnvironment, listener);
} catch (IOException e) {
throw new RuntimeException(e);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
};

@Extension
public static class XvfbBuildWrapperDescriptor extends BuildWrapperDescriptor {

Expand Down Expand Up @@ -147,38 +130,69 @@ private FormValidation validateOptionalPositiveInteger(final String value) {
}
}

private static final int MILLIS_IN_SECOND = 1000;
@SuppressWarnings("rawtypes")
@Extension
public static final RunListener<Run> xvfbShutdownListener = new RunListener<Run>() {
@Override
public void onCompleted(final Run r, final TaskListener listener) {
final XvfbEnvironment xvfbEnvironment = r.getAction(XvfbEnvironment.class);

if (xvfbEnvironment != null && xvfbEnvironment.isShutdownWithBuild()) {
try {
shutdownAndCleanup(xvfbEnvironment, listener);
} catch (final IOException e) {
throw new RuntimeException(e);
} catch (final InterruptedException e) {
throw new RuntimeException(e);
}
}
}
};

private static final int MILLIS_IN_SECOND = 1000;

/** default screen configuration for Xvfb, used by default, and if user left screen configuration blank */
private static final String DEFAULT_SCREEN = "1024x768x24";
private static final String DEFAULT_SCREEN = "1024x768x24";

private static void shutdownAndCleanup(final XvfbEnvironment environment, final TaskListener listener) throws IOException, InterruptedException {
final FilePath frameBufferDir = environment.getFrameBufferDir();
final Proc process = environment.getProcess();

listener.getLogger().println(Messages.XvfbBuildWrapper_Stopping());
process.kill();
frameBufferDir.deleteRecursive();
}

/** Name of the installation used in a configured job. */
private final String installationName;
private final String installationName;

/** X11 DISPLAY name, if NULL chosen by random. */
private final Integer displayName;
private final Integer displayName;

/** Xvfb screen argument, in the form WxHxD (width x height x pixel depth), i.e. 800x600x8. */
private String screen = DEFAULT_SCREEN;
private String screen = DEFAULT_SCREEN;

/** Should the Xvfb output be displayed in job output. */
private boolean debug = false;
private boolean debug = false;

/** Time in milliseconds to wait for Xvfb initialization, by default 0 -- do not wait. */
private final long timeout;
private final long timeout;

/** Offset for display names, default is 1. Display names are taken from build executor's number, i.e. if the build is performed by executor 4, and offset is 100, display name will be 104. */
private int displayNameOffset = 1;
private int displayNameOffset = 1;

/** Additional options to be passed to Xvfb */
private final String additionalOptions;
private final String additionalOptions;

/** Should the Xvfb display be around for post build actions, i.e. should it terminate with the whole build */
private boolean shutdownWithBuild = false;
private boolean shutdownWithBuild = false;

/** Should the displayName be offsetted by the node index */
private boolean useNodeOffsets = false;

@DataBoundConstructor
public XvfbBuildWrapper(final String installationName, final Integer displayName, final String screen, final Boolean debug, final int timeout, final int displayNameOffset,
final String additionalOptions, Boolean shutdownWithBuild) {
final String additionalOptions, final Boolean shutdownWithBuild, final Boolean useNodeOffsets) {
this.installationName = installationName;
this.displayName = displayName;

Expand All @@ -200,6 +214,8 @@ public XvfbBuildWrapper(final String installationName, final Integer displayName
this.additionalOptions = additionalOptions;

this.shutdownWithBuild = shutdownWithBuild;

this.useNodeOffsets = useNodeOffsets;
}

public String getAdditionalOptions() {
Expand Down Expand Up @@ -255,19 +271,27 @@ public boolean isShutdownWithBuild() {
return shutdownWithBuild;
}

public boolean isUseNodeOffsets() {
return useNodeOffsets;
}

private XvfbEnvironment launchXvfb(@SuppressWarnings("rawtypes") final AbstractBuild build, final Launcher launcher, final BuildListener listener) throws IOException, InterruptedException {
int displayNameUsed;

final Computer currentComputer = Computer.currentComputer();
final Node currentNode = currentComputer.getNode();

if (displayName == null) {
final Executor executor = build.getExecutor();
displayNameUsed = executor.getNumber() + displayNameOffset;

final int nodeOffset = nodeOffsetFor(currentNode);

displayNameUsed = nodeOffset + executor.getNumber() + displayNameOffset;
}
else {
displayNameUsed = displayName;
}

final Computer currentComputer = Computer.currentComputer();
final Node currentNode = currentComputer.getNode();
final FilePath rootPath = currentNode.getRootPath();

final FilePath frameBufferDir = rootPath.createTempDir(build.getId(), "xvfb");
Expand Down Expand Up @@ -338,13 +362,22 @@ public void makeBuildVariables(@SuppressWarnings("rawtypes") final AbstractBuild
}
}

private static void shutdownAndCleanup(XvfbEnvironment environment, TaskListener listener) throws IOException, InterruptedException {
final FilePath frameBufferDir = environment.getFrameBufferDir();
final Proc process = environment.getProcess();
private int nodeOffsetFor(final Node currentNode) {
final String nodeName = currentNode.getNodeName();

listener.getLogger().println(Messages.XvfbBuildWrapper_Stopping());
process.kill();
frameBufferDir.deleteRecursive();
if (!useNodeOffsets || "".equals(nodeName)) {
return 0;
}

final List<Node> nodes = Jenkins.getInstance().getNodes();

final int idx = nodes.indexOf(currentNode);

if (idx < 0) {
return 0;
}

return 1000 * (idx + 1);
}

@Override
Expand Down
Expand Up @@ -22,18 +22,14 @@ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
-->
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define"
xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">

<f:advanced>
<j:if test="${!empty(descriptor.installations)}">
<f:entry title="${%Xvfb installation}" field="installationName">
<select name="xvfb.installationName" class="setting-input">
<j:forEach var="installation"
items="${descriptor.installations}">
<f:option
selected="${installation.name == instance.installationName}"
value="${installation.name}">
<j:forEach var="installation" items="${descriptor.installations}">
<f:option selected="${installation.name == instance.installationName}" value="${installation.name}">
${installation.name}
</f:option>
</j:forEach>
Expand Down Expand Up @@ -65,11 +61,13 @@ SUCH DAMAGE.
<f:checkbox value="${instance.debug}" />
</f:entry>

<f:entry
title="${%Shoutdown Xvfb with whole job, not just with the main build action}"
field="shutdownWithBuild">
<f:entry title="${%Shoutdown Xvfb with whole job, not just with the main build action}" field="shutdownWithBuild">
<f:checkbox value="${instance.shutdownWithBuild}" />
</f:entry>

<f:entry title="${%Use display name node offset}" field="useNodeOffsets">
<f:checkbox value="${instance.useNodeOffsets}" />
</f:entry>
</f:advanced>

</j:jelly>
@@ -0,0 +1,30 @@
<!--
Copyright (c) 2012 Zoran Regvart All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
-->
<div>
Should the display name be offsetted by the index of the node (slave),
this helps with if you're running more than one slave per machine. The
display name will be 1000 * <i>node index</i> + <i>executor number</i>
+ <i>display name offset</i>
</div>

0 comments on commit 61ca30d

Please sign in to comment.