Skip to content
Permalink
Browse files

[JENKINS-44112] - Enable WorkDir in JNLPLauncher (#2945)

* [JENKINS-44112] - Enable Work Directories by defaul in new agents with JNLP launcher

* [JENKINS-44112] - Add test for the legacy data migration

* [JENKINS-44112] - Add test for API calls

* [JENKINS-44112] - Apply new APIs within core and tests

* [JENKINS-44112] - Cleanup tests

* [JENKINS-44112] - Cleanup API of JNLPLauncher

* [JENKINS-44112] - Address the leftover code review comments
  • Loading branch information...
oleg-nenashev committed Jul 29, 2017
1 parent f4c3cd1 commit 9dc2cdbb4892ddfb1579658fc88b4018a1ad7d8a
@@ -225,7 +225,8 @@ public void setUserId(String userId){
}

public ComputerLauncher getLauncher() {
return launcher == null ? new JNLPLauncher() : launcher;
// Default launcher does not use Work Directory
return launcher == null ? new JNLPLauncher(false) : launcher;
}

public void setLauncher(ComputerLauncher launcher) {
@@ -504,7 +505,7 @@ protected Object readResolve() {
// convert the old format to the new one
if (launcher == null) {
launcher = (agentCommand == null || agentCommand.trim().length() == 0)
? new JNLPLauncher()
? new JNLPLauncher(false)
: new CommandLauncher(agentCommand);
}
if(nodeProperties==null)
@@ -25,13 +25,17 @@

import hudson.Extension;
import hudson.Util;
import hudson.model.Computer;
import hudson.model.Descriptor;
import hudson.model.DescriptorVisibilityFilter;
import hudson.model.TaskListener;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import jenkins.model.Jenkins;
import jenkins.slaves.RemotingWorkDirSettings;
import org.jenkinsci.Symbol;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.DataBoundConstructor;

/**
@@ -52,24 +56,72 @@
*
* @since 1.250
*/
@CheckForNull
public final String tunnel;

/**
* Additional JVM arguments. Can be null.
* @since 1.297
*/
@CheckForNull
public final String vmargs;

@Nonnull
private RemotingWorkDirSettings workDirSettings;

@DataBoundConstructor
public JNLPLauncher(String tunnel, String vmargs) {
public JNLPLauncher(@CheckForNull String tunnel, @CheckForNull String vmargs, @Nonnull RemotingWorkDirSettings workDirSettings) {
this.tunnel = Util.fixEmptyAndTrim(tunnel);
this.vmargs = Util.fixEmptyAndTrim(vmargs);
this.workDirSettings = workDirSettings;
}

@Deprecated
public JNLPLauncher(String tunnel, String vmargs) {
// TODO: Enable workDir by default in API? Otherwise classes inheriting from JNLPLauncher
// will need to enable the feature by default as well.
// https://github.com/search?q=org%3Ajenkinsci+%22extends+JNLPLauncher%22&type=Code
this(tunnel, vmargs, RemotingWorkDirSettings.getDisabledDefaults());
}

/**
* @deprecated This Launcher does not enable the work directory.
* It is recommended to use {@link #JNLPLauncher(boolean)}
*/
@Deprecated
public JNLPLauncher() {
this(null,null);
this(false);
}

/**
* Constructor with default options.
*
* @param enableWorkDir If {@code true}, the work directory will be enabled with default settings.
*/
public JNLPLauncher(boolean enableWorkDir) {
this(null, null, enableWorkDir
? RemotingWorkDirSettings.getEnabledDefaults()
: RemotingWorkDirSettings.getDisabledDefaults());
}

protected Object readResolve() {
if (workDirSettings == null) {
// For the migrated code agents are always disabled
workDirSettings = RemotingWorkDirSettings.getDisabledDefaults();
}
return this;
}

/**
* Returns work directory settings.
*
* @since TODO
*/
@Nonnull
public RemotingWorkDirSettings getWorkDirSettings() {
return workDirSettings;
}

@Override
public boolean isLaunchSupported() {
return false;
@@ -86,6 +138,22 @@ public void launch(SlaveComputer computer, TaskListener listener) {
*/
public static /*almost final*/ Descriptor<ComputerLauncher> DESCRIPTOR;

/**
* Gets work directory options as a String.
*
* In public API {@code getWorkDirSettings().toCommandLineArgs(computer)} should be used instead
* @param computer Computer
* @return Command line options for launching with the WorkDir
*/
@Nonnull
@Restricted(NoExternalUse.class)
public String getWorkDirOptions(@Nonnull Computer computer) {
if(!(computer instanceof SlaveComputer)) {
return "";
}
return workDirSettings.toCommandLineString((SlaveComputer)computer);
}

@Extension @Symbol("jnlp")
public static class DescriptorImpl extends Descriptor<ComputerLauncher> {
public DescriptorImpl() {
@@ -38,6 +38,7 @@
import java.io.File;
import java.io.IOException;
import java.util.Set;
import javax.annotation.Nonnull;

/**
* Used to build up arguments for a process invocation.
@@ -132,6 +133,16 @@ public ArgumentListBuilder add(String... args) {
}
return this;
}

/**
* @since TODO
*/
public ArgumentListBuilder add(@Nonnull Iterable<String> args) {
for (String arg : args) {
add(arg);
}
return this;
}

/**
* Decomposes the given token into multiple arguments by splitting via whitespace.
@@ -0,0 +1,229 @@
/*
* The MIT License
*
* Copyright (c) 2017 CloudBees, 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 jenkins.slaves;

import hudson.Extension;
import hudson.Util;
import hudson.model.Describable;
import hudson.model.Descriptor;
import hudson.model.Slave;
import hudson.slaves.SlaveComputer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import jenkins.model.Jenkins;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.DataBoundConstructor;

/**
* Defines settings of the Remoting work directory.
*
* This class contains Remoting Work Directory settings, which can be used when starting Jenkins agents.
* See <a href="https://github.com/jenkinsci/remoting/blob/master/docs/workDir.md">Remoting Work Dir Documentation</a>.
*
* @author Oleg Nenashev
* @since TODO
*/
public class RemotingWorkDirSettings implements Describable<RemotingWorkDirSettings> {

private static final String DEFAULT_INTERNAL_DIR = "remoting";
private static final RemotingWorkDirSettings LEGACY_DEFAULT = new RemotingWorkDirSettings(true, null, DEFAULT_INTERNAL_DIR, false);
private static final RemotingWorkDirSettings ENABLED_DEFAULT = new RemotingWorkDirSettings(false, null, DEFAULT_INTERNAL_DIR, false);


private final boolean disabled;
@CheckForNull
private final String workDirPath;
@Nonnull
private final String internalDir;
private final boolean failIfWorkDirIsMissing;

@DataBoundConstructor
public RemotingWorkDirSettings(boolean disabled,
@CheckForNull String workDirPath, @CheckForNull String internalDir,
boolean failIfWorkDirIsMissing) {
this.disabled = disabled;
this.workDirPath = Util.fixEmptyAndTrim(workDirPath);
this.failIfWorkDirIsMissing = failIfWorkDirIsMissing;
String internalDirName = Util.fixEmptyAndTrim(internalDir);
this.internalDir = internalDirName != null ? internalDirName : DEFAULT_INTERNAL_DIR;
}

public RemotingWorkDirSettings() {
// Enabled by default
this(false, null, DEFAULT_INTERNAL_DIR, false);
}

/**
* Check if workdir is disabled.
*
* @return {@code true} if the property is disabled.
* In such case Remoting will use the legacy mode.
*/
public boolean isDisabled() {
return disabled;
}

/**
* Indicates that agent root directory should be used as work directory.
*
* @return {@code true} if the agent root should be a work directory.
*/
public boolean isUseAgentRootDir() {
return workDirPath == null;
}

/**
* Check if startup should fail if the workdir is missing.
*
* @return {@code true} if Remoting should fail if the the work directory is missing instead of creating it
*/
public boolean isFailIfWorkDirIsMissing() {
return failIfWorkDirIsMissing;
}

/**
* Gets path to the custom workdir path.
*
* @return Custom workdir path.
* If {@code null}, an agent root directory path should be used instead.
*/
@CheckForNull
public String getWorkDirPath() {
return workDirPath;
}

@Nonnull
public String getInternalDir() {
return internalDir;
}

@Override
public Descriptor<RemotingWorkDirSettings> getDescriptor() {
return Jenkins.getInstance().getDescriptor(RemotingWorkDirSettings.class);
}

/**
* Gets list of command-line arguments for the work directory.
* @param computer Computer, for which the arguments are being created
* @return Non-modifiable list of command-line arguments
*/
public List<String> toCommandLineArgs(@Nonnull SlaveComputer computer) {
if(disabled) {
return Collections.emptyList();
}

ArrayList<String> args = new ArrayList<>();
args.add("-workDir");
if (workDirPath == null) {
Slave node = computer.getNode();
if (node == null) {
// It is not possible to launch this node anyway.
return Collections.emptyList();
}
args.add(node.getRemoteFS());
} else {
args.add(workDirPath);
}

if (!DEFAULT_INTERNAL_DIR.equals(internalDir)) {
args.add("-internalDir");
args.add(internalDir);;
}

if (failIfWorkDirIsMissing) {
args.add(" -failIfWorkDirIsMissing");
}

return Collections.unmodifiableList(args);
}

/**
* Gets a command line string, which can be passed to agent start command.
*
* @param computer Computer, for which the arguments need to be constructed.
* @return Command line arguments.
* It may be empty if the working directory is disabled or
* if the Computer type is not {@link SlaveComputer}.
*/
@Nonnull
@Restricted(NoExternalUse.class)
public String toCommandLineString(@Nonnull SlaveComputer computer) {
if(disabled) {
return "";
}

StringBuilder bldr = new StringBuilder();
bldr.append("-workDir \"");
if (workDirPath == null) {
Slave node = computer.getNode();
if (node == null) {
// It is not possible to launch this node anyway.
return "";
}
bldr.append(node.getRemoteFS());
} else {
bldr.append(workDirPath);
}
bldr.append("\"");

if (!DEFAULT_INTERNAL_DIR.equals(internalDir)) {
bldr.append(" -internalDir \"");
bldr.append(internalDir);
bldr.append("\"");
}

if (failIfWorkDirIsMissing) {
bldr.append(" -failIfWorkDirIsMissing");
}

return bldr.toString();
}

@Extension
public static class DescriptorImpl extends Descriptor<RemotingWorkDirSettings> {

}

/**
* Gets default settings for the disabled work directory.
*
* @return Legacy value: disabled work directory.
*/
@Nonnull
public static RemotingWorkDirSettings getDisabledDefaults() {
return LEGACY_DEFAULT;
}

/**
* Gets default settings of the enabled work directory.
*/
@Nonnull
public static RemotingWorkDirSettings getEnabledDefaults() {
return ENABLED_DEFAULT;
}
}
@@ -24,6 +24,7 @@ THE SOFTWARE.

<?jelly escape-by-default='true'?>
<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:property title="${%Enable work directory}" field="workDirSettings" />
<f:advanced>
<f:entry title="${%Tunnel connection through}" help="/help/system-config/master-slave/jnlp-tunnel.html">
<f:textbox field="tunnel"/>
Oops, something went wrong.

0 comments on commit 9dc2cdb

Please sign in to comment.
You can’t perform that action at this time.