Skip to content

Commit

Permalink
Merge pull request #3193 from dwnusbaum/JENKINS-48365
Browse files Browse the repository at this point in the history
[JENKINS-48365] Install detached plugins when upgrading Jenkins past the version the plugins were detached
  • Loading branch information
oleg-nenashev committed Dec 17, 2017
2 parents 76c9f8b + 2476d14 commit 968b6ad
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 4 deletions.
5 changes: 2 additions & 3 deletions core/src/main/java/hudson/PluginManager.java
Expand Up @@ -679,9 +679,8 @@ protected static void addDependencies(URL hpiResUrl, String fromPath, Set<URL> d
* </ul>
*/
protected void loadDetachedPlugins() {
InstallState installState = Jenkins.getActiveInstance().getInstallState();
if (InstallState.UPGRADE.equals(installState)) {
VersionNumber lastExecVersion = new VersionNumber(InstallUtil.getLastExecVersion());
VersionNumber lastExecVersion = new VersionNumber(InstallUtil.getLastExecVersion());
if (lastExecVersion.isNewerThan(InstallUtil.NEW_INSTALL_VERSION) && lastExecVersion.isOlderThan(Jenkins.getVersion())) {

LOGGER.log(INFO, "Upgrading Jenkins. The last running version was {0}. This Jenkins is version {1}.",
new Object[] {lastExecVersion, Jenkins.VERSION});
Expand Down
3 changes: 2 additions & 1 deletion core/src/main/java/jenkins/install/InstallUtil.java
Expand Up @@ -70,7 +70,8 @@ public class InstallUtil {
private static final Logger LOGGER = Logger.getLogger(InstallUtil.class.getName());

// tests need this to be 1.0
private static final VersionNumber NEW_INSTALL_VERSION = new VersionNumber("1.0");
@Restricted(NoExternalUse.class)
public static final VersionNumber NEW_INSTALL_VERSION = new VersionNumber("1.0");
private static final VersionNumber FORCE_NEW_INSTALL_VERSION = new VersionNumber("0.0");

/**
Expand Down
11 changes: 11 additions & 0 deletions test/src/test/java/hudson/PluginManagerUtil.java
Expand Up @@ -26,6 +26,8 @@
import jenkins.RestartRequiredException;
import jenkins.model.Jenkins;
import org.apache.commons.io.FileUtils;
import org.junit.runner.Description;
import org.jvnet.hudson.test.RestartableJenkinsRule;
import org.jvnet.hudson.test.JenkinsRule;

import java.io.File;
Expand All @@ -47,6 +49,15 @@ public void before() throws Throwable {
};
}

public static RestartableJenkinsRule newRestartableJenkinsRule() {
return new RestartableJenkinsRule() {
@Override
public JenkinsRule createJenkinsRule(Description description) {
return newJenkinsRule();
}
};
}

public static void dynamicLoad(String plugin, Jenkins jenkins) throws IOException, InterruptedException, RestartRequiredException {
dynamicLoad(plugin, jenkins, false);
}
Expand Down
121 changes: 121 additions & 0 deletions test/src/test/java/jenkins/install/LoadDetachedPluginsTest.java
@@ -0,0 +1,121 @@
/*
* The MIT License
*
* Copyright 2017 CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

package jenkins.install;

import hudson.ClassicPluginStrategy;
import hudson.ClassicPluginStrategy.DetachedPlugin;
import hudson.PluginManager;
import hudson.PluginManagerUtil;
import hudson.PluginWrapper;
import hudson.util.VersionNumber;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.RestartableJenkinsRule;
import org.jvnet.hudson.test.recipes.LocalData;

import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;

public class LoadDetachedPluginsTest {

@Rule public RestartableJenkinsRule rr = PluginManagerUtil.newRestartableJenkinsRule();

@Issue("JENKINS-48365")
@Test
@LocalData
public void upgradeFromJenkins1() throws IOException {
VersionNumber since = new VersionNumber("1.550");
rr.then(r -> {
List<DetachedPlugin> detachedPlugins = ClassicPluginStrategy.getDetachedPlugins(since);
assertThat("Plugins have been detached since the pre-upgrade version",
detachedPlugins.size(), greaterThan(4));
assertThat("Plugins detached between the pre-upgrade version and the current version should be installed",
getInstalledDetachedPlugins(r, detachedPlugins).size(), equalTo(detachedPlugins.size()));
assertNoFailedPlugins(r);
});
}

@Issue("JENKINS-48365")
@Test
@LocalData
public void upgradeFromJenkins2() {
VersionNumber since = new VersionNumber("2.0");
rr.then(r -> {
List<DetachedPlugin> detachedPlugins = ClassicPluginStrategy.getDetachedPlugins(since);
assertThat("Plugins have been detached since the pre-upgrade version",
detachedPlugins.size(), greaterThan(1));
assertThat("Plugins detached between the pre-upgrade version and the current version should be installed",
getInstalledDetachedPlugins(r, detachedPlugins).size(), equalTo(detachedPlugins.size()));
assertNoFailedPlugins(r);
});
}

@Test
public void newInstallation() {
rr.then(r -> {
List<DetachedPlugin> detachedPlugins = ClassicPluginStrategy.getDetachedPlugins();
assertThat("Detached plugins should exist", detachedPlugins, not(empty()));
assertThat("Detached plugins should not be installed on a new instance",
getInstalledDetachedPlugins(r, detachedPlugins), empty());
assertNoFailedPlugins(r);
});
rr.then(r -> {
List<DetachedPlugin> detachedPlugins = ClassicPluginStrategy.getDetachedPlugins();
assertThat("Detached plugins should exist", detachedPlugins, not(empty()));
assertThat("Detached plugins should not be installed after restarting",
getInstalledDetachedPlugins(r, detachedPlugins), empty());
assertNoFailedPlugins(r);
});
}

private List<PluginWrapper> getInstalledDetachedPlugins(JenkinsRule r, List<DetachedPlugin> detachedPlugins) {
PluginManager pluginManager = r.jenkins.getPluginManager();
List<PluginWrapper> installedPlugins = new ArrayList<>();
for (DetachedPlugin plugin : detachedPlugins) {
PluginWrapper wrapper = pluginManager.getPlugin(plugin.getShortName());
if (wrapper != null) {
installedPlugins.add(wrapper);
assertTrue("Detached plugins should be active if installed", wrapper.isActive());
assertThat("Detached plugins should not have dependency errors", wrapper.getDependencyErrors(), empty());
}
}
return installedPlugins;
}

private void assertNoFailedPlugins(JenkinsRule r) {
assertThat("Detached plugins and their dependencies should not fail to install",
r.jenkins.getPluginManager().getFailedPlugins(), empty());
}

}
@@ -0,0 +1,34 @@
<?xml version='1.0' encoding='UTF-8'?>
<hudson>
<disabledAdministrativeMonitors/>
<version>1.550</version>
<numExecutors>2</numExecutors>
<mode>NORMAL</mode>
<useSecurity>true</useSecurity>
<authorizationStrategy class="hudson.security.AuthorizationStrategy$Unsecured"/>
<securityRealm class="hudson.security.SecurityRealm$None"/>
<disableRememberMe>false</disableRememberMe>
<projectNamingStrategy class="jenkins.model.ProjectNamingStrategy$DefaultProjectNamingStrategy"/>
<workspaceDir>${JENKINS_HOME}/workspace/${ITEM_FULLNAME}</workspaceDir>
<buildsDir>${ITEM_ROOTDIR}/builds</buildsDir>
<jdks/>
<viewsTabBar class="hudson.views.DefaultViewsTabBar"/>
<myViewsTabBar class="hudson.views.DefaultMyViewsTabBar"/>
<clouds/>
<quietPeriod>5</quietPeriod>
<scmCheckoutRetryCount>0</scmCheckoutRetryCount>
<views>
<hudson.model.AllView>
<owner class="hudson" reference="../../.."/>
<name>All</name>
<filterExecutors>false</filterExecutors>
<filterQueue>false</filterQueue>
<properties class="hudson.model.View$PropertyList"/>
</hudson.model.AllView>
</views>
<primaryView>All</primaryView>
<slaveAgentPort>0</slaveAgentPort>
<label></label>
<nodeProperties/>
<globalNodeProperties/>
</hudson>
@@ -0,0 +1 @@
2.0

0 comments on commit 968b6ad

Please sign in to comment.