Skip to content

Commit

Permalink
[FIXED JENKINS-12753] Added PluginStrategy.getShortName to avoid actu…
Browse files Browse the repository at this point in the history
…ally trying to unpack a plugin before throwing RestartRequiredException.
  • Loading branch information
jglick committed May 20, 2014
1 parent 9062a94 commit 7f856b5
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 15 deletions.
3 changes: 3 additions & 0 deletions changelog.html
Expand Up @@ -55,6 +55,9 @@
<!-- Record your changes in the trunk here. -->
<div id="trunk" style="display:none"><!--=TRUNK-BEGIN=-->
<ul class=image>
<li class="bug">
Misleading error message trying to dynamically update a plugin (which is impossible) on an NFS filesystem.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-12753">issue 12753</a>)
<li class="bug">
Updated component used by the swap space monitor to better support Debian and AIX.
<li class="bug">
Expand Down
40 changes: 32 additions & 8 deletions core/src/main/java/hudson/ClassicPluginStrategy.java
Expand Up @@ -66,6 +66,7 @@
import java.util.List;
import java.util.Vector;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.logging.Level;
import java.util.logging.Logger;
Expand All @@ -92,15 +93,26 @@ public ClassicPluginStrategy(PluginManager pluginManager) {
this.pluginManager = pluginManager;
}

public PluginWrapper createPluginWrapper(File archive) throws IOException {
final Manifest manifest;
URL baseResourceURL;
@Override public String getShortName(File archive) throws IOException {
Manifest manifest;
if (isLinked(archive)) {
manifest = loadLinkedManifest(archive);
} else {
JarFile jf = new JarFile(archive, false);
try {
manifest = jf.getManifest();
} finally {
jf.close();
}
}
return PluginWrapper.computeShortName(manifest, archive);
}

File expandDir = null;
// if .hpi, this is the directory where war is expanded
private static boolean isLinked(File archive) {
return archive.getName().endsWith(".hpl") || archive.getName().endsWith(".jpl");
}

boolean isLinked = archive.getName().endsWith(".hpl") || archive.getName().endsWith(".jpl");
if (isLinked) {
private static Manifest loadLinkedManifest(File archive) throws IOException {
// resolve the .hpl file to the location of the manifest file
final String firstLine = IOUtils.readFirstLine(new FileInputStream(archive), "UTF-8");

This comment has been minimized.

Copy link
@oleg-nenashev

oleg-nenashev May 30, 2014

Member

There're complains from Coverity (not related to the commit):

  1. new_resource: new java.io.FileInputStream(archive) creates a new resource.
  2. noescape: Resource new java.io.FileInputStream(archive) is not closed or saved in readFirstLine. [show details]
    CID 1219446 (#1 of 1): Resource leak (RESOURCE_LEAK)3. leaked_resource: Failing to save or close resource created by new java.io.FileInputStream(archive) leaks it
if (firstLine.startsWith("Manifest-Version:")) {

This comment has been minimized.

Copy link
@oleg-nenashev

oleg-nenashev May 30, 2014

Member

Coverity complains about empty manifests (Is it really possible??)
CID 1219445 (#1 of 1): Dereference null return value (NULL_RETURNS)3. null_method_call: Calling a method on null object firstLine.

This comment has been minimized.

Copy link
@jglick

jglick Jun 2, 2014

Author Member

Any new complaints or just old code that is not written very robustly that got moved around?

Expand All @@ -112,12 +124,24 @@ public PluginWrapper createPluginWrapper(File archive) throws IOException {
// then parse manifest
FileInputStream in = new FileInputStream(archive);
try {
manifest = new Manifest(in);
return new Manifest(in);
} catch (IOException e) {
throw new IOException("Failed to load " + archive, e);
} finally {
in.close();
}
}

@Override public PluginWrapper createPluginWrapper(File archive) throws IOException {
final Manifest manifest;
URL baseResourceURL;

File expandDir = null;
// if .hpi, this is the directory where war is expanded

boolean isLinked = isLinked(archive);
if (isLinked) {
manifest = loadLinkedManifest(archive);
} else {
if (archive.isDirectory()) {// already expanded
expandDir = archive;
Expand Down
19 changes: 15 additions & 4 deletions core/src/main/java/hudson/PluginManager.java
Expand Up @@ -412,11 +412,21 @@ private boolean containsHpiJpi(Collection<String> bundledPlugins, String name) {
*/
public void dynamicLoad(File arc) throws IOException, InterruptedException, RestartRequiredException {
LOGGER.info("Attempting to dynamic load "+arc);
final PluginWrapper p = strategy.createPluginWrapper(arc);
String sn = p.getShortName();
PluginWrapper p = null;
String sn;
try {
sn = strategy.getShortName(arc);
} catch (AbstractMethodError x) {
LOGGER.log(WARNING, "JENKINS-12753 fix not active: {0}", x.getMessage());
p = strategy.createPluginWrapper(arc);
sn = p.getShortName();
}
if (getPlugin(sn)!=null)
throw new RestartRequiredException(Messages._PluginManager_PluginIsAlreadyInstalled_RestartRequired(sn));

if (p == null) {
p = strategy.createPluginWrapper(arc);
}
if (p.supportsDynamicLoad()== YesNoMaybe.NO)
throw new RestartRequiredException(Messages._PluginManager_PluginDoesntSupportDynamicLoad_RestartRequired(sn));

Expand All @@ -442,10 +452,11 @@ public void dynamicLoad(File arc) throws IOException, InterruptedException, Rest

// run initializers in the added plugin
Reactor r = new Reactor(InitMilestone.ordering());
r.addAll(new InitializerFinder(p.classLoader) {
final ClassLoader loader = p.classLoader;
r.addAll(new InitializerFinder(loader) {
@Override
protected boolean filter(Method e) {
return e.getDeclaringClass().getClassLoader()!=p.classLoader || super.filter(e);
return e.getDeclaringClass().getClassLoader() != loader || super.filter(e);
}
}.discoverTasks(r));
try {
Expand Down
10 changes: 8 additions & 2 deletions core/src/main/java/hudson/PluginStrategy.java
Expand Up @@ -24,11 +24,10 @@
package hudson;

import hudson.model.Hudson;
import jenkins.model.Jenkins;

import java.io.File;
import java.io.IOException;
import java.util.List;
import javax.annotation.Nonnull;

/**
* Pluggability point for how to create {@link PluginWrapper}.
Expand All @@ -50,6 +49,13 @@ public interface PluginStrategy extends ExtensionPoint {
PluginWrapper createPluginWrapper(File archive)
throws IOException;

/**
* Finds the plugin name without actually unpacking anything {@link #createPluginWrapper} would.
* Needed by {@link PluginManager#dynamicLoad} to decide whether such a plugin is already installed.
* @return the {@link PluginWrapper#getShortName}
*/
@Nonnull String getShortName(File archive) throws IOException;

/**
* Loads the plugin and starts it.
*
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/java/hudson/PluginWrapper.java
Expand Up @@ -238,7 +238,7 @@ public URL getIndexPage() {
return idx != null && idx.toString().contains(shortName) ? idx : null;
}

private String computeShortName(Manifest manifest, File archive) {
static String computeShortName(Manifest manifest, File archive) {
// use the name captured in the manifest, as often plugins
// depend on the specific short name in its URLs.
String n = manifest.getMainAttributes().getValue("Short-Name");
Expand Down
19 changes: 19 additions & 0 deletions test/src/test/java/hudson/PluginManagerTest.java
Expand Up @@ -40,6 +40,7 @@
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Future;
import jenkins.RestartRequiredException;
import org.apache.commons.io.FileUtils;
import org.apache.tools.ant.filters.StringInputStream;
import static org.junit.Assert.*;
Expand Down Expand Up @@ -356,4 +357,22 @@ private String callDependerValue() throws Exception {
assertTrue(r.jenkins.getExtensionList("org.jenkinsci.plugins.dependencytest.dependee.DependeeExtensionPoint").isEmpty());
}

@Bug(12753)
@WithPlugin("tasks.jpi")
@Test public void dynamicLoadRestartRequiredException() throws Exception {
File jpi = new File(r.jenkins.getRootDir(), "plugins/tasks.jpi");
assertTrue(jpi.isFile());
FileUtils.touch(jpi);
File timestamp = new File(r.jenkins.getRootDir(), "plugins/tasks/.timestamp2");
assertTrue(timestamp.isFile());
long lastMod = timestamp.lastModified();
try {
r.jenkins.getPluginManager().dynamicLoad(jpi);
fail("should not have worked");
} catch (RestartRequiredException x) {
// good
}
assertEquals("should not have tried to delete & unpack", lastMod, timestamp.lastModified());
}

}

0 comments on commit 7f856b5

Please sign in to comment.