提交 c6d81ef2 编写于 作者: K Kohsuke Kawaguchi

hooking up the dynamic loading logic into the UI

上级 f4474cc8
......@@ -34,10 +34,13 @@ import hudson.model.UpdateSite;
import hudson.util.CyclicGraphDetector;
import hudson.util.CyclicGraphDetector.CycleDetectedException;
import hudson.util.FormValidation;
import hudson.util.IOException2;
import hudson.util.PersistedList;
import hudson.util.Service;
import jenkins.ClassLoaderReflectionToolkit;
import jenkins.InitReactorRunner;
import jenkins.RestartRequiredException;
import jenkins.YesNoMaybe;
import jenkins.model.Jenkins;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
......@@ -47,6 +50,7 @@ import org.apache.commons.io.FilenameUtils;
import org.apache.commons.logging.LogFactory;
import org.jvnet.hudson.reactor.Executable;
import org.jvnet.hudson.reactor.Reactor;
import org.jvnet.hudson.reactor.ReactorException;
import org.jvnet.hudson.reactor.TaskBuilder;
import org.jvnet.hudson.reactor.TaskGraphBuilder;
import org.kohsuke.stapler.HttpRedirect;
......@@ -124,7 +128,7 @@ public abstract class PluginManager extends AbstractModelObject {
/**
* Once plugin is uploaded, this flag becomes true.
* This is used to report a message that Hudson needs to be restarted
* This is used to report a message that Jenkins needs to be restarted
* for new plugins to take effect.
*/
public volatile boolean pluginUploaded = false;
......@@ -330,10 +334,15 @@ public abstract class PluginManager extends AbstractModelObject {
/**
* TODO: revisit where/how to expose this. This is an experiment.
*/
public void dynamicLoad(File arc) throws Exception {
public void dynamicLoad(File arc) throws IOException, InterruptedException, RestartRequiredException {
LOGGER.info("Attempting to dynamic load "+arc);
final PluginWrapper p = strategy.createPluginWrapper(arc);
if (getPlugin(p.getShortName())!=null)
throw new IllegalArgumentException("Dynamic reloading isn't possible");
String sn = p.getShortName();
if (getPlugin(sn)!=null)
throw new RestartRequiredException(Messages._PluginManager_PluginIsAlreadyInstalled_RestartRequired(sn));
if (p.supportsDynamicLoad()== YesNoMaybe.NO)
throw new RestartRequiredException(Messages._PluginManager_PluginDoesntSupportDynamicLoad_RestartRequired(sn));
// there's no need to do cyclic dependency check, because we are deploying one at a time,
// so existing plugins can't be depending on this newly deployed one.
......@@ -349,10 +358,10 @@ public abstract class PluginManager extends AbstractModelObject {
p.getPlugin().postInitialize();
} catch (Exception e) {
failedPlugins.add(new FailedPlugin(p.getShortName(), e));
failedPlugins.add(new FailedPlugin(sn, e));
activePlugins.remove(p);
plugins.remove(p);
throw e;
throw new IOException2("Failed to install "+ sn +" plugin",e);
}
// run initializers in the added plugin
......@@ -363,7 +372,12 @@ public abstract class PluginManager extends AbstractModelObject {
return e.getDeclaringClass().getClassLoader()!=p.classLoader || super.filter(e);
}
}.discoverTasks(r));
new InitReactorRunner().run(r);
try {
new InitReactorRunner().run(r);
} catch (ReactorException e) {
throw new IOException2("Failed to initialize "+ sn +" plugin",e);
}
LOGGER.info("Plugin " + sn + " dynamically installed");
}
/**
......@@ -559,6 +573,8 @@ public abstract class PluginManager extends AbstractModelObject {
* Performs the installation of the plugins.
*/
public void doInstall(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
boolean dynamicLoad = req.getParameter("dynamicLoad")!=null;
Enumeration<String> en = req.getParameterNames();
while (en.hasMoreElements()) {
String n = en.nextElement();
......@@ -569,7 +585,7 @@ public abstract class PluginManager extends AbstractModelObject {
UpdateSite.Plugin p = Jenkins.getInstance().getUpdateCenter().getById(pluginInfo[1]).getPlugin(pluginInfo[0]);
if(p==null)
throw new Failure("No such plugin: "+n);
p.deploy();
p.deploy(dynamicLoad);
}
}
}
......
......@@ -25,6 +25,7 @@
package hudson;
import hudson.PluginManager.PluginInstanceStore;
import jenkins.YesNoMaybe;
import jenkins.model.Jenkins;
import hudson.model.UpdateCenter;
import hudson.model.UpdateSite;
......@@ -290,6 +291,15 @@ public class PluginWrapper implements Comparable<PluginWrapper> {
return shortName;
}
/**
* Does this plugin supports dynamic loading?
*/
public YesNoMaybe supportsDynamicLoad() {
String v = manifest.getMainAttributes().getValue("Support-Dynamic-Loading");
if (v==null) return YesNoMaybe.MAYBE;
return Boolean.parseBoolean(v) ? YesNoMaybe.YES : YesNoMaybe.NO;
}
/**
* Returns the version number of this plugin
*/
......
......@@ -25,6 +25,7 @@ package hudson.cli;
import hudson.Extension;
import hudson.FilePath;
import hudson.PluginManager;
import hudson.util.IOException2;
import jenkins.model.Jenkins;
import hudson.model.UpdateSite;
......@@ -65,9 +66,13 @@ public class InstallPluginCommand extends CLICommand {
@Option(name="-restart",usage="Restart Jenkins upon successful installation")
public boolean restart;
@Option(name="-deploy",usage="Deploy plugins right away without postponing them until the reboot.")
public boolean dynamicLoad;
protected int run() throws Exception {
Jenkins h = Jenkins.getInstance();
h.checkPermission(Jenkins.ADMINISTER);
PluginManager pm = h.getPluginManager();
for (String source : sources) {
// is this a file?
......@@ -76,7 +81,9 @@ public class InstallPluginCommand extends CLICommand {
stdout.println(Messages.InstallPluginCommand_InstallingPluginFromLocalFile(f));
if (name==null)
name = f.getBaseName();
f.copyTo(getTargetFile());
f.copyTo(getTargetFilePath());
if (dynamicLoad)
pm.dynamicLoad(getTargetFile());
continue;
}
......@@ -91,7 +98,9 @@ public class InstallPluginCommand extends CLICommand {
int idx = name.lastIndexOf('.');
if (idx>0) name = name.substring(0,idx);
}
getTargetFile().copyFrom(u);
getTargetFilePath().copyFrom(u);
if (dynamicLoad)
pm.dynamicLoad(getTargetFile());
continue;
} catch (MalformedURLException e) {
// not an URL
......@@ -101,7 +110,7 @@ public class InstallPluginCommand extends CLICommand {
UpdateSite.Plugin p = h.getUpdateCenter().getPlugin(source);
if (p!=null) {
stdout.println(Messages.InstallPluginCommand_InstallingFromUpdateCenter(source));
Throwable e = p.deploy().get().getError();
Throwable e = p.deploy(dynamicLoad).get().getError();
if (e!=null)
throw new IOException2("Failed to install plugin "+source,e);
continue;
......@@ -134,7 +143,11 @@ public class InstallPluginCommand extends CLICommand {
return 0; // all success
}
private FilePath getTargetFile() {
return new FilePath(new File(Jenkins.getInstance().getPluginManager().rootDir,name+".hpi"));
private FilePath getTargetFilePath() {
return new FilePath(getTargetFile());
}
private File getTargetFile() {
return new File(Jenkins.getInstance().getPluginManager().rootDir,name+".hpi");
}
}
......@@ -36,6 +36,7 @@ import static hudson.init.InitMilestone.PLUGINS_STARTED;
import hudson.init.Initializer;
import hudson.lifecycle.Lifecycle;
import hudson.lifecycle.RestartNotSupportedException;
import hudson.model.UpdateCenter.DownloadJob;
import hudson.model.UpdateSite.Data;
import hudson.model.UpdateSite.Plugin;
import hudson.model.listeners.SaveableListener;
......@@ -45,10 +46,12 @@ import hudson.util.HttpResponses;
import hudson.util.IOException2;
import hudson.util.PersistedList;
import hudson.util.XStream2;
import jenkins.RestartRequiredException;
import jenkins.model.Jenkins;
import org.acegisecurity.Authentication;
import org.apache.commons.io.input.CountingInputStream;
import org.apache.commons.io.output.NullOutputStream;
import org.jvnet.localizer.Localizable;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
......@@ -951,6 +954,10 @@ public class UpdateCenter extends AbstractModelObject implements Saveable {
LOGGER.info("Installation successful: "+getName());
status = new Success();
onSuccess();
} catch (RestartRequiredException e) {
status = new SuccessButRequiresRestart(e.message);
LOGGER.log(Level.INFO, "Installation successful but restart required: "+getName(), e);
onSuccess();
} catch (Throwable e) {
LOGGER.log(Level.SEVERE, "Failed to install "+getName(),e);
status = new Failure(e);
......@@ -958,7 +965,7 @@ public class UpdateCenter extends AbstractModelObject implements Saveable {
}
}
protected void _run() throws IOException {
protected void _run() throws IOException, RestartRequiredException {
URL src = getURL();
config.preValidate(this, src);
......@@ -1011,6 +1018,23 @@ public class UpdateCenter extends AbstractModelObject implements Saveable {
}
}
/**
* Indicates that the installation was successful but a restart is needed.
*
* @see
*/
public class SuccessButRequiresRestart extends InstallationStatus {
private final Localizable message;
public SuccessButRequiresRestart(Localizable message) {
this.message = message;
}
public String getMessage() {
return message.toString();
}
}
/**
* Indicates that the plugin was successfully installed.
*/
......@@ -1052,9 +1076,22 @@ public class UpdateCenter extends AbstractModelObject implements Saveable {
private final PluginManager pm = Jenkins.getInstance().getPluginManager();
/**
* True to load the plugin into this Jenkins, false to wait until restart.
*/
private final boolean dynamicLoad;
/**
* @deprecated as of 1.DynamicExtensionFinder
*/
public InstallationJob(Plugin plugin, UpdateSite site, Authentication auth) {
this(plugin,site,auth,false);
}
public InstallationJob(Plugin plugin, UpdateSite site, Authentication auth, boolean dynamicLoad) {
super(site, auth);
this.plugin = plugin;
this.dynamicLoad = dynamicLoad;
}
protected URL getURL() throws MalformedURLException {
......@@ -1071,7 +1108,7 @@ public class UpdateCenter extends AbstractModelObject implements Saveable {
}
@Override
public void _run() throws IOException {
public void _run() throws IOException, RestartRequiredException {
super._run();
// if this is a bundled plugin, make sure it won't get overwritten
......@@ -1083,6 +1120,16 @@ public class UpdateCenter extends AbstractModelObject implements Saveable {
} finally {
SecurityContextHolder.clearContext();
}
if (dynamicLoad) {
try {
pm.dynamicLoad(getDestination());
} catch (RestartRequiredException e) {
throw e; // pass through
} catch (Exception e) {
throw new IOException2("Failed to dynamically deploy this plugin",e);
}
}
}
protected void onSuccess() {
......
......@@ -45,6 +45,7 @@ import org.jvnet.hudson.crypto.CertificateUtil;
import org.jvnet.hudson.crypto.SignatureOutputStream;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
......@@ -689,21 +690,29 @@ public class UpdateSite {
deploy();
}
public Future<UpdateCenterJob> deploy() {
return deploy(false);
}
/**
* Schedules the installation of this plugin.
*
* <p>
* This is mainly intended to be called from the UI. The actual installation work happens
* asynchronously in another thread.
*
* @param dynamicLoad
* If true, the plugin will be dynamically loaded into this Jenkins. If false,
* the plugin will only take effect after the reboot.
*/
public Future<UpdateCenterJob> deploy() {
public Future<UpdateCenterJob> deploy(boolean dynamicLoad) {
Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER);
UpdateCenter uc = Jenkins.getInstance().getUpdateCenter();
for (Plugin dep : getNeededDependencies()) {
LOGGER.log(Level.WARNING, "Adding dependent install of " + dep.name + " for plugin " + name);
dep.deploy();
dep.deploy(dynamicLoad);
}
return uc.addJob(uc.new InstallationJob(this, UpdateSite.this, Jenkins.getAuthentication()));
return uc.addJob(uc.new InstallationJob(this, UpdateSite.this, Jenkins.getAuthentication(), dynamicLoad));
}
/**
......@@ -717,17 +726,22 @@ public class UpdateSite {
/**
* Making the installation web bound.
*/
public void doInstall(StaplerResponse rsp) throws IOException {
deploy();
rsp.sendRedirect2("../..");
public HttpResponse doInstall() throws IOException {
deploy(false);
return HttpResponses.redirectTo("../..");
}
public HttpResponse doInstallNow() throws IOException {
deploy(true);
return HttpResponses.redirectTo("../..");
}
/**
* Performs the downgrade of the plugin.
*/
public void doDowngrade(StaplerResponse rsp) throws IOException {
public HttpResponse doDowngrade() throws IOException {
deployBackup();
rsp.sendRedirect2("../..");
return HttpResponses.redirectTo("../..");
}
}
......
/*
* The MIT License
*
* Copyright (c) 2011, CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package jenkins;
import org.jvnet.localizer.Localizable;
/**
* Indicates that the plugin cannot be deployed without a restart.
*
* @author Kohsuke Kawaguchi
*/
public class RestartRequiredException extends Exception {
public final Localizable message;
public RestartRequiredException(Localizable message) {
this.message = message;
}
public RestartRequiredException(Localizable message, Throwable cause) {
super(cause);
this.message = message;
}
}
......@@ -35,6 +35,8 @@ FilePath.validateRelativePath.notDirectory=''{0}'' is not a directory
FilePath.validateRelativePath.noSuchFile=No such file: ''{0}''
FilePath.validateRelativePath.noSuchDirectory=No such directory: ''{0}''
PluginManager.PluginDoesntSupportDynamicLoad.RestartRequired={0} plugin doesn''t support dynamic loading. Jenkins needs to be restarted for the update to take effect
PluginManager.PluginIsAlreadyInstalled.RestartRequired={0} plugin is already installed. Jenkins needs to be restarted for the update to take effect
Util.millisecond={0} ms
Util.second={0} sec
Util.minute={0} min
......
......@@ -137,7 +137,9 @@ THE SOFTWARE.
<j:if test="${!empty(list)}">
<div style="margin-top:1em">
<f:submit value="${%Install}" />
<f:submit value="${%Install}" name="dynamicLoad" />
<span style="margin-left:2em;"></span>
<f:submit value="${%Download now and install after restart}" />
</div>
</j:if>
<d:invokeBody />
......
/*
* The MIT License
*
* Copyright (c) 2011, CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
img(src:"${imagesURL}/24x24/yellow.png",height:24,width:24)
text(" ");
text(my.message)
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册