From 7cb96cec759e48ddf167d30d122ddd6c1d2d3ae2 Mon Sep 17 00:00:00 2001 From: Duarte Meneses Date: Mon, 16 Oct 2017 11:02:32 +0200 Subject: [PATCH] SONAR-9946 Background download and "pre-installation" of an edition --- .../ce/container/CePluginJarExploderTest.java | 15 ++ .../server/platform/ServerFileSystem.java | 29 +++- .../server/platform/ServerFileSystemImpl.java | 19 +++ .../platformlevel/PlatformLevel4.java | 12 ++ .../plugins/AbstractPluginDownloader.java | 151 ++++++++++++++++++ .../plugins/AbstractPluginUninstaller.java | 91 +++++++++++ .../server/plugins/PluginDownloader.java | 126 +-------------- .../server/plugins/PluginUninstaller.java | 28 ++++ .../plugins/ServerPluginRepository.java | 70 +++----- .../plugins/edition/EditionInstaller.java | 100 ++++++++++++ .../edition/EditionInstallerExecutor.java | 45 ++++++ .../edition/EditionPluginDownloader.java | 59 +++++++ .../edition/EditionPluginUninstaller.java | 31 ++++ .../server/plugins/edition/package-info.java | 24 +++ .../server/plugins/ws/CancelAllAction.java | 10 +- .../server/plugins/ws/PendingAction.java | 7 +- .../server/plugins/ws/UninstallAction.java | 19 +-- .../server/plugins/PluginDownloaderTest.java | 9 +- .../server/plugins/PluginUninstallerTest.java | 105 ++++++++++++ .../plugins/ServerPluginRepositoryTest.java | 30 +--- .../edition/EditionInstallerExecutorTest.java | 35 ++++ .../plugins/edition/EditionInstallerTest.java | 102 ++++++++++++ .../edition/EditionPluginDownloaderTest.java | 95 +++++++++++ .../edition/EditionPluginUninstallerTest.java | 52 ++++++ ...tUpdateCenterBasedPluginsWsActionTest.java | 10 +- .../plugins/ws/CancelAllActionTest.java | 7 +- .../server/plugins/ws/InstallActionTest.java | 3 +- .../server/plugins/ws/PendingActionTest.java | 11 +- .../ws/PluginUpdateAggregatorTest.java | 6 +- .../plugins/ws/UninstallActionTest.java | 20 +-- .../server/plugins/ws/UpdateActionTest.java | 3 +- 31 files changed, 1066 insertions(+), 258 deletions(-) create mode 100644 server/sonar-server/src/main/java/org/sonar/server/plugins/AbstractPluginDownloader.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/plugins/AbstractPluginUninstaller.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/plugins/PluginUninstaller.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/plugins/edition/EditionInstaller.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/plugins/edition/EditionInstallerExecutor.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/plugins/edition/EditionPluginDownloader.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/plugins/edition/EditionPluginUninstaller.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/plugins/edition/package-info.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/plugins/PluginUninstallerTest.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/plugins/edition/EditionInstallerExecutorTest.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/plugins/edition/EditionInstallerTest.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/plugins/edition/EditionPluginDownloaderTest.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/plugins/edition/EditionPluginUninstallerTest.java diff --git a/server/sonar-ce/src/test/java/org/sonar/ce/container/CePluginJarExploderTest.java b/server/sonar-ce/src/test/java/org/sonar/ce/container/CePluginJarExploderTest.java index 32412c40721..6389cf6c017 100644 --- a/server/sonar-ce/src/test/java/org/sonar/ce/container/CePluginJarExploderTest.java +++ b/server/sonar-ce/src/test/java/org/sonar/ce/container/CePluginJarExploderTest.java @@ -149,5 +149,20 @@ public class CePluginJarExploderTest { throw new UnsupportedOperationException(); } + @Override + public File getEditionDownloadedPluginsDir() { + throw new UnsupportedOperationException(); + } + + @Override + public File getUninstalledPluginsDir() { + throw new UnsupportedOperationException(); + } + + @Override + public File getEditionUninstalledPluginsDir() { + throw new UnsupportedOperationException(); + } + } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/ServerFileSystem.java b/server/sonar-server/src/main/java/org/sonar/server/platform/ServerFileSystem.java index c001dfe0504..7b2f99577e7 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/ServerFileSystem.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/ServerFileSystem.java @@ -37,7 +37,7 @@ public interface ServerFileSystem { /** * Directory accessible by scanners through web server - * @return a non-null directory that MAY exist + * @return a directory which may or not exist */ File getDeployDir(); @@ -55,27 +55,34 @@ public interface ServerFileSystem { /** * Files of plugins published by web server for scanners - * @return a non-null directory that MAY exist + * @return a directory which may or not exist */ File getDeployedPluginsDir(); /** * Directory of plugins downloaded through update center. Files * will be moved to {@link #getInstalledPluginsDir()} on startup. - * @return an existing directory + * @return a directory which may or not exist */ File getDownloadedPluginsDir(); + /** + * Directory of commercial plugins downloaded as part of the installation of an edition. Files + * will be moved to {@link #getInstalledPluginsDir()} on startup. + * @return a directory which may or not exist + */ + File getEditionDownloadedPluginsDir(); + /** * Directory of currently installed plugins. Used at startup. - * @return an existing directory + * @return a directory which may or not exist */ File getInstalledPluginsDir(); /** * Directory of the plugins packaged by default with application. These * plugins are installed during the first fresh startup. - * @return an existing directory + * @return a directory which may or not exist */ File getBundledPluginsDir(); @@ -84,4 +91,16 @@ public interface ServerFileSystem { * @return an existing file */ File getPluginIndex(); + + /** + * Directory where plugins to be uninstalled are moved to. + * @return a directory which may or not exist + */ + File getUninstalledPluginsDir(); + + /** + * Directory where plugins that are part of an edition and are to be uninstalled are moved to. + * @return a directory which may or not exist + */ + File getEditionUninstalledPluginsDir(); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/ServerFileSystemImpl.java b/server/sonar-server/src/main/java/org/sonar/server/platform/ServerFileSystemImpl.java index 295a38e39ec..3256b045380 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/ServerFileSystemImpl.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/ServerFileSystemImpl.java @@ -35,12 +35,16 @@ public class ServerFileSystemImpl implements ServerFileSystem, org.sonar.api.pla private final File tempDir; private final File dataDir; private final File deployDir; + private final File uninstallDir; + private final File editionUninstallDir; public ServerFileSystemImpl(Configuration config) { this.homeDir = new File(config.get(ProcessProperties.PATH_HOME).get()); this.tempDir = new File(config.get(ProcessProperties.PATH_TEMP).get()); this.dataDir = new File(config.get(ProcessProperties.PATH_DATA).get()); this.deployDir = new File(this.dataDir, TomcatContexts.WEB_DEPLOY_PATH_RELATIVE_TO_DATA_DIR); + this.uninstallDir = new File(getTempDir(), "uninstalled-plugins"); + this.editionUninstallDir = new File(getTempDir(), "uninstalled-edition-plugins"); } @Override @@ -83,6 +87,11 @@ public class ServerFileSystemImpl implements ServerFileSystem, org.sonar.api.pla return new File(getHomeDir(), "extensions/downloads"); } + @Override + public File getEditionDownloadedPluginsDir() { + return new File(getHomeDir(), "extensions/new-edition"); + } + @Override public File getInstalledPluginsDir() { return new File(getHomeDir(), "extensions/plugins"); @@ -98,4 +107,14 @@ public class ServerFileSystemImpl implements ServerFileSystem, org.sonar.api.pla return new File(getDeployDir(), "plugins/index.txt"); } + @Override + public File getUninstalledPluginsDir() { + return uninstallDir; + } + + @Override + public File getEditionUninstalledPluginsDir() { + return editionUninstallDir; + } + } diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java index 7038d965aef..e7befa67c58 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java @@ -128,7 +128,12 @@ import org.sonar.server.platform.ws.StatusAction; import org.sonar.server.platform.ws.SystemWs; import org.sonar.server.platform.ws.UpgradesAction; import org.sonar.server.plugins.PluginDownloader; +import org.sonar.server.plugins.PluginUninstaller; import org.sonar.server.plugins.ServerExtensionInstaller; +import org.sonar.server.plugins.edition.EditionInstaller; +import org.sonar.server.plugins.edition.EditionInstallerExecutor; +import org.sonar.server.plugins.edition.EditionPluginDownloader; +import org.sonar.server.plugins.edition.EditionPluginUninstaller; import org.sonar.server.plugins.privileged.PrivilegedPluginsBootstraper; import org.sonar.server.plugins.privileged.PrivilegedPluginsStopper; import org.sonar.server.plugins.ws.AvailableAction; @@ -258,6 +263,7 @@ public class PlatformLevel4 extends PlatformLevel { LogServerId.class, LogOAuthWarning.class, PluginDownloader.class, + PluginUninstaller.class, DeprecatedViews.class, PageRepository.class, ResourceTypes.class, @@ -270,6 +276,12 @@ public class PlatformLevel4 extends PlatformLevel { IndexDefinitions.class, WebPagesFilter.class, + // edition + EditionInstaller.class, + EditionPluginDownloader.class, + EditionInstallerExecutor.class, + EditionPluginUninstaller.class, + // batch BatchWsModule.class, diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/AbstractPluginDownloader.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/AbstractPluginDownloader.java new file mode 100644 index 00000000000..e29cd75f341 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/AbstractPluginDownloader.java @@ -0,0 +1,151 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.plugins; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import org.apache.commons.io.FileUtils; +import org.picocontainer.Startable; +import org.sonar.api.utils.HttpDownloader; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.core.platform.PluginInfo; +import org.sonar.core.util.stream.MoreCollectors; +import org.sonar.updatecenter.common.Release; + +import static org.apache.commons.io.FileUtils.copyFile; +import static org.apache.commons.io.FileUtils.copyFileToDirectory; +import static org.apache.commons.io.FileUtils.forceMkdir; +import static org.apache.commons.io.FileUtils.toFile; +import static org.apache.commons.lang.StringUtils.substringAfterLast; +import static org.sonar.core.util.FileUtils.deleteQuietly; + +public class AbstractPluginDownloader implements Startable { + private static final Logger LOG = Loggers.get(AbstractPluginDownloader.class); + + private static final String TMP_SUFFIX = "tmp"; + private static final String PLUGIN_EXTENSION = "jar"; + + private final HttpDownloader downloader; + private final File downloadDir; + + protected AbstractPluginDownloader(File downloadDir, HttpDownloader downloader) { + this.downloadDir = downloadDir; + this.downloader = downloader; + } + + /** + * Deletes the temporary files remaining from previous downloads + */ + @Override + public void start() { + try { + forceMkdir(downloadDir); + cleanTempFiles(); + } catch (IOException e) { + throw new IllegalStateException("Fail to create the directory: " + downloadDir, e); + } + } + + @Override + public void stop() { + // Nothing to do + } + + public void cancelDownloads() { + try { + if (downloadDir.exists()) { + org.sonar.core.util.FileUtils.cleanDirectory(downloadDir); + } + } catch (IOException e) { + throw new IllegalStateException("Fail to clean the plugin downloads directory: " + downloadDir, e); + } + } + + boolean hasDownloads() { + return !getDownloadedPluginFilenames().isEmpty(); + } + + List getDownloadedPluginFilenames() { + List names = new ArrayList<>(); + for (File file : listPlugins(this.downloadDir)) { + names.add(file.getName()); + } + return names; + } + + /** + * @return the list of download plugins as {@link PluginInfo} instances + */ + public Collection getDownloadedPlugins() { + return listPlugins(this.downloadDir) + .stream() + .map(PluginInfo::create) + .collect(MoreCollectors.toList()); + } + + protected void download(Release release) { + try { + downloadRelease(release); + } catch (Exception e) { + String message = String.format("Fail to download the plugin (%s, version %s) from %s (error is : %s)", + release.getArtifact().getKey(), release.getVersion().getName(), release.getDownloadUrl(), e.getMessage()); + LOG.debug(message, e); + throw new IllegalStateException(message, e); + } + } + + private void downloadRelease(Release release) throws URISyntaxException, IOException { + String url = release.getDownloadUrl(); + + URI uri = new URI(url); + if (url.startsWith("file:")) { + // used for tests + File file = toFile(uri.toURL()); + copyFileToDirectory(file, downloadDir); + } else { + String filename = substringAfterLast(uri.getPath(), "/"); + if (!filename.endsWith("." + PLUGIN_EXTENSION)) { + filename = release.getKey() + "-" + release.getVersion() + "." + PLUGIN_EXTENSION; + } + File targetFile = new File(downloadDir, filename); + File tempFile = new File(downloadDir, filename + "." + TMP_SUFFIX); + downloader.download(uri, tempFile); + copyFile(tempFile, targetFile); + deleteQuietly(tempFile); + } + } + + protected void cleanTempFiles() { + Collection tempFiles = FileUtils.listFiles(downloadDir, new String[] {TMP_SUFFIX}, false); + for (File tempFile : tempFiles) { + deleteQuietly(tempFile); + } + } + + private static Collection listPlugins(File dir) { + return FileUtils.listFiles(dir, new String[] {PLUGIN_EXTENSION}, false); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/AbstractPluginUninstaller.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/AbstractPluginUninstaller.java new file mode 100644 index 00000000000..d3b88927b77 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/AbstractPluginUninstaller.java @@ -0,0 +1,91 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.plugins; + +import java.io.File; +import java.io.IOException; +import java.util.Collection; +import java.util.Collections; +import org.apache.commons.io.FileUtils; +import org.picocontainer.Startable; +import org.sonar.core.platform.PluginInfo; +import org.sonar.core.util.stream.MoreCollectors; + +import static java.lang.String.format; +import static org.apache.commons.io.FileUtils.forceMkdir; + +public abstract class AbstractPluginUninstaller implements Startable { + private static final String PLUGIN_EXTENSION = "jar"; + + private final ServerPluginRepository serverPluginRepository; + private final File uninstallDir; + + protected AbstractPluginUninstaller(ServerPluginRepository serverPluginRepository, File uninstallDir) { + this.serverPluginRepository = serverPluginRepository; + this.uninstallDir = uninstallDir; + } + + @Override + public void start() { + try { + forceMkdir(uninstallDir); + } catch (IOException e) { + throw new IllegalStateException("Fail to create the directory: " + uninstallDir, e); + } + } + + @Override + public void stop() { + // Nothing to do + } + + public void uninstall(String pluginKey) { + ensurePluginIsInstalled(pluginKey); + serverPluginRepository.uninstall(pluginKey, uninstallDir); + } + + public void cancelUninstalls() { + serverPluginRepository.cancelUninstalls(uninstallDir); + } + + /** + * @return the list of plugins to be uninstalled as {@link PluginInfo} instances + */ + public Collection getUninstalledPlugins() { + return listJarFiles(uninstallDir) + .stream() + .map(PluginInfo::create) + .collect(MoreCollectors.toList()); + } + + private static Collection listJarFiles(File dir) { + if (dir.exists()) { + return FileUtils.listFiles(dir, new String[] {PLUGIN_EXTENSION}, false); + } + return Collections.emptyList(); + } + + private void ensurePluginIsInstalled(String key) { + if (!serverPluginRepository.hasPlugin(key)) { + throw new IllegalArgumentException(format("Plugin [%s] is not installed", key)); + } + } + +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/PluginDownloader.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/PluginDownloader.java index 1a6fa83b6fe..a2e087ae19d 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/plugins/PluginDownloader.java +++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/PluginDownloader.java @@ -20,105 +20,25 @@ package org.sonar.server.plugins; import com.google.common.base.Optional; -import java.io.File; -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.Collection; import java.util.List; -import org.apache.commons.io.FileUtils; -import org.picocontainer.Startable; import org.sonar.api.utils.HttpDownloader; -import org.sonar.api.utils.SonarException; -import org.sonar.api.utils.log.Logger; -import org.sonar.api.utils.log.Loggers; -import org.sonar.core.platform.PluginInfo; -import org.sonar.core.util.stream.MoreCollectors; import org.sonar.server.platform.ServerFileSystem; import org.sonar.updatecenter.common.Release; import org.sonar.updatecenter.common.UpdateCenter; import org.sonar.updatecenter.common.Version; -import static org.apache.commons.io.FileUtils.copyFile; -import static org.apache.commons.io.FileUtils.copyFileToDirectory; -import static org.apache.commons.io.FileUtils.forceMkdir; -import static org.apache.commons.io.FileUtils.toFile; -import static org.apache.commons.lang.StringUtils.substringAfterLast; -import static org.sonar.core.util.FileUtils.deleteQuietly; import static org.sonar.server.ws.WsUtils.checkRequest; /** * Downloads plugins from update center. Files are copied in the directory extensions/downloads and then * moved to extensions/plugins after server restart. */ -public class PluginDownloader implements Startable { - - private static final Logger LOG = Loggers.get(PluginDownloader.class); - private static final String TMP_SUFFIX = "tmp"; - private static final String PLUGIN_EXTENSION = "jar"; - +public class PluginDownloader extends AbstractPluginDownloader { private final UpdateCenterMatrixFactory updateCenterMatrixFactory; - private final HttpDownloader downloader; - private final File downloadDir; - public PluginDownloader(UpdateCenterMatrixFactory updateCenterMatrixFactory, HttpDownloader downloader, - ServerFileSystem fileSystem) { + public PluginDownloader(UpdateCenterMatrixFactory updateCenterMatrixFactory, HttpDownloader downloader, ServerFileSystem fileSystem) { + super(fileSystem.getDownloadedPluginsDir(), downloader); this.updateCenterMatrixFactory = updateCenterMatrixFactory; - this.downloader = downloader; - this.downloadDir = fileSystem.getDownloadedPluginsDir(); - } - - /** - * Deletes the temporary files remaining from previous downloads - */ - @Override - public void start() { - try { - forceMkdir(downloadDir); - for (File tempFile : listTempFile(this.downloadDir)) { - deleteQuietly(tempFile); - } - } catch (IOException e) { - throw new IllegalStateException("Fail to create the directory: " + downloadDir, e); - } - } - - @Override - public void stop() { - // Nothing to do - } - - public void cancelDownloads() { - try { - if (downloadDir.exists()) { - org.sonar.core.util.FileUtils.cleanDirectory(downloadDir); - } - } catch (IOException e) { - throw new IllegalStateException("Fail to clean the plugin downloads directory: " + downloadDir, e); - } - } - - public boolean hasDownloads() { - return !getDownloadedPluginFilenames().isEmpty(); - } - - public List getDownloadedPluginFilenames() { - List names = new ArrayList<>(); - for (File file : listPlugins(this.downloadDir)) { - names.add(file.getName()); - } - return names; - } - - /** - * @return the list of download plugins as {@link PluginInfo} instances - */ - public Collection getDownloadedPlugins() { - return listPlugins(this.downloadDir) - .stream() - .map(PluginInfo::create) - .collect(MoreCollectors.toList()); } public void download(String pluginKey, Version version) { @@ -126,45 +46,9 @@ public class PluginDownloader implements Startable { if (updateCenter.isPresent()) { List installablePlugins = updateCenter.get().findInstallablePlugins(pluginKey, version); checkRequest(!installablePlugins.isEmpty(), "Error while downloading plugin '%s' with version '%s'. No compatible plugin found.", pluginKey, version.getName()); - for (Release release : installablePlugins) { - try { - downloadRelease(release); - } catch (Exception e) { - String message = String.format("Fail to download the plugin (%s, version %s) from %s (error is : %s)", - release.getArtifact().getKey(), release.getVersion().getName(), release.getDownloadUrl(), e.getMessage()); - LOG.debug(message); - throw new SonarException(message, e); - } - } - } - } - - private void downloadRelease(Release release) throws URISyntaxException, IOException { - String url = release.getDownloadUrl(); - - URI uri = new URI(url); - if (url.startsWith("file:")) { - // used for tests - File file = toFile(uri.toURL()); - copyFileToDirectory(file, downloadDir); - } else { - String filename = substringAfterLast(uri.getPath(), "/"); - if (!filename.endsWith("." + PLUGIN_EXTENSION)) { - filename = release.getKey() + "-" + release.getVersion() + "." + PLUGIN_EXTENSION; + for (Release r : installablePlugins) { + super.download(r); } - File targetFile = new File(downloadDir, filename); - File tempFile = new File(downloadDir, filename + "." + TMP_SUFFIX); - downloader.download(uri, tempFile); - copyFile(tempFile, targetFile); - deleteQuietly(tempFile); } } - - private static Collection listTempFile(File dir) { - return FileUtils.listFiles(dir, new String[] {TMP_SUFFIX}, false); - } - - private static Collection listPlugins(File dir) { - return FileUtils.listFiles(dir, new String[] {PLUGIN_EXTENSION}, false); - } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/PluginUninstaller.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/PluginUninstaller.java new file mode 100644 index 00000000000..44d2f16eee9 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/PluginUninstaller.java @@ -0,0 +1,28 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.plugins; + +import org.sonar.server.platform.ServerFileSystem; + +public class PluginUninstaller extends AbstractPluginUninstaller { + public PluginUninstaller(ServerPluginRepository serverPluginRepository, ServerFileSystem fs) { + super(serverPluginRepository, fs.getUninstalledPluginsDir()); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginRepository.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginRepository.java index 45d8a0c9cf2..bd2834c84dc 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginRepository.java +++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginRepository.java @@ -47,7 +47,6 @@ import org.sonar.api.utils.log.Loggers; import org.sonar.core.platform.PluginInfo; import org.sonar.core.platform.PluginLoader; import org.sonar.core.platform.PluginRepository; -import org.sonar.core.util.stream.MoreCollectors; import org.sonar.server.platform.ServerFileSystem; import org.sonar.updatecenter.common.Version; @@ -92,8 +91,7 @@ public class ServerPluginRepository implements PluginRepository, Startable { private final Map pluginInstancesByKeys = new HashMap<>(); private final Map keysByClassLoader = new HashMap<>(); - public ServerPluginRepository(SonarRuntime runtime, ServerUpgradeStatus upgradeStatus, - ServerFileSystem fs, PluginLoader loader) { + public ServerPluginRepository(SonarRuntime runtime, ServerUpgradeStatus upgradeStatus, ServerFileSystem fs, PluginLoader loader) { this.runtime = runtime; this.upgradeStatus = upgradeStatus; this.fs = fs; @@ -110,6 +108,7 @@ public class ServerPluginRepository implements PluginRepository, Startable { loadPreInstalledPlugins(); copyBundledPlugins(); moveDownloadedPlugins(); + moveDownloadedEditionPlugins(); unloadIncompatiblePlugins(); logInstalledPlugins(); loadInstances(); @@ -156,6 +155,14 @@ public class ServerPluginRepository implements PluginRepository, Startable { } } + private void moveDownloadedEditionPlugins() { + if (fs.getEditionDownloadedPluginsDir().exists()) { + for (File sourceFile : listJarFiles(fs.getEditionDownloadedPluginsDir())) { + overrideAndRegisterPlugin(sourceFile, true); + } + } + } + /** * Copies the plugins bundled with SonarQube distribution to directory extensions/plugins. * Does nothing if not a fresh installation. @@ -302,28 +309,36 @@ public class ServerPluginRepository implements PluginRepository, Startable { /** * Uninstall a plugin and its dependents */ - public void uninstall(String pluginKey) { - checkState(started.get(), NOT_STARTED_YET); - + public void uninstall(String pluginKey, File uninstallDir) { Set uninstallKeys = new HashSet<>(); uninstallKeys.add(pluginKey); appendDependentPluginKeys(pluginKey, uninstallKeys); for (String uninstallKey : uninstallKeys) { - PluginInfo info = pluginInfosByKeys.get(uninstallKey); + PluginInfo info = getPluginInfo(uninstallKey); try { LOG.info("Uninstalling plugin {} [{}]", info.getName(), info.getKey()); // we don't reuse info.getFile() just to be sure that file is located in from extensions/plugins File masterFile = new File(fs.getInstalledPluginsDir(), info.getNonNullJarFile().getName()); - moveFileToDirectory(masterFile, uninstalledPluginsDir(), true); + moveFileToDirectory(masterFile, uninstallDir, true); } catch (IOException e) { throw new IllegalStateException(format("Fail to uninstall plugin %s [%s]", info.getName(), info.getKey()), e); } } } + public void cancelUninstalls(File uninstallDir) { + for (File file : listJarFiles(uninstallDir)) { + try { + moveFileToDirectory(file, fs.getInstalledPluginsDir(), false); + } catch (IOException e) { + throw new IllegalStateException("Fail to cancel plugin uninstalls", e); + } + } + } + private void appendDependentPluginKeys(String pluginKey, Set appendTo) { - for (PluginInfo otherPlugin : pluginInfosByKeys.values()) { + for (PluginInfo otherPlugin : getPluginInfos()) { if (!otherPlugin.getKey().equals(pluginKey)) { for (PluginInfo.RequiredPlugin requirement : otherPlugin.getRequiredPlugins()) { if (requirement.getKey().equals(pluginKey)) { @@ -335,30 +350,6 @@ public class ServerPluginRepository implements PluginRepository, Startable { } } - public List getUninstalledPluginFilenames() { - return listJarFiles(uninstalledPluginsDir()).stream().map(File::getName).collect(MoreCollectors.toList()); - } - - /** - * @return the list of plugins to be uninstalled as {@link PluginInfo} instances - */ - public Collection getUninstalledPlugins() { - return listJarFiles(uninstalledPluginsDir()) - .stream() - .map(PluginInfo::create) - .collect(MoreCollectors.toList()); - } - - public void cancelUninstalls() { - for (File file : listJarFiles(uninstalledPluginsDir())) { - try { - moveFileToDirectory(file, fs.getInstalledPluginsDir(), false); - } catch (IOException e) { - throw new IllegalStateException("Fail to cancel plugin uninstalls", e); - } - } - } - public Map getPluginInfosByKeys() { return pluginInfosByKeys; } @@ -393,19 +384,6 @@ public class ServerPluginRepository implements PluginRepository, Startable { return pluginInfosByKeys.containsKey(key); } - /** - * @return existing trash dir - */ - private File uninstalledPluginsDir() { - File dir = new File(fs.getTempDir(), "uninstalled-plugins"); - try { - FileUtils.forceMkdir(dir); - return dir; - } catch (IOException e) { - throw new IllegalStateException("Fail to create temp directory: " + dir.getAbsolutePath(), e); - } - } - private static Collection listJarFiles(File dir) { if (dir.exists()) { return FileUtils.listFiles(dir, JAR_FILE_EXTENSIONS, false); diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/edition/EditionInstaller.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/edition/EditionInstaller.java new file mode 100644 index 00000000000..966aa26c4cf --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/edition/EditionInstaller.java @@ -0,0 +1,100 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.plugins.edition; + +import java.util.Set; +import java.util.concurrent.locks.ReentrantLock; +import java.util.stream.Collectors; +import org.sonar.core.platform.PluginInfo; +import org.sonar.server.plugins.ServerPluginRepository; + +public class EditionInstaller { + private final ReentrantLock lock = new ReentrantLock(); + private final EditionInstallerExecutor executor; + private final EditionPluginDownloader editionPluginDownloader; + private final EditionPluginUninstaller editionPluginUninstaller; + private final ServerPluginRepository pluginRepository; + + public EditionInstaller(EditionPluginDownloader editionDownloader, EditionPluginUninstaller editionPluginUninstaller, + ServerPluginRepository pluginRepository, EditionInstallerExecutor executor) { + this.editionPluginDownloader = editionDownloader; + this.editionPluginUninstaller = editionPluginUninstaller; + this.pluginRepository = pluginRepository; + this.executor = executor; + } + + public void install(Set editionPlugins) { + if (lock.tryLock()) { + try { + if (!requiresInstallationChange(editionPlugins)) { + return; + } + + executor.execute(() -> asyncInstall(editionPlugins)); + } catch (RuntimeException e) { + lock.unlock(); + throw e; + } + } else { + throw new IllegalStateException("Another installation of an edition is already running"); + } + } + + public boolean requiresInstallationChange(Set editionPluginKeys) { + return !pluginsToInstall(editionPluginKeys).isEmpty() || !pluginsToRemove(editionPluginKeys).isEmpty(); + } + + private void asyncInstall(Set editionPluginKeys) { + try { + // TODO clean previously staged edition installations, or fail? + // editionPluginDownloader.cancelDownloads(); + // editionPluginUninstaller.cancelUninstalls(); + editionPluginDownloader.installEdition(pluginsToInstall(editionPluginKeys)); + for (String pluginKey : pluginsToRemove(editionPluginKeys)) { + editionPluginUninstaller.uninstall(pluginKey); + } + } finally { + lock.unlock(); + } + } + + private Set pluginsToInstall(Set editionPluginKeys) { + Set installedKeys = pluginRepository.getPluginInfosByKeys().keySet(); + return editionPluginKeys.stream() + .filter(p -> !installedKeys.contains(p)) + .collect(Collectors.toSet()); + } + + private Set pluginsToRemove(Set editionPluginKeys) { + Set installedCommercialPluginKeys = pluginRepository.getPluginInfos().stream() + .filter(EditionInstaller::isSonarSourceCommercialPlugin) + .map(PluginInfo::getKey) + .collect(Collectors.toSet()); + + return installedCommercialPluginKeys.stream() + .filter(p -> !editionPluginKeys.contains(p)) + .collect(Collectors.toSet()); + } + + private static boolean isSonarSourceCommercialPlugin(PluginInfo pluginInfo) { + return "Commercial".equals(pluginInfo.getLicense()) && "SonarSource".equals(pluginInfo.getOrganizationName()); + } + +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/edition/EditionInstallerExecutor.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/edition/EditionInstallerExecutor.java new file mode 100644 index 00000000000..9c5228d3733 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/edition/EditionInstallerExecutor.java @@ -0,0 +1,45 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.plugins.edition; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import org.sonar.server.util.AbstractStoppableExecutorService; + +public class EditionInstallerExecutor extends AbstractStoppableExecutorService { + public EditionInstallerExecutor() { + super(createExecutor()); + } + + @Override + public void execute(Runnable r) { + delegate.submit(r); + } + + private static ExecutorService createExecutor() { + ThreadFactory threadFactory = new ThreadFactoryBuilder() + .setDaemon(true) + .setNameFormat("edition-installation-%d") + .build(); + return Executors.newSingleThreadExecutor(threadFactory); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/edition/EditionPluginDownloader.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/edition/EditionPluginDownloader.java new file mode 100644 index 00000000000..6ff88874acf --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/edition/EditionPluginDownloader.java @@ -0,0 +1,59 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.plugins.edition; + +import com.google.common.base.Optional; +import java.util.HashSet; +import java.util.Set; +import org.sonar.api.utils.HttpDownloader; +import org.sonar.server.platform.ServerFileSystem; +import org.sonar.server.plugins.AbstractPluginDownloader; +import org.sonar.server.plugins.UpdateCenterMatrixFactory; +import org.sonar.updatecenter.common.Release; +import org.sonar.updatecenter.common.UpdateCenter; +import org.sonar.updatecenter.common.Version; + +public class EditionPluginDownloader extends AbstractPluginDownloader { + private final UpdateCenterMatrixFactory updateCenterMatrixFactory; + + public EditionPluginDownloader(UpdateCenterMatrixFactory updateCenterMatrixFactory, HttpDownloader downloader, ServerFileSystem fileSystem) { + super(fileSystem.getEditionDownloadedPluginsDir(), downloader); + this.updateCenterMatrixFactory = updateCenterMatrixFactory; + } + + public void installEdition(Set pluginKeys) { + try { + Optional updateCenter = updateCenterMatrixFactory.getUpdateCenter(true); + if (updateCenter.isPresent()) { + Set pluginsToInstall = new HashSet<>(); + for (String pluginKey : pluginKeys) { + pluginsToInstall.addAll(updateCenter.get().findInstallablePlugins(pluginKey, Version.create(""))); + } + + for (Release r : pluginsToInstall) { + super.download(r); + } + } + } catch (Exception e) { + cleanTempFiles(); + throw e; + } + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/edition/EditionPluginUninstaller.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/edition/EditionPluginUninstaller.java new file mode 100644 index 00000000000..2cb8fb687a2 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/edition/EditionPluginUninstaller.java @@ -0,0 +1,31 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.plugins.edition; + +import org.sonar.server.platform.ServerFileSystem; +import org.sonar.server.plugins.AbstractPluginUninstaller; +import org.sonar.server.plugins.ServerPluginRepository; + +public class EditionPluginUninstaller extends AbstractPluginUninstaller { + + public EditionPluginUninstaller(ServerPluginRepository serverPluginRepository, ServerFileSystem fs) { + super(serverPluginRepository, fs.getEditionUninstalledPluginsDir()); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/edition/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/edition/package-info.java new file mode 100644 index 00000000000..dfb86bc4801 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/edition/package-info.java @@ -0,0 +1,24 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +@ParametersAreNonnullByDefault +package org.sonar.server.plugins.edition; + +import javax.annotation.ParametersAreNonnullByDefault; + diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/CancelAllAction.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/CancelAllAction.java index db6c24d22c1..24dc3076c05 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/CancelAllAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/CancelAllAction.java @@ -23,18 +23,18 @@ import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.WebService; import org.sonar.server.plugins.PluginDownloader; -import org.sonar.server.plugins.ServerPluginRepository; +import org.sonar.server.plugins.PluginUninstaller; import org.sonar.server.user.UserSession; public class CancelAllAction implements PluginsWsAction { private final PluginDownloader pluginDownloader; - private final ServerPluginRepository pluginRepository; + private final PluginUninstaller pluginUninstaller; private final UserSession userSession; - public CancelAllAction(PluginDownloader pluginDownloader, ServerPluginRepository pluginRepository, UserSession userSession) { + public CancelAllAction(PluginDownloader pluginDownloader, PluginUninstaller pluginUninstaller, UserSession userSession) { this.pluginDownloader = pluginDownloader; - this.pluginRepository = pluginRepository; + this.pluginUninstaller = pluginUninstaller; this.userSession = userSession; } @@ -54,7 +54,7 @@ public class CancelAllAction implements PluginsWsAction { userSession.checkIsSystemAdministrator(); pluginDownloader.cancelDownloads(); - pluginRepository.cancelUninstalls(); + pluginUninstaller.cancelUninstalls(); response.noContent(); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/PendingAction.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/PendingAction.java index ac821b5e67a..95adaff20a8 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/PendingAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/PendingAction.java @@ -33,6 +33,7 @@ import org.sonar.api.server.ws.WebService; import org.sonar.api.utils.text.JsonWriter; import org.sonar.core.platform.PluginInfo; import org.sonar.server.plugins.PluginDownloader; +import org.sonar.server.plugins.PluginUninstaller; import org.sonar.server.plugins.ServerPluginRepository; import org.sonar.server.plugins.UpdateCenterMatrixFactory; import org.sonar.server.user.UserSession; @@ -57,13 +58,15 @@ public class PendingAction implements PluginsWsAction { private final ServerPluginRepository installer; private final PluginWSCommons pluginWSCommons; private final UpdateCenterMatrixFactory updateCenterMatrixFactory; + private final PluginUninstaller pluginUninstaller; public PendingAction(UserSession userSession, PluginDownloader pluginDownloader, - ServerPluginRepository installer, + ServerPluginRepository installer, PluginUninstaller pluginUninstaller, PluginWSCommons pluginWSCommons, UpdateCenterMatrixFactory updateCenterMatrixFactory) { this.userSession = userSession; this.pluginDownloader = pluginDownloader; this.installer = installer; + this.pluginUninstaller = pluginUninstaller; this.pluginWSCommons = pluginWSCommons; this.updateCenterMatrixFactory = updateCenterMatrixFactory; } @@ -93,7 +96,7 @@ public class PendingAction implements PluginsWsAction { } private void writePlugins(JsonWriter json, Map compatiblePluginsByKey) { - Collection uninstalledPlugins = installer.getUninstalledPlugins(); + Collection uninstalledPlugins = pluginUninstaller.getUninstalledPlugins(); Collection downloadedPlugins = pluginDownloader.getDownloadedPlugins(); Collection installedPlugins = installer.getPluginInfos(); MatchPluginKeys matchPluginKeys = new MatchPluginKeys(from(installedPlugins).transform(PluginInfoToKey.INSTANCE).toSet()); diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/UninstallAction.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/UninstallAction.java index 3d18adbc20a..7cf974e8d68 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/UninstallAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/UninstallAction.java @@ -22,22 +22,20 @@ package org.sonar.server.plugins.ws; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.WebService; -import org.sonar.server.plugins.ServerPluginRepository; +import org.sonar.server.plugins.PluginUninstaller; import org.sonar.server.user.UserSession; -import static java.lang.String.format; - /** * Implementation of the {@code uninstall} action for the Plugins WebService. */ public class UninstallAction implements PluginsWsAction { private static final String PARAM_KEY = "key"; - private final ServerPluginRepository pluginRepository; + private final PluginUninstaller pluginUninstaller; private final UserSession userSession; - public UninstallAction(ServerPluginRepository pluginRepository, UserSession userSession) { - this.pluginRepository = pluginRepository; + public UninstallAction(PluginUninstaller pluginUninstaller, UserSession userSession) { + this.pluginUninstaller = pluginUninstaller; this.userSession = userSession; } @@ -61,15 +59,8 @@ public class UninstallAction implements PluginsWsAction { userSession.checkIsSystemAdministrator(); String key = request.mandatoryParam(PARAM_KEY); - ensurePluginIsInstalled(key); - pluginRepository.uninstall(key); + pluginUninstaller.uninstall(key); response.noContent(); } - // FIXME should be moved to ServerPluginRepository#uninstall(String) - private void ensurePluginIsInstalled(String key) { - if (!pluginRepository.hasPlugin(key)) { - throw new IllegalArgumentException(format("Plugin [%s] is not installed", key)); - } - } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/PluginDownloaderTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/PluginDownloaderTest.java index a25f586dacc..4325e0f8d25 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/plugins/PluginDownloaderTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/PluginDownloaderTest.java @@ -32,7 +32,6 @@ import org.mockito.ArgumentMatcher; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.sonar.api.utils.HttpDownloader; -import org.sonar.api.utils.SonarException; import org.sonar.core.platform.PluginInfo; import org.sonar.server.exceptions.BadRequestException; import org.sonar.server.platform.ServerFileSystem; @@ -77,9 +76,9 @@ public class PluginDownloaderTest { when(updateCenterMatrixFactory.getUpdateCenter(anyBoolean())).thenReturn(Optional.of(updateCenter)); httpDownloader = mock(HttpDownloader.class); - doAnswer(new Answer() { + doAnswer(new Answer() { @Override - public Object answer(InvocationOnMock inv) throws Throwable { + public Void answer(InvocationOnMock inv) throws Throwable { File toFile = (File) inv.getArguments()[1]; touch(toFile); return null; @@ -212,7 +211,7 @@ public class PluginDownloaderTest { try { pluginDownloader.download("foo", create("1.0")); fail(); - } catch (SonarException e) { + } catch (IllegalStateException e) { // ok } } @@ -230,7 +229,7 @@ public class PluginDownloaderTest { try { pluginDownloader.download("foo", create("1.0")); fail(); - } catch (SonarException e) { + } catch (IllegalStateException e) { // ok } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/PluginUninstallerTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/PluginUninstallerTest.java new file mode 100644 index 00000000000..90fb048e2ab --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/PluginUninstallerTest.java @@ -0,0 +1,105 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.plugins; + +import java.io.File; +import java.io.IOException; +import org.apache.commons.io.FileUtils; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; +import org.sonar.server.platform.ServerFileSystem; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; + +public class PluginUninstallerTest { + @Rule + public TemporaryFolder testFolder = new TemporaryFolder(); + + @Rule + public ExpectedException exception = ExpectedException.none(); + + private File uninstallDir; + private PluginUninstaller underTest; + private ServerPluginRepository serverPluginRepository; + private ServerFileSystem fs; + + @Before + public void setUp() throws IOException { + serverPluginRepository = mock(ServerPluginRepository.class); + uninstallDir = testFolder.newFolder("uninstall"); + fs = mock(ServerFileSystem.class); + when(fs.getUninstalledPluginsDir()).thenReturn(uninstallDir); + underTest = new PluginUninstaller(serverPluginRepository, fs); + } + + @Test + public void uninstall() throws Exception { + when(serverPluginRepository.hasPlugin("plugin")).thenReturn(true); + underTest.uninstall("plugin"); + verify(serverPluginRepository).uninstall("plugin", uninstallDir); + } + + @Test + public void fail_uninstall_if_plugin_not_installed() { + when(serverPluginRepository.hasPlugin("plugin")).thenReturn(false); + exception.expect(IllegalArgumentException.class); + exception.expectMessage("Plugin [plugin] is not installed"); + underTest.uninstall("plugin"); + verifyZeroInteractions(serverPluginRepository); + } + + @Test + public void create_uninstall_dir() { + File dir = new File(testFolder.getRoot(), "dir"); + when(fs.getUninstalledPluginsDir()).thenReturn(dir); + underTest = new PluginUninstaller(serverPluginRepository, fs); + underTest.start(); + assertThat(dir).isDirectory(); + } + + @Test + public void cancel() { + underTest.cancelUninstalls(); + verify(serverPluginRepository).cancelUninstalls(uninstallDir); + verifyNoMoreInteractions(serverPluginRepository); + } + + @Test + public void list_uninstalled_plugins() throws IOException { + new File(uninstallDir, "file1").createNewFile(); + copyTestPluginTo("test-base-plugin", uninstallDir); + assertThat(underTest.getUninstalledPlugins()).extracting("key").containsOnly("testbase"); + } + + private File copyTestPluginTo(String testPluginName, File toDir) throws IOException { + File jar = TestProjectUtils.jarOf(testPluginName); + // file is copied because it's supposed to be moved by the test + FileUtils.copyFileToDirectory(jar, toDir); + return new File(toDir, jar.getName()); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginRepositoryTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginRepositoryTest.java index 679ddddb215..b77b9dc4046 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginRepositoryTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginRepositoryTest.java @@ -119,7 +119,6 @@ public class ServerPluginRepositoryTest { assertThat(underTest.getPluginInfos()).isEmpty(); assertThat(underTest.getPluginInfosByKeys()).isEmpty(); - assertThat(underTest.getUninstalledPlugins()).isEmpty(); assertThat(underTest.hasPlugin("testbase")).isFalse(); } @@ -227,45 +226,30 @@ public class ServerPluginRepositoryTest { @Test public void uninstall() throws Exception { File installedJar = copyTestPluginTo("test-base-plugin", fs.getInstalledPluginsDir()); - + File uninstallDir = temp.newFolder("uninstallDir"); + underTest.start(); assertThat(underTest.getPluginInfosByKeys()).containsOnlyKeys("testbase"); - underTest.uninstall("testbase"); + underTest.uninstall("testbase", uninstallDir); assertThat(installedJar).doesNotExist(); // still up. Will be dropped after next startup assertThat(underTest.getPluginInfosByKeys()).containsOnlyKeys("testbase"); - assertThat(underTest.getUninstalledPluginFilenames()).containsOnly(installedJar.getName()); - assertThat(underTest.getUninstalledPlugins()).extracting("key").containsOnly("testbase"); + assertThat(uninstallDir.list()).containsOnly(installedJar.getName()); } @Test public void uninstall_dependents() throws Exception { File base = copyTestPluginTo("test-base-plugin", fs.getInstalledPluginsDir()); File extension = copyTestPluginTo("test-require-plugin", fs.getInstalledPluginsDir()); + File uninstallDir = temp.newFolder("uninstallDir"); underTest.start(); assertThat(underTest.getPluginInfos()).hasSize(2); - underTest.uninstall("testbase"); - + underTest.uninstall("testbase", uninstallDir); assertThat(base).doesNotExist(); assertThat(extension).doesNotExist(); - assertThat(underTest.getUninstalledPluginFilenames()).containsOnly(base.getName(), extension.getName()); - assertThat(underTest.getUninstalledPlugins()).extracting("key").containsOnly("testbase", "testrequire"); - } - - @Test - public void cancel_uninstall() throws Exception { - File base = copyTestPluginTo("test-base-plugin", fs.getInstalledPluginsDir()); - underTest.start(); - - underTest.uninstall("testbase"); - assertThat(base).doesNotExist(); - - underTest.cancelUninstalls(); - assertThat(base).exists(); - assertThat(underTest.getUninstalledPluginFilenames()).isEmpty(); - assertThat(underTest.getUninstalledPlugins()).isEmpty(); + assertThat(uninstallDir.list()).containsOnly(base.getName(), extension.getName()); } @Test diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/edition/EditionInstallerExecutorTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/edition/EditionInstallerExecutorTest.java new file mode 100644 index 00000000000..7b13f425373 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/edition/EditionInstallerExecutorTest.java @@ -0,0 +1,35 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.plugins.edition; + +import org.junit.Test; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verify; + +public class EditionInstallerExecutorTest { + @Test + public void execute() { + Runnable r = mock(Runnable.class); + new EditionInstallerExecutor().execute(r); + verify(r, timeout(5000)).run(); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/edition/EditionInstallerTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/edition/EditionInstallerTest.java new file mode 100644 index 00000000000..47a6a587c17 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/edition/EditionInstallerTest.java @@ -0,0 +1,102 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.plugins.edition; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.sonar.core.platform.PluginInfo; +import org.sonar.server.plugins.ServerPluginRepository; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +public class EditionInstallerTest { + private static final String pluginKey = "key"; + @Mock + private EditionPluginDownloader downloader; + @Mock + private EditionPluginUninstaller uninstaller; + @Mock + private ServerPluginRepository pluginRepository; + + private EditionInstallerExecutor executor = new EditionInstallerExecutor() { + public void execute(Runnable r) { + r.run(); + } + }; + + private EditionInstaller installer; + + @Before + public void before() { + MockitoAnnotations.initMocks(this); + installer = new EditionInstaller(downloader, uninstaller, pluginRepository, executor); + } + + @Test + public void install() { + installer.install(Collections.singleton(pluginKey)); + verify(downloader).installEdition(Collections.singleton(pluginKey)); + } + + @Test + public void check_plugins_to_install_and_remove() { + PluginInfo commercial1 = createPluginInfo("p1", true); + PluginInfo commercial2 = createPluginInfo("p2", true); + PluginInfo open1 = createPluginInfo("p3", false); + mockPluginRepository(commercial1, commercial2, open1); + + Set editionPlugins = new HashSet<>(); + editionPlugins.add("p1"); + editionPlugins.add("p4"); + installer.install(editionPlugins); + + verify(downloader).installEdition(Collections.singleton("p4")); + verify(uninstaller).uninstall("p2"); + verifyNoMoreInteractions(uninstaller); + verifyNoMoreInteractions(downloader); + + } + + private void mockPluginRepository(PluginInfo... installedPlugins) { + Map pluginsByKey = Arrays.asList(installedPlugins).stream().collect(Collectors.toMap(p -> p.getKey(), p -> p)); + when(pluginRepository.getPluginInfosByKeys()).thenReturn(pluginsByKey); + when(pluginRepository.getPluginInfos()).thenReturn(Arrays.asList(installedPlugins)); + } + + private static PluginInfo createPluginInfo(String pluginKey, boolean commercial) { + PluginInfo info = new PluginInfo(pluginKey); + if (commercial) { + info.setOrganizationName("SonarSource"); + info.setLicense("Commercial"); + } + return info; + } + +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/edition/EditionPluginDownloaderTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/edition/EditionPluginDownloaderTest.java new file mode 100644 index 00000000000..b2288f96358 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/edition/EditionPluginDownloaderTest.java @@ -0,0 +1,95 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.plugins.edition; + +import com.google.common.base.Optional; +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Collections; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.sonar.api.utils.HttpDownloader; +import org.sonar.server.platform.ServerFileSystem; +import org.sonar.server.plugins.UpdateCenterMatrixFactory; +import org.sonar.updatecenter.common.Release; +import org.sonar.updatecenter.common.UpdateCenter; +import org.sonar.updatecenter.common.Version; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Matchers.anyBoolean; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class EditionPluginDownloaderTest { + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + @Mock + private UpdateCenterMatrixFactory updateCenterMatrixFactory; + @Mock + private UpdateCenter updateCenter; + @Mock + private ServerFileSystem fs; + @Mock + private HttpDownloader httpDownloader; + + private File downloadDir; + private EditionPluginDownloader downloader; + + @Before + public void setUp() throws IOException { + MockitoAnnotations.initMocks(this); + downloadDir = temp.newFolder(); + when(updateCenterMatrixFactory.getUpdateCenter(anyBoolean())).thenReturn(Optional.of(updateCenter)); + when(fs.getEditionDownloadedPluginsDir()).thenReturn(downloadDir); + downloader = new EditionPluginDownloader(updateCenterMatrixFactory, httpDownloader, fs); + } + + @Test + public void download_plugin_to_tmp_and_rename_it() throws IOException, URISyntaxException { + String url = "http://host/plugin.jar"; + Release release = createRelease("pluginKey", "1.0", url); + + // mock file downloaded + File tmp = new File(downloadDir, "plugin.jar.tmp"); + tmp.createNewFile(); + + when(updateCenter.findInstallablePlugins("pluginKey", Version.create(""))).thenReturn(Collections.singletonList(release)); + downloader.installEdition(Collections.singleton("pluginKey")); + + verify(httpDownloader).download(new URI(url), tmp); + assertThat(tmp).doesNotExist(); + assertThat(new File(downloadDir, "plugin.jar")).isFile(); + } + + private static Release createRelease(String key, String version, String url) { + Release release = mock(Release.class); + when(release.getKey()).thenReturn(key); + when(release.getVersion()).thenReturn(Version.create(version)); + when(release.getDownloadUrl()).thenReturn(url); + return release; + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/edition/EditionPluginUninstallerTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/edition/EditionPluginUninstallerTest.java new file mode 100644 index 00000000000..c36876680c1 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/edition/EditionPluginUninstallerTest.java @@ -0,0 +1,52 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.plugins.edition; + +import java.io.File; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.server.platform.ServerFileSystem; +import org.sonar.server.plugins.ServerPluginRepository; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +public class EditionPluginUninstallerTest { + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Test + public void start_creates_uninstall_directory() { + File dir = new File(temp.getRoot(), "uninstall"); + ServerPluginRepository repo = mock(ServerPluginRepository.class); + ServerFileSystem fs = mock(ServerFileSystem.class); + when(fs.getEditionUninstalledPluginsDir()).thenReturn(dir); + EditionPluginUninstaller uninstaller = new EditionPluginUninstaller(repo, fs); + + uninstaller.start(); + verify(fs).getEditionUninstalledPluginsDir(); + verifyNoMoreInteractions(fs); + assertThat(dir).isDirectory(); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/AbstractUpdateCenterBasedPluginsWsActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/AbstractUpdateCenterBasedPluginsWsActionTest.java index 95f1bb89faa..5aea270bb83 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/AbstractUpdateCenterBasedPluginsWsActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/AbstractUpdateCenterBasedPluginsWsActionTest.java @@ -40,10 +40,9 @@ import static org.sonar.updatecenter.common.Version.create; public abstract class AbstractUpdateCenterBasedPluginsWsActionTest { protected static final String DUMMY_CONTROLLER_KEY = "dummy"; - protected static final String JSON_EMPTY_PLUGIN_LIST = - "{" + - " \"plugins\":" + "[]" + - "}"; + protected static final String JSON_EMPTY_PLUGIN_LIST = "{" + + " \"plugins\":" + "[]" + + "}"; protected static final Plugin PLUGIN_1 = Plugin.factory("pkey1").setName("p_name_1"); protected static final Plugin PLUGIN_2 = Plugin.factory("pkey2").setName("p_name_2").setDescription("p_desc_2"); @@ -68,8 +67,7 @@ public abstract class AbstractUpdateCenterBasedPluginsWsActionTest { protected static PluginUpdate pluginUpdate(String key, String name) { return PluginUpdate.createWithStatus( new Release(Plugin.factory(key).setName(name), Version.create("1.0")), - COMPATIBLE - ); + COMPATIBLE); } @Before diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/CancelAllActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/CancelAllActionTest.java index 8afbd90df8b..bf3166325c6 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/CancelAllActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/CancelAllActionTest.java @@ -26,6 +26,7 @@ import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.WebService; import org.sonar.server.exceptions.ForbiddenException; import org.sonar.server.plugins.PluginDownloader; +import org.sonar.server.plugins.PluginUninstaller; import org.sonar.server.plugins.ServerPluginRepository; import org.sonar.server.tester.UserSessionRule; import org.sonar.server.ws.WsTester; @@ -44,8 +45,8 @@ public class CancelAllActionTest { public ExpectedException expectedException = ExpectedException.none(); private PluginDownloader pluginDownloader = mock(PluginDownloader.class); - private ServerPluginRepository pluginRepository = mock(ServerPluginRepository.class); - private CancelAllAction underTest = new CancelAllAction(pluginDownloader, pluginRepository, userSessionRule); + private PluginUninstaller pluginUninstaller = mock(PluginUninstaller.class); + private CancelAllAction underTest = new CancelAllAction(pluginDownloader, pluginUninstaller, userSessionRule); private Request request = mock(Request.class); private WsTester.TestResponse response = new WsTester.TestResponse(); @@ -94,7 +95,7 @@ public class CancelAllActionTest { underTest.handle(request, response); verify(pluginDownloader, times(1)).cancelDownloads(); - verify(pluginRepository, times(1)).cancelUninstalls(); + verify(pluginUninstaller, times(1)).cancelUninstalls(); assertThat(response.outputAsString()).isEmpty(); } diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/InstallActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/InstallActionTest.java index cf44b13927c..17f9004c9f1 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/InstallActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/InstallActionTest.java @@ -147,8 +147,7 @@ public class InstallActionTest { logInAsSystemAdministrator(); Version version = Version.create("1.0"); when(updateCenter.findAvailablePlugins()).thenReturn(ImmutableList.of( - PluginUpdate.createWithStatus(new Release(Plugin.factory(PLUGIN_KEY), version), PluginUpdate.Status.COMPATIBLE) - )); + PluginUpdate.createWithStatus(new Release(Plugin.factory(PLUGIN_KEY), version), PluginUpdate.Status.COMPATIBLE))); WsTester.Result result = validRequest.execute(); diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PendingActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PendingActionTest.java index eee591dc71a..6824c101484 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PendingActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PendingActionTest.java @@ -30,6 +30,7 @@ import org.sonar.api.server.ws.WebService; import org.sonar.core.platform.PluginInfo; import org.sonar.server.exceptions.ForbiddenException; import org.sonar.server.plugins.PluginDownloader; +import org.sonar.server.plugins.PluginUninstaller; import org.sonar.server.plugins.ServerPluginRepository; import org.sonar.server.plugins.UpdateCenterMatrixFactory; import org.sonar.server.tester.UserSessionRule; @@ -55,9 +56,11 @@ public class PendingActionTest { public ExpectedException expectedException = ExpectedException.none(); private PluginDownloader pluginDownloader = mock(PluginDownloader.class); + private PluginUninstaller pluginUninstaller = mock(PluginUninstaller.class); private ServerPluginRepository serverPluginRepository = mock(ServerPluginRepository.class); private UpdateCenterMatrixFactory updateCenterMatrixFactory = mock(UpdateCenterMatrixFactory.class, RETURNS_DEEP_STUBS); - private PendingAction underTest = new PendingAction(userSession, pluginDownloader, serverPluginRepository, new PluginWSCommons(), updateCenterMatrixFactory); + private PendingAction underTest = new PendingAction(userSession, pluginDownloader, serverPluginRepository, + pluginUninstaller, new PluginWSCommons(), updateCenterMatrixFactory); private Request request = mock(Request.class); private WsTester.TestResponse response = new WsTester.TestResponse(); @@ -157,7 +160,7 @@ public class PendingActionTest { @Test public void verify_properties_displayed_in_json_per_removing_plugin() throws Exception { logInAsSystemAdministrator(); - when(serverPluginRepository.getUninstalledPlugins()).thenReturn(of(newScmGitPluginInfo())); + when(pluginUninstaller.getUninstalledPlugins()).thenReturn(of(newScmGitPluginInfo())); underTest.handle(request, response); @@ -214,7 +217,7 @@ public class PendingActionTest { newUpdateCenter("scmgit"); when(serverPluginRepository.getPluginInfos()).thenReturn(of(installed)); - when(serverPluginRepository.getUninstalledPlugins()).thenReturn(of(removedPlugin)); + when(pluginUninstaller.getUninstalledPlugins()).thenReturn(of(removedPlugin)); when(pluginDownloader.getDownloadedPlugins()).thenReturn(of(newPlugin, installed)); underTest.handle(request, response); @@ -277,7 +280,7 @@ public class PendingActionTest { @Test public void removing_plugins_are_sorted_and_unique() throws Exception { logInAsSystemAdministrator(); - when(serverPluginRepository.getUninstalledPlugins()).thenReturn(of( + when(pluginUninstaller.getUninstalledPlugins()).thenReturn(of( newPluginInfo(0).setName("Foo"), newPluginInfo(3).setName("Bar"), newPluginInfo(2).setName("Bar"))); diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PluginUpdateAggregatorTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PluginUpdateAggregatorTest.java index 33a97bb4c8d..3db9d6d45ab 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PluginUpdateAggregatorTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PluginUpdateAggregatorTest.java @@ -53,8 +53,7 @@ public class PluginUpdateAggregatorTest { createPluginUpdate("key1"), createPluginUpdate("key0"), createPluginUpdate("key2"), - createPluginUpdate("key0") - )); + createPluginUpdate("key0"))); assertThat(aggregates).hasSize(3); assertThat(aggregates).extracting("plugin.key").containsOnlyOnce("key1", "key0", "key2"); @@ -68,8 +67,7 @@ public class PluginUpdateAggregatorTest { Collection aggregates = underTest.aggregate(ImmutableList.of( pluginUpdate1, pluginUpdate2, - pluginUpdate3 - )); + pluginUpdate3)); assertThat(aggregates).hasSize(1); Collection releases = aggregates.iterator().next().getUpdates(); diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/UninstallActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/UninstallActionTest.java index a0c5ea72136..75902e3a982 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/UninstallActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/UninstallActionTest.java @@ -25,14 +25,13 @@ import org.junit.rules.ExpectedException; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.WebService; import org.sonar.server.exceptions.ForbiddenException; -import org.sonar.server.plugins.ServerPluginRepository; +import org.sonar.server.plugins.PluginUninstaller; import org.sonar.server.tester.UserSessionRule; import org.sonar.server.ws.WsTester; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; public class UninstallActionTest { private static final String DUMMY_CONTROLLER_KEY = "dummy"; @@ -46,8 +45,8 @@ public class UninstallActionTest { @Rule public ExpectedException expectedException = ExpectedException.none(); - private ServerPluginRepository pluginRepository = mock(ServerPluginRepository.class); - private UninstallAction underTest = new UninstallAction(pluginRepository, userSessionRule); + private PluginUninstaller pluginUninstaller = mock(PluginUninstaller.class); + private UninstallAction underTest = new UninstallAction(pluginUninstaller, userSessionRule); private WsTester wsTester = new WsTester(new PluginsWs(underTest)); private Request invalidRequest = wsTester.newGetRequest(CONTROLLER_KEY, ACTION_KEY); @@ -106,24 +105,13 @@ public class UninstallActionTest { underTest.handle(invalidRequest, response); } - @Test - public void IAE_is_raised_when_plugin_is_not_installed() throws Exception { - logInAsSystemAdministrator(); - - expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("Plugin [findbugs] is not installed"); - - underTest.handle(validRequest, response); - } - @Test public void if_plugin_is_installed_uninstallation_is_triggered() throws Exception { logInAsSystemAdministrator(); - when(pluginRepository.hasPlugin(PLUGIN_KEY)).thenReturn(true); underTest.handle(validRequest, response); - verify(pluginRepository).uninstall(PLUGIN_KEY); + verify(pluginUninstaller).uninstall(PLUGIN_KEY); assertThat(response.outputAsString()).isEmpty(); } diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/UpdateActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/UpdateActionTest.java index bc1bbfda0d4..aeecbd715a2 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/UpdateActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/UpdateActionTest.java @@ -151,8 +151,7 @@ public class UpdateActionTest { logInAsSystemAdministrator(); Version version = Version.create("1.0"); when(updateCenter.findPluginUpdates()).thenReturn(ImmutableList.of( - PluginUpdate.createWithStatus(new Release(Plugin.factory(PLUGIN_KEY), version), Status.COMPATIBLE) - )); + PluginUpdate.createWithStatus(new Release(Plugin.factory(PLUGIN_KEY), version), Status.COMPATIBLE))); underTest.handle(validRequest, response); -- 2.39.5