Merged revisions 23666-23667,23670,23711,23782-23785,23788,23809-23810,23817,23859,23870,23879,23899-23904,23906-23907,23909,23913 via svnmerge from

https://www.dev.java.net/svn/hudson/branches/managed-startup

........
  r23666 | kohsuke | 2009-11-11 15:56:46 -0800 (Wed, 11 Nov 2009) | 1 line
  
  Reworked the initialization logic
........
  r23667 | kohsuke | 2009-11-11 16:01:56 -0800 (Wed, 11 Nov 2009) | 1 line
  
  fixed a failing test
........
  r23670 | kohsuke | 2009-11-11 16:42:32 -0800 (Wed, 11 Nov 2009) | 1 line
  
  use a non-snapshot
........
  r23711 | kohsuke | 2009-11-13 14:27:28 -0800 (Fri, 13 Nov 2009) | 1 line
  
  Session -> Reactor
........
  r23782 | kohsuke | 2009-11-17 10:35:14 -0800 (Tue, 17 Nov 2009) | 1 line
  
  Poorman's clone of JDK6 ServiceLoader.
........
  r23783 | kohsuke | 2009-11-17 10:56:25 -0800 (Tue, 17 Nov 2009) | 1 line
  
  Integrated the fatal support so that all errors are sent to the listener.
........
  r23784 | kohsuke | 2009-11-17 10:58:07 -0800 (Tue, 17 Nov 2009) | 1 line
  
  with the new reactor initialization, things are getting too verbose
........
  r23785 | kohsuke | 2009-11-17 11:18:45 -0800 (Tue, 17 Nov 2009) | 1 line
  
  failing to load jobs are non-fatal, too
........
  r23788 | kohsuke | 2009-11-17 13:18:59 -0800 (Tue, 17 Nov 2009) | 1 line
  
  logging the exception properly
........
  r23809 | kohsuke | 2009-11-17 18:45:47 -0800 (Tue, 17 Nov 2009) | 1 line
  
  releasing 1.1 as milestone
........
  r23810 | kohsuke | 2009-11-17 18:47:37 -0800 (Tue, 17 Nov 2009) | 2 lines
  
  - use a better idiom for building a sequence
  - use null display name to signify internal book-keeping tasks that's not worth displaying
........
  r23817 | kohsuke | 2009-11-18 07:54:17 -0800 (Wed, 18 Nov 2009) | 1 line
  
  removed accidental JDK6 dependency.
........
  r23859 | kohsuke | 2009-11-19 11:03:28 -0800 (Thu, 19 Nov 2009) | 1 line
  
  introducing the strategy pattern to hook into key decision making points during the start up
........
  r23870 | kohsuke | 2009-11-19 14:33:07 -0800 (Thu, 19 Nov 2009) | 1 line
  
  bug fix. These steps add additional tasks to the reactor, so they need to also prevent future milestones from getting attained.
........
  r23879 | kohsuke | 2009-11-19 16:54:59 -0800 (Thu, 19 Nov 2009) | 1 line
  
  moved the logic to the strategy.
........
  r23899 | kohsuke | 2009-11-20 09:32:13 -0800 (Fri, 20 Nov 2009) | 1 line
  
  handle duplicate plugins better
........
  r23900 | kohsuke | 2009-11-20 10:06:42 -0800 (Fri, 20 Nov 2009) | 1 line
  
  support pre-exploded plugin
........
  r23901 | kohsuke | 2009-11-20 10:49:04 -0800 (Fri, 20 Nov 2009) | 1 line
  
  making it a subtype
........
  r23902 | kohsuke | 2009-11-20 10:54:42 -0800 (Fri, 20 Nov 2009) | 1 line
  
  allow InitStrategy to skip some tasks
........
  r23903 | kohsuke | 2009-11-20 11:03:38 -0800 (Fri, 20 Nov 2009) | 1 line
  
  doc improvement.
........
  r23904 | kohsuke | 2009-11-20 11:03:49 -0800 (Fri, 20 Nov 2009) | 1 line
  
  no need to throw exceptions
........
  r23906 | kohsuke | 2009-11-20 11:17:34 -0800 (Fri, 20 Nov 2009) | 1 line
  
  debug switch to disable the automatic launch of slaves
........
  r23907 | kohsuke | 2009-11-20 11:25:15 -0800 (Fri, 20 Nov 2009) | 1 line
  
  added a magic object for cancelling all saves
........
  r23909 | kohsuke | 2009-11-20 11:29:53 -0800 (Fri, 20 Nov 2009) | 1 line
  
  moving initialization up
........
  r23913 | kohsuke | 2009-11-20 13:32:08 -0800 (Fri, 20 Nov 2009) | 1 line
  
  seeing heap space problem during tests
........


git-svn-id: https://hudson.dev.java.net/svn/hudson/trunk/hudson/main@23992 71c3de6d-444a-0410-be80-ed276b4c234a
This commit is contained in:
kohsuke committed 2009-11-23 19:33:33 +00:00
1 parent ef7768a0a9
commit fd13eee6d5
26 files changed
+1154 -325

No files matched your search

+11 -1
View File
@@ -157,8 +157,18 @@ public class BulkChange {
*/
public static boolean contains(Saveable s) {
for(BulkChange b=current(); b!=null; b=b.parent)
if(b.saveable== s)
if(b.saveable==s || b.saveable==ALL)
return true;
return false;
}
/**
* Magic {@link Saveable} instance that can make {@link BulkChange} veto
* all the save operations by making the {@link #contains(Saveable)} method return
* true for everything.
*/
public static final Saveable ALL = new Saveable() {
public void save() {
}
};
}
@@ -72,16 +72,16 @@ public class ClassicPluginStrategy implements PluginStrategy {
}
public PluginWrapper createPluginWrapper(File archive) throws IOException {
LOGGER.info("Loading plugin: " + archive);
LOGGER.info("Preparing plugin: " + archive);
final Manifest manifest;
URL baseResourceURL;
boolean isLinked = archive.getName().endsWith(".hpl");
File expandDir = null;
// if .hpi, this is the directory where war is expanded
boolean isLinked = archive.getName().endsWith(".hpl");
if (isLinked) {
// resolve the .hpl file to the location of the manifest file
String firstLine = new BufferedReader(new FileReader(archive))
@@ -102,21 +102,25 @@ public class ClassicPluginStrategy implements PluginStrategy {
in.close();
}
} else {
expandDir = new File(archive.getParentFile(), PluginWrapper.getBaseName(archive));
explode(archive, expandDir);
if (archive.isDirectory()) {// already expanded
expandDir = archive;
} else {
expandDir = new File(archive.getParentFile(), PluginWrapper.getBaseName(archive));
explode(archive, expandDir);
}
File manifestFile = new File(expandDir, "META-INF/MANIFEST.MF");
if (!manifestFile.exists()) {
throw new IOException(
"Plugin installation failed. No manifest at "
+ manifestFile);
}
FileInputStream fin = new FileInputStream(manifestFile);
try {
manifest = new Manifest(fin);
} finally {
fin.close();
}
File manifestFile = new File(expandDir, "META-INF/MANIFEST.MF");
if (!manifestFile.exists()) {
throw new IOException(
"Plugin installation failed. No manifest at "
+ manifestFile);
}
FileInputStream fin = new FileInputStream(manifestFile);
try {
manifest = new Manifest(fin);
} finally {
fin.close();
}
}
final Attributes atts = manifest.getMainAttributes();
@@ -327,8 +331,6 @@ public class ClassicPluginStrategy implements PluginStrategy {
if(explodeTime.exists() && explodeTime.lastModified()==archive.lastModified())
return; // no need to expand
LOGGER.info("Extracting "+archive);
// delete the contents so that old files won't interfere with new files
Util.deleteContentsRecursive(destDir);
+119 -65
View File
@@ -23,17 +23,25 @@
*/
package hudson;
import static hudson.init.InitMilestone.PLUGINS_PREPARED;
import static hudson.init.InitMilestone.PLUGINS_STARTED;
import static hudson.init.InitMilestone.PLUGINS_LISTED;
import hudson.init.InitStrategy;
import hudson.model.AbstractModelObject;
import hudson.model.Failure;
import hudson.model.Hudson;
import hudson.model.UpdateSite;
import hudson.model.UpdateCenter;
import hudson.model.UpdateSite;
import hudson.util.Service;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FileUtils;
import org.apache.commons.logging.LogFactory;
import org.jvnet.hudson.reactor.Executable;
import org.jvnet.hudson.reactor.Reactor;
import org.jvnet.hudson.reactor.TaskBuilder;
import org.jvnet.hudson.reactor.TaskGraphBuilder;
import org.kohsuke.stapler.HttpRedirect;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.QueryParameter;
@@ -44,17 +52,17 @@ import org.kohsuke.stapler.WebApp;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Map;
import java.util.HashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -102,7 +110,7 @@ public final class PluginManager extends AbstractModelObject {
/**
* Strategy for creating and initializing plugins
*/
private PluginStrategy strategy;
private final PluginStrategy strategy;
public PluginManager(ServletContext context) {
this.context = context;
@@ -112,58 +120,8 @@ public final class PluginManager extends AbstractModelObject {
rootDir = new File(Hudson.getInstance().getRootDir(),"plugins");
if(!rootDir.exists())
rootDir.mkdirs();
Collection<String> bundledPlugins = loadBundledPlugins();
File[] archives = rootDir.listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.endsWith(".hpi") // plugin jar file
|| name.endsWith(".hpl"); // linked plugin. for debugging.
}
});
if(archives==null) {
LOGGER.severe("Hudson is unable to create "+rootDir+"\nPerhaps its security privilege is insufficient");
return;
}
strategy = createPluginStrategy();
// load plugins from a system property, for use in the "mvn hudson-dev:run"
List<File> archivesList = new ArrayList<File>(Arrays.asList(archives));
String hplProperty = System.getProperty("hudson.bundled.plugins");
if (hplProperty != null) {
for (String hplLocation: hplProperty.split(",")) {
File hpl = new File(hplLocation.trim());
if (hpl.exists())
archivesList.add(hpl);
else
LOGGER.warning("bundled plugin " + hplLocation + " does not exist");
}
}
for( File arc : archivesList ) {
try {
PluginWrapper p = strategy.createPluginWrapper(arc);
p.isBundled = bundledPlugins.contains(arc.getName());
plugins.add(p);
if(p.isActive())
activePlugins.add(p);
} catch (IOException e) {
failedPlugins.add(new FailedPlugin(arc.getName(),e));
LOGGER.log(Level.SEVERE, "Failed to load a plug-in " + arc, e);
}
}
for (PluginWrapper p : activePlugins.toArray(new PluginWrapper[activePlugins.size()]))
try {
strategy.load(p);
} catch (IOException e) {
failedPlugins.add(new FailedPlugin(p.getShortName(),e));
LOGGER.log(Level.SEVERE, "Failed to load a plug-in " + p.getShortName(), e);
activePlugins.remove(p);
plugins.remove(p);
}
}
/**
@@ -171,18 +129,114 @@ public final class PluginManager extends AbstractModelObject {
* This is a separate method so that code executed from here will see a valid value in
* {@link Hudson#pluginManager}.
*/
public void initialize() {
for (PluginWrapper p : activePlugins.toArray(new PluginWrapper[activePlugins.size()])) {
strategy.initializeComponents(p);
try {
p.getPlugin().postInitialize();
} catch (Exception e) {
failedPlugins.add(new FailedPlugin(p.getShortName(), e));
LOGGER.log(Level.SEVERE, "Failed to post-initialize a plug-in " + p.getShortName(), e);
activePlugins.remove(p);
plugins.remove(p);
public TaskBuilder initTasks(final InitStrategy initStrategy) {
return new TaskGraphBuilder() {
List<File> archives;
Collection<String> bundledPlugins;
{
Handle loadBundledPlugins = add("Loading bundled plugins", new Executable() {
public void run(Reactor session) throws Exception {
bundledPlugins = loadBundledPlugins();
}
});
Handle listUpPlugins = requires(loadBundledPlugins).add("Listing up plugins", new Executable() {
public void run(Reactor session) throws Exception {
archives = initStrategy.listPluginArchives(PluginManager.this);
}
});
requires(listUpPlugins).attains(PLUGINS_LISTED).add("Preparing plugins",new Executable() {
public void run(Reactor session) throws Exception {
TaskGraphBuilder g = new TaskGraphBuilder();
final Map<String,File> inspectedShortNames = new HashMap<String,File>();
for( final File arc : archives ) {
g.followedBy().notFatal().attains(PLUGINS_LISTED).add("Inspecting plugin " + arc, new Executable() {
public void run(Reactor session) throws Exception {
try {
PluginWrapper p = strategy.createPluginWrapper(arc);
if (isDuplicate(p)) return;
p.isBundled = bundledPlugins.contains(arc.getName());
plugins.add(p);
if(p.isActive())
activePlugins.add(p);
} catch (IOException e) {
failedPlugins.add(new FailedPlugin(arc.getName(),e));
throw e;
}
}
/**
* Inspects duplication. this happens when you run hpi:run on a bundled plugin,
* as well as putting numbered hpi files, like "cobertura-1.0.hpi" and "cobertura-1.1.hpi"
*/
private boolean isDuplicate(PluginWrapper p) {
String shortName = p.getShortName();
if (inspectedShortNames.containsKey(shortName)) {
LOGGER.info("Ignoring "+arc+" because "+inspectedShortNames.get(shortName)+" is already loaded");
return true;
}
inspectedShortNames.put(shortName,arc);
return false;
}
});
}
g.requires(PLUGINS_LISTED).attains(PLUGINS_PREPARED).add("Loading plugins",new Executable() {
/**
* Once the plugins are listed, schedule their initialization.
*/
public void run(Reactor session) throws Exception {
TaskGraphBuilder g = new TaskGraphBuilder();
// schedule execution of loading plugins
for (final PluginWrapper p : activePlugins.toArray(new PluginWrapper[activePlugins.size()])) {
g.followedBy().notFatal().attains(PLUGINS_PREPARED).add("Loading plugin " + p.getShortName(), new Executable() {
public void run(Reactor session) throws Exception {
try {
strategy.load(p);
} catch (IOException e) {
failedPlugins.add(new FailedPlugin(p.getShortName(), e));
activePlugins.remove(p);
plugins.remove(p);
throw e;
}
}
});
}
// schedule execution of initializing plugins
for (final PluginWrapper p : activePlugins.toArray(new PluginWrapper[activePlugins.size()])) {
g.followedBy().notFatal().attains(PLUGINS_STARTED).add("Initializing plugin " + p.getShortName(), new Executable() {
public void run(Reactor session) throws Exception {
try {
p.getPlugin().postInitialize();
} catch (Exception e) {
failedPlugins.add(new FailedPlugin(p.getShortName(), e));
activePlugins.remove(p);
plugins.remove(p);
throw e;
}
}
});
}
// register them all
session.addAll(g.discoverTasks(session));
}
});
// register them all
session.addAll(g.discoverTasks(session));
}
});
}
}
};
}
/**
@@ -41,8 +41,7 @@ public interface PluginStrategy extends ExtensionPoint {
/**
* Creates a plugin wrapper, which provides a management interface for the plugin
* @param archive
* @return
* @throws IOException
* Either a directory that points to a pre-exploded plugin, or an hpi file, or an hpl file.
*/
public abstract PluginWrapper createPluginWrapper(File archive)
throws IOException;
+1 -2
View File
@@ -295,8 +295,7 @@ public final class PluginWrapper {
try {
plugin.stop();
} catch(Throwable t) {
System.err.println("Failed to shut down "+shortName);
System.err.println(t);
LOGGER.log(WARNING, "Failed to shut down "+shortName, t);
}
// Work around a bug in commons-logging.
// See http://www.szegedi.org/articles/memleak.html
@@ -0,0 +1,91 @@
/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, 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.init;
import org.jvnet.hudson.reactor.Executable;
import org.jvnet.hudson.reactor.Milestone;
import org.jvnet.hudson.reactor.TaskBuilder;
import org.jvnet.hudson.reactor.TaskGraphBuilder;
/**
* Various key milestone in the initialization process of Hudson.
*
* <p>
* Plugins can use these milestones to execute their initialization at the right moment
* (in addition to defining their own milestones by implementing {@link Milestone}.
*
* <p>
* These milestones are achieve in this order.
*
* @author Kohsuke Kawaguchi
*/
public enum InitMilestone implements Milestone {
/**
* The very first milestone that gets achieved without doing anything.
*
* This is used in {@link Initializer#after()} since annotations cannot have null as the default value.
*/
STARTED,
/**
* By this milestone, all plugins metadata are inspected and their dependencies figured out.
*/
PLUGINS_LISTED,
/**
* By this milestone, all plugin metadata are loaded and its classloader set up.
*/
PLUGINS_PREPARED,
/**
* By this milestone, all plugins start executing.
*
* <p>
* This is a separate milestone from {@link #PLUGINS_PREPARED} since the execution
* of a plugin often involves finding extension point implementations, which in turn
* require all the classes from all the plugins to be loadable.
*/
PLUGINS_STARTED,
/**
* By this milestone, all jobs and their build records are loaded from disk.
*/
JOB_LOADED,
/**
* The very last milestone
*
* This is used in {@link Initializer#before()} since annotations cannot have null as the default value.
*/
COMPLETED;
/**
* Creates a set of dummy tasks to enforce ordering among {@link InitMilestone}s.
*/
public static TaskBuilder ordering() {
TaskGraphBuilder b = new TaskGraphBuilder();
InitMilestone[] v = values();
for (int i=0; i<v.length-1; i++)
b.add(null, Executable.NOOP).requires(v[i]).attains(v[i+1]);
return b;
}
}
@@ -0,0 +1,22 @@
package hudson.init;
import org.jvnet.hudson.reactor.ReactorListener;
import org.kohsuke.MetaInfServices;
import hudson.model.Hudson;
/**
* {@link ReactorListener}s that get notified of the Hudson initialization process.
*
* <p>
* Because the act of initializing plugins is a part of the Hudson initialization,
* this extension point cannot be implemented in a plugin. You need to place your jar
* inside {@code WEB-INF/lib} instead.
*
* <p>
* To register, put {@link MetaInfServices} on your implementation.
*
* @author Kohsuke Kawaguchi
* @see Hudson#buildReactorListener()
*/
public interface InitReactorListener extends ReactorListener {
}
@@ -0,0 +1,101 @@
package hudson.init;
import org.kohsuke.MetaInfServices;
import org.jvnet.hudson.reactor.Task;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.logging.Logger;
import hudson.PluginManager;
import hudson.util.ServiceLoader;
/**
* Strategy pattern of the various key decision making during the Hudson initialization.
*
* <p>
* Because the act of initializing plugins is a part of the Hudson initialization,
* this extension point cannot be implemented in a plugin. You need to place your jar
* inside {@code WEB-INF/lib} instead.
*
* <p>
* To register, put {@link MetaInfServices} on your implementation.
*
* @author Kohsuke Kawaguchi
*/
public class InitStrategy {
/**
* Returns the list of *.hpi and *.hpl to expand and load.
*
* <p>
* Normally we look at {@code $HUDSON_HOME/plugins/*.hpi} and *.hpl.
*
* @return
* never null but can be empty. The list can contain different versions of the same plugin,
* and when that happens, Hudson will ignore all but the first one in the list.
*/
public List<File> listPluginArchives(PluginManager pm) throws IOException {
File[] archives = pm.rootDir.listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.endsWith(".hpi") // plugin jar file
|| name.endsWith(".hpl"); // linked plugin. for debugging.
}
});
if (archives == null)
throw new IOException("Hudson is unable to create " + pm.rootDir + "\nPerhaps its security privilege is insufficient");
List<File> r = new ArrayList<File>();
getBundledPluginsFromProperty(r);
r.addAll(Arrays.asList(archives));
return r;
}
/**
* Lists up additional bundled plugins from the system property.
*
* For use in the "mvn hudson-dev:run".
* TODO: maven-hpi-plugin should inject its own InitStrategy instead of having this in the core.
*/
protected void getBundledPluginsFromProperty(List<File> r) {
String hplProperty = System.getProperty("hudson.bundled.plugins");
if (hplProperty != null) {
for (String hplLocation : hplProperty.split(",")) {
File hpl = new File(hplLocation.trim());
if (hpl.exists())
r.add(hpl);
else
LOGGER.warning("bundled plugin " + hplLocation + " does not exist");
}
}
}
/**
* Selectively skip some of the initialization tasks.
*
* @return
* true to skip the execution.
*/
public boolean skipInitTask(Task task) {
return false;
}
/**
* Obtains the instance to be used.
*/
public static InitStrategy get(ClassLoader cl) throws IOException {
List<InitStrategy> r = ServiceLoader.load(cl, InitStrategy.class);
if (r.isEmpty()) return new InitStrategy(); // default
InitStrategy s = r.get(0);
LOGGER.fine("Using "+s+" as InitStrategy");
return s;
}
private static final Logger LOGGER = Logger.getLogger(InitStrategy.class.getName());
}
@@ -0,0 +1,92 @@
/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, 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.init;
import org.jvnet.hudson.annotation_indexer.Indexed;
import org.jvnet.hudson.reactor.Task;
import java.lang.annotation.Documented;
import static java.lang.annotation.ElementType.METHOD;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Target;
import static hudson.init.InitMilestone.STARTED;
import static hudson.init.InitMilestone.COMPLETED;
/**
* Placed on static methods to indicate that this method is to be run during the Hudson start up to perform
* some sort of initialization tasks.
*
* @author Kohsuke Kawaguchi
*/
@Indexed
@Documented
@Retention(RUNTIME)
@Target(METHOD)
public @interface Initializer {
/**
* Indicates that the specified milestone is necessary before executing this initializer.
*
* <p>
* This has the identical purpose as {@link #requires()}, but it's separated to allow better type-safety
* when using {@link InitMilestone} as a requirement (since enum member definitions need to be constant.)
*/
InitMilestone after() default STARTED;
/**
* Indicates that this initializer is a necessary step before achieving the specified milestone.
*
* <p>
* This has the identical purpose as {@link #attains()}. See {@link #after()} for why there are two things
* to achieve the same goal.
*/
InitMilestone before() default COMPLETED;
/**
* Indicates the milestones necessary before executing this initializer.
*/
String[] requires() default {};
/**
* Indicates the milestones that this initializer contributes to.
*
* A milestone is considered attained if all the initializers that attains the given milestone
* completes. So it works as a kind of join.
*/
String[] attains() default {};
/**
* Key in <tt>Messages.properties</tt> that represents what this task is about. Used for rendering the progress.
* Defaults to "${short class name}.${method Name}".
*/
String displayName() default "";
/**
* Should the failure in this task prevent Hudson from starting up?
*
* @see Task#failureIsFatal()
*/
boolean fatal() default true;
}
@@ -0,0 +1,183 @@
/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, 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.init;
import org.jvnet.hudson.annotation_indexer.Index;
import org.jvnet.hudson.reactor.Milestone;
import org.jvnet.hudson.reactor.Task;
import org.jvnet.hudson.reactor.TaskBuilder;
import org.jvnet.hudson.reactor.MilestoneImpl;
import org.jvnet.hudson.reactor.Reactor;
import org.jvnet.localizer.ResourceBundleHolder;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import hudson.model.Hudson;
/**
* Discovers initialization tasks from {@link Initializer}.
*
* @author Kohsuke Kawaguchi
*/
public class InitializerFinder extends TaskBuilder {
private final ClassLoader cl;
public InitializerFinder(ClassLoader cl) {
this.cl = cl;
}
public InitializerFinder() {
this(Thread.currentThread().getContextClassLoader());
}
public Collection<Task> discoverTasks(Reactor session) throws IOException {
List<Task> result = new ArrayList<Task>();
for (Method e : Index.list(Initializer.class,cl,Method.class)) {
if (!Modifier.isStatic(e.getModifiers()))
throw new IOException(e+" is not a static method");
Initializer i = e.getAnnotation(Initializer.class);
if (i==null) continue; // stale index
result.add(new TaskImpl(i, e));
}
return result;
}
/**
* Obtains the display name of the given initialization task
*/
protected String getDisplayNameOf(Method e, Initializer i) {
try {
Class<?> c = e.getDeclaringClass();
ResourceBundleHolder rb = ResourceBundleHolder.get(c.getClassLoader().loadClass(c.getPackage().getName() + ".Messages"));
String key = i.displayName();
if (key.length()==0) return c.getSimpleName()+"."+e.getName();
return rb.format(key);
} catch (ClassNotFoundException x) {
throw (Error)new NoClassDefFoundError(x.getMessage()+" for "+e.toString()).initCause(x);
}
}
/**
* Invokes the given initialization method.
*/
protected void invoke(Method e) {
try {
Class<?>[] pt = e.getParameterTypes();
Object[] args = new Object[pt.length];
for (int i=0; i<args.length; i++)
args[i] = lookUp(pt[i]);
e.invoke(null,args);
} catch (IllegalAccessException x) {
throw (Error)new IllegalAccessError().initCause(x);
} catch (InvocationTargetException x) {
throw new Error(x);
}
}
/**
* Determines the parameter injection of the initialization method.
*/
private Object lookUp(Class<?> type) {
if (type== Hudson.class)
return Hudson.getInstance();
throw new IllegalArgumentException("Unable to inject "+type);
}
/**
* Task implementation.
*/
public class TaskImpl implements Task {
final Collection<Milestone> requires;
final Collection<Milestone> attains;
private final Initializer i;
private final Method e;
private TaskImpl(Initializer i, Method e) {
this.i = i;
this.e = e;
requires = toMilestones(i.requires(), i.after());
attains = toMilestones(i.attains(), i.before());
}
/**
* {@link Initializer} annotaion on the {@linkplain #getMethod() method}
*/
public Initializer getAnnotation() {
return i;
}
/**
* Static method that runs the initialization, that this task wraps.
*/
public Method getMethod() {
return e;
}
public Collection<Milestone> requires() {
return requires;
}
public Collection<Milestone> attains() {
return attains;
}
public String getDisplayName() {
return getDisplayNameOf(e, i);
}
public boolean failureIsFatal() {
return i.fatal();
}
public void run(Reactor session) {
invoke(e);
}
public String toString() {
return e.toString();
}
private Collection<Milestone> toMilestones(String[] tokens, InitMilestone m) {
List<Milestone> r = new ArrayList<Milestone>();
for (String s : tokens) {
try {
r.add(InitMilestone.valueOf(s));
} catch (IllegalArgumentException x) {
r.add(new MilestoneImpl(s));
}
}
r.add(m);
return r;
}
}
}
@@ -0,0 +1,54 @@
/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, 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.init.impl;
import groovy.lang.GroovyShell;
import java.io.File;
import java.io.IOException;
import java.util.logging.Logger;
import hudson.model.Hudson;
import static hudson.init.InitMilestone.JOB_LOADED;
import hudson.init.Initializer;
/**
* Run the initialization script, if it exists.
*
* @author Kohsuke Kawaguchi
*/
public class GroovyInitScript {
@Initializer(after=JOB_LOADED)
public static void init(Hudson h) throws IOException {
File initScript = new File(h.getRootDir(),"init.groovy");
if(initScript.exists()) {
LOGGER.info("Executing "+initScript);
GroovyShell shell = new GroovyShell(h.getPluginManager().uberClassLoader);
shell.evaluate(initScript);
}
}
private static final Logger LOGGER = Logger.getLogger(GroovyInitScript.class.getName());
}
@@ -0,0 +1,48 @@
/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, 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.init.impl;
import static hudson.init.InitMilestone.JOB_LOADED;
import hudson.init.Initializer;
import hudson.model.Hudson;
import hudson.model.Messages;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
/**
* Prepares userContent folder and put a readme if it doesn't exist.
* @author Kohsuke Kawaguchi
*/
public class InitialUserContent {
@Initializer(after=JOB_LOADED)
public static void init(Hudson h) throws IOException {
File userContentDir = new File(h.getRootDir(), "userContent");
if(!userContentDir.exists()) {
userContentDir.mkdirs();
FileUtils.writeStringToFile(new File(userContentDir,"readme.txt"), Messages.Hudson_USER_CONTENT_README());
}
}
}
@@ -0,0 +1,47 @@
/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, 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.
*/
/**
* Logic for Hudson startup.
*
* <p>
* Hudson's start up is based on the same idea as the modern Unix init mechanism like initng/upstart/SMF.
* It first builds a set of {@link Task}s that are units of the initialization work, and have them declare
* dependencies among themselves. For example, jobs are only loaded after all the plugins are initialized,
* and restoring the build queue requires all the jobs to be loaded.
*
* <p>
* Such micro-scopic dependencies are organized into a bigger directed acyclic graph, which is then executed
* via {@link Session}. During execution of the reactor, additional tasks can be discovred and added to
* the DAG. We use this additional indirection to:
*
* <ol>
* <li>Perform initialization in parallel where possible.
* <li>Provide progress report on where we are in the initialization.
* <li>Collect status of the initialization and their failures.
* </ol>
*/
package hudson.init;
import org.jvnet.hudson.reactor.Task;
@@ -25,6 +25,7 @@ package hudson.logging;
import hudson.FeedAdapter;
import hudson.Functions;
import hudson.init.Initializer;
import hudson.model.AbstractModelObject;
import hudson.model.Hudson;
import hudson.model.RSS;
@@ -177,4 +178,9 @@ public class LogRecorderManager extends AbstractModelObject {
}
},req,rsp);
}
@Initializer
public static void init(Hudson h) throws IOException {
h.getLog().load();
}
}
+261 -229
View File
@@ -24,8 +24,12 @@
package hudson.model;
import com.thoughtworks.xstream.XStream;
import groovy.lang.GroovyShell;
import hudson.BulkChange;
import hudson.DescriptorExtensionList;
import hudson.Extension;
import hudson.ExtensionList;
import hudson.ExtensionListView;
import hudson.ExtensionPoint;
import hudson.FilePath;
import hudson.Functions;
import hudson.Launcher;
@@ -36,35 +40,34 @@ import hudson.PluginWrapper;
import hudson.ProxyConfiguration;
import hudson.StructuredForm;
import hudson.TcpSlaveAgentListener;
import hudson.UDPBroadcastThread;
import hudson.Util;
import static hudson.Util.fixEmpty;
import static hudson.Util.fixNull;
import hudson.WebAppMain;
import hudson.XmlFile;
import hudson.UDPBroadcastThread;
import hudson.ExtensionList;
import hudson.ExtensionPoint;
import hudson.DescriptorExtensionList;
import hudson.ExtensionListView;
import hudson.Extension;
import hudson.tools.ToolInstallation;
import hudson.tools.ToolDescriptor;
import hudson.cli.CliEntryPoint;
import hudson.cli.CliManagerImpl;
import hudson.cli.declarative.CLIMethod;
import hudson.cli.declarative.CLIResolver;
import hudson.logging.LogRecorderManager;
import static hudson.init.InitMilestone.JOB_LOADED;
import static hudson.init.InitMilestone.PLUGINS_STARTED;
import hudson.init.InitializerFinder;
import hudson.init.InitMilestone;
import hudson.init.InitReactorListener;
import hudson.init.InitStrategy;
import hudson.lifecycle.Lifecycle;
import hudson.logging.LogRecorderManager;
import hudson.lifecycle.RestartNotSupportedException;
import hudson.model.Descriptor.FormException;
import hudson.model.listeners.ItemListener;
import hudson.model.listeners.JobListener;
import hudson.model.listeners.JobListener.JobListenerAdapter;
import hudson.model.listeners.SaveableListener;
import hudson.model.listeners.SCMListener;
import hudson.model.listeners.SaveableListener;
import hudson.remoting.Channel;
import hudson.remoting.LocalChannel;
import hudson.remoting.VirtualChannel;
import hudson.remoting.Channel;
import hudson.scm.RepositoryBrowser;
import hudson.scm.SCM;
import hudson.scm.SCMDescriptor;
@@ -82,70 +85,87 @@ import hudson.security.PermissionGroup;
import hudson.security.SecurityMode;
import hudson.security.SecurityRealm;
import hudson.security.csrf.CrumbIssuer;
import hudson.slaves.ComputerListener;
import hudson.slaves.NodeProperty;
import hudson.slaves.NodePropertyDescriptor;
import hudson.slaves.RetentionStrategy;
import hudson.slaves.NodeList;
import hudson.slaves.Cloud;
import hudson.slaves.ComputerListener;
import hudson.slaves.DumbSlave;
import hudson.slaves.NodeDescriptor;
import hudson.slaves.NodeList;
import hudson.slaves.NodeProperty;
import hudson.slaves.NodePropertyDescriptor;
import hudson.slaves.NodeProvisioner;
import hudson.slaves.OfflineCause;
import hudson.slaves.RetentionStrategy;
import hudson.tasks.BuildWrapper;
import hudson.tasks.Builder;
import hudson.tasks.Mailer;
import hudson.tasks.Publisher;
import hudson.tools.ToolDescriptor;
import hudson.tools.ToolInstallation;
import hudson.triggers.Trigger;
import hudson.triggers.TriggerDescriptor;
import hudson.util.AdministrativeError;
import hudson.util.CaseInsensitiveComparator;
import hudson.util.ClockDifference;
import hudson.util.CopyOnWriteList;
import hudson.util.CopyOnWriteMap;
import hudson.util.DaemonThreadFactory;
import hudson.util.DescribableList;
import hudson.util.FormValidation;
import hudson.util.Futures;
import hudson.util.HudsonIsLoading;
import hudson.util.HudsonIsRestarting;
import hudson.util.Iterators;
import hudson.util.Memoizer;
import hudson.util.MultipartFormDataParser;
import hudson.util.RemotingDiagnostics;
import hudson.util.TextFile;
import hudson.util.XStream2;
import hudson.util.HudsonIsRestarting;
import hudson.util.DescribableList;
import hudson.util.Futures;
import hudson.util.Memoizer;
import hudson.util.Iterators;
import hudson.util.FormValidation;
import hudson.util.VersionNumber;
import hudson.util.StreamTaskListener;
import hudson.util.AdministrativeError;
import hudson.util.TextFile;
import hudson.util.VersionNumber;
import hudson.util.XStream2;
import hudson.util.ServiceLoader;
import hudson.widgets.Widget;
import net.sf.json.JSONObject;
import org.acegisecurity.*;
import org.acegisecurity.AccessDeniedException;
import org.acegisecurity.AcegiSecurityException;
import org.acegisecurity.Authentication;
import org.acegisecurity.GrantedAuthority;
import org.acegisecurity.GrantedAuthorityImpl;
import org.acegisecurity.context.SecurityContextHolder;
import org.acegisecurity.providers.anonymous.AnonymousAuthenticationToken;
import org.acegisecurity.ui.AbstractProcessingFilter;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.jelly.Script;
import org.apache.commons.jelly.JellyException;
import org.apache.commons.io.FileUtils;
import org.apache.commons.jelly.Script;
import org.apache.commons.logging.LogFactory;
import org.jvnet.hudson.reactor.Executable;
import org.jvnet.hudson.reactor.ReactorException;
import org.jvnet.hudson.reactor.Task;
import org.jvnet.hudson.reactor.TaskBuilder;
import org.jvnet.hudson.reactor.TaskGraphBuilder;
import org.jvnet.hudson.reactor.Milestone;
import org.jvnet.hudson.reactor.Reactor;
import org.jvnet.hudson.reactor.ReactorListener;
import org.jvnet.hudson.reactor.TaskGraphBuilder.Handle;
import org.kohsuke.stapler.Ancestor;
import org.kohsuke.stapler.HttpRedirect;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.HttpResponses;
import org.kohsuke.stapler.MetaClass;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.StaplerFallback;
import org.kohsuke.stapler.StaplerProxy;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.StaplerFallback;
import org.kohsuke.stapler.WebApp;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.HttpRedirect;
import org.kohsuke.stapler.HttpResponses;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.jelly.JellyClassLoaderTearOff;
import org.kohsuke.stapler.jelly.JellyRequestDispatcher;
import org.kohsuke.stapler.framework.adjunct.AdjunctManager;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;
import org.kohsuke.stapler.framework.adjunct.AdjunctManager;
import org.kohsuke.stapler.jelly.JellyClassLoaderTearOff;
import org.kohsuke.stapler.jelly.JellyRequestDispatcher;
import org.xml.sax.InputSource;
import javax.crypto.SecretKey;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
@@ -157,14 +177,15 @@ import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.InputStream;
import java.net.URL;
import java.io.PrintWriter;
import java.net.BindException;
import java.net.URL;
import java.nio.charset.Charset;
import java.security.SecureRandom;
import java.text.Collator;
import java.text.NumberFormat;
import java.text.ParseException;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -176,32 +197,29 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.Stack;
import java.util.StringTokenizer;
import java.util.Timer;
import java.util.TreeSet;
import java.util.Properties;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.logging.Level;
import static java.util.logging.Level.SEVERE;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import static java.util.logging.Level.SEVERE;
import java.util.regex.Pattern;
import java.nio.charset.Charset;
import javax.servlet.RequestDispatcher;
import javax.crypto.SecretKey;
/**
* Root object of the system.
@@ -509,7 +527,7 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
*/
private transient final LogRecorderManager log = new LogRecorderManager();
public Hudson(File root, ServletContext context) throws IOException, InterruptedException {
public Hudson(File root, ServletContext context) throws IOException, InterruptedException, ReactorException {
// As hudson is starting, grant this process full controll
SecurityContextHolder.getContext().setAuthentication(ACL.SYSTEM);
try {
@@ -520,8 +538,9 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
throw new IllegalStateException("second instance");
theInstance = this;
log.load();
// doing this early allows InitStrategy to set environment upfront
final InitStrategy is = InitStrategy.get(Thread.currentThread().getContextClassLoader());
Trigger.timer = new Timer("Hudson cron thread");
queue = new Queue(CONSISTENT_HASH?LoadBalancer.CONSISTENT_HASH:LoadBalancer.DEFAULT);
@@ -551,30 +570,18 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
} catch (IOException e) {
LOGGER.log(SEVERE, "Failed to load proxy configuration", e);
}
// load plugins.
pluginManager = new PluginManager(context);
pluginManager.initialize();
// if we are loading old data that doesn't have this field
if(slaves==null) slaves = new NodeList();
pluginManager = new PluginManager(context);
adjuncts = new AdjunctManager(servletContext, pluginManager.uberClassLoader,"adjuncts/"+VERSION_HASH);
load();
// try {
// // fill up the cache
// load();
//
// Controller c = new Controller();
// c.startCPUProfiling(ProfilingModes.CPU_TRACING,""); // "java.*");
// load();
// c.stopCPUProfiling();
// c.captureSnapshot(ProfilingModes.SNAPSHOT_WITHOUT_HEAP);
// } catch (Exception e) {
// throw new Error(e);
// }
// initialization consists of ...
executeReactor( is,
new InitializerFinder(), // misc. stuff
pluginManager.initTasks(is), // loading and preparing plugins
loadTasks(), // load jobs
InitMilestone.ordering() // forced ordering among key milestones
);
if(slaveAgentPort!=-1) {
try {
@@ -599,54 +606,89 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
cl.onOnline(c,new StreamTaskListener(System.out));
}
getQueue().load();
for (ItemListener l : ItemListener.all())
l.onLoaded();
// run the initialization script, if it exists.
File initScript = new File(getRootDir(),"init.groovy");
if(initScript.exists()) {
LOGGER.info("Executing "+initScript);
GroovyShell shell = new GroovyShell(pluginManager.uberClassLoader);
try {
shell.evaluate(initScript);
} catch (Throwable t) {
t.printStackTrace();
}
}
File userContentDir = new File(getRootDir(), "userContent");
if(!userContentDir.exists()) {
userContentDir.mkdirs();
FileUtils.writeStringToFile(new File(userContentDir,"readme.txt"),Messages.Hudson_USER_CONTENT_README());
}
updateCenter.load(); // this has to wait until after all plugins load, to let custom UpdateCenterConfiguration take effect first.
Trigger.init();
// pending SEZPOZ-8
// // invoke post initialization methods
// for ( IndexItem<PostInit,Void> i : Index.load(PostInit.class, Void.class, pluginManager.uberClassLoader)) {
// try {
// Method m = (Method)i.element();
// if (Modifier.isStatic(m.getModifiers()))
// m.invoke(null);
// else
// LOGGER.severe(m+" is annotated with @PostInit but it's not a static method");
// } catch (InstantiationException e) {
// LOGGER.log(SEVERE,"Failed to invoke @PostInit: "+i,e);
// } catch (IllegalAccessException e) {
// LOGGER.log(SEVERE,"Failed to invoke @PostInit: "+i,e);
// } catch (InvocationTargetException e) {
// LOGGER.log(SEVERE,"Failed to invoke @PostInit: "+i,e);
// }
// }
} finally {
SecurityContextHolder.clearContext();
}
}
/**
* Executes a reactor.
*
* @param is
* If non-null, this can be consulted for ignoring some tasks. Only used during the initialization of Hudson.
*/
private void executeReactor(final InitStrategy is, TaskBuilder... builders) throws IOException, InterruptedException, ReactorException {
Reactor reactor = new Reactor(builders) {
/**
* Sets the thread name to the task for better diagnostics.
*/
protected void runTask(Task task) throws Exception {
if (is!=null && is.skipInitTask(task)) return;
SecurityContextHolder.getContext().setAuthentication(ACL.SYSTEM); // full access in the initialization thread
String taskName = task.getDisplayName();
Thread t = Thread.currentThread();
String name = t.getName();
if (taskName !=null)
t.setName(taskName);
try {
long start = System.currentTimeMillis();
super.runTask(task);
if(LOG_STARTUP_PERFORMANCE)
LOGGER.info(String.format("Took %dms for %s by %s",
System.currentTimeMillis()-start, taskName, name));
} finally {
t.setName(name);
SecurityContextHolder.clearContext();
}
}
};
ExecutorService es;
if (PARALLEL_LOAD)
es = new ThreadPoolExecutor(
TWICE_CPU_NUM, TWICE_CPU_NUM, 5L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), new DaemonThreadFactory());
else
es = Executors.newSingleThreadExecutor(new DaemonThreadFactory());
try {
reactor.execute(es,buildReactorListener());
} finally {
es.shutdownNow(); // upon a successful return the executor queue should be empty. Upon an exception, we want to cancel all pending tasks
}
}
/**
* Aggregates all the listeners into one and returns it.
*
* <p>
* At this point plugins are not loaded yet, so we fall back to the META-INF/services look up to discover implementations.
* As such there's no way for plugins to participate into this process.
*/
private ReactorListener buildReactorListener() throws IOException {
List<ReactorListener> r = (List) ServiceLoader.load(Thread.currentThread().getContextClassLoader(), InitReactorListener.class);
r.add(new ReactorListener() {
public void onTaskStarted(Task t) {
LOGGER.fine("Started "+t.getDisplayName());
}
public void onTaskCompleted(Task t) {
LOGGER.fine("Completed "+t.getDisplayName());
}
public void onTaskFailed(Task t, Throwable err, boolean fatal) {
LOGGER.log(Level.SEVERE, "Failed "+t.getDisplayName(),err);
}
public void onAttained(Milestone milestone) {
LOGGER.fine("Attained "+milestone.toString());
}
});
return new ReactorListener.Aggregator(r);
}
public TcpSlaveAgentListener getTcpSlaveAgentListener() {
return tcpSlaveAgentListener;
}
@@ -1036,7 +1078,7 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
} else {
if(n.getNumExecutors()>0) {
computers.put(n,c=n.createComputer());
if (!n.holdOffLaunchUntilSave) {
if (!n.holdOffLaunchUntilSave && AUTOMATIC_SLAVE_LAUNCH) {
RetentionStrategy retentionStrategy = c.getRetentionStrategy();
if (retentionStrategy != null) {
// if there is a retention strategy, it is responsible for deciding to start the computer
@@ -1998,18 +2040,7 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
return new MasterComputer();
}
private synchronized void load() throws IOException {
long startTime = System.currentTimeMillis();
XmlFile cfg = getConfigFile();
if(cfg.exists()) {
// reset some data that may not exit in the disk file
// so that we can take a proper compensation action later.
primaryView = null;
views.clear();
cfg.unmarshal(this);
}
clouds.setOwner(this);
private synchronized TaskBuilder loadTasks() throws IOException {
File projectsDir = new File(root,"jobs");
if(!projectsDir.isDirectory() && !projectsDir.mkdirs()) {
if(projectsDir.exists())
@@ -2021,110 +2052,95 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
return child.isDirectory() && Items.getConfigFile(child).exists();
}
});
items.clear();
if(PARALLEL_LOAD) {
// load jobs in parallel for better performance
LOGGER.info("Loading in "+TWICE_CPU_NUM+" parallel threads");
List<Future<TopLevelItem>> loaders = new ArrayList<Future<TopLevelItem>>();
for (final File subdir : subdirs) {
loaders.add(threadPoolForLoad.submit(new Callable<TopLevelItem>() {
public TopLevelItem call() throws Exception {
Thread t = Thread.currentThread();
String name = t.getName();
t.setName("Loading "+subdir);
try {
long start = System.currentTimeMillis();
TopLevelItem item = (TopLevelItem) Items.load(Hudson.this, subdir);
if(LOG_STARTUP_PERFORMANCE)
LOGGER.info("Loaded "+item.getName()+" in "+(System.currentTimeMillis()-start)+"ms by "+name);
return item;
} finally {
t.setName(name);
}
}
}));
}
for (Future<TopLevelItem> loader : loaders) {
try {
TopLevelItem item = loader.get();
items.put(item.getName(), item);
} catch (ExecutionException e) {
LOGGER.log(Level.WARNING, "Failed to load a project",e.getCause());
} catch (InterruptedException e) {
e.printStackTrace(); // this is probably not the right thing to do
TaskGraphBuilder g = new TaskGraphBuilder();
Handle loadHudson = g.requires(PLUGINS_STARTED).attains(JOB_LOADED).add("Loading global config", new Executable() {
public void run(Reactor session) throws Exception {
XmlFile cfg = getConfigFile();
if (cfg.exists()) {
// reset some data that may not exit in the disk file
// so that we can take a proper compensation action later.
primaryView = null;
views.clear();
// load from disk
cfg.unmarshal(Hudson.this);
}
// if we are loading old data that doesn't have this field
if (slaves == null) slaves = new NodeList();
clouds.setOwner(Hudson.this);
items.clear();
}
} else {
for (File subdir : subdirs) {
try {
long start = System.currentTimeMillis();
TopLevelItem item = (TopLevelItem)Items.load(this,subdir);
if(LOG_STARTUP_PERFORMANCE)
LOGGER.info("Loaded "+item.getName()+" in "+(System.currentTimeMillis()-start)+"ms");
});
for (final File subdir : subdirs) {
g.requires(loadHudson).attains(JOB_LOADED).notFatal().add("Loading job "+subdir.getName(),new Executable() {
public void run(Reactor session) throws Exception {
TopLevelItem item = (TopLevelItem) Items.load(Hudson.this, subdir);
items.put(item.getName(), item);
} catch (Error e) {
LOGGER.log(Level.WARNING, "Failed to load "+subdir,e);
} catch (RuntimeException e) {
LOGGER.log(Level.WARNING, "Failed to load "+subdir,e);
} catch (IOException e) {
LOGGER.log(Level.WARNING, "Failed to load "+subdir,e);
}
});
}
g.requires(JOB_LOADED).add("Finalizing set up",new Executable() {
public void run(Reactor session) throws Exception {
rebuildDependencyGraph();
{// recompute label objects - populates the labels mapping.
for (Node slave : slaves)
// Note that not all labels are visible until the slaves have connected.
slave.getAssignedLabels();
getAssignedLabels();
}
// initialize views by inserting the default view if necessary
// this is both for clean Hudson and for backward compatibility.
if(views.size()==0 || primaryView==null) {
View v = new AllView(Messages.Hudson_ViewName());
v.owner = Hudson.this;
views.add(0,v);
primaryView = v.getViewName();
}
// read in old data that doesn't have the security field set
if(authorizationStrategy==null) {
if(useSecurity==null || !useSecurity)
authorizationStrategy = AuthorizationStrategy.UNSECURED;
else
authorizationStrategy = new LegacyAuthorizationStrategy();
}
if(securityRealm==null) {
if(useSecurity==null || !useSecurity)
setSecurityRealm(SecurityRealm.NO_AUTHENTICATION);
else
setSecurityRealm(new LegacySecurityRealm());
} else {
// force the set to proxy
setSecurityRealm(securityRealm);
}
if(useSecurity!=null && !useSecurity) {
// forced reset to the unsecure mode.
// this works as an escape hatch for people who locked themselves out.
authorizationStrategy = AuthorizationStrategy.UNSECURED;
setSecurityRealm(SecurityRealm.NO_AUTHENTICATION);
}
// Initialize the filter with the crumb issuer
setCrumbIssuer(crumbIssuer);
// auto register root actions
for (Action a : getExtensionList(RootAction.class))
if (!actions.contains(a)) actions.add(a);
if(KILL_AFTER_LOAD)
System.exit(0);
}
}
rebuildDependencyGraph();
});
{// recompute label objects - populates the labels mapping.
for (Node slave : slaves)
// Note that all labels are not visible until the slaves have
// connected.
slave.getAssignedLabels();
getAssignedLabels();
}
// initialize views by inserting the default view if necessary
// this is both for clean Hudson and for backward compatibility.
if(views.size()==0 || primaryView==null) {
View v = new AllView(Messages.Hudson_ViewName());
v.owner = this;
views.add(0,v);
primaryView = v.getViewName();
}
// read in old data that doesn't have the security field set
if(authorizationStrategy==null) {
if(useSecurity==null || !useSecurity)
authorizationStrategy = AuthorizationStrategy.UNSECURED;
else
authorizationStrategy = new LegacyAuthorizationStrategy();
}
if(securityRealm==null) {
if(useSecurity==null || !useSecurity)
setSecurityRealm(SecurityRealm.NO_AUTHENTICATION);
else
setSecurityRealm(new LegacySecurityRealm());
} else {
// force the set to proxy
setSecurityRealm(securityRealm);
}
if(useSecurity!=null && !useSecurity) {
// forced reset to the unsecure mode.
// this works as an escape hatch for people who locked themselves out.
authorizationStrategy = AuthorizationStrategy.UNSECURED;
setSecurityRealm(SecurityRealm.NO_AUTHENTICATION);
}
// Initialize the filter with the crumb issuer
setCrumbIssuer(crumbIssuer);
// auto register root actions
for (Action a : getExtensionList(RootAction.class))
if (!actions.contains(a)) actions.add(a);
LOGGER.info(String.format("Took %s ms to load",System.currentTimeMillis()-startTime));
if(KILL_AFTER_LOAD)
System.exit(0);
return g;
}
/**
@@ -2396,14 +2412,14 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
}
@CLIMethod(name="quiet-down")
public synchronized HttpRedirect doQuietDown() throws IOException, ServletException {
public synchronized HttpRedirect doQuietDown() {
checkPermission(ADMINISTER);
isQuietingDown = true;
return new HttpRedirect(".");
}
@CLIMethod(name="cancel-quiet-down")
public synchronized HttpRedirect doCancelQuietDown() throws IOException, ServletException {
public synchronized HttpRedirect doCancelQuietDown() {
checkPermission(ADMINISTER);
isQuietingDown = false;
getQueue().scheduleMaintenance();
@@ -2703,11 +2719,13 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
@Override
public void run() {
try {
load();
User.reload();
servletContext.setAttribute("app",Hudson.this);
reload();
} catch (IOException e) {
LOGGER.log(SEVERE,"Failed to reload Hudson config",e);
} catch (ReactorException e) {
LOGGER.log(SEVERE,"Failed to reload Hudson config",e);
} catch (InterruptedException e) {
LOGGER.log(SEVERE,"Failed to reload Hudson config",e);
}
}
}.start();
@@ -2715,6 +2733,15 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
return HttpResponses.redirectViaContextPath("/");
}
/**
* Reloads the configuration synchronously.
*/
public void reload() throws IOException, InterruptedException, ReactorException {
executeReactor(null,loadTasks());
User.reload();
servletContext.setAttribute("app", this);
}
/**
* Do a finger-print check.
*/
@@ -3546,6 +3573,11 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
*/
public static boolean CONCURRENT_BUILD = true;
/**
* Automatically try to launch a slave when Hudson is initialized or a new slave is created.
*/
public static boolean AUTOMATIC_SLAVE_LAUNCH = true;
private static final Logger LOGGER = Logger.getLogger(Hudson.class.getName());
private static final Pattern ICON_SIZE = Pattern.compile("\\d+x\\d+");
@@ -29,6 +29,8 @@ import hudson.ExtensionPoint;
import hudson.Util;
import hudson.XmlFile;
import hudson.matrix.MatrixConfiguration;
import hudson.init.Initializer;
import static hudson.init.InitMilestone.JOB_LOADED;
import hudson.cli.declarative.CLIMethod;
import hudson.cli.declarative.CLIResolver;
import hudson.remoting.AsyncFutureImpl;
@@ -46,6 +48,7 @@ import hudson.util.OneShotEvent;
import hudson.util.TimeUnit2;
import hudson.util.XStream2;
import hudson.util.ConsistentHash;
import hudson.util.DoubleLaunchChecker;
import hudson.util.ConsistentHash.Hash;
import java.io.BufferedReader;
@@ -1573,4 +1576,12 @@ public class Queue extends ResourceController implements Saveable {
public static Queue getInstance() {
return Hudson.getInstance().getQueue();
}
/**
* Restores the queue content during the start up.
*/
@Initializer(after=JOB_LOADED)
public static void init(Hudson h) {
h.getQueue().load();
}
}
@@ -32,6 +32,11 @@ import hudson.PluginWrapper;
import hudson.ProxyConfiguration;
import hudson.Util;
import hudson.XmlFile;
import hudson.triggers.SafeTimerTask;
import hudson.init.Initializer;
import hudson.init.InitMilestone;
import static hudson.init.InitMilestone.JOB_LOADED;
import static hudson.init.InitMilestone.PLUGINS_STARTED;
import hudson.lifecycle.Lifecycle;
import hudson.model.UpdateSite.Data;
import hudson.model.UpdateSite.Plugin;
@@ -40,6 +45,7 @@ import hudson.util.DaemonThreadFactory;
import hudson.util.IOException2;
import hudson.util.PersistedList;
import hudson.util.XStream2;
import hudson.util.DoubleLaunchChecker;
import org.acegisecurity.Authentication;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.input.CountingInputStream;
@@ -839,6 +845,16 @@ public class UpdateCenter extends AbstractModelObject implements Saveable {
}
}
/**
* Initializes the update center.
*
* This has to wait until after all plugins load, to let custom UpdateCenterConfiguration take effect first.
*/
@Initializer(after=PLUGINS_STARTED)
public static void init(Hudson h) throws IOException {
h.getUpdateCenter().load();
}
/**
* Sequence number generator.
*/
@@ -849,6 +865,7 @@ public class UpdateCenter extends AbstractModelObject implements Saveable {
public static boolean neverUpdate = Boolean.getBoolean(UpdateCenter.class.getName()+".never");
public static final XStream2 XSTREAM = new XStream2();
static {
XSTREAM.alias("site",UpdateSite.class);
XSTREAM.alias("sites",PersistedList.class);
@@ -29,6 +29,9 @@ import hudson.DependencyRunner.ProjectRunnable;
import hudson.ExtensionPoint;
import hudson.DescriptorExtensionList;
import hudson.Extension;
import hudson.init.Initializer;
import hudson.init.InitMilestone;
import static hudson.init.InitMilestone.JOB_LOADED;
import hudson.model.AbstractProject;
import hudson.model.Action;
import hudson.model.Build;
@@ -250,6 +253,7 @@ public abstract class Trigger<J extends Item> implements Describable<Trigger<?>>
*/
public static Timer timer;
@Initializer(after=JOB_LOADED)
public static void init() {
new DoubleLaunchChecker().schedule();
@@ -0,0 +1,50 @@
package hudson.util;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import static java.util.logging.Level.WARNING;
import java.util.logging.Logger;
/**
* Poorman's clone of JDK6 ServiceLoader.
*
* @author Kohsuke Kawaguchi
*/
public class ServiceLoader {
public static <T> List<T> load(ClassLoader classLoader, Class<T> type) throws IOException {
List<T> result = new ArrayList<T>();
final Enumeration<URL> e = classLoader.getResources("META-INF/services/"+type.getName());
while (e.hasMoreElements()) {
URL url = e.nextElement();
BufferedReader configFile = new BufferedReader(new InputStreamReader(url.openStream(),"UTF-8"));
String line;
while ((line = configFile.readLine()) != null) {
line = line.trim();
if (line.startsWith("#") || line.length()==0) continue;
try {
Class<?> t = classLoader.loadClass(line);
if (!type.isAssignableFrom(t)) continue; // invalid type
result.add(type.cast(t.newInstance()));
} catch (ClassNotFoundException x) {
LOGGER.log(WARNING,"Failed to load "+line,x);
} catch (InstantiationException x) {
LOGGER.log(WARNING,"Failed to load "+line,x);
} catch (IllegalAccessException x) {
LOGGER.log(WARNING,"Failed to load "+line,x);
}
}
}
return result;
}
private static final Logger LOGGER = Logger.getLogger(ServiceLoader.class.getName());
}