Under some conditions Jenkins startup could fail because of incorrectly linked extensions; now recovering more gracefully.
- (issue 25440)
+ (issue 25440)
Move periodic task log files from JENKINS_HOME/*.log to JENKINS_HOME/logs/tasks/*.log and rotate them periodically rather than overwrite every execution.
- (issue 33068)
+ (issue 33068)
Fix documentation of proxy configuration.
(pull 2060)
diff --git a/cli/pom.xml b/cli/pom.xml
index a41c23bcba..992b9f05c1 100644
--- a/cli/pom.xml
+++ b/cli/pom.xml
@@ -5,7 +5,7 @@
org.jenkins-ci.mainpom
- 2.0-alpha-4-SNAPSHOT
+ 2.0-alpha-5-SNAPSHOTcli
diff --git a/core/pom.xml b/core/pom.xml
index 64049e8e31..ac6b04361b 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -29,7 +29,7 @@ THE SOFTWARE.
org.jenkins-ci.mainpom
- 2.0-alpha-4-SNAPSHOT
+ 2.0-alpha-5-SNAPSHOTjenkins-core
@@ -147,7 +147,7 @@ THE SOFTWARE.
org.kohsukewindows-package-checker
- 1.0
+ 1.2org.kohsuke.stapler
diff --git a/core/src/main/java/hudson/ClassicPluginStrategy.java b/core/src/main/java/hudson/ClassicPluginStrategy.java
index 3b0f907288..6903addc10 100644
--- a/core/src/main/java/hudson/ClassicPluginStrategy.java
+++ b/core/src/main/java/hudson/ClassicPluginStrategy.java
@@ -241,8 +241,8 @@ public class ClassicPluginStrategy implements PluginStrategy {
}
}
}
- for (DetachedPlugin detached : DETACHED_LIST)
- detached.fix(atts,optionalDependencies);
+
+ fix(atts,optionalDependencies);
// Register global classpath mask. This is useful for hiding JavaEE APIs that you might see from the container,
// such as database plugin for JPA support. The Mask-Classes attribute is insufficient because those classes
@@ -260,6 +260,41 @@ public class ClassicPluginStrategy implements PluginStrategy {
createClassLoader(paths, dependencyLoader, atts), disableFile, dependencies, optionalDependencies);
}
+ private static void fix(Attributes atts, List optionalDependencies) {
+ String pluginName = atts.getValue("Short-Name");
+
+ String jenkinsVersion = atts.getValue("Jenkins-Version");
+ if (jenkinsVersion==null)
+ jenkinsVersion = atts.getValue("Hudson-Version");
+
+ optionalDependencies.addAll(getImpliedDependencies(pluginName, jenkinsVersion));
+ }
+
+ /**
+ * Returns all the plugin dependencies that are implicit based on a particular Jenkins version
+ * @since 2.0
+ */
+ @Nonnull
+ public static List getImpliedDependencies(String pluginName, String jenkinsVersion) {
+ List out = new ArrayList<>();
+ for (DetachedPlugin detached : DETACHED_LIST) {
+ // don't fix the dependency for itself, or else we'll have a cycle
+ if (detached.shortName.equals(pluginName)) {
+ continue;
+ }
+ if (BREAK_CYCLES.contains(pluginName + '/' + detached.shortName)) {
+ LOGGER.log(Level.FINE, "skipping implicit dependency {0} → {1}", new Object[] {pluginName, detached.shortName});
+ continue;
+ }
+ // some earlier versions of maven-hpi-plugin apparently puts "null" as a literal in Hudson-Version. watch out for them.
+ if (jenkinsVersion == null || jenkinsVersion.equals("null") || new VersionNumber(jenkinsVersion).compareTo(detached.splitWhen) <= 0) {
+ out.add(new PluginWrapper.Dependency(detached.shortName + ':' + detached.requireVersion));
+ LOGGER.log(Level.FINE, "adding implicit dependency {0} → {1} because of {2}", new Object[] {pluginName, detached.shortName, jenkinsVersion});
+ }
+ }
+ return out;
+ }
+
@Deprecated
protected ClassLoader createClassLoader(List paths, ClassLoader parent) throws IOException {
return createClassLoader( paths, parent, null );
@@ -377,25 +412,6 @@ public class ClassicPluginStrategy implements PluginStrategy {
public VersionNumber getSplitWhen() {
return splitWhen;
}
-
- private void fix(Attributes atts, List optionalDependencies) {
- // don't fix the dependency for yourself, or else we'll have a cycle
- String yourName = atts.getValue("Short-Name");
- if (shortName.equals(yourName)) return;
- if (BREAK_CYCLES.contains(yourName + '/' + shortName)) {
- LOGGER.log(Level.FINE, "skipping implicit dependency {0} → {1}", new Object[] {yourName, shortName});
- return;
- }
-
- // some earlier versions of maven-hpi-plugin apparently puts "null" as a literal in Hudson-Version. watch out for them.
- String jenkinsVersion = atts.getValue("Jenkins-Version");
- if (jenkinsVersion==null)
- jenkinsVersion = atts.getValue("Hudson-Version");
- if (jenkinsVersion == null || jenkinsVersion.equals("null") || new VersionNumber(jenkinsVersion).compareTo(splitWhen) <= 0) {
- optionalDependencies.add(new PluginWrapper.Dependency(shortName + ':' + requireVersion));
- LOGGER.log(Level.FINE, "adding implicit dependency {0} → {1} because of {2}", new Object[] {yourName, shortName, jenkinsVersion});
- }
- }
}
private static final List DETACHED_LIST = Collections.unmodifiableList(Arrays.asList(
diff --git a/core/src/main/java/hudson/ExtensionList.java b/core/src/main/java/hudson/ExtensionList.java
index 46447fbcda..397f57030d 100644
--- a/core/src/main/java/hudson/ExtensionList.java
+++ b/core/src/main/java/hudson/ExtensionList.java
@@ -390,14 +390,14 @@ public class ExtensionList extends AbstractList {
/**
* Gets the extension list for a given type.
* Normally calls {@link Jenkins#getExtensionList(Class)} but falls back to an empty list
- * in case {@link Jenkins#getInstance} is null.
+ * in case {@link Jenkins#getInstanceOrNull()} is null.
* Thus it is useful to call from {@code all()} methods which need to behave gracefully during startup or shutdown.
* @param type the extension point type
* @return some list
* @since 1.572
*/
public static @Nonnull ExtensionList lookup(Class type) {
- Jenkins j = Jenkins.getInstance();
+ Jenkins j = Jenkins.getInstanceOrNull();
return j == null ? create((Jenkins) null, type) : j.getExtensionList(type);
}
diff --git a/core/src/main/java/hudson/FilePath.java b/core/src/main/java/hudson/FilePath.java
index adb28461f8..72383256e4 100644
--- a/core/src/main/java/hudson/FilePath.java
+++ b/core/src/main/java/hudson/FilePath.java
@@ -1133,7 +1133,7 @@ public final class FilePath implements Serializable {
* @since 1.571
*/
public @CheckForNull Computer toComputer() {
- Jenkins j = Jenkins.getInstance();
+ Jenkins j = Jenkins.getInstanceOrNull();
if (j != null) {
for (Computer c : j.getComputers()) {
if (getChannel()==c.getChannel()) {
diff --git a/core/src/main/java/hudson/Functions.java b/core/src/main/java/hudson/Functions.java
index fb28418cc6..4142366f42 100644
--- a/core/src/main/java/hudson/Functions.java
+++ b/core/src/main/java/hudson/Functions.java
@@ -74,6 +74,8 @@ import hudson.tasks.Publisher;
import hudson.tasks.UserAvatarResolver;
import hudson.util.Area;
import hudson.util.FormValidation.CheckMethod;
+import hudson.util.HudsonIsLoading;
+import hudson.util.HudsonIsRestarting;
import hudson.util.Iterators;
import hudson.util.jna.GNUCLibrary;
import hudson.util.Secret;
@@ -207,20 +209,28 @@ public class Functions {
/**
* During Jenkins start-up, before {@link InitMilestone#PLUGINS_STARTED} the extensions lists will be empty
- * and they are not guaranteed to be fully populated until after {@link InitMilestone#EXTENSIONS_AUGMENTED}.
+ * and they are not guaranteed to be fully populated until after {@link InitMilestone#EXTENSIONS_AUGMENTED},
+ * similarly, during termination after {@link Jenkins#isTerminating()} is set, it is no longer safe to access
+ * the extensions lists.
* If you attempt to access the extensions list from a UI thread while the extensions are being loaded you will
* hit a big honking great monitor lock that will block until the effective extension list has been determined
* (as if a plugin fails to start, all of the failed plugin's extensions and any dependent plugins' extensions
* will have to be evicted from the list of extensions. In practical terms this only affects the
* "Jenkins is loading" screen, but as that screen uses the generic layouts we provide this utility method
* so that the generic layouts can avoid iterating extension lists while Jenkins is starting up.
+ * If you attempt to access the extensions list from a UI thread while Jenkins is being shut down, the extensions
+ * themselves may no longer be in a valid state and could attempt to revive themselves and block termination.
+ * In actual terms the termination only affects those views required to render {@link HudsonIsRestarting}'s
+ * {@code index.jelly} which is the same set as the {@link HudsonIsLoading} pages so it makes sense to
+ * use both checks here.
*
* @return {@code true} if the extensions lists have been populated.
* @since 1.607
*/
public static boolean isExtensionsAvailable() {
- final Jenkins jenkins = Jenkins.getInstance();
- return jenkins != null && jenkins.getInitLevel().compareTo(InitMilestone.EXTENSIONS_AUGMENTED) >= 0;
+ final Jenkins jenkins = Jenkins.getInstanceOrNull();
+ return jenkins != null && jenkins.getInitLevel().compareTo(InitMilestone.EXTENSIONS_AUGMENTED) >= 0
+ && !jenkins.isTerminating();
}
public static void initPageVariables(JellyContext context) {
@@ -1649,7 +1659,7 @@ public class Functions {
*/
public static List getPageDecorators() {
// this method may be called to render start up errors, at which point Hudson doesn't exist yet. see HUDSON-3608
- if(Jenkins.getInstance()==null) return Collections.emptyList();
+ if(Jenkins.getInstanceOrNull()==null) return Collections.emptyList();
return PageDecorator.all();
}
@@ -1671,13 +1681,13 @@ public class Functions {
}
public static String getCrumb(StaplerRequest req) {
- Jenkins h = Jenkins.getInstance();
+ Jenkins h = Jenkins.getInstanceOrNull();
CrumbIssuer issuer = h != null ? h.getCrumbIssuer() : null;
return issuer != null ? issuer.getCrumb(req) : "";
}
public static String getCrumbRequestField() {
- Jenkins h = Jenkins.getInstance();
+ Jenkins h = Jenkins.getInstanceOrNull();
CrumbIssuer issuer = h != null ? h.getCrumbIssuer() : null;
return issuer != null ? issuer.getDescriptor().getCrumbRequestField() : "";
}
@@ -1907,7 +1917,7 @@ public class Functions {
* discovery of Jenkins.
*/
public static void advertiseHeaders(HttpServletResponse rsp) {
- Jenkins j = Jenkins.getInstance();
+ Jenkins j = Jenkins.getInstanceOrNull();
if (j!=null) {
rsp.setHeader("X-Hudson","1.395");
rsp.setHeader("X-Jenkins", Jenkins.VERSION);
diff --git a/core/src/main/java/hudson/PluginManager.java b/core/src/main/java/hudson/PluginManager.java
index 794f7a660c..ae96fc347a 100644
--- a/core/src/main/java/hudson/PluginManager.java
+++ b/core/src/main/java/hudson/PluginManager.java
@@ -1141,7 +1141,9 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
String pluginName = n.substring(0, index);
String siteName = n.substring(index + 1);
UpdateSite.Plugin plugin = getPlugin(pluginName, siteName);
- // TODO: Someone that understands what the following logic is about, please add a comment.
+ // There could be cases like:
+ // 'plugin.ambiguous.updatesite' where both
+ // 'plugin' @ 'ambigiuous.updatesite' and 'plugin.ambiguous' @ 'updatesite' resolve to valid plugins
if (plugin != null) {
if (p != null) {
throw new Failure("Ambiguous plugin: " + n);
@@ -1151,6 +1153,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
index = n.indexOf('.', index + 1);
}
}
+
if (p == null) {
throw new Failure("No such plugin: " + n);
}
diff --git a/core/src/main/java/hudson/ProxyConfiguration.java b/core/src/main/java/hudson/ProxyConfiguration.java
index ae86927729..f841953062 100644
--- a/core/src/main/java/hudson/ProxyConfiguration.java
+++ b/core/src/main/java/hudson/ProxyConfiguration.java
@@ -47,7 +47,9 @@ import java.net.URLConnection;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;
+import javax.annotation.CheckForNull;
import jenkins.model.Jenkins;
+import jenkins.util.JenkinsJVM;
import org.apache.commons.httpclient.Credentials;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
@@ -216,8 +218,7 @@ public final class ProxyConfiguration extends AbstractDescribableImpl) {
- for (Run r : ((Job, ?>) obj).getBuilds()) {
- odm.data.remove(referTo(r));
- }
+ OldDataMonitor odm = get(j);
+ SecurityContext oldContext = ACL.impersonate(ACL.SYSTEM);
+ try {
+ odm.data.remove(referTo(obj));
+ if (isDelete && obj instanceof Job, ?>) {
+ for (Run r : ((Job, ?>) obj).getBuilds()) {
+ odm.data.remove(referTo(r));
}
- } finally {
- SecurityContextHolder.setContext(oldContext);
}
+ } finally {
+ SecurityContextHolder.setContext(oldContext);
}
}
@@ -207,8 +205,8 @@ public class OldDataMonitor extends AdministrativeMonitor {
}
}
if (buf.length() == 0) return;
- Jenkins j = Jenkins.getInstance();
- if (j == null) {
+ Jenkins j = Jenkins.getInstanceOrNull();
+ if (j == null) { // Need this path, at least for unit tests, but also in case of very broken startup
// Startup failed, something is very broken, so report what we can.
for (Throwable t : errors) {
LOGGER.log(Level.WARNING, "could not read " + obj + " (and Jenkins did not start up)", t);
diff --git a/core/src/main/java/hudson/diagnosis/ReverseProxySetupMonitor.java b/core/src/main/java/hudson/diagnosis/ReverseProxySetupMonitor.java
index 63f4cb255c..61f6b0fe6e 100644
--- a/core/src/main/java/hudson/diagnosis/ReverseProxySetupMonitor.java
+++ b/core/src/main/java/hudson/diagnosis/ReverseProxySetupMonitor.java
@@ -63,7 +63,6 @@ public class ReverseProxySetupMonitor extends AdministrativeMonitor {
public HttpResponse doTest() {
String referer = Stapler.getCurrentRequest().getReferer();
Jenkins j = Jenkins.getInstance();
- assert j != null;
// May need to send an absolute URL, since handling of HttpRedirect with a relative URL does not currently honor X-Forwarded-Proto/Port at all.
String redirect = j.getRootUrl() + "administrativeMonitor/" + id + "/testForReverseProxySetup/" + (referer != null ? Util.rawEncode(referer) : "NO-REFERER") + "/";
LOGGER.log(Level.FINE, "coming from {0} and redirecting to {1}", new Object[] {referer, redirect});
@@ -72,7 +71,6 @@ public class ReverseProxySetupMonitor extends AdministrativeMonitor {
public void getTestForReverseProxySetup(String rest) {
Jenkins j = Jenkins.getInstance();
- assert j != null;
String inferred = j.getRootUrlFromRequest() + "manage";
// TODO this could also verify that j.getRootUrl() has been properly configured, and send a different message if not
if (rest.startsWith(inferred)) { // not using equals due to JENKINS-24014
diff --git a/core/src/main/java/hudson/init/TaskMethodFinder.java b/core/src/main/java/hudson/init/TaskMethodFinder.java
index 9ced2cb1ab..8fc677f651 100644
--- a/core/src/main/java/hudson/init/TaskMethodFinder.java
+++ b/core/src/main/java/hudson/init/TaskMethodFinder.java
@@ -115,14 +115,13 @@ abstract class TaskMethodFinder extends TaskBuilder {
* Determines the parameter injection of the initialization method.
*/
private Object lookUp(Class> type) {
- if (type==Jenkins.class || type==Hudson.class)
- return Jenkins.getInstance();
Jenkins j = Jenkins.getInstance();
- if (j!=null) {
- Injector i = j.getInjector();
- if (i!=null)
- return i.getInstance(type);
- }
+ assert j != null : "This method is only invoked after the Jenkins singleton instance has been set";
+ if (type==Jenkins.class || type==Hudson.class)
+ return j;
+ Injector i = j.getInjector();
+ if (i!=null)
+ return i.getInstance(type);
throw new IllegalArgumentException("Unable to inject "+type);
}
diff --git a/core/src/main/java/hudson/lifecycle/SolarisSMFLifecycle.java b/core/src/main/java/hudson/lifecycle/SolarisSMFLifecycle.java
index 9da1505229..ca90fb83d4 100644
--- a/core/src/main/java/hudson/lifecycle/SolarisSMFLifecycle.java
+++ b/core/src/main/java/hudson/lifecycle/SolarisSMFLifecycle.java
@@ -38,7 +38,7 @@ public class SolarisSMFLifecycle extends Lifecycle {
*/
@Override
public void restart() throws IOException, InterruptedException {
- Jenkins h = Jenkins.getInstance();
+ Jenkins h = Jenkins.getInstanceOrNull(); // guard against repeated concurrent calls to restart
if (h != null)
h.cleanUp();
System.exit(0);
diff --git a/core/src/main/java/hudson/lifecycle/UnixLifecycle.java b/core/src/main/java/hudson/lifecycle/UnixLifecycle.java
index 0e489277e5..5d545a4fe4 100644
--- a/core/src/main/java/hudson/lifecycle/UnixLifecycle.java
+++ b/core/src/main/java/hudson/lifecycle/UnixLifecycle.java
@@ -65,7 +65,7 @@ public class UnixLifecycle extends Lifecycle {
@Override
public void restart() throws IOException, InterruptedException {
- Jenkins h = Jenkins.getInstance();
+ Jenkins h = Jenkins.getInstanceOrNull(); // guard against repeated concurrent calls to restart
if (h != null)
h.cleanUp();
diff --git a/core/src/main/java/hudson/model/AbstractProject.java b/core/src/main/java/hudson/model/AbstractProject.java
index f1d4a15d68..d8b4851da9 100644
--- a/core/src/main/java/hudson/model/AbstractProject.java
+++ b/core/src/main/java/hudson/model/AbstractProject.java
@@ -2078,9 +2078,6 @@ public abstract class AbstractProject
,R extends A
Messages.AbstractProject_AssignedLabelString_InvalidBooleanExpression(e.getMessage()));
}
Jenkins j = Jenkins.getInstance();
- if (j == null) {
- return FormValidation.ok(); // ?
- }
Label l = j.getLabel(value);
if (l.isEmpty()) {
for (LabelAtom a : l.listAtoms()) {
diff --git a/core/src/main/java/hudson/model/Cause.java b/core/src/main/java/hudson/model/Cause.java
index 2ee6ba7982..bca7831012 100644
--- a/core/src/main/java/hudson/model/Cause.java
+++ b/core/src/main/java/hudson/model/Cause.java
@@ -37,6 +37,7 @@ import com.thoughtworks.xstream.converters.UnmarshallingContext;
import hudson.Util;
import java.io.IOException;
import java.util.HashSet;
+import java.util.Objects;
import java.util.Set;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
@@ -212,12 +213,10 @@ public abstract class Cause {
final UpstreamCause o = (UpstreamCause) rhs;
- if (upstreamBuild != o.upstreamBuild) return false;
- if (!upstreamCauses.equals(o.upstreamCauses)) return false;
- if (upstreamUrl == null ? o.upstreamUrl != null : !upstreamUrl.equals(o.upstreamUrl)) return false;
- if (upstreamProject == null ? o.upstreamProject != null : !upstreamProject.equals(o.upstreamProject)) return false;
-
- return true;
+ return Objects.equals(upstreamBuild, o.upstreamBuild) &&
+ Objects.equals(upstreamCauses, o.upstreamCauses) &&
+ Objects.equals(upstreamUrl, o.upstreamUrl) &&
+ Objects.equals(upstreamProject, o.upstreamProject);
}
/**
@@ -225,12 +224,7 @@ public abstract class Cause {
*/
@Override
public int hashCode() {
-
- int hashCode = 17;
- hashCode = hashCode * 31 + upstreamCauses.hashCode();
- hashCode = hashCode * 31 + upstreamBuild;
- hashCode = hashCode * 31 + (upstreamUrl == null ? 0 : upstreamUrl.hashCode ());
- return hashCode * 31 + (upstreamProject == null ? 0 : upstreamProject.hashCode ());
+ return Objects.hash(upstreamCauses, upstreamBuild, upstreamUrl, upstreamProject);
}
private @Nonnull Cause trim(@Nonnull Cause c, int depth, Set traversed) {
@@ -430,13 +424,12 @@ public abstract class Cause {
@Override
public boolean equals(Object o) {
- return o instanceof UserIdCause && Arrays.equals(new Object[]{userId},
- new Object[]{((UserIdCause) o).userId});
+ return o instanceof UserIdCause && Objects.equals(userId, ((UserIdCause) o).userId);
}
@Override
public int hashCode() {
- return 295 + (this.userId != null ? this.userId.hashCode() : 0);
+ return Objects.hash(userId);
}
}
@@ -473,16 +466,12 @@ public abstract class Cause {
@Override
public boolean equals(Object o) {
- return o instanceof RemoteCause && Arrays.equals(new Object[] {addr, note},
- new Object[] {((RemoteCause)o).addr, ((RemoteCause)o).note});
+ return o instanceof RemoteCause && Objects.equals(addr, ((RemoteCause) o).addr) && Objects.equals(note, ((RemoteCause) o).note);
}
@Override
public int hashCode() {
- int hash = 5;
- hash = 83 * hash + (this.addr != null ? this.addr.hashCode() : 0);
- hash = 83 * hash + (this.note != null ? this.note.hashCode() : 0);
- return hash;
+ return Objects.hash(addr, note);
}
}
}
diff --git a/core/src/main/java/hudson/model/CauseAction.java b/core/src/main/java/hudson/model/CauseAction.java
index 252c91c722..09e5b03274 100644
--- a/core/src/main/java/hudson/model/CauseAction.java
+++ b/core/src/main/java/hudson/model/CauseAction.java
@@ -34,6 +34,7 @@ import com.thoughtworks.xstream.converters.UnmarshallingContext;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -47,35 +48,55 @@ public class CauseAction implements FoldableAction, RunAction2 {
@Deprecated
// there can be multiple causes, so this is deprecated
private transient Cause cause;
-
- private List causes = new ArrayList();
+
+ /** @deprecated JENKINS-33467 inefficient */
+ @Deprecated
+ private transient List causes;
+
+ private Map causeBag = new LinkedHashMap<>();
public CauseAction(Cause c) {
- this.causes.add(c);
+ this.causeBag.put(c, 1);
}
+ private void addCause(Cause c) {
+ synchronized (causeBag) {
+ Integer cnt = causeBag.get(c);
+ causeBag.put(c, cnt == null ? 1 : cnt + 1);
+ }
+ }
+ private void addCauses(Collection extends Cause> causes) {
+ for (Cause cause : causes) {
+ addCause(cause);
+ }
+ }
+
public CauseAction(Cause... c) {
this(Arrays.asList(c));
}
public CauseAction(Collection extends Cause> causes) {
- this.causes.addAll(causes);
+ addCauses(causes);
}
public CauseAction(CauseAction ca) {
- this.causes.addAll(ca.causes);
+ addCauses(ca.getCauses());
}
@Exported(visibility=2)
public List getCauses() {
- return causes;
+ List r = new ArrayList<>();
+ for (Map.Entry entry : causeBag.entrySet()) {
+ r.addAll(Collections.nCopies(entry.getValue(), entry.getKey()));
+ }
+ return r;
}
/**
* Finds the cause of the specific type.
*/
public T findCause(Class type) {
- for (Cause c : causes)
+ for (Cause c : causeBag.keySet())
if (type.isInstance(c))
return type.cast(c);
return null;
@@ -99,14 +120,7 @@ public class CauseAction implements FoldableAction, RunAction2 {
* @return Map of Cause to number of occurrences of that Cause
*/
public Map getCauseCounts() {
- Map result = new LinkedHashMap();
- for (Cause c : causes) {
- if (c != null) {
- Integer i = result.get(c);
- result.put(c, i == null ? 1 : i.intValue() + 1);
- }
- }
- return result;
+ return Collections.unmodifiableMap(causeBag);
}
/**
@@ -115,12 +129,14 @@ public class CauseAction implements FoldableAction, RunAction2 {
*/
@Deprecated
public String getShortDescription() {
- if(causes.isEmpty()) return "N/A";
- return causes.get(0).getShortDescription();
+ if (causeBag.isEmpty()) {
+ return "N/A";
+ }
+ return causeBag.keySet().iterator().next().getShortDescription();
}
@Override public void onLoad(Run,?> owner) {
- for (Cause c : causes) {
+ for (Cause c : causeBag.keySet()) {
if (c != null) {
c.onLoad(owner);
}
@@ -131,7 +147,7 @@ public class CauseAction implements FoldableAction, RunAction2 {
* When hooked up to build, notify {@link Cause}s.
*/
@Override public void onAttached(Run,?> owner) {
- for (Cause c : causes) {
+ for (Cause c : causeBag.keySet()) {
if (c != null) {
c.onAddedTo(owner);
}
@@ -141,7 +157,7 @@ public class CauseAction implements FoldableAction, RunAction2 {
public void foldIntoExisting(hudson.model.Queue.Item item, Task owner, List otherActions) {
CauseAction existing = item.getAction(CauseAction.class);
if (existing!=null) {
- existing.causes.addAll(this.causes);
+ existing.addCauses(getCauses());
return;
}
// no CauseAction found, so add a copy of this one
@@ -153,9 +169,19 @@ public class CauseAction implements FoldableAction, RunAction2 {
@Override protected void callback(CauseAction ca, UnmarshallingContext context) {
// if we are being read in from an older version
if (ca.cause != null) {
- if (ca.causes == null) ca.causes = new ArrayList();
- ca.causes.add(ca.cause);
+ if (ca.causeBag == null) {
+ ca.causeBag = new LinkedHashMap<>();
+ }
+ ca.addCause(ca.cause);
OldDataMonitor.report(context, "1.288");
+ ca.cause = null;
+ } else if (ca.causes != null) {
+ if (ca.causeBag == null) {
+ ca.causeBag = new LinkedHashMap<>();
+ }
+ ca.addCauses(ca.causes);
+ OldDataMonitor.report(context, "1.653");
+ ca.causes = null;
}
}
}
diff --git a/core/src/main/java/hudson/model/Computer.java b/core/src/main/java/hudson/model/Computer.java
index 3eedf30bd3..eebd99f3a5 100644
--- a/core/src/main/java/hudson/model/Computer.java
+++ b/core/src/main/java/hudson/model/Computer.java
@@ -564,8 +564,9 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
* null if the configuration has changed and the node is removed, yet the corresponding {@link Computer}
* is not yet gone.
*/
- public @CheckForNull Node getNode() {
- Jenkins j = Jenkins.getInstance();
+ @CheckForNull
+ public Node getNode() {
+ Jenkins j = Jenkins.getInstanceOrNull(); // TODO confirm safe to assume non-null and use getInstance()
if (j == null) {
return null;
}
@@ -1011,8 +1012,8 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
executors.remove(e);
addNewExecutorIfNecessary();
if (!isAlive()) {
- AbstractCIBase ciBase = Jenkins.getInstance();
- if (ciBase != null) {
+ AbstractCIBase ciBase = Jenkins.getInstanceOrNull();
+ if (ciBase != null) { // TODO confirm safe to assume non-null and use getInstance()
ciBase.removeComputer(Computer.this);
}
}
@@ -1701,7 +1702,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
*/
public static final Permission EXTENDED_READ = new Permission(PERMISSIONS,"ExtendedRead", Messages._Computer_ExtendedReadPermission_Description(), CONFIGURE, Boolean.getBoolean("hudson.security.ExtendedReadPermission"), new PermissionScope[]{PermissionScope.COMPUTER});
public static final Permission DELETE = new Permission(PERMISSIONS,"Delete", Messages._Computer_DeletePermission_Description(), Permission.DELETE, PermissionScope.COMPUTER);
- public static final Permission CREATE = new Permission(PERMISSIONS,"Create", Messages._Computer_CreatePermission_Description(), Permission.CREATE, PermissionScope.COMPUTER);
+ public static final Permission CREATE = new Permission(PERMISSIONS,"Create", Messages._Computer_CreatePermission_Description(), Permission.CREATE, PermissionScope.JENKINS);
public static final Permission DISCONNECT = new Permission(PERMISSIONS,"Disconnect", Messages._Computer_DisconnectPermission_Description(), Jenkins.ADMINISTER, PermissionScope.COMPUTER);
public static final Permission CONNECT = new Permission(PERMISSIONS,"Connect", Messages._Computer_ConnectPermission_Description(), DISCONNECT, PermissionScope.COMPUTER);
public static final Permission BUILD = new Permission(PERMISSIONS, "Build", Messages._Computer_BuildPermission_Description(), Permission.WRITE, PermissionScope.COMPUTER);
diff --git a/core/src/main/java/hudson/model/Executor.java b/core/src/main/java/hudson/model/Executor.java
index 817166c60e..06e9453fd0 100644
--- a/core/src/main/java/hudson/model/Executor.java
+++ b/core/src/main/java/hudson/model/Executor.java
@@ -958,8 +958,9 @@ public class Executor extends Thread implements ModelObject {
* or null if it could not be found (for example because the execution has already completed)
* @since 1.607
*/
- public static @CheckForNull Executor of(Executable executable) {
- Jenkins jenkins = Jenkins.getInstance();
+ @CheckForNull
+ public static Executor of(Executable executable) {
+ Jenkins jenkins = Jenkins.getInstanceOrNull(); // TODO confirm safe to assume non-null and use getInstance()
if (jenkins == null) {
return null;
}
diff --git a/core/src/main/java/hudson/model/Fingerprint.java b/core/src/main/java/hudson/model/Fingerprint.java
index 8cb6347168..9c4a6cf2a3 100644
--- a/core/src/main/java/hudson/model/Fingerprint.java
+++ b/core/src/main/java/hudson/model/Fingerprint.java
@@ -50,6 +50,7 @@ import java.io.EOFException;
import jenkins.model.FingerprintFacet;
import jenkins.model.Jenkins;
import jenkins.model.TransientFingerprintFacetFactory;
+import org.apache.commons.lang.StringUtils;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;
@@ -129,7 +130,7 @@ public class Fingerprint implements ModelObject, Saveable {
// We expose the data to Jenkins administrators in order to
// let them manage the data for deleted jobs (also works for SYSTEM)
final Jenkins instance = Jenkins.getInstance();
- if (instance != null && instance.hasPermission(Jenkins.ADMINISTER)) {
+ if (instance.hasPermission(Jenkins.ADMINISTER)) {
return true;
}
@@ -687,21 +688,77 @@ public class Fingerprint implements ModelObject, Saveable {
*/
public static RangeSet fromString(String list, boolean skipError) {
RangeSet rs = new RangeSet();
- for (String s : Util.tokenize(list,",")) {
+
+ // Reject malformed ranges like "1---10", "1,,,,3" etc.
+ if (list.contains("--") || list.contains(",,")) {
+ if (!skipError) {
+ throw new IllegalArgumentException(
+ String.format("Unable to parse '%s', expected correct notation M,N or M-N", list));
+ }
+ // ignore malformed notation
+ return rs;
+ }
+
+ String[] items = Util.tokenize(list,",");
+ if(items.length > 1 && items.length <= StringUtils.countMatches(list, ",")) {
+ if (!skipError) {
+ throw new IllegalArgumentException(
+ String.format("Unable to parse '%s', expected correct notation M,N or M-N", list));
+ }
+ // ignore malformed notation like ",1,2" or "1,2,"
+ return rs;
+ }
+
+ for (String s : items) {
s = s.trim();
// s is either single number or range "x-y".
// note that the end range is inclusive in this notation, but not in the Range class
try {
+ if (s.isEmpty()) {
+ if (!skipError) {
+ throw new IllegalArgumentException(
+ String.format("Unable to parse '%s', expected number", list)); }
+ // ignore "" element
+ continue;
+ }
+
if(s.contains("-")) {
+ if(StringUtils.countMatches(s, "-") > 1) {
+ if (!skipError) {
+ throw new IllegalArgumentException(String.format(
+ "Unable to parse '%s', expected correct notation M,N or M-N", list));
+ }
+ // ignore malformed ranges like "-5-2" or "2-5-"
+ continue;
+ }
String[] tokens = Util.tokenize(s,"-");
if (tokens.length == 2) {
- rs.ranges.add(new Range(Integer.parseInt(tokens[0]), Integer.parseInt(tokens[1]) + 1));
+ int left = Integer.parseInt(tokens[0]);
+ int right = Integer.parseInt(tokens[1]);
+ if(left < 0 || right < 0) {
+ if (!skipError) {
+ throw new IllegalArgumentException(
+ String.format("Unable to parse '%s', expected number above zero", list));
+ }
+ // ignore a range which starts or ends under zero like "-5-3"
+ continue;
+ }
+ if(left > right) {
+ if (!skipError) {
+ throw new IllegalArgumentException(String.format(
+ "Unable to parse '%s', expected string with a range M-N where M _getUsages() {
List r = new ArrayList();
final Jenkins instance = Jenkins.getInstance();
- if (instance == null) {
- return r;
- }
-
for (Entry e : usages.entrySet()) {
final String itemName = e.getKey();
if (instance.hasPermission(Jenkins.ADMINISTER) || canDiscoverItem(itemName)) {
@@ -1369,10 +1422,7 @@ public class Fingerprint implements ModelObject, Saveable {
*/
private static boolean canDiscoverItem(@Nonnull final String fullName) {
final Jenkins jenkins = Jenkins.getInstance();
- if (jenkins == null) {
- return false;
- }
-
+
// Fast check to avoid security context switches
Item item = null;
try {
diff --git a/core/src/main/java/hudson/model/Hudson.java b/core/src/main/java/hudson/model/Hudson.java
index 4234697923..02b9d949a8 100644
--- a/core/src/main/java/hudson/model/Hudson.java
+++ b/core/src/main/java/hudson/model/Hudson.java
@@ -34,6 +34,7 @@ import hudson.model.listeners.ItemListener;
import hudson.slaves.ComputerListener;
import hudson.util.CopyOnWriteList;
import hudson.util.FormValidation;
+import javax.annotation.Nonnull;
import jenkins.model.Jenkins;
import org.jvnet.hudson.reactor.ReactorException;
import org.kohsuke.stapler.QueryParameter;
@@ -71,7 +72,8 @@ public class Hudson extends Jenkins {
/** @deprecated Here only for compatibility. Use {@link Jenkins#getInstance} instead. */
@Deprecated
@CLIResolver
- public static @CheckForNull Hudson getInstance() {
+ @Nonnull
+ public static Hudson getInstance() {
return (Hudson)Jenkins.getInstance();
}
diff --git a/core/src/main/java/hudson/model/Job.java b/core/src/main/java/hudson/model/Job.java
index d60f144d55..263787ffcb 100644
--- a/core/src/main/java/hudson/model/Job.java
+++ b/core/src/main/java/hudson/model/Job.java
@@ -848,7 +848,10 @@ public abstract class Job, RunT extends Run V withLock(hudson.remoting.Callable callable) throws T {
- final Jenkins jenkins = Jenkins.getInstance();
+ final Jenkins jenkins = Jenkins.getInstanceOrNull();
+ // TODO confirm safe to assume non-null and use getInstance()
final Queue queue = jenkins == null ? null : jenkins.getQueue();
if (queue == null) {
return callable.call();
@@ -1222,7 +1223,8 @@ public class Queue extends ResourceController implements Saveable {
* @since 1.592
*/
public static V withLock(java.util.concurrent.Callable callable) throws Exception {
- final Jenkins jenkins = Jenkins.getInstance();
+ final Jenkins jenkins = Jenkins.getInstanceOrNull();
+ // TODO confirm safe to assume non-null and use getInstance()
final Queue queue = jenkins == null ? null : jenkins.getQueue();
if (queue == null) {
return callable.call();
@@ -1239,7 +1241,8 @@ public class Queue extends ResourceController implements Saveable {
* @since 1.618
*/
public static boolean tryWithLock(Runnable runnable) {
- final Jenkins jenkins = Jenkins.getInstance();
+ final Jenkins jenkins = Jenkins.getInstanceOrNull();
+ // TODO confirm safe to assume non-null and use getInstance()
final Queue queue = jenkins == null ? null : jenkins.getQueue();
if (queue == null) {
runnable.run();
@@ -1255,7 +1258,8 @@ public class Queue extends ResourceController implements Saveable {
* @since 1.618
*/
public static Runnable wrapWithLock(Runnable runnable) {
- final Jenkins jenkins = Jenkins.getInstance();
+ final Jenkins jenkins = Jenkins.getInstanceOrNull();
+ // TODO confirm safe to assume non-null and use getInstance()
final Queue queue = jenkins == null ? null : jenkins.getQueue();
return queue == null ? runnable : new LockedRunnable(runnable);
}
@@ -1267,7 +1271,8 @@ public class Queue extends ResourceController implements Saveable {
* @since 1.618
*/
public static hudson.remoting.Callable wrapWithLock(hudson.remoting.Callable callable) {
- final Jenkins jenkins = Jenkins.getInstance();
+ final Jenkins jenkins = Jenkins.getInstanceOrNull();
+ // TODO confirm safe to assume non-null and use getInstance()
final Queue queue = jenkins == null ? null : jenkins.getQueue();
return queue == null ? callable : new LockedHRCallable<>(callable);
}
@@ -1279,7 +1284,8 @@ public class Queue extends ResourceController implements Saveable {
* @since 1.618
*/
public static java.util.concurrent.Callable wrapWithLock(java.util.concurrent.Callable callable) {
- final Jenkins jenkins = Jenkins.getInstance();
+ final Jenkins jenkins = Jenkins.getInstanceOrNull();
+ // TODO confirm safe to assume non-null and use getInstance()
final Queue queue = jenkins == null ? null : jenkins.getQueue();
return queue == null ? callable : new LockedJUCCallable(callable);
}
diff --git a/core/src/main/java/hudson/model/Run.java b/core/src/main/java/hudson/model/Run.java
index 229fb82806..7c5aba4be5 100644
--- a/core/src/main/java/hudson/model/Run.java
+++ b/core/src/main/java/hudson/model/Run.java
@@ -2276,9 +2276,6 @@ public abstract class Run ,RunT extends Run job = j.getItemByFullName(jobName, Job.class);
if (job == null) {
return null;
diff --git a/core/src/main/java/hudson/model/UpdateSite.java b/core/src/main/java/hudson/model/UpdateSite.java
index 09c38fb6e7..b6acc6202b 100644
--- a/core/src/main/java/hudson/model/UpdateSite.java
+++ b/core/src/main/java/hudson/model/UpdateSite.java
@@ -25,6 +25,7 @@
package hudson.model;
+import hudson.ClassicPluginStrategy;
import hudson.PluginManager;
import hudson.PluginWrapper;
import hudson.Util;
@@ -467,7 +468,17 @@ public class UpdateSite {
core = null;
}
for(Map.Entry e : (Set>)o.getJSONObject("plugins").entrySet()) {
- plugins.put(e.getKey(),new Plugin(sourceId, e.getValue()));
+ Plugin p = new Plugin(sourceId, e.getValue());
+ // JENKINS-33308 - include implied dependencies for older plugins that may need them
+ List implicitDeps = ClassicPluginStrategy.getImpliedDependencies(p.name, p.requiredCore);
+ if(!implicitDeps.isEmpty()) {
+ for(PluginWrapper.Dependency dep : implicitDeps) {
+ if(!p.dependencies.containsKey(dep.shortName)) {
+ p.dependencies.put(dep.shortName, dep.version);
+ }
+ }
+ }
+ plugins.put(e.getKey(), p);
}
connectionCheckUrl = (String)o.get("connectionCheckUrl");
@@ -612,7 +623,7 @@ public class UpdateSite {
public final String[] categories;
/**
- * Dependencies of this plugin.
+ * Dependencies of this plugin, a name -> version mapping.
*/
@Exported
public final Map dependencies = new HashMap();
@@ -634,10 +645,8 @@ public class UpdateSite {
this.categories = o.has("labels") ? (String[])o.getJSONArray("labels").toArray(new String[0]) : null;
for(Object jo : o.getJSONArray("dependencies")) {
JSONObject depObj = (JSONObject) jo;
- // Make sure there's a name attribute, that that name isn't maven-plugin - we ignore that one -
- // and that the optional value isn't true.
- if (get(depObj,"name")!=null
- && !get(depObj,"name").equals("maven-plugin")) {
+ // Make sure there's a name attribute and that the optional value isn't true.
+ if (get(depObj,"name")!=null) {
if (get(depObj, "optional").equals("false")) {
dependencies.put(get(depObj, "name"), get(depObj, "version"));
} else {
diff --git a/core/src/main/java/hudson/model/UsageStatistics.java b/core/src/main/java/hudson/model/UsageStatistics.java
index b6d2af2496..4d2cde5411 100644
--- a/core/src/main/java/hudson/model/UsageStatistics.java
+++ b/core/src/main/java/hudson/model/UsageStatistics.java
@@ -31,7 +31,6 @@ import hudson.node_monitors.ArchitectureMonitor.DescriptorImpl;
import hudson.util.IOUtils;
import hudson.util.Secret;
import static hudson.util.TimeUnit2.DAYS;
-import static hudson.init.InitMilestone.COMPLETED;
import jenkins.model.Jenkins;
import net.sf.json.JSONObject;
@@ -96,12 +95,9 @@ public class UsageStatistics extends PageDecorator {
* Returns true if it's time for us to check for new version.
*/
public boolean isDue() {
- final Jenkins j = Jenkins.getInstance();
- // user opted out or Jenkins not fully initialized. no data collection.
- if (j == null || j.isUsageStatisticsCollected() || DISABLED || COMPLETED.compareTo(j.getInitLevel()) > 0) {
- return false;
- }
-
+ // user opted out. no data collection.
+ if(!Jenkins.getInstance().isUsageStatisticsCollected() || DISABLED) return false;
+
long now = System.currentTimeMillis();
if(now - lastAttempt > DAY) {
lastAttempt = now;
diff --git a/core/src/main/java/hudson/model/User.java b/core/src/main/java/hudson/model/User.java
index f2ef1d857e..0ff29ea78d 100644
--- a/core/src/main/java/hudson/model/User.java
+++ b/core/src/main/java/hudson/model/User.java
@@ -152,9 +152,6 @@ public class User extends AbstractModelObject implements AccessControlled, Descr
@Nonnull
public static IdStrategy idStrategy() {
Jenkins j = Jenkins.getInstance();
- if (j == null) {
- return IdStrategy.CASE_INSENSITIVE;
- }
SecurityRealm realm = j.getSecurityRealm();
if (realm == null) {
return IdStrategy.CASE_INSENSITIVE;
diff --git a/core/src/main/java/hudson/model/listeners/RunListener.java b/core/src/main/java/hudson/model/listeners/RunListener.java
index e65347bc02..6fd2172ba9 100644
--- a/core/src/main/java/hudson/model/listeners/RunListener.java
+++ b/core/src/main/java/hudson/model/listeners/RunListener.java
@@ -28,6 +28,7 @@ import hudson.ExtensionListView;
import hudson.Extension;
import hudson.ExtensionList;
import hudson.FilePath;
+import hudson.Functions;
import hudson.Launcher;
import hudson.model.AbstractBuild;
import hudson.model.BuildListener;
@@ -223,7 +224,7 @@ public abstract class RunListener implements ExtensionPoint {
* Fires the {@link #onFinalized(Run)} event.
*/
public static void fireFinalized(Run r) {
- if (Jenkins.getInstance() == null) {
+ if (Jenkins.getInstanceOrNull() == null) { // TODO use !Functions.isExtensionsAvailable() once JENKINS-33377
return;
}
for (RunListener l : all()) {
diff --git a/core/src/main/java/hudson/model/listeners/SCMListener.java b/core/src/main/java/hudson/model/listeners/SCMListener.java
index 3f570aa24f..161fb0f603 100644
--- a/core/src/main/java/hudson/model/listeners/SCMListener.java
+++ b/core/src/main/java/hudson/model/listeners/SCMListener.java
@@ -126,8 +126,8 @@ public abstract class SCMListener implements ExtensionPoint {
*/
@SuppressWarnings("deprecation")
public static Collection extends SCMListener> all() {
- Jenkins j = Jenkins.getInstance();
- if (j == null) {
+ Jenkins j = Jenkins.getInstanceOrNull();
+ if (j == null) { // TODO use !Functions.isExtensionsAvailable() once JENKINS-33377
return Collections.emptySet();
}
List r = new ArrayList(j.getExtensionList(SCMListener.class));
@@ -140,20 +140,12 @@ public abstract class SCMListener implements ExtensionPoint {
/** @deprecated Use {@link Extension} instead. */
@Deprecated
public final void register() {
- Jenkins j = Jenkins.getInstance();
- if (j != null) {
- j.getSCMListeners().add(this);
- }
+ Jenkins.getInstance().getSCMListeners().add(this);
}
/** @deprecated Use {@link Extension} instead. */
@Deprecated
public final boolean unregister() {
- Jenkins j = Jenkins.getInstance();
- if (j != null) {
- return j.getSCMListeners().remove(this);
- } else {
- return false;
- }
+ return Jenkins.getInstance().getSCMListeners().remove(this);
}
}
diff --git a/core/src/main/java/hudson/model/queue/WorkUnit.java b/core/src/main/java/hudson/model/queue/WorkUnit.java
index 58c4ba31ca..68a3d7fce7 100644
--- a/core/src/main/java/hudson/model/queue/WorkUnit.java
+++ b/core/src/main/java/hudson/model/queue/WorkUnit.java
@@ -71,6 +71,9 @@ public final class WorkUnit {
public void setExecutor(@CheckForNull Executor e) {
executor = e;
+ if (e != null) {
+ context.future.addExecutor(e);
+ }
}
/**
diff --git a/core/src/main/java/hudson/model/queue/WorkUnitContext.java b/core/src/main/java/hudson/model/queue/WorkUnitContext.java
index 503a177491..7c030234aa 100644
--- a/core/src/main/java/hudson/model/queue/WorkUnitContext.java
+++ b/core/src/main/java/hudson/model/queue/WorkUnitContext.java
@@ -88,14 +88,9 @@ public final class WorkUnitContext {
}
/**
- * Called by the executor that executes a member {@link SubTask} that belongs to this task
- * to create its {@link WorkUnit}.
+ * Called within the queue maintenance process to create a {@link WorkUnit} for the given {@link SubTask}
*/
public WorkUnit createWorkUnit(SubTask execUnit) {
- Executor executor = Executor.currentExecutor();
- if (executor != null) { // TODO is it legal for this to be called by a non-executor thread?
- future.addExecutor(executor);
- }
WorkUnit wu = new WorkUnit(this, execUnit);
workUnits.add(wu);
return wu;
diff --git a/core/src/main/java/hudson/security/HudsonFilter.java b/core/src/main/java/hudson/security/HudsonFilter.java
index 3a1868344a..b37b2ea4ea 100644
--- a/core/src/main/java/hudson/security/HudsonFilter.java
+++ b/core/src/main/java/hudson/security/HudsonFilter.java
@@ -104,7 +104,7 @@ public class HudsonFilter implements Filter {
// this is how we make us available to the rest of Hudson.
filterConfig.getServletContext().setAttribute(HudsonFilter.class.getName(),this);
try {
- Jenkins hudson = Jenkins.getInstance();
+ Jenkins hudson = Jenkins.getInstanceOrNull();
if (hudson != null) {
// looks like we are initialized after Hudson came into being. initialize it now. See #3069
LOGGER.fine("Security wasn't initialized; Initializing it...");
diff --git a/core/src/main/java/hudson/security/HudsonPrivateSecurityRealm.java b/core/src/main/java/hudson/security/HudsonPrivateSecurityRealm.java
index fbcc62fcbe..9288ba018f 100644
--- a/core/src/main/java/hudson/security/HudsonPrivateSecurityRealm.java
+++ b/core/src/main/java/hudson/security/HudsonPrivateSecurityRealm.java
@@ -82,7 +82,6 @@ import java.util.Collections;
import java.util.List;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
-import java.util.logging.Level;
import java.util.logging.Logger;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
@@ -359,7 +358,7 @@ public class HudsonPrivateSecurityRealm extends AbstractPasswordBasedSecurityRea
if(si.fullname==null || si.fullname.length()==0)
si.fullname = si.username;
- if(si.email==null || !si.email.contains("@"))
+ if(isMailerPluginPresent() && (si.email==null || !si.email.contains("@")))
si.errorMessage = Messages.HudsonPrivateSecurityRealm_CreateAccount_InvalidEmailAddress();
if (! User.isIdOrFullnameAllowed(si.username)) {
@@ -380,19 +379,30 @@ public class HudsonPrivateSecurityRealm extends AbstractPasswordBasedSecurityRea
// register the user
User user = createAccount(si.username,si.password1);
user.setFullName(si.fullname);
- try {
- // legacy hack. mail support has moved out to a separate plugin
- Class> up = Jenkins.getInstance().pluginManager.uberClassLoader.loadClass("hudson.tasks.Mailer$UserProperty");
- Constructor> c = up.getDeclaredConstructor(String.class);
- user.addProperty((UserProperty)c.newInstance(si.email));
- } catch (RuntimeException e) {
- throw e;
- } catch (Exception e) {
- LOGGER.log(Level.WARNING, "Failed to set the e-mail address",e);
+ if(isMailerPluginPresent()) {
+ try {
+ // legacy hack. mail support has moved out to a separate plugin
+ Class> up = Jenkins.getInstance().pluginManager.uberClassLoader.loadClass("hudson.tasks.Mailer$UserProperty");
+ Constructor> c = up.getDeclaredConstructor(String.class);
+ user.addProperty((UserProperty)c.newInstance(si.email));
+ } catch (ReflectiveOperationException e) {
+ throw new RuntimeException(e);
+ }
}
user.save();
return user;
}
+
+ @Restricted(NoExternalUse.class)
+ public boolean isMailerPluginPresent() {
+ try {
+ // mail support has moved to a separate plugin
+ return null != Jenkins.getInstance().pluginManager.uberClassLoader.loadClass("hudson.tasks.Mailer$UserProperty");
+ } catch (ClassNotFoundException e) {
+ LOGGER.finer("Mailer plugin not present");
+ }
+ return false;
+ }
/**
* Creates a new user account by registering a password to the user.
diff --git a/core/src/main/java/hudson/security/PermissionScope.java b/core/src/main/java/hudson/security/PermissionScope.java
index c647b33e2c..cf258d62ea 100644
--- a/core/src/main/java/hudson/security/PermissionScope.java
+++ b/core/src/main/java/hudson/security/PermissionScope.java
@@ -29,6 +29,8 @@ import hudson.model.Computer;
import hudson.model.Item;
import hudson.model.ItemGroup;
import hudson.model.Job;
+import hudson.model.ModelObject;
+import hudson.model.Node;
import hudson.model.Run;
import jenkins.model.Jenkins;
@@ -56,7 +58,7 @@ public final class PermissionScope {
/**
* Domain model type that approximates this scope.
*/
- public final Class modelClass;
+ public final Class extends ModelObject> modelClass;
/**
* Other bigger scopes that this scope divides. For example, permissions scoped to {@link ItemGroup}
@@ -65,7 +67,7 @@ public final class PermissionScope {
*/
private final Set containers;
- public PermissionScope(Class modelClass, PermissionScope... containers) {
+ public PermissionScope(Class extends ModelObject> modelClass, PermissionScope... containers) {
this.modelClass = modelClass;
this.containers = ImmutableSet.copyOf(containers);
}
@@ -110,7 +112,7 @@ public final class PermissionScope {
public static final PermissionScope RUN = new PermissionScope(Run.class,ITEM);
/**
- * Permissions scoped to current instances of {@link Computer}s.
+ * Permissions scoped to {@link Node}s or {@link Computer}s (generally interchangeably).
*/
public static final PermissionScope COMPUTER = new PermissionScope(Computer.class,JENKINS);
}
diff --git a/core/src/main/java/hudson/security/TokenBasedRememberMeServices2.java b/core/src/main/java/hudson/security/TokenBasedRememberMeServices2.java
index 6ae4d1c426..9b81ae5b19 100644
--- a/core/src/main/java/hudson/security/TokenBasedRememberMeServices2.java
+++ b/core/src/main/java/hudson/security/TokenBasedRememberMeServices2.java
@@ -93,7 +93,7 @@ public class TokenBasedRememberMeServices2 extends TokenBasedRememberMeServices
return;
}
- Jenkins j = Jenkins.getInstance();
+ Jenkins j = Jenkins.getInstanceOrNull();
if (j != null && j.isDisableRememberMe()) {
if (logger.isDebugEnabled()) {
logger.debug("Did not send remember-me cookie because 'Remember Me' is disabled in " +
diff --git a/core/src/main/java/hudson/security/csrf/CrumbFilter.java b/core/src/main/java/hudson/security/csrf/CrumbFilter.java
index 8e0fe64cb6..9080a54692 100644
--- a/core/src/main/java/hudson/security/csrf/CrumbFilter.java
+++ b/core/src/main/java/hudson/security/csrf/CrumbFilter.java
@@ -35,7 +35,7 @@ public class CrumbFilter implements Filter {
* we cannot use Hudson to the CrumbIssuer into CrumbFilter eagerly.
*/
public CrumbIssuer getCrumbIssuer() {
- Jenkins h = Jenkins.getInstance();
+ Jenkins h = Jenkins.getInstanceOrNull();
if(h==null) return null; // before Jenkins is initialized?
return h.getCrumbIssuer();
}
diff --git a/core/src/main/java/hudson/slaves/SlaveComputer.java b/core/src/main/java/hudson/slaves/SlaveComputer.java
index 23d4a6e872..ed846e66de 100644
--- a/core/src/main/java/hudson/slaves/SlaveComputer.java
+++ b/core/src/main/java/hudson/slaves/SlaveComputer.java
@@ -835,7 +835,7 @@ public class SlaveComputer extends Computer {
* @since 1.362
*/
public static VirtualChannel getChannelToMaster() {
- if (Jenkins.getInstance()!=null)
+ if (Jenkins.getInstanceOrNull()!=null) // check if calling thread is on master or on slave
return FilePath.localChannel;
// if this method is called from within the agent computation thread, this should work
diff --git a/core/src/main/java/hudson/tools/JDKInstaller.java b/core/src/main/java/hudson/tools/JDKInstaller.java
index d89c217875..8a42e7e1ca 100644
--- a/core/src/main/java/hudson/tools/JDKInstaller.java
+++ b/core/src/main/java/hudson/tools/JDKInstaller.java
@@ -449,8 +449,7 @@ public class JDKInstaller extends ToolInstaller {
HttpClient hc = new HttpClient();
hc.getParams().setParameter("http.useragent","Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US)");
- Jenkins j = Jenkins.getInstance();
- ProxyConfiguration jpc = j!=null ? j.proxy : null;
+ ProxyConfiguration jpc = Jenkins.getInstance().proxy;
if(jpc != null) {
hc.getHostConfiguration().setProxy(jpc.name, jpc.port);
if(jpc.getUserName() != null)
diff --git a/core/src/main/java/hudson/util/ArgumentListBuilder.java b/core/src/main/java/hudson/util/ArgumentListBuilder.java
index e9edd17b1e..255208737a 100644
--- a/core/src/main/java/hudson/util/ArgumentListBuilder.java
+++ b/core/src/main/java/hudson/util/ArgumentListBuilder.java
@@ -314,42 +314,46 @@ public class ArgumentListBuilder implements Serializable, Cloneable {
* @since 1.386
*/
public ArgumentListBuilder toWindowsCommand(boolean escapeVars) {
- StringBuilder quotedArgs = new StringBuilder();
+ ArgumentListBuilder windowsCommand = new ArgumentListBuilder().add("cmd.exe", "/C");
boolean quoted, percent;
- for (String arg : args) {
+ for (int i = 0; i < args.size(); i++) {
+ StringBuilder quotedArgs = new StringBuilder();
+ String arg = args.get(i);
quoted = percent = false;
- for (int i = 0; i < arg.length(); i++) {
- char c = arg.charAt(i);
+ for (int j = 0; j < arg.length(); j++) {
+ char c = arg.charAt(j);
if (!quoted && (c == ' ' || c == '*' || c == '?' || c == ',' || c == ';')) {
- quoted = startQuoting(quotedArgs, arg, i);
+ quoted = startQuoting(quotedArgs, arg, j);
}
else if (c == '^' || c == '&' || c == '<' || c == '>' || c == '|') {
- if (!quoted) quoted = startQuoting(quotedArgs, arg, i);
+ if (!quoted) quoted = startQuoting(quotedArgs, arg, j);
// quotedArgs.append('^'); See note in javadoc above
}
else if (c == '"') {
- if (!quoted) quoted = startQuoting(quotedArgs, arg, i);
+ if (!quoted) quoted = startQuoting(quotedArgs, arg, j);
quotedArgs.append('"');
}
else if (percent && escapeVars
&& ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'))) {
- if (!quoted) quoted = startQuoting(quotedArgs, arg, i);
+ if (!quoted) quoted = startQuoting(quotedArgs, arg, j);
quotedArgs.append('"').append(c);
c = '"';
}
percent = (c == '%');
if (quoted) quotedArgs.append(c);
}
+ if(i == 0 && quoted) quotedArgs.insert(0, '"'); else if (i == 0 && !quoted) quotedArgs.append('"');
if (quoted) quotedArgs.append('"'); else quotedArgs.append(arg);
- quotedArgs.append(' ');
+
+ windowsCommand.add(quotedArgs, mask.get(i));
}
// (comment copied from old code in hudson.tasks.Ant)
// on Windows, executing batch file can't return the correct error code,
// so we need to wrap it into cmd.exe.
// double %% is needed because we want ERRORLEVEL to be expanded after
// batch file executed, not before. This alone shows how broken Windows is...
- quotedArgs.append("&& exit %%ERRORLEVEL%%");
- return new ArgumentListBuilder().add("cmd.exe", "/C").addQuoted(quotedArgs.toString());
+ windowsCommand.add("&&").add("exit").add("%%ERRORLEVEL%%\"");
+ return windowsCommand;
}
/**
diff --git a/core/src/main/java/hudson/util/ConcurrentHashMapConverter.java b/core/src/main/java/hudson/util/ConcurrentHashMapConverter.java
deleted file mode 100644
index ae917d4996..0000000000
--- a/core/src/main/java/hudson/util/ConcurrentHashMapConverter.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * The MIT License
- *
- * Copyright (c) 2010, InfraDNA, 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.util;
-
-import com.thoughtworks.xstream.XStream;
-import com.thoughtworks.xstream.converters.UnmarshallingContext;
-import com.thoughtworks.xstream.converters.collections.MapConverter;
-import com.thoughtworks.xstream.converters.reflection.ReflectionProvider;
-import com.thoughtworks.xstream.converters.reflection.SerializableConverter;
-import com.thoughtworks.xstream.io.HierarchicalStreamReader;
-import com.thoughtworks.xstream.mapper.Mapper;
-
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * {@link ConcurrentHashMap} should convert like a map, instead of via serialization.
- *
- * @author Kohsuke Kawaguchi
- */
-public class ConcurrentHashMapConverter extends MapConverter {
- private final SerializableConverter sc;
-
- public ConcurrentHashMapConverter(XStream xs) {
- this(xs.getMapper(),xs.getReflectionProvider());
- }
-
- public ConcurrentHashMapConverter(Mapper mapper, ReflectionProvider reflectionProvider) {
- super(mapper);
- sc = new SerializableConverter(mapper,reflectionProvider);
- }
-
- @Override
- public boolean canConvert(Class type) {
- return type==ConcurrentHashMap.class;
- }
-
- @Override
- public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
- // ConcurrentHashMap used to serialize as custom serialization,
- // so read it in a compatible fashion.
- String s = reader.getAttribute("serialization");
- if(s!=null && s.equals("custom")) {
- return sc.unmarshal(reader,context);
- } else {
- return super.unmarshal(reader, context);
- }
- }
-}
diff --git a/core/src/main/java/hudson/util/PluginServletFilter.java b/core/src/main/java/hudson/util/PluginServletFilter.java
index 41d83899a5..06fd67a925 100644
--- a/core/src/main/java/hudson/util/PluginServletFilter.java
+++ b/core/src/main/java/hudson/util/PluginServletFilter.java
@@ -42,6 +42,8 @@ import java.util.Vector;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
+import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.NoExternalUse;
/**
* Servlet {@link Filter} that chains multiple {@link Filter}s, provided by plugins
@@ -93,7 +95,7 @@ public class PluginServletFilter implements Filter, ExtensionPoint {
}
public static void addFilter(Filter filter) throws ServletException {
- Jenkins j = Jenkins.getInstance();
+ Jenkins j = Jenkins.getInstanceOrNull();
PluginServletFilter container = null;
if(j != null) {
@@ -111,7 +113,7 @@ public class PluginServletFilter implements Filter, ExtensionPoint {
}
public static void removeFilter(Filter filter) throws ServletException {
- Jenkins j = Jenkins.getInstance();
+ Jenkins j = Jenkins.getInstanceOrNull();
if (j==null || getInstance(j.servletContext) == null) {
LEGACY.remove(filter);
} else {
@@ -142,5 +144,25 @@ public class PluginServletFilter implements Filter, ExtensionPoint {
list.clear();
}
+ @Restricted(NoExternalUse.class)
+ public static void cleanUp() {
+ PluginServletFilter instance = getInstance(Jenkins.getInstance().servletContext);
+ if (instance != null) {
+ for (Iterator iterator = instance.list.iterator(); iterator.hasNext(); ) {
+ Filter f = iterator.next();
+ try {
+ f.destroy();
+ } catch (RuntimeException e) {
+ LOGGER.log(Level.WARNING, "Filter " + f + " propagated an exception from its destroy method", e);
+ } catch (Error e) {
+ throw e; // we are not supposed to catch errors, don't log as could be an OOM
+ } catch (Throwable e) {
+ LOGGER.log(Level.SEVERE, "Filter " + f + " propagated an exception from its destroy method", e);
+ }
+ iterator.remove();
+ }
+ }
+ }
+
private static final Logger LOGGER = Logger.getLogger(PluginServletFilter.class.getName());
}
diff --git a/core/src/main/java/hudson/util/ProcessKillingVeto.java b/core/src/main/java/hudson/util/ProcessKillingVeto.java
index a410a610fd..0235494032 100644
--- a/core/src/main/java/hudson/util/ProcessKillingVeto.java
+++ b/core/src/main/java/hudson/util/ProcessKillingVeto.java
@@ -74,9 +74,10 @@ public abstract class ProcessKillingVeto implements ExtensionPoint {
* list if Jenkins is not available, never null.
*/
public static List all() {
- Jenkins jenkins = Jenkins.getInstance();
+ // check if we are a thread running on the master JVM or a thread running in a remote JVM
+ Jenkins jenkins = Jenkins.getInstanceOrNull();
if (jenkins == null)
- return Collections.emptyList();
+ return Collections.emptyList(); // we are remote, no body gets to veto
return jenkins.getExtensionList(ProcessKillingVeto.class);
}
diff --git a/core/src/main/java/hudson/util/SubClassGenerator.java b/core/src/main/java/hudson/util/SubClassGenerator.java
index f6076ddda1..0dbb8d6dec 100644
--- a/core/src/main/java/hudson/util/SubClassGenerator.java
+++ b/core/src/main/java/hudson/util/SubClassGenerator.java
@@ -76,7 +76,7 @@ public class SubClassGenerator extends ClassLoader {
Class extends T> c = defineClass(name, image, 0, image.length).asSubclass(base);
- Jenkins h = Jenkins.getInstance();
+ Jenkins h = Jenkins.getInstanceOrNull();
if (h!=null) // null only during tests.
((UberClassLoader)h.pluginManager.uberClassLoader).addNamedClass(name,c); // can't change the field type as it breaks binary compatibility
diff --git a/core/src/main/java/hudson/util/XStream2.java b/core/src/main/java/hudson/util/XStream2.java
index bfe924e70a..4f3c7ba613 100644
--- a/core/src/main/java/hudson/util/XStream2.java
+++ b/core/src/main/java/hudson/util/XStream2.java
@@ -106,7 +106,7 @@ public class XStream2 extends XStream {
public Object unmarshal(HierarchicalStreamReader reader, Object root, DataHolder dataHolder) {
// init() is too early to do this
// defensive because some use of XStream happens before plugins are initialized.
- Jenkins h = Jenkins.getInstance();
+ Jenkins h = Jenkins.getInstanceOrNull();
if(h!=null && h.pluginManager!=null && h.pluginManager.uberClassLoader!=null) {
setClassLoader(h.pluginManager.uberClassLoader);
}
@@ -151,7 +151,6 @@ public class XStream2 extends XStream {
registerConverter(new ImmutableSortedSetConverter(getMapper(),getReflectionProvider()),10);
registerConverter(new ImmutableSetConverter(getMapper(),getReflectionProvider()),10);
registerConverter(new ImmutableListConverter(getMapper(),getReflectionProvider()),10);
- registerConverter(new ConcurrentHashMapConverter(getMapper(),getReflectionProvider()),10);
registerConverter(new CopyOnWriteMap.Tree.ConverterImpl(getMapper()),10); // needs to override MapConverter
registerConverter(new DescribableList.ConverterImpl(getMapper()),10); // explicitly added to handle subtypes
registerConverter(new Label.ConverterImpl(),10);
@@ -422,7 +421,7 @@ public class XStream2 extends XStream {
return classOwnership.ownerOf(clazz);
}
if (pm == null) {
- Jenkins j = Jenkins.getInstance();
+ Jenkins j = Jenkins.getInstanceOrNull();
if (j != null) {
pm = j.getPluginManager();
}
diff --git a/core/src/main/java/hudson/util/io/ParserConfigurator.java b/core/src/main/java/hudson/util/io/ParserConfigurator.java
index bac60c7e79..878fba535b 100644
--- a/core/src/main/java/hudson/util/io/ParserConfigurator.java
+++ b/core/src/main/java/hudson/util/io/ParserConfigurator.java
@@ -75,7 +75,7 @@ public abstract class ParserConfigurator implements ExtensionPoint, Serializable
public static void applyConfiguration(SAXReader reader, Object context) throws IOException, InterruptedException {
Collection all = Collections.emptyList();
- if (Jenkins.getInstance()==null) {
+ if (Jenkins.getInstanceOrNull()==null) {
Channel ch = Channel.current();
if (ch!=null)
all = ch.call(new SlaveToMasterCallable, IOException>() {
diff --git a/core/src/main/java/jenkins/install/SetupWizard.java b/core/src/main/java/jenkins/install/SetupWizard.java
index b7e3f13a95..ea9a2e885f 100644
--- a/core/src/main/java/jenkins/install/SetupWizard.java
+++ b/core/src/main/java/jenkins/install/SetupWizard.java
@@ -1,7 +1,9 @@
package jenkins.install;
import java.io.IOException;
+import java.util.Iterator;
import java.util.Locale;
+import java.util.Map;
import java.util.UUID;
import java.util.logging.Logger;
@@ -14,27 +16,43 @@ import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
+import hudson.util.VersionNumber;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import hudson.BulkChange;
-import hudson.ExtensionList;
-import hudson.model.User;
-import hudson.model.UserProperty;
+import hudson.FilePath;
import hudson.security.FullControlOnceLoggedInAuthorizationStrategy;
import hudson.security.HudsonPrivateSecurityRealm;
-import hudson.security.PermissionAdder;
import hudson.security.SecurityRealm;
import hudson.security.csrf.DefaultCrumbIssuer;
import hudson.util.HttpResponses;
import hudson.util.PluginServletFilter;
import jenkins.model.Jenkins;
import jenkins.security.s2m.AdminWhitelistRule;
+import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.NoExternalUse;
+import org.kohsuke.stapler.HttpResponse;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import java.io.IOException;
+import java.util.Locale;
+import java.util.UUID;
+import java.util.logging.Logger;
/**
* A Jenkins instance used during first-run to provide a limited set of services while
* initial installation is in progress
+ *
+ * @since 2.0
*/
public class SetupWizard {
/**
@@ -44,62 +62,68 @@ public class SetupWizard {
private final Logger LOGGER = Logger.getLogger(SetupWizard.class.getName());
- public SetupWizard(Jenkins j) throws IOException {
- User admin;
+ private final Jenkins jenkins;
+
+ public SetupWizard(Jenkins j) throws IOException, InterruptedException {
+ this.jenkins = j;
// Create an admin user by default with a
// difficult password
+ FilePath iapf = getInitialAdminPasswordFile();
if(j.getSecurityRealm() == null || j.getSecurityRealm() == SecurityRealm.NO_AUTHENTICATION) { // this seems very fragile
BulkChange bc = new BulkChange(j);
-
- HudsonPrivateSecurityRealm securityRealm = new HudsonPrivateSecurityRealm(false, false, null);
- j.setSecurityRealm(securityRealm);
- String randomUUID = UUID.randomUUID().toString().replace("-", "").toLowerCase(Locale.ENGLISH);
- admin = securityRealm.createAccount(SetupWizard.initialSetupAdminUserName, randomUUID);
- admin.addProperty(new SetupWizard.AuthenticationKey(randomUUID));
-
- // Lock Jenkins down:
- FullControlOnceLoggedInAuthorizationStrategy authStrategy = new FullControlOnceLoggedInAuthorizationStrategy();
- authStrategy.setAllowAnonymousRead(false);
- j.setAuthorizationStrategy(authStrategy);
-
- // Shut down all the ports we can by default:
- j.setSlaveAgentPort(-1); // -1 to disable
-
- // require a crumb issuer
- j.setCrumbIssuer(new DefaultCrumbIssuer(false));
-
- // set master -> slave security:
- j.getInjector().getInstance(AdminWhitelistRule.class)
- .setMasterKillSwitch(false);
-
try{
+ HudsonPrivateSecurityRealm securityRealm = new HudsonPrivateSecurityRealm(false, false, null);
+ j.setSecurityRealm(securityRealm);
+ String randomUUID = UUID.randomUUID().toString().replace("-", "").toLowerCase(Locale.ENGLISH);
+
+ // create an admin user
+ securityRealm.createAccount(SetupWizard.initialSetupAdminUserName, randomUUID);
+
+ // JENKINS-33599 - write to a file in the jenkins home directory
+ // most native packages of Jenkins creates a machine user account 'jenkins' to run Jenkins,
+ // and use group 'jenkins' for admins. So we allo groups to read this file
+ iapf.write(randomUUID, "UTF-8");
+ iapf.chmod(0640);
+
+ // Lock Jenkins down:
+ FullControlOnceLoggedInAuthorizationStrategy authStrategy = new FullControlOnceLoggedInAuthorizationStrategy();
+ authStrategy.setAllowAnonymousRead(false);
+ j.setAuthorizationStrategy(authStrategy);
+
+ // Shut down all the ports we can by default:
+ j.setSlaveAgentPort(-1); // -1 to disable
+
+ // require a crumb issuer
+ j.setCrumbIssuer(new DefaultCrumbIssuer(false));
+
+ // set master -> slave security:
+ j.getInjector().getInstance(AdminWhitelistRule.class)
+ .setMasterKillSwitch(false);
+
j.save(); // !!
- } finally {
bc.commit();
+ } finally {
+ bc.abort();
}
}
- else {
- admin = j.getUser(SetupWizard.initialSetupAdminUserName);
- }
- String setupKey = null;
- if(admin != null && admin.getProperty(SetupWizard.AuthenticationKey.class) != null) {
- setupKey = admin.getProperty(SetupWizard.AuthenticationKey.class).getKey();
- }
- if(setupKey != null) {
- LOGGER.info("\n\n*************************************************************\n"
- + "*************************************************************\n"
- + "*************************************************************\n"
- + "\n"
- + "Jenkins initial setup is required. A security token is required to proceed. \n"
- + "Please use the following security token to proceed to installation: \n"
- + "\n"
- + "" + setupKey + "\n"
- + "\n"
- + "*************************************************************\n"
- + "*************************************************************\n"
- + "*************************************************************\n");
- }
+ String setupKey = iapf.readToString().trim();
+
+ LOGGER.info("\n\n*************************************************************\n"
+ + "*************************************************************\n"
+ + "*************************************************************\n"
+ + "\n"
+ + "Jenkins initial setup is required. An admin user has been created and"
+ + "a password generated. \n"
+ + "Please use the following password to proceed to installation: \n"
+ + "\n"
+ + "" + setupKey + "\n"
+ + "\n"
+ + "This may also be found at: " + iapf.getRemote() + "\n"
+ + "\n"
+ + "*************************************************************\n"
+ + "*************************************************************\n"
+ + "*************************************************************\n");
try {
PluginServletFilter.addFilter(FORCE_SETUP_WIZARD_FILTER);
@@ -108,37 +132,29 @@ public class SetupWizard {
}
}
+ /**
+ * Gets the file used to store the initial admin password
+ */
+ @Restricted(NoExternalUse.class) // use by Jelly
+ public FilePath getInitialAdminPasswordFile() {
+ return jenkins.getRootPath().child("secrets/initialAdminPassword");
+ }
+
/**
* Remove the setupWizard filter, ensure all updates are written to disk, etc
*/
- public HttpResponse doCompleteInstall(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
- Jenkins j = Jenkins.getActiveInstance();
- j.setInstallState(InstallState.INITIAL_SETUP_COMPLETED);
+ public HttpResponse doCompleteInstall() throws IOException, ServletException {
+ jenkins.setInstallState(InstallState.INITIAL_SETUP_COMPLETED);
InstallUtil.saveLastExecVersion();
PluginServletFilter.removeFilter(FORCE_SETUP_WIZARD_FILTER);
// Also, clean up the setup wizard if it's completed
- j.setSetupWizard(null);
- return HttpResponses.okJSON();
- }
+ jenkins.setSetupWizard(null);
- // Stores a user property for the authentication key, which is really the auto-generated user's password
- public static class AuthenticationKey extends UserProperty {
- String key;
-
- public AuthenticationKey() {
- }
-
- public AuthenticationKey(String key) {
- this.key = key;
- }
-
- public String getKey() {
- return key;
- }
-
- public void setKey(String key) {
- this.key = key;
- }
+ UpgradeWizard uw = jenkins.getInjector().getInstance(UpgradeWizard.class);
+ if (uw!=null)
+ uw.setCurrentLevel(new VersionNumber("2.0"));
+
+ return HttpResponses.okJSON();
}
/**
diff --git a/core/src/main/java/jenkins/install/UpgradeWizard.java b/core/src/main/java/jenkins/install/UpgradeWizard.java
new file mode 100644
index 0000000000..dd15d04ac9
--- /dev/null
+++ b/core/src/main/java/jenkins/install/UpgradeWizard.java
@@ -0,0 +1,147 @@
+package jenkins.install;
+
+import hudson.Extension;
+import hudson.model.PageDecorator;
+import hudson.model.UpdateSite.Plugin;
+import hudson.util.HttpResponses;
+import hudson.util.VersionNumber;
+import jenkins.model.Jenkins;
+import org.apache.commons.io.FileUtils;
+import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.NoExternalUse;
+import org.kohsuke.stapler.HttpResponse;
+import org.kohsuke.stapler.interceptor.RequirePOST;
+
+import javax.inject.Inject;
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Logger;
+
+import static java.util.logging.Level.*;
+import static org.apache.commons.io.FileUtils.*;
+import static org.apache.commons.lang.StringUtils.*;
+
+/**
+ * This is a stop-gap measure until JENKINS-33663 comes in.
+ * This call may go away. Please don't use it from outside.
+ *
+ * @author Kohsuke Kawaguchi
+ */
+@Restricted(NoExternalUse.class)
+@Extension
+public class UpgradeWizard extends PageDecorator {
+ @Inject
+ private Jenkins jenkins;
+
+ /**
+ * Is this instance fully upgraded?
+ */
+ private volatile boolean upToDate;
+
+ /**
+ * File that captures the state of upgrade.
+ *
+ * This file records the vesrion number that the installation has upgraded to.
+ */
+ /*package*/ File getStateFile() {
+ return new File(Jenkins.getInstance().getRootDir(),"upgraded");
+ }
+
+ public UpgradeWizard() throws IOException {
+ updateUpToDate();
+ }
+
+ private void updateUpToDate() throws IOException {
+ upToDate = new VersionNumber("2.0").compareTo(getCurrentLevel()) <= 0;
+ }
+
+ /**
+ * What is the version the upgrade wizard has run the last time and upgraded to?
+ */
+ private VersionNumber getCurrentLevel() throws IOException {
+ VersionNumber from = new VersionNumber("1.0");
+ File state = getStateFile();
+ if (state.exists()) {
+ from = new VersionNumber(defaultIfBlank(readFileToString(state), "1.0").trim());
+ }
+ return from;
+ }
+
+ /*package*/
+ public void setCurrentLevel(VersionNumber v) throws IOException {
+ FileUtils.writeStringToFile(getStateFile(), v.toString());
+ updateUpToDate();
+ }
+
+ /**
+ * Do we need to show the upgrade wizard prompt?
+ */
+ public boolean isDue() {
+ if (upToDate)
+ return false;
+
+ // only admin users should see this
+ if (!jenkins.hasPermission(Jenkins.ADMINISTER))
+ return false;
+
+ return System.currentTimeMillis() > getStateFile().lastModified();
+ }
+
+ /**
+ * Snooze the upgrade wizard notice.
+ */
+ @RequirePOST
+ public HttpResponse doSnooze() throws IOException {
+ jenkins.checkPermission(Jenkins.ADMINISTER);
+ File f = getStateFile();
+ FileUtils.touch(f);
+ f.setLastModified(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1));
+ LOGGER.log(FINE, "Snoozed the upgrade wizard notice");
+ return HttpResponses.redirectToContextRoot();
+ }
+
+ /**
+ * Performs the upgrade activity.
+ */
+ @RequirePOST
+ public HttpResponse doUpgrade() throws IOException {
+ jenkins.checkPermission(Jenkins.ADMINISTER);
+ try {
+ if (new VersionNumber("2.0").compareTo(getCurrentLevel())>0) {
+ // 1.0 -> 2.0 upgrade
+ LOGGER.log(WARNING, "Performing 1.0 to 2.0 upgrade");
+
+ for (String shortName : Arrays.asList("workflow-aggregator", "pipeline-stage-view", "github-organization-folder")) {
+ Plugin p = jenkins.getUpdateCenter().getPlugin(shortName);
+ if (p==null) {
+ LOGGER.warning("Plugin not found in the update center: " + shortName);
+ } else {
+ p.deploy(true);
+ }
+ }
+
+ // upgrade to 2.0 complete (if plugin installations fail, that's too bad)
+ FileUtils.writeStringToFile(getStateFile(),"2.0");
+
+ // send the user to the update center so that people can see the installation & restart if need be.
+ return HttpResponses.redirectViaContextPath("updateCenter/");
+ }
+
+// in the future...
+// if (new VersionNumber("3.0").compareTo(getCurrentLevel())>0) {
+//
+// }
+
+ return NOOP;
+ } finally {
+ updateUpToDate();
+ }
+ }
+
+ /*package*/ static final HttpResponse NOOP = HttpResponses.redirectToContextRoot();
+
+
+ private static final Logger LOGGER = Logger.getLogger(UpgradeWizard.class.getName());
+}
diff --git a/core/src/main/java/jenkins/model/Jenkins.java b/core/src/main/java/jenkins/model/Jenkins.java
index 0db1c5a2e0..2b315da5d6 100644
--- a/core/src/main/java/jenkins/model/Jenkins.java
+++ b/core/src/main/java/jenkins/model/Jenkins.java
@@ -59,6 +59,7 @@ import hudson.cli.declarative.CLIMethod;
import hudson.cli.declarative.CLIResolver;
import hudson.init.InitMilestone;
import hudson.init.InitStrategy;
+import hudson.init.TermMilestone;
import hudson.init.TerminatorFinder;
import hudson.lifecycle.Lifecycle;
import hudson.lifecycle.RestartNotSupportedException;
@@ -178,6 +179,7 @@ import hudson.util.JenkinsReloadFailed;
import hudson.util.Memoizer;
import hudson.util.MultipartFormDataParser;
import hudson.util.NamingThreadFactory;
+import hudson.util.PluginServletFilter;
import hudson.util.RemotingDiagnostics;
import hudson.util.RemotingDiagnostics.HeapDump;
import hudson.util.TextFile;
@@ -189,6 +191,8 @@ import hudson.views.DefaultViewsTabBar;
import hudson.views.MyViewsTabBar;
import hudson.views.ViewsTabBar;
import hudson.widgets.Widget;
+import java.util.TimerTask;
+import java.util.concurrent.CountDownLatch;
import jenkins.ExtensionComponentSet;
import jenkins.ExtensionRefreshException;
import jenkins.InitReactorRunner;
@@ -201,8 +205,10 @@ import jenkins.security.ConfidentialStore;
import jenkins.security.SecurityListener;
import jenkins.security.MasterToSlaveCallable;
import jenkins.slaves.WorkspaceLocator;
+import jenkins.util.JenkinsJVM;
import jenkins.util.Timer;
import jenkins.util.io.FileBoolean;
+import net.jcip.annotations.GuardedBy;
import net.sf.json.JSONObject;
import org.acegisecurity.AccessDeniedException;
import org.acegisecurity.AcegiSecurityException;
@@ -216,8 +222,10 @@ import org.apache.commons.jelly.JellyException;
import org.apache.commons.jelly.Script;
import org.apache.commons.logging.LogFactory;
import org.jvnet.hudson.reactor.Executable;
+import org.jvnet.hudson.reactor.Milestone;
import org.jvnet.hudson.reactor.Reactor;
import org.jvnet.hudson.reactor.ReactorException;
+import org.jvnet.hudson.reactor.ReactorListener;
import org.jvnet.hudson.reactor.Task;
import org.jvnet.hudson.reactor.TaskBuilder;
import org.jvnet.hudson.reactor.TaskGraphBuilder;
@@ -445,6 +453,8 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
private transient volatile boolean isQuietingDown;
private transient volatile boolean terminating;
+ @GuardedBy("Jenkins.class")
+ private transient boolean cleanUpStarted;
private volatile List jdks = new ArrayList();
@@ -703,12 +713,15 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
/**
* Gets the {@link Jenkins} singleton.
- * {@link #getInstance()} provides the unchecked versions of the method.
+ * {@link #getInstanceOrNull()} provides the unchecked versions of the method.
* @return {@link Jenkins} instance
* @throws IllegalStateException {@link Jenkins} has not been started, or was already shut down
* @since 1.590
+ * @deprecated use {@link #getInstance()}
*/
- public static @Nonnull Jenkins getActiveInstance() throws IllegalStateException {
+ @Deprecated
+ @Nonnull
+ public static Jenkins getActiveInstance() throws IllegalStateException {
Jenkins instance = HOLDER.getInstance();
if (instance == null) {
throw new IllegalStateException("Jenkins has not been started, or was already shut down");
@@ -721,10 +734,27 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
* {@link #getActiveInstance()} provides the checked versions of the method.
* @return The instance. Null if the {@link Jenkins} instance has not been started,
* or was already shut down
+ * @since 1.653
+ */
+ @CheckForNull
+ public static Jenkins getInstanceOrNull() {
+ return HOLDER.getInstance();
+ }
+
+ /**
+ * Gets the {@link Jenkins} singleton. In certain rare cases you may have code that is intended to run before
+ * Jenkins starts or while Jenkins is being shut-down. For those rare cases use {@link #getInstanceOrNull()}.
+ * In other cases you may have code that might end up running on a remote JVM and not on the Jenkins master,
+ * for those cases you really should rewrite your code so that when the {@link Callable} is sent over the remoting
+ * channel it uses a {@code writeReplace} method or similar to ensure that the {@link Jenkins} class is not being
+ * loaded into the remote class loader
+ * @return The instance.
+ * @throws IllegalStateException {@link Jenkins} has not been started, or was already shut down
*/
@CLIResolver
- @CheckForNull
+ @Nullable // TODO replace with non-null once Jenkins 2.0+
public static Jenkins getInstance() {
+ // TODO throw an IllegalStateException in Jenkins 2.0+
return HOLDER.getInstance();
}
@@ -752,6 +782,8 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
*/
private transient final LogRecorderManager log = new LogRecorderManager();
+ private transient final boolean oldJenkinsJVM;
+
protected Jenkins(File root, ServletContext context) throws IOException, InterruptedException, ReactorException {
this(root, context, null);
}
@@ -765,6 +797,8 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
"ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD" // Trigger.timer
})
protected Jenkins(File root, ServletContext context, PluginManager pluginManager) throws IOException, InterruptedException, ReactorException {
+ oldJenkinsJVM = JenkinsJVM.isJenkinsJVM(); // capture to restore in cleanUp()
+ JenkinsJVMAccess._setJenkinsJVM(true); // set it for unit tests as they will not have gone through WebAppMain
long start = System.currentTimeMillis();
// As Jenkins is starting, grant this process full control
@@ -1039,9 +1073,10 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
}
}
} catch (BindException e) {
+ LOGGER.log(Level.WARNING, String.format("Failed to listen to incoming agent connections through JNLP port %s. Change the JNLP port number", slaveAgentPort), e);
new AdministrativeError(administrativeMonitorId,
- "Failed to listen to incoming agent connection",
- "Failed to listen to incoming agent connection. Change the port number to solve the problem.", e);
+ "Failed to listen to incoming agent connections through JNLP",
+ "Failed to listen to incoming agent connections through JNLP. Change the JNLP port number to solve the problem.", e);
}
}
}
@@ -2883,9 +2918,99 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
*/
@edu.umd.cs.findbugs.annotations.SuppressWarnings("ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD")
public void cleanUp() {
- for (ItemListener l : ItemListener.all())
- l.onBeforeShutdown();
+ if (theInstance != this && theInstance != null) {
+ LOGGER.log(Level.WARNING, "This instance is no longer the singleton, ignoring cleanUp()");
+ return;
+ }
+ synchronized (Jenkins.class) {
+ if (cleanUpStarted) {
+ LOGGER.log(Level.WARNING, "Jenkins.cleanUp() already started, ignoring repeated cleanUp()");
+ return;
+ }
+ cleanUpStarted = true;
+ }
+ try {
+ LOGGER.log(Level.INFO, "Stopping Jenkins");
+ final List errors = new ArrayList<>();
+
+ fireBeforeShutdown(errors);
+
+ _cleanUpRunTerminators(errors);
+
+ terminating = true;
+
+ final Set> pending = _cleanUpDisconnectComputers(errors);
+
+ _cleanUpShutdownUDPBroadcast(errors);
+
+ _cleanUpCloseDNSMulticast(errors);
+
+ _cleanUpInterruptReloadThread(errors);
+
+ _cleanUpShutdownTriggers(errors);
+
+ _cleanUpShutdownTimer(errors);
+
+ _cleanUpShutdownTcpSlaveAgent(errors);
+
+ _cleanUpShutdownPluginManager(errors);
+
+ _cleanUpPersistQueue(errors);
+
+ _cleanUpShutdownThreadPoolForLoad(errors);
+
+ _cleanUpAwaitDisconnects(errors, pending);
+
+ _cleanUpPluginServletFilters(errors);
+
+ _cleanUpReleaseAllLoggers(errors);
+
+ LOGGER.log(Level.INFO, "Jenkins stopped");
+
+ if (!errors.isEmpty()) {
+ StringBuilder message = new StringBuilder("Unexpected issues encountered during cleanUp: ");
+ Iterator iterator = errors.iterator();
+ message.append(iterator.next().getMessage());
+ while (iterator.hasNext()) {
+ message.append("; ");
+ message.append(iterator.next().getMessage());
+ }
+ iterator = errors.iterator();
+ RuntimeException exception = new RuntimeException(message.toString(), iterator.next());
+ while (iterator.hasNext()) {
+ exception.addSuppressed(iterator.next());
+ }
+ throw exception;
+ }
+ } finally {
+ theInstance = null;
+ if (JenkinsJVM.isJenkinsJVM()) {
+ JenkinsJVMAccess._setJenkinsJVM(oldJenkinsJVM);
+ }
+ }
+ }
+
+ private void fireBeforeShutdown(List errors) {
+ LOGGER.log(Level.FINE, "Notifying termination");
+ for (ItemListener l : ItemListener.all()) {
+ try {
+ l.onBeforeShutdown();
+ } catch (OutOfMemoryError e) {
+ // we should just propagate this, no point trying to log
+ throw e;
+ } catch (LinkageError e) {
+ LOGGER.log(Level.WARNING, "ItemListener " + l + ": " + e.getMessage(), e);
+ // safe to ignore and continue for this one
+ } catch (Throwable e) {
+ LOGGER.log(Level.WARNING, "ItemListener " + l + ": " + e.getMessage(), e);
+ // save for later
+ errors.add(e);
+ }
+ }
+ }
+
+ private void _cleanUpRunTerminators(List errors) {
try {
final TerminatorFinder tf = new TerminatorFinder(
pluginManager != null ? pluginManager.uberClassLoader : Thread.currentThread().getContextClassLoader());
@@ -2894,71 +3019,325 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
public void execute(Runnable command) {
command.run();
}
- });
- } catch (InterruptedException e) {
- LOGGER.log(SEVERE, "Failed to execute termination",e);
- e.printStackTrace();
- } catch (ReactorException e) {
- LOGGER.log(SEVERE, "Failed to execute termination",e);
- } catch (IOException e) {
- LOGGER.log(SEVERE, "Failed to execute termination",e);
- }
+ }, new ReactorListener() {
+ final Level level = Level.parse(Configuration.getStringConfigParameter("termLogLevel", "FINE"));
+ public void onTaskStarted(Task t) {
+ LOGGER.log(level, "Started " + t.getDisplayName());
+ }
+
+ public void onTaskCompleted(Task t) {
+ LOGGER.log(level, "Completed " + t.getDisplayName());
+ }
+
+ public void onTaskFailed(Task t, Throwable err, boolean fatal) {
+ LOGGER.log(SEVERE, "Failed " + t.getDisplayName(), err);
+ }
+
+ public void onAttained(Milestone milestone) {
+ Level lv = level;
+ String s = "Attained " + milestone.toString();
+ if (milestone instanceof TermMilestone) {
+ lv = Level.INFO; // noteworthy milestones --- at least while we debug problems further
+ s = milestone.toString();
+ }
+ LOGGER.log(lv, s);
+ }
+ });
+ } catch (InterruptedException | ReactorException | IOException e) {
+ LOGGER.log(SEVERE, "Failed to execute termination",e);
+ errors.add(e);
+ } catch (OutOfMemoryError e) {
+ // we should just propagate this, no point trying to log
+ throw e;
+ } catch (LinkageError e) {
+ LOGGER.log(SEVERE, "Failed to execute termination", e);
+ // safe to ignore and continue for this one
+ } catch (Throwable e) {
+ LOGGER.log(SEVERE, "Failed to execute termination", e);
+ // save for later
+ errors.add(e);
+ }
+ }
+
+ private Set> _cleanUpDisconnectComputers(final List errors) {
+ LOGGER.log(Level.INFO, "Starting node disconnection");
final Set> pending = new HashSet>();
- terminating = true;
// JENKINS-28840 we know we will be interrupting all the Computers so get the Queue lock once for all
Queue.withLock(new Runnable() {
@Override
public void run() {
for( Computer c : computers.values() ) {
- c.interrupt();
- killComputer(c);
- pending.add(c.disconnect(null));
+ try {
+ c.interrupt();
+ killComputer(c);
+ pending.add(c.disconnect(null));
+ } catch (OutOfMemoryError e) {
+ // we should just propagate this, no point trying to log
+ throw e;
+ } catch (LinkageError e) {
+ LOGGER.log(Level.WARNING, "Could not disconnect " + c + ": " + e.getMessage(), e);
+ // safe to ignore and continue for this one
+ } catch (Throwable e) {
+ LOGGER.log(Level.WARNING, "Could not disconnect " + c + ": " + e.getMessage(), e);
+ // save for later
+ errors.add(e);
+ }
}
}
});
- if(udpBroadcastThread!=null)
- udpBroadcastThread.shutdown();
- if(dnsMultiCast!=null)
- dnsMultiCast.close();
- interruptReloadThread();
+ return pending;
+ }
- java.util.Timer timer = Trigger.timer;
- if (timer != null) {
- timer.cancel();
+ private void _cleanUpShutdownUDPBroadcast(List errors) {
+ if(udpBroadcastThread!=null) {
+ LOGGER.log(Level.FINE, "Shutting down {0}", udpBroadcastThread.getName());
+ try {
+ udpBroadcastThread.shutdown();
+ } catch (OutOfMemoryError e) {
+ // we should just propagate this, no point trying to log
+ throw e;
+ } catch (LinkageError e) {
+ LOGGER.log(SEVERE, "Failed to shutdown UDP Broadcast Thread", e);
+ // safe to ignore and continue for this one
+ } catch (Throwable e) {
+ LOGGER.log(SEVERE, "Failed to shutdown UDP Broadcast Thread", e);
+ // save for later
+ errors.add(e);
+ }
}
- // TODO: how to wait for the completion of the last job?
- Trigger.timer = null;
+ }
- Timer.shutdown();
+ private void _cleanUpCloseDNSMulticast(List errors) {
+ if(dnsMultiCast!=null) {
+ LOGGER.log(Level.FINE, "Closing DNS Multicast service");
+ try {
+ dnsMultiCast.close();
+ } catch (OutOfMemoryError e) {
+ // we should just propagate this, no point trying to log
+ throw e;
+ } catch (LinkageError e) {
+ LOGGER.log(SEVERE, "Failed to close DNS Multicast service", e);
+ // safe to ignore and continue for this one
+ } catch (Throwable e) {
+ LOGGER.log(SEVERE, "Failed to close DNS Multicast service", e);
+ // save for later
+ errors.add(e);
+ }
+ }
+ }
- if(tcpSlaveAgentListener!=null)
- tcpSlaveAgentListener.shutdown();
+ private void _cleanUpInterruptReloadThread(List errors) {
+ LOGGER.log(Level.FINE, "Interrupting reload thread");
+ try {
+ interruptReloadThread();
+ } catch (SecurityException e) {
+ LOGGER.log(WARNING, "Not permitted to interrupt reload thread", e);
+ errors.add(e);
+ } catch (OutOfMemoryError e) {
+ // we should just propagate this, no point trying to log
+ throw e;
+ } catch (LinkageError e) {
+ LOGGER.log(SEVERE, "Failed to interrupt reload thread", e);
+ // safe to ignore and continue for this one
+ } catch (Throwable e) {
+ LOGGER.log(SEVERE, "Failed to interrupt reload thread", e);
+ // save for later
+ errors.add(e);
+ }
+ }
- if(pluginManager!=null) // be defensive. there could be some ugly timing related issues
- pluginManager.stop();
+ private void _cleanUpShutdownTriggers(List errors) {
+ LOGGER.log(Level.FINE, "Shutting down triggers");
+ try {
+ final java.util.Timer timer = Trigger.timer;
+ if (timer != null) {
+ final CountDownLatch latch = new CountDownLatch(1);
+ timer.schedule(new TimerTask() {
+ @Override
+ public void run() {
+ timer.cancel();
+ latch.countDown();
+ }
+ }, 0);
+ if (latch.await(10, TimeUnit.SECONDS)) {
+ LOGGER.log(Level.FINE, "Triggers shut down successfully");
+ } else {
+ timer.cancel();
+ LOGGER.log(Level.INFO, "Gave up waiting for triggers to finish running");
+ }
+ }
+ Trigger.timer = null;
+ } catch (OutOfMemoryError e) {
+ // we should just propagate this, no point trying to log
+ throw e;
+ } catch (LinkageError e) {
+ LOGGER.log(SEVERE, "Failed to shut down triggers", e);
+ // safe to ignore and continue for this one
+ } catch (Throwable e) {
+ LOGGER.log(SEVERE, "Failed to shut down triggers", e);
+ // save for later
+ errors.add(e);
+ }
+ }
- if(getRootDir().exists())
+ private void _cleanUpShutdownTimer(List errors) {
+ LOGGER.log(Level.FINE, "Shutting down timer");
+ try {
+ Timer.shutdown();
+ } catch (SecurityException e) {
+ LOGGER.log(WARNING, "Not permitted to shut down Timer", e);
+ errors.add(e);
+ } catch (OutOfMemoryError e) {
+ // we should just propagate this, no point trying to log
+ throw e;
+ } catch (LinkageError e) {
+ LOGGER.log(SEVERE, "Failed to shut down Timer", e);
+ // safe to ignore and continue for this one
+ } catch (Throwable e) {
+ LOGGER.log(SEVERE, "Failed to shut down Timer", e);
+ // save for later
+ errors.add(e);
+ }
+ }
+
+ private void _cleanUpShutdownTcpSlaveAgent(List errors) {
+ if(tcpSlaveAgentListener!=null) {
+ LOGGER.log(FINE, "Shutting down TCP/IP slave agent listener");
+ try {
+ tcpSlaveAgentListener.shutdown();
+ } catch (OutOfMemoryError e) {
+ // we should just propagate this, no point trying to log
+ throw e;
+ } catch (LinkageError e) {
+ LOGGER.log(SEVERE, "Failed to shut down TCP/IP slave agent listener", e);
+ // safe to ignore and continue for this one
+ } catch (Throwable e) {
+ LOGGER.log(SEVERE, "Failed to shut down TCP/IP slave agent listener", e);
+ // save for later
+ errors.add(e);
+ }
+ }
+ }
+
+ private void _cleanUpShutdownPluginManager(List errors) {
+ if(pluginManager!=null) {// be defensive. there could be some ugly timing related issues
+ LOGGER.log(Level.INFO, "Stopping plugin manager");
+ try {
+ pluginManager.stop();
+ } catch (OutOfMemoryError e) {
+ // we should just propagate this, no point trying to log
+ throw e;
+ } catch (LinkageError e) {
+ LOGGER.log(SEVERE, "Failed to stop plugin manager", e);
+ // safe to ignore and continue for this one
+ } catch (Throwable e) {
+ LOGGER.log(SEVERE, "Failed to stop plugin manager", e);
+ // save for later
+ errors.add(e);
+ }
+ }
+ }
+
+ private void _cleanUpPersistQueue(List errors) {
+ if(getRootDir().exists()) {
// if we are aborting because we failed to create JENKINS_HOME,
// don't try to save. Issue #536
- getQueue().save();
+ LOGGER.log(Level.INFO, "Persisting build queue");
+ try {
+ getQueue().save();
+ } catch (OutOfMemoryError e) {
+ // we should just propagate this, no point trying to log
+ throw e;
+ } catch (LinkageError e) {
+ LOGGER.log(SEVERE, "Failed to persist build queue", e);
+ // safe to ignore and continue for this one
+ } catch (Throwable e) {
+ LOGGER.log(SEVERE, "Failed to persist build queue", e);
+ // save for later
+ errors.add(e);
+ }
+ }
+ }
- threadPoolForLoad.shutdown();
- for (Future> f : pending)
+ private void _cleanUpShutdownThreadPoolForLoad(List errors) {
+ LOGGER.log(FINE, "Shuting down Jenkins load thread pool");
+ try {
+ threadPoolForLoad.shutdown();
+ } catch (SecurityException e) {
+ LOGGER.log(WARNING, "Not permitted to shut down Jenkins load thread pool", e);
+ errors.add(e);
+ } catch (OutOfMemoryError e) {
+ // we should just propagate this, no point trying to log
+ throw e;
+ } catch (LinkageError e) {
+ LOGGER.log(SEVERE, "Failed to shut down Jenkins load thread pool", e);
+ // safe to ignore and continue for this one
+ } catch (Throwable e) {
+ LOGGER.log(SEVERE, "Failed to shut down Jenkins load thread pool", e);
+ // save for later
+ errors.add(e);
+ }
+ }
+
+ private void _cleanUpAwaitDisconnects(List errors, Set> pending) {
+ if (!pending.isEmpty()) {
+ LOGGER.log(Level.INFO, "Waiting for node disconnection completion");
+ }
+ for (Future> f : pending) {
try {
f.get(10, TimeUnit.SECONDS); // if clean up operation didn't complete in time, we fail the test
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break; // someone wants us to die now. quick!
} catch (ExecutionException e) {
- LOGGER.log(Level.WARNING, "Failed to shut down properly",e);
+ LOGGER.log(Level.WARNING, "Failed to shut down remote computer connection cleanly", e);
} catch (TimeoutException e) {
- LOGGER.log(Level.WARNING, "Failed to shut down properly",e);
+ LOGGER.log(Level.WARNING, "Failed to shut down remote computer connection within 10 seconds", e);
+ } catch (OutOfMemoryError e) {
+ // we should just propagate this, no point trying to log
+ throw e;
+ } catch (LinkageError e) {
+ LOGGER.log(Level.WARNING, "Failed to shut down remote computer connection", e);
+ } catch (Throwable e) {
+ LOGGER.log(Level.SEVERE, "Unexpected error while waiting for remote computer connection disconnect", e);
+ errors.add(e);
}
+ }
+ }
- LogFactory.releaseAll();
+ private void _cleanUpPluginServletFilters(List errors) {
+ LOGGER.log(Level.FINE, "Stopping filters");
+ try {
+ PluginServletFilter.cleanUp();
+ } catch (OutOfMemoryError e) {
+ // we should just propagate this, no point trying to log
+ throw e;
+ } catch (LinkageError e) {
+ LOGGER.log(SEVERE, "Failed to stop filters", e);
+ // safe to ignore and continue for this one
+ } catch (Throwable e) {
+ LOGGER.log(SEVERE, "Failed to stop filters", e);
+ // save for later
+ errors.add(e);
+ }
+ }
- theInstance = null;
+ private void _cleanUpReleaseAllLoggers(List errors) {
+ LOGGER.log(Level.FINE, "Releasing all loggers");
+ try {
+ LogFactory.releaseAll();
+ } catch (OutOfMemoryError e) {
+ // we should just propagate this, no point trying to log
+ throw e;
+ } catch (LinkageError e) {
+ LOGGER.log(SEVERE, "Failed to release all loggers", e);
+ // safe to ignore and continue for this one
+ } catch (Throwable e) {
+ LOGGER.log(SEVERE, "Failed to release all loggers", e);
+ // save for later
+ errors.add(e);
+ }
}
public Object getDynamic(String token) {
@@ -4200,10 +4579,10 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
}
/**
- * Shortcut for {@code Jenkins.getInstance().lookup.get(type)}
+ * Shortcut for {@code Jenkins.getInstanceOrNull()?.lookup.get(type)}
*/
public static @CheckForNull T lookup(Class type) {
- Jenkins j = Jenkins.getInstance();
+ Jenkins j = Jenkins.getInstanceOrNull();
return j != null ? j.lookup.get(type) : null;
}
@@ -4453,4 +4832,10 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
}
}
+ private static final class JenkinsJVMAccess extends JenkinsJVM {
+ private static void _setJenkinsJVM(boolean jenkinsJVM) {
+ JenkinsJVM.setJenkinsJVM(jenkinsJVM);
+ }
+ }
+
}
diff --git a/core/src/main/java/jenkins/model/UnlabeledLoadStatistics.java b/core/src/main/java/jenkins/model/UnlabeledLoadStatistics.java
index cf4abda281..158316f00f 100644
--- a/core/src/main/java/jenkins/model/UnlabeledLoadStatistics.java
+++ b/core/src/main/java/jenkins/model/UnlabeledLoadStatistics.java
@@ -78,11 +78,7 @@ public class UnlabeledLoadStatistics extends LoadStatistics {
@Override
public int computeQueueLength() {
- final Jenkins j = Jenkins.getInstance();
- if (j == null) { // Consider queue as empty when Jenkins is inactive
- return 0;
- }
- return j.getQueue().strictCountBuildableItemsFor(null);
+ return Jenkins.getInstance().getQueue().strictCountBuildableItemsFor(null);
}
@Override
diff --git a/core/src/main/java/jenkins/security/ApiTokenProperty.java b/core/src/main/java/jenkins/security/ApiTokenProperty.java
index 10214177bd..36e0b205a2 100644
--- a/core/src/main/java/jenkins/security/ApiTokenProperty.java
+++ b/core/src/main/java/jenkins/security/ApiTokenProperty.java
@@ -120,10 +120,7 @@ public class ApiTokenProperty extends UserProperty {
private boolean hasPermissionToSeeToken() {
final Jenkins jenkins = Jenkins.getInstance();
- if (jenkins == null) {
- return false; // Should not happen - we don't display UIs in this stage
- }
-
+
// Administrators can do whatever they want
if (SHOW_TOKEN_TO_ADMINS && jenkins.hasPermission(Jenkins.ADMINISTER)) {
return true;
diff --git a/core/src/main/java/jenkins/security/ConfidentialStore.java b/core/src/main/java/jenkins/security/ConfidentialStore.java
index 1c747b32c8..9434035d2a 100644
--- a/core/src/main/java/jenkins/security/ConfidentialStore.java
+++ b/core/src/main/java/jenkins/security/ConfidentialStore.java
@@ -62,9 +62,6 @@ public abstract class ConfidentialStore {
if (TEST!=null) return TEST.get();
Jenkins j = Jenkins.getInstance();
- if (j == null) {
- throw new IllegalStateException("cannot initialize confidential key store until Jenkins has started");
- }
Lookup lookup = j.lookup;
ConfidentialStore cs = lookup.get(ConfidentialStore.class);
if (cs==null) {
diff --git a/core/src/main/java/jenkins/security/QueueItemAuthenticatorProvider.java b/core/src/main/java/jenkins/security/QueueItemAuthenticatorProvider.java
index bb43c58f8a..2b6529ddea 100644
--- a/core/src/main/java/jenkins/security/QueueItemAuthenticatorProvider.java
+++ b/core/src/main/java/jenkins/security/QueueItemAuthenticatorProvider.java
@@ -2,6 +2,7 @@ package jenkins.security;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Extension;
+import hudson.ExtensionList;
import hudson.ExtensionPoint;
import jenkins.model.Jenkins;
@@ -32,10 +33,7 @@ public abstract class QueueItemAuthenticatorProvider implements ExtensionPoint {
private Iterator delegate = null;
private IteratorImpl() {
- final Jenkins jenkins = Jenkins.getInstance();
- providers = new ArrayList(jenkins == null
- ? Collections.emptyList()
- : jenkins.getExtensionList(QueueItemAuthenticatorProvider.class)).iterator();
+ providers = ExtensionList.lookup(QueueItemAuthenticatorProvider.class).iterator();
}
@Override
diff --git a/core/src/main/java/jenkins/slaves/DefaultJnlpSlaveReceiver.java b/core/src/main/java/jenkins/slaves/DefaultJnlpSlaveReceiver.java
index 7733f27c02..f82ad9ca18 100644
--- a/core/src/main/java/jenkins/slaves/DefaultJnlpSlaveReceiver.java
+++ b/core/src/main/java/jenkins/slaves/DefaultJnlpSlaveReceiver.java
@@ -7,6 +7,7 @@ import hudson.model.Slave;
import hudson.remoting.Channel;
import hudson.slaves.SlaveComputer;
import jenkins.model.Jenkins;
+import org.jenkinsci.remoting.engine.JnlpServerHandshake;
import java.io.IOException;
import java.security.SecureRandom;
@@ -27,7 +28,7 @@ import java.util.logging.Logger;
@Extension
public class DefaultJnlpSlaveReceiver extends JnlpAgentReceiver {
@Override
- public boolean handle(String nodeName, JnlpSlaveHandshake handshake) throws IOException, InterruptedException {
+ public boolean handle(String nodeName, JnlpServerHandshake handshake) throws IOException, InterruptedException {
SlaveComputer computer = (SlaveComputer) Jenkins.getInstance().getComputer(nodeName);
if (computer==null) {
@@ -43,9 +44,7 @@ public class DefaultJnlpSlaveReceiver extends JnlpAgentReceiver {
LOGGER.info("Disconnecting "+nodeName+" as we are reconnected from the current peer");
try {
computer.disconnect(new ConnectionFromCurrentPeer()).get(15, TimeUnit.SECONDS);
- } catch (ExecutionException e) {
- throw new IOException("Failed to disconnect the current client",e);
- } catch (TimeoutException e) {
+ } catch (ExecutionException | TimeoutException e) {
throw new IOException("Failed to disconnect the current client",e);
}
} else {
@@ -87,7 +86,7 @@ public class DefaultJnlpSlaveReceiver extends JnlpAgentReceiver {
* @return
* true if the agent secret matches the handshake secret, false otherwise.
*/
- private boolean matchesSecret(String nodeName, JnlpSlaveHandshake handshake){
+ private boolean matchesSecret(String nodeName, JnlpServerHandshake handshake){
SlaveComputer computer = (SlaveComputer) Jenkins.getInstance().getComputer(nodeName);
String handshakeSecret = handshake.getRequestProperty("Secret-Key");
// Verify that the agent secret matches the handshake secret.
diff --git a/core/src/main/java/jenkins/slaves/JnlpAgentReceiver.java b/core/src/main/java/jenkins/slaves/JnlpAgentReceiver.java
index 3526c4a73c..7865c88526 100644
--- a/core/src/main/java/jenkins/slaves/JnlpAgentReceiver.java
+++ b/core/src/main/java/jenkins/slaves/JnlpAgentReceiver.java
@@ -3,6 +3,8 @@ package jenkins.slaves;
import hudson.ExtensionList;
import hudson.ExtensionPoint;
import hudson.model.Slave;
+import jenkins.model.Jenkins;
+import org.jenkinsci.remoting.engine.JnlpServerHandshake;
import java.io.IOException;
import java.util.Properties;
@@ -55,7 +57,16 @@ public abstract class JnlpAgentReceiver implements ExtensionPoint {
* @throws Exception
* Any exception thrown from this method will fatally terminate the connection.
*/
- public abstract boolean handle(String name, JnlpSlaveHandshake handshake) throws IOException, InterruptedException;
+ public abstract boolean handle(String name, JnlpServerHandshake handshake) throws IOException, InterruptedException;
+
+ /**
+ * @deprecated
+ * Use {@link #handle(String, JnlpServerHandshake)}
+ */
+ public boolean handle(String name, JnlpSlaveHandshake handshake) throws IOException, InterruptedException {
+ return handle(name,(JnlpServerHandshake)handshake);
+ }
+
public static ExtensionList all() {
return ExtensionList.lookup(JnlpAgentReceiver.class);
diff --git a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol.java b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol.java
index 753449375a..36ebaf2969 100644
--- a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol.java
+++ b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol.java
@@ -2,6 +2,7 @@ package jenkins.slaves;
import hudson.AbortException;
import hudson.Extension;
+import hudson.model.Computer;
import hudson.remoting.Channel;
import hudson.remoting.Channel.Listener;
import hudson.remoting.ChannelBuilder;
@@ -12,6 +13,7 @@ import jenkins.model.Jenkins;
import jenkins.security.ChannelConfigurator;
import jenkins.security.HMACConfidentialKey;
import org.jenkinsci.Symbol;
+import org.jenkinsci.remoting.engine.JnlpServerHandshake;
import org.jenkinsci.remoting.nio.NioChannelHub;
import javax.inject.Inject;
@@ -68,7 +70,7 @@ public class JnlpSlaveAgentProtocol extends AgentProtocol {
new Handler(hub.getHub(),socket).run();
}
- protected static class Handler extends JnlpSlaveHandshake {
+ protected static class Handler extends JnlpServerHandshake {
/**
* @deprecated as of 1.559
@@ -80,9 +82,7 @@ public class JnlpSlaveAgentProtocol extends AgentProtocol {
}
public Handler(NioChannelHub hub, Socket socket) throws IOException {
- super(hub,socket,
- new DataInputStream(socket.getInputStream()),
- new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(),"UTF-8")),true));
+ super(hub, Computer.threadPoolForRemoting, socket);
}
protected void run() throws IOException, InterruptedException {
diff --git a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol2.java b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol2.java
index 124e3bd1bf..ff9ce64c1a 100644
--- a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol2.java
+++ b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol2.java
@@ -2,6 +2,7 @@ package jenkins.slaves;
import hudson.Extension;
import org.jenkinsci.Symbol;
+import org.jenkinsci.remoting.engine.JnlpServerHandshake;
import org.jenkinsci.remoting.nio.NioChannelHub;
import java.io.ByteArrayInputStream;
@@ -55,8 +56,13 @@ public class JnlpSlaveAgentProtocol2 extends JnlpSlaveAgentProtocol {
final String nodeName = request.getProperty("Node-Name");
for (JnlpAgentReceiver recv : JnlpAgentReceiver.all()) {
- if (recv.handle(nodeName,this))
- return;
+ try {
+ if (recv.handle(nodeName,this))
+ return;
+ } catch (AbstractMethodError e) {
+ if (recv.handle(nodeName,new JnlpSlaveHandshake(this)))
+ return;
+ }
}
error("JNLP2-connect: rejected connection for node: " + nodeName);
diff --git a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java
new file mode 100644
index 0000000000..8b6b3df129
--- /dev/null
+++ b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java
@@ -0,0 +1,137 @@
+package jenkins.slaves;
+
+import hudson.AbortException;
+import hudson.Extension;
+import hudson.Util;
+import hudson.model.Computer;
+import hudson.remoting.Channel;
+import hudson.remoting.ChannelBuilder;
+import hudson.slaves.SlaveComputer;
+import jenkins.AgentProtocol;
+import jenkins.model.Jenkins;
+import jenkins.security.ChannelConfigurator;
+import org.jenkinsci.remoting.engine.JnlpServer3Handshake;
+import org.jenkinsci.remoting.nio.NioChannelHub;
+import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.NoExternalUse;
+
+import javax.inject.Inject;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.net.Socket;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Master-side implementation for JNLP3-connect protocol.
+ *
+ *
@see {@link org.jenkinsci.remoting.engine.JnlpProtocol3} for more details.
+ *
+ * @author Akshay Dayal
+ * @since 1.XXX
+ */
+@Extension
+public class JnlpSlaveAgentProtocol3 extends AgentProtocol {
+ @Inject
+ NioChannelSelector hub;
+
+ @Override
+ public String getName() {
+ if (ENABLED) return "JNLP3-connect";
+ else return "JNLP3-disabled";
+ }
+
+ @Override
+ public void handle(Socket socket) throws IOException, InterruptedException {
+ new Handler(hub.getHub(), socket).run();
+ }
+
+ static class Handler extends JnlpServer3Handshake {
+ private SlaveComputer computer;
+ private PrintWriter logw;
+ private OutputStream log;
+
+ public Handler(NioChannelHub hub, Socket socket) throws IOException {
+ super(hub, Computer.threadPoolForRemoting, socket);
+ }
+
+ protected void run() throws IOException, InterruptedException {
+ try {
+ Channel channel = connect();
+
+ computer.setChannel(channel, log,
+ new Channel.Listener() {
+ @Override
+ public void onClosed(Channel channel, IOException cause) {
+ if (cause != null)
+ LOGGER.log(Level.WARNING,
+ Thread.currentThread().getName() + " for + " +
+ getNodeName() + " terminated", cause);
+ try {
+ socket.close();
+ } catch (IOException e) {
+ // Do nothing.
+ }
+ }
+ });
+ } catch (AbortException e) {
+ logw.println(e.getMessage());
+ logw.println("Failed to establish the connection with the agent");
+ throw e;
+ } catch (IOException e) {
+ logw.println("Failed to establish the connection with the agent " + getNodeName());
+ e.printStackTrace(logw);
+ throw e;
+ }
+ }
+
+ @Override
+ public ChannelBuilder createChannelBuilder(String nodeName) {
+ log = computer.openLogFile();
+ logw = new PrintWriter(log,true);
+ logw.println("JNLP agent connected from " + socket.getInetAddress());
+
+ ChannelBuilder cb = super.createChannelBuilder(nodeName).withHeaderStream(log);
+
+ for (ChannelConfigurator cc : ChannelConfigurator.all()) {
+ cc.onChannelBuilding(cb, computer);
+ }
+
+ return cb;
+ }
+
+ @Override
+ protected String getNodeSecret(String nodeName) throws Failure {
+ computer = (SlaveComputer) Jenkins.getInstance().getComputer(nodeName);
+ if (computer == null) {
+ throw new Failure("Agent trying to register for invalid node: " + nodeName);
+ }
+ return computer.getJnlpMac();
+ }
+
+ }
+
+ private static final Logger LOGGER = Logger.getLogger(JnlpSlaveAgentProtocol3.class.getName());
+
+ /**
+ * Flag to control the activation of JNLP3 protocol.
+ * This feature is being A/B tested right now.
+ *
+ *
+ * Once this will be on by default, the flag and this field will disappear. The system property is
+ * an escape hatch for those who hit any issues and those who are trying this out.
+ */
+ @Restricted(NoExternalUse.class)
+ public static boolean ENABLED;
+
+ static {
+ String propName = JnlpSlaveAgentProtocol3.class.getName() + ".enabled";
+ if (System.getProperties().containsKey(propName))
+ ENABLED = Boolean.getBoolean(propName);
+ else {
+ byte hash = Util.fromHexString(Jenkins.getActiveInstance().getLegacyInstanceId())[0];
+ ENABLED = (hash%10)==0;
+ }
+ }
+}
diff --git a/core/src/main/java/jenkins/slaves/JnlpSlaveHandshake.java b/core/src/main/java/jenkins/slaves/JnlpSlaveHandshake.java
index 341d8c0881..1b67b98ecf 100644
--- a/core/src/main/java/jenkins/slaves/JnlpSlaveHandshake.java
+++ b/core/src/main/java/jenkins/slaves/JnlpSlaveHandshake.java
@@ -1,118 +1,22 @@
package jenkins.slaves;
-import hudson.model.Computer;
-import hudson.remoting.Channel;
-import hudson.remoting.ChannelBuilder;
-import hudson.remoting.Engine;
+import org.jenkinsci.remoting.engine.JnlpServerHandshake;
import org.jenkinsci.remoting.nio.NioChannelHub;
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
import java.io.IOException;
-import java.io.PrintWriter;
import java.net.Socket;
-import java.util.Map.Entry;
-import java.util.Properties;
-import java.util.logging.Level;
-import java.util.logging.Logger;
+import java.util.concurrent.ExecutorService;
/**
* Palette of objects to talk to the incoming JNLP agent connection.
*
* @author Kohsuke Kawaguchi
* @since 1.561
+ * @deprecated as of 1.609
+ * Use {@link JnlpServerHandshake}
*/
-public class JnlpSlaveHandshake {
- /**
- * Useful for creating a {@link Channel} with NIO as the underlying transport.
- */
- /*package*/ final NioChannelHub hub;
-
- /**
- * Socket connection to the agent.
- */
- /*package*/ final Socket socket;
-
- /**
- * Wrapping Socket input stream.
- */
- /*package*/ final DataInputStream in;
-
- /**
- * For writing handshaking response.
- *
- * This is a poor design choice that we just carry forward for compatibility.
- * For better protocol design, {@link DataOutputStream} is preferred for newer
- * protocols.
- */
- /*package*/ final PrintWriter out;
-
- /**
- * Bag of properties the JNLP agent have sent us during the hand-shake.
- */
- /*package*/ final Properties request = new Properties();
-
-
- /*package*/ JnlpSlaveHandshake(NioChannelHub hub, Socket socket, DataInputStream in, PrintWriter out) {
- this.hub = hub;
- this.socket = socket;
- this.in = in;
- this.out = out;
+public class JnlpSlaveHandshake extends JnlpServerHandshake {
+ /*package*/ JnlpSlaveHandshake(JnlpServerHandshake rhs) {
+ super(rhs);
}
-
- public NioChannelHub getHub() {
- return hub;
- }
-
- public Socket getSocket() {
- return socket;
- }
-
- public DataInputStream getIn() {
- return in;
- }
-
- public PrintWriter getOut() {
- return out;
- }
-
- public Properties getRequestProperties() {
- return request;
- }
-
- public String getRequestProperty(String name) {
- return request.getProperty(name);
- }
-
-
- /**
- * Sends the error output and bail out.
- */
- public void error(String msg) throws IOException {
- out.println(msg);
- LOGGER.log(Level.WARNING,Thread.currentThread().getName()+" is aborted: "+msg);
- socket.close();
- }
-
- /**
- * {@link JnlpAgentReceiver} calls this method to tell the client that the server
- * is happy with the handshaking and is ready to move on to build a channel.
- */
- public void success(Properties response) {
- out.println(Engine.GREETING_SUCCESS);
- for (Entry