Browse Source

SONAR-10817 Cleanup of old editions

tags/7.5
Eric Hartmann 6 years ago
parent
commit
b99727fafb
56 changed files with 68 additions and 6031 deletions
  1. 1
    16
      server/sonar-ce/src/test/java/org/sonar/ce/container/CePluginJarExploderTest.java
  2. 0
    64
      server/sonar-server/src/main/java/org/sonar/server/edition/EditionManagementState.java
  3. 0
    44
      server/sonar-server/src/main/java/org/sonar/server/edition/EditionsWsModule.java
  4. 0
    120
      server/sonar-server/src/main/java/org/sonar/server/edition/FinalizeEditionChange.java
  5. 0
    94
      server/sonar-server/src/main/java/org/sonar/server/edition/License.java
  6. 0
    104
      server/sonar-server/src/main/java/org/sonar/server/edition/MutableEditionManagementState.java
  7. 0
    381
      server/sonar-server/src/main/java/org/sonar/server/edition/StandaloneEditionManagementStateImpl.java
  8. 0
    23
      server/sonar-server/src/main/java/org/sonar/server/edition/package-info.java
  9. 0
    126
      server/sonar-server/src/main/java/org/sonar/server/edition/ws/ApplyLicenseAction.java
  10. 0
    56
      server/sonar-server/src/main/java/org/sonar/server/edition/ws/ClearErrorMessageAction.java
  11. 0
    40
      server/sonar-server/src/main/java/org/sonar/server/edition/ws/EditionsWs.java
  12. 0
    28
      server/sonar-server/src/main/java/org/sonar/server/edition/ws/EditionsWsAction.java
  13. 0
    73
      server/sonar-server/src/main/java/org/sonar/server/edition/ws/FormDataAction.java
  14. 0
    127
      server/sonar-server/src/main/java/org/sonar/server/edition/ws/PreviewAction.java
  15. 0
    63
      server/sonar-server/src/main/java/org/sonar/server/edition/ws/StatusAction.java
  16. 0
    66
      server/sonar-server/src/main/java/org/sonar/server/edition/ws/UninstallAction.java
  17. 0
    23
      server/sonar-server/src/main/java/org/sonar/server/edition/ws/package-info.java
  18. 0
    37
      server/sonar-server/src/main/java/org/sonar/server/license/LicenseCommit.java
  19. 0
    23
      server/sonar-server/src/main/java/org/sonar/server/license/package-info.java
  20. 0
    18
      server/sonar-server/src/main/java/org/sonar/server/platform/ServerFileSystem.java
  21. 2
    20
      server/sonar-server/src/main/java/org/sonar/server/platform/ServerFileSystemImpl.java
  22. 1
    19
      server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
  23. 0
    91
      server/sonar-server/src/main/java/org/sonar/server/plugins/AbstractPluginUninstaller.java
  24. 64
    2
      server/sonar-server/src/main/java/org/sonar/server/plugins/PluginUninstaller.java
  25. 0
    9
      server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginRepository.java
  26. 0
    149
      server/sonar-server/src/main/java/org/sonar/server/plugins/edition/EditionInstaller.java
  27. 0
    45
      server/sonar-server/src/main/java/org/sonar/server/plugins/edition/EditionInstallerExecutor.java
  28. 0
    108
      server/sonar-server/src/main/java/org/sonar/server/plugins/edition/EditionPluginDownloader.java
  29. 0
    31
      server/sonar-server/src/main/java/org/sonar/server/plugins/edition/EditionPluginUninstaller.java
  30. 0
    5
      server/sonar-server/src/main/resources/org/sonar/server/edition/ws/example-edition-apply_license.json
  31. 0
    4
      server/sonar-server/src/main/resources/org/sonar/server/edition/ws/example-edition-form_data.json
  32. 0
    4
      server/sonar-server/src/main/resources/org/sonar/server/edition/ws/example-edition-preview.json
  33. 0
    5
      server/sonar-server/src/main/resources/org/sonar/server/edition/ws/example-edition-status.json
  34. 0
    39
      server/sonar-server/src/test/java/org/sonar/server/edition/EditionsWsModuleTest.java
  35. 0
    275
      server/sonar-server/src/test/java/org/sonar/server/edition/FinalizeEditionChangeTest.java
  36. 0
    150
      server/sonar-server/src/test/java/org/sonar/server/edition/LicenseTest.java
  37. 0
    1311
      server/sonar-server/src/test/java/org/sonar/server/edition/StandaloneEditionManagementStateImplTest.java
  38. 0
    356
      server/sonar-server/src/test/java/org/sonar/server/edition/ws/ApplyLicenseActionTest.java
  39. 0
    90
      server/sonar-server/src/test/java/org/sonar/server/edition/ws/ClearErrorMessageActionTest.java
  40. 0
    130
      server/sonar-server/src/test/java/org/sonar/server/edition/ws/FormDataActionTest.java
  41. 0
    260
      server/sonar-server/src/test/java/org/sonar/server/edition/ws/PreviewActionTest.java
  42. 0
    132
      server/sonar-server/src/test/java/org/sonar/server/edition/ws/StatusActionTest.java
  43. 0
    137
      server/sonar-server/src/test/java/org/sonar/server/edition/ws/UninstallActionTest.java
  44. 0
    35
      server/sonar-server/src/test/java/org/sonar/server/plugins/edition/EditionInstallerExecutorTest.java
  45. 0
    293
      server/sonar-server/src/test/java/org/sonar/server/plugins/edition/EditionInstallerTest.java
  46. 0
    133
      server/sonar-server/src/test/java/org/sonar/server/plugins/edition/EditionPluginDownloaderTest.java
  47. 0
    52
      server/sonar-server/src/test/java/org/sonar/server/plugins/edition/EditionPluginUninstallerTest.java
  48. 0
    143
      sonar-plugin-api/src/main/java/org/sonar/api/config/License.java
  49. 0
    161
      sonar-plugin-api/src/test/java/org/sonar/api/config/LicenseTest.java
  50. 0
    8
      sonar-ws/src/main/java/org/sonarqube/ws/client/DefaultWsClient.java
  51. 0
    3
      sonar-ws/src/main/java/org/sonarqube/ws/client/WsClient.java
  52. 0
    47
      sonar-ws/src/main/java/org/sonarqube/ws/client/editions/ApplyLicenseRequest.java
  53. 0
    125
      sonar-ws/src/main/java/org/sonarqube/ws/client/editions/EditionsService.java
  54. 0
    47
      sonar-ws/src/main/java/org/sonarqube/ws/client/editions/PreviewRequest.java
  55. 0
    26
      sonar-ws/src/main/java/org/sonarqube/ws/client/editions/package-info.java
  56. 0
    60
      sonar-ws/src/main/protobuf/ws-editions.proto

+ 1
- 16
server/sonar-ce/src/test/java/org/sonar/ce/container/CePluginJarExploderTest.java View File

@@ -24,9 +24,9 @@ import java.io.IOException;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.sonar.server.platform.ServerFileSystem;
import org.sonar.core.platform.ExplodedPlugin;
import org.sonar.core.platform.PluginInfo;
import org.sonar.server.platform.ServerFileSystem;

import static org.apache.commons.io.FileUtils.sizeOfDirectory;
import static org.assertj.core.api.Assertions.assertThat;
@@ -97,11 +97,6 @@ public class CePluginJarExploderTest {
this.temp = temp;
}

@Override
public File getDataDir() {
throw new UnsupportedOperationException();
}

@Override
public File getHomeDir() {
throw new UnsupportedOperationException();
@@ -139,20 +134,10 @@ 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();
}

}
}

+ 0
- 64
server/sonar-server/src/main/java/org/sonar/server/edition/EditionManagementState.java View File

@@ -1,64 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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.edition;

import java.util.Optional;

public interface EditionManagementState {
/**
* @return {@link Optional#empty() empty} if there is no edition installed
*/
Optional<String> getCurrentEditionKey();

/**
* @return the pending installation status.
*/
PendingStatus getPendingInstallationStatus();

/**
* @return {@link Optional#empty() empty} when {@link #getPendingInstallationStatus()} returns {@link PendingStatus#NONE},
* otherwise a {@link String}
*/
Optional<String> getPendingEditionKey();

/**
* The license string.
*
* @return {@link Optional#empty() empty} when {@link #getPendingInstallationStatus()} returns {@link PendingStatus#NONE},
* otherwise a {@link String}
*/
Optional<String> getPendingLicense();

/**
* The message explaining the error that made the install fail (if any).
*
* @return a {@link String} if {@link #getPendingInstallationStatus()} returns {@link PendingStatus#NONE} and an error
* occurred during install, otherwise {@link Optional#empty() empty}
*/
Optional<String> getInstallErrorMessage();

enum PendingStatus {
NONE,
AUTOMATIC_IN_PROGRESS,
AUTOMATIC_READY,
MANUAL_IN_PROGRESS,
UNINSTALL_IN_PROGRESS
}
}

+ 0
- 44
server/sonar-server/src/main/java/org/sonar/server/edition/EditionsWsModule.java View File

@@ -1,44 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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.edition;

import org.sonar.core.platform.Module;
import org.sonar.server.edition.ws.ApplyLicenseAction;
import org.sonar.server.edition.ws.ClearErrorMessageAction;
import org.sonar.server.edition.ws.EditionsWs;
import org.sonar.server.edition.ws.FormDataAction;
import org.sonar.server.edition.ws.PreviewAction;
import org.sonar.server.edition.ws.StatusAction;
import org.sonar.server.edition.ws.UninstallAction;

public class EditionsWsModule extends Module {
@Override
protected void configureModule() {
add(
StandaloneEditionManagementStateImpl.class,
StatusAction.class,
ApplyLicenseAction.class,
PreviewAction.class,
FormDataAction.class,
ClearErrorMessageAction.class,
UninstallAction.class,
EditionsWs.class);
}
}

+ 0
- 120
server/sonar-server/src/main/java/org/sonar/server/edition/FinalizeEditionChange.java View File

@@ -1,120 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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.edition;

import java.util.Optional;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.sonar.api.Startable;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
import org.sonar.server.edition.EditionManagementState.PendingStatus;
import org.sonar.server.license.LicenseCommit;

public class FinalizeEditionChange implements Startable {
private static final Logger LOG = Loggers.get(FinalizeEditionChange.class);

private final MutableEditionManagementState editionManagementState;
@CheckForNull
private final LicenseCommit licenseCommit;

/**
* Used by Pico when license-manager is not installed and therefore no implementation of {@link LicenseCommit} is
* is available.
*/
public FinalizeEditionChange(MutableEditionManagementState editionManagementState) {
this(editionManagementState, null);
}

public FinalizeEditionChange(MutableEditionManagementState editionManagementState, @Nullable LicenseCommit licenseCommit) {
this.editionManagementState = editionManagementState;
this.licenseCommit = licenseCommit;
}

@Override
public void start() {
EditionManagementState.PendingStatus status = editionManagementState.getPendingInstallationStatus();
switch (status) {
case NONE:
editionManagementState.clearInstallErrorMessage();
return;
case MANUAL_IN_PROGRESS:
case AUTOMATIC_READY:
finalizeInstall();
break;
case AUTOMATIC_IN_PROGRESS:
editionManagementState.installFailed("SonarQube was restarted before asynchronous installation of edition completed");
break;
case UNINSTALL_IN_PROGRESS:
failIfLicenseCommitIsPresent();
editionManagementState.finalizeInstallation(null);
break;
default:
throw new IllegalStateException("Unsupported status " + status);
}
}

private void failIfLicenseCommitIsPresent() {
if (licenseCommit != null) {
throw new IllegalStateException("License Manager plugin is still present after uninstallation of the edition. Please remove it.");
}
}

private void finalizeInstall() {
String errorMessage = null;

try {
if (licenseCommit == null) {
errorMessage = "Edition installation didn't complete. Some plugins were not installed.";
LOG.warn(errorMessage);
return;
}

Optional<String> newLicense = editionManagementState.getPendingLicense();
if (!newLicense.isPresent()) {
errorMessage = "Edition installation didn't complete. License was not found.";
LOG.warn(errorMessage);
return;
}

try {
licenseCommit.update(newLicense.get());
} catch (IllegalArgumentException e) {
errorMessage = "Edition installation didn't complete. License is not valid. Please set a new license.";
LOG.warn(errorMessage, e);
}
} finally {
editionManagementState.finalizeInstallation(errorMessage);
}
}

@Override
public void stop() {
EditionManagementState.PendingStatus status = editionManagementState.getPendingInstallationStatus();
if (status == PendingStatus.UNINSTALL_IN_PROGRESS) {
if (licenseCommit != null) {
LOG.debug("Removing license");
licenseCommit.delete();
} else {
LOG.warn("License Manager plugin not found - cannot remove the license");
}
}
}
}

+ 0
- 94
server/sonar-server/src/main/java/org/sonar/server/edition/License.java View File

@@ -1,94 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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.edition;

import com.google.common.collect.ImmutableSet;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collection;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import javax.annotation.concurrent.Immutable;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang.StringUtils;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

@Immutable
public class License {
private static final Logger LOG = Loggers.get(License.class);
private static final String EDITION_KEY = "Edition";
private static final String PLUGINS_KEY = "Plugins";

private final String editionKey;
private final Set<String> pluginKeys;
private final String content;

public License(String editionKey, Collection<String> pluginKeys, String content) {
this.editionKey = enforceNotNullNorEmpty(editionKey, "editionKey");
this.pluginKeys = ImmutableSet.copyOf(pluginKeys);
this.content = enforceNotNullNorEmpty(content, "content");
}

private static String enforceNotNullNorEmpty(String str, String propertyName) {
checkNotNull(str, "%s can't be null", propertyName);
checkArgument(!str.isEmpty(), "%s can't be empty", propertyName);
return str;
}

public String getEditionKey() {
return editionKey;
}

public Set<String> getPluginKeys() {
return pluginKeys;
}

public String getContent() {
return content;
}

public static Optional<License> parse(String base64) {
try {
String data = new String(Base64.decodeBase64(base64.trim().getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8);

Properties props = new Properties();
props.load(new StringReader(data));

String[] plugins = StringUtils.split(props.getProperty(PLUGINS_KEY), ',');
String editionKey = props.getProperty(EDITION_KEY);

if (StringUtils.isNotBlank(editionKey) && plugins.length > 0) {
return Optional.of(new License(editionKey, Arrays.asList(plugins), base64));
} else {
LOG.debug("Failed to parse license: no edition key and/or no plugin found");
}
} catch (Exception e) {
LOG.debug("Failed to parse license", e);
}
return Optional.empty();

}
}

+ 0
- 104
server/sonar-server/src/main/java/org/sonar/server/edition/MutableEditionManagementState.java View File

@@ -1,104 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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.edition;

import javax.annotation.Nullable;

/**
* Provides access to operations which will alter the Edition management state.
*/
public interface MutableEditionManagementState extends EditionManagementState {
/**
* Stage the specified license and records that an automatic install of the plugins will be performed.
*
* @return the new {@link PendingStatus}, always {@link PendingStatus#AUTOMATIC_IN_PROGRESS}
* an exception.
*
* @throws IllegalStateException if current status is not {@link PendingStatus#NONE}
*/
PendingStatus startAutomaticInstall(License license);

/**
* Stage the specified license and records that a manual install of the plugins will be performed.
*
* @return the new {@link PendingStatus}, always {@link PendingStatus#MANUAL_IN_PROGRESS}
*
* @throws IllegalStateException if current status is not {@link PendingStatus#NONE}
*/
PendingStatus startManualInstall(License license);

/**
* Records that the specified edition has been installed without plugin changes.
*
* @param newEditionKey can't be {@code null} nor empty
*
* @return the new {@link PendingStatus}, always {@link PendingStatus#NONE}
*
* @throws IllegalStateException if current status is not {@link PendingStatus#NONE}
*/
PendingStatus newEditionWithoutInstall(String newEditionKey);

/**
* Records that automatic install is ready to be finalized.
*
* @return the new pending status, always {@link PendingStatus#AUTOMATIC_READY}
*
* @throws IllegalStateException if current status is not {@link PendingStatus#AUTOMATIC_IN_PROGRESS}
*/
PendingStatus automaticInstallReady();

/**
* Records that install failed with the specified (optional) error message to explain the cause of the
* failure.
*
* @return the new pending status, always {@link PendingStatus#NONE}
*
* @throws IllegalStateException if current status is neither {@link PendingStatus#AUTOMATIC_IN_PROGRESS} nor
* {@link PendingStatus#MANUAL_IN_PROGRESS}
*/
PendingStatus installFailed(@Nullable String errorMessage);

/**
* Clears the error message set by {@link #installFailed(String)} (String)} if there is any and which ever the current
* status.
*/
void clearInstallErrorMessage();

/**
* Uninstalls the currently installed edition
*
* @return the new pending status, always {@link PendingStatus#UNINSTALL_IN_PROGRESS}
*
* @throws IllegalStateException if current status is not {@link PendingStatus#NONE} or if there is
* no edition currently installed.
*/
PendingStatus uninstall();

/**
* Finalize an automatic or manual install with the specified (optional) error message to explain a partially
* finalized install.
*
* @return the new pending status, always {@link PendingStatus#NONE}
*
* @throws IllegalStateException if current status is neither {@link PendingStatus#AUTOMATIC_READY} nor
* {@link PendingStatus#MANUAL_IN_PROGRESS}
*/
PendingStatus finalizeInstallation(@Nullable String errorMessage);
}

+ 0
- 381
server/sonar-server/src/main/java/org/sonar/server/edition/StandaloneEditionManagementStateImpl.java View File

@@ -1,381 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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.edition;

import com.google.common.collect.ImmutableSet;
import java.util.Arrays;
import java.util.Map;
import java.util.Optional;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import org.picocontainer.Startable;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.property.InternalPropertiesDao;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static java.util.Objects.requireNonNull;
import static java.util.Optional.empty;
import static org.sonar.server.edition.EditionManagementState.PendingStatus.AUTOMATIC_IN_PROGRESS;
import static org.sonar.server.edition.EditionManagementState.PendingStatus.AUTOMATIC_READY;
import static org.sonar.server.edition.EditionManagementState.PendingStatus.MANUAL_IN_PROGRESS;
import static org.sonar.server.edition.EditionManagementState.PendingStatus.NONE;
import static org.sonar.server.edition.EditionManagementState.PendingStatus.UNINSTALL_IN_PROGRESS;

public class StandaloneEditionManagementStateImpl implements MutableEditionManagementState, Startable {
private static final String CURRENT_EDITION_KEY = "currentEditionKey";
private static final String PENDING_INSTALLATION_STATUS = "pendingInstallStatus";
private static final String PENDING_EDITION_KEY = "pendingEditionKey";
private static final String PENDING_LICENSE = "pendingLicense";
private static final String INSTALL_ERROR_MESSAGE = "installError";

private final DbClient dbClient;
@CheckForNull
private State state;

public StandaloneEditionManagementStateImpl(DbClient dbClient) {
this.dbClient = dbClient;
}

@Override
public void start() {
try (DbSession dbSession = dbClient.openSession(false)) {
// load current state value
Map<String, Optional<String>> internalPropertyValues = dbClient.internalPropertiesDao().selectByKeys(dbSession,
ImmutableSet.of(CURRENT_EDITION_KEY, PENDING_INSTALLATION_STATUS, PENDING_EDITION_KEY, PENDING_LICENSE, INSTALL_ERROR_MESSAGE));

PendingStatus pendingInstallationStatus = internalPropertyValues.getOrDefault(PENDING_INSTALLATION_STATUS, empty())
.map(StandaloneEditionManagementStateImpl::emptyToNull)
.map(PendingStatus::valueOf)
.orElse(NONE);
State.Builder builder = State.newBuilder(pendingInstallationStatus);
builder
.setCurrentEditionKey(internalPropertyValues.getOrDefault(CURRENT_EDITION_KEY, empty())
.map(StandaloneEditionManagementStateImpl::emptyToNull)
.orElse(null))
.setPendingEditionKey(internalPropertyValues.getOrDefault(PENDING_EDITION_KEY, empty())
.map(StandaloneEditionManagementStateImpl::emptyToNull)
.orElse(null))
.setPendingLicense(internalPropertyValues.getOrDefault(PENDING_LICENSE, empty())
.map(StandaloneEditionManagementStateImpl::emptyToNull)
.orElse(null))
.setInstallErrorMessage(internalPropertyValues.getOrDefault(INSTALL_ERROR_MESSAGE, empty())
.map(StandaloneEditionManagementStateImpl::emptyToNull)
.orElse(null));
state = builder.build();
}
}

@Override
public void stop() {
// nothing to do
}

@Override
public Optional<String> getCurrentEditionKey() {
ensureStarted();
return Optional.ofNullable(state.getCurrentEditionKey());
}

@Override
public PendingStatus getPendingInstallationStatus() {
ensureStarted();
return state.getPendingInstallationStatus();
}

@Override
public Optional<String> getPendingEditionKey() {
ensureStarted();
return Optional.ofNullable(state.getPendingEditionKey());
}

@Override
public Optional<String> getPendingLicense() {
ensureStarted();
return Optional.ofNullable(state.getPendingLicense());
}

@Override
public Optional<String> getInstallErrorMessage() {
ensureStarted();
return Optional.ofNullable(state.getInstallErrorMessage());
}

@Override
public synchronized PendingStatus startAutomaticInstall(License license) {
ensureStarted();
checkLicense(license);
State newState = changeStatusToFrom(AUTOMATIC_IN_PROGRESS, NONE)
.setPendingLicense(license.getContent())
.setPendingEditionKey(license.getEditionKey())
.clearAutomaticInstallErrorMessage()
.build();
persistProperties(newState);
return newState.getPendingInstallationStatus();
}

@Override
public synchronized PendingStatus startManualInstall(License license) {
ensureStarted();
checkLicense(license);
State newState = changeStatusToFrom(MANUAL_IN_PROGRESS, NONE)
.setPendingLicense(license.getContent())
.setPendingEditionKey(license.getEditionKey())
.clearAutomaticInstallErrorMessage()
.build();
persistProperties(newState);
return newState.getPendingInstallationStatus();
}

@Override
public synchronized PendingStatus newEditionWithoutInstall(String newEditionKey) {
ensureStarted();
requireNonNull(newEditionKey, "newEditionKey can't be null");
checkArgument(!newEditionKey.isEmpty(), "newEditionKey can't be empty");
State newState = changeStatusToFrom(NONE, NONE)
.setCurrentEditionKey(newEditionKey)
.clearAutomaticInstallErrorMessage()
.build();
persistProperties(newState);
return newState.getPendingInstallationStatus();
}

@Override
public synchronized PendingStatus automaticInstallReady() {
ensureStarted();
State newState = changeStatusToFrom(AUTOMATIC_READY, AUTOMATIC_IN_PROGRESS)
.clearAutomaticInstallErrorMessage()
.build();
persistProperties(newState);
return newState.getPendingInstallationStatus();
}

@Override
public synchronized PendingStatus installFailed(@Nullable String errorMessage) {
ensureStarted();
State newState = changeStatusToFrom(NONE, AUTOMATIC_IN_PROGRESS, MANUAL_IN_PROGRESS)
.setInstallErrorMessage(nullableTrimmedEmptyToNull(errorMessage))
.clearPendingFields()
.build();
persistProperties(newState);
return newState.getPendingInstallationStatus();
}

@Override
public synchronized void clearInstallErrorMessage() {
ensureStarted();
State currentState = this.state;
if (currentState.getInstallErrorMessage() != null) {
State newState = State.newBuilder(currentState)
.clearAutomaticInstallErrorMessage()
.build();
persistProperties(newState);
}
}

@Override
public synchronized PendingStatus finalizeInstallation(@Nullable String errorMessage) {
ensureStarted();
State newState = changeStatusToFrom(NONE, AUTOMATIC_READY, MANUAL_IN_PROGRESS, UNINSTALL_IN_PROGRESS)
.commitPendingEditionKey()
.clearPendingFields()
.setInstallErrorMessage(nullableTrimmedEmptyToNull(errorMessage))
.build();
persistProperties(newState);
return newState.getPendingInstallationStatus();
}

@Override
public synchronized PendingStatus uninstall() {
ensureStarted();
State.Builder builder = changeStatusToFrom(UNINSTALL_IN_PROGRESS, NONE);
checkState(state.currentEditionKey != null, "There is no edition currently installed");
State newState = builder
.clearPendingFields()
.clearCurrentEditionKey()
.clearAutomaticInstallErrorMessage()
.build();
persistProperties(newState);
return newState.getPendingInstallationStatus();
}

private void ensureStarted() {
checkState(state != null, "%s is not started", getClass().getSimpleName());
}

private State.Builder changeStatusToFrom(PendingStatus newStatus, PendingStatus... validPendingStatuses) {
State currentState = this.state;
if (Arrays.stream(validPendingStatuses).noneMatch(s -> s == currentState.getPendingInstallationStatus())) {
throw new IllegalStateException(String.format("Can't move to %s when status is %s (should be any of %s)",
newStatus, currentState.getPendingInstallationStatus(), Arrays.toString(validPendingStatuses)));
}

return State.newBuilder(currentState, newStatus);
}

private void persistProperties(State newState) {
try (DbSession dbSession = dbClient.openSession(false)) {
InternalPropertiesDao internalPropertiesDao = dbClient.internalPropertiesDao();
saveInternalProperty(internalPropertiesDao, dbSession, PENDING_EDITION_KEY, newState.getPendingEditionKey());
saveInternalProperty(internalPropertiesDao, dbSession, PENDING_LICENSE, newState.getPendingLicense());
saveInternalProperty(internalPropertiesDao, dbSession, INSTALL_ERROR_MESSAGE, newState.getInstallErrorMessage());
saveInternalProperty(internalPropertiesDao, dbSession, CURRENT_EDITION_KEY, newState.getCurrentEditionKey());
saveInternalProperty(internalPropertiesDao, dbSession, PENDING_INSTALLATION_STATUS, newState.getPendingInstallationStatus().name());
dbSession.commit();
this.state = newState;
}
}

private static void saveInternalProperty(InternalPropertiesDao dao, DbSession dbSession, String key, @Nullable String value) {
if (value == null) {
dao.saveAsEmpty(dbSession, key);
} else {
dao.save(dbSession, key, value);
}
}

private static void checkLicense(License license) {
requireNonNull(license, "license can't be null");
}

private static String nullableTrimmedEmptyToNull(@Nullable String s) {
if (s == null) {
return null;
}
String v = s.trim();
return v.isEmpty() ? null : v;
}

private static String emptyToNull(String s) {
return s.isEmpty() ? null : s;
}

@Immutable
private static final class State {
private final String currentEditionKey;
private final PendingStatus pendingInstallationStatus;
private final String pendingEditionKey;
private final String pendingLicense;
private final String installErrorMessage;

public State(Builder builder) {
this.currentEditionKey = builder.currentEditionKey;
this.pendingInstallationStatus = builder.pendingInstallationStatus;
this.pendingEditionKey = builder.pendingEditionKey;
this.pendingLicense = builder.pendingLicense;
this.installErrorMessage = builder.installErrorMessage;
}

public String getCurrentEditionKey() {
return currentEditionKey;
}

public PendingStatus getPendingInstallationStatus() {
return pendingInstallationStatus;
}

public String getPendingEditionKey() {
return pendingEditionKey;
}

public String getPendingLicense() {
return pendingLicense;
}

public String getInstallErrorMessage() {
return installErrorMessage;
}

public static Builder newBuilder(PendingStatus pendingInstallationStatus) {
return new Builder(pendingInstallationStatus);
}

public static Builder newBuilder(State from) {
return newBuilder(from, from.getPendingInstallationStatus());
}

public static Builder newBuilder(State from, PendingStatus newStatus) {
return new Builder(newStatus)
.setCurrentEditionKey(from.currentEditionKey)
.setPendingEditionKey(from.pendingEditionKey)
.setPendingLicense(from.pendingLicense)
.setInstallErrorMessage(from.installErrorMessage);
}

private static class Builder {
private PendingStatus pendingInstallationStatus;
private String currentEditionKey;
private String pendingEditionKey;
private String pendingLicense;
private String installErrorMessage;

private Builder(PendingStatus pendingInstallationStatus) {
this.pendingInstallationStatus = requireNonNull(pendingInstallationStatus);
}

public Builder setCurrentEditionKey(@Nullable String currentEditionKey) {
this.currentEditionKey = currentEditionKey;
return this;
}

public Builder setPendingEditionKey(@Nullable String pendingEditionKey) {
this.pendingEditionKey = pendingEditionKey;
return this;
}

public Builder setPendingLicense(@Nullable String pendingLicense) {
this.pendingLicense = pendingLicense;
return this;
}

public Builder setInstallErrorMessage(@Nullable String installErrorMessage) {
this.installErrorMessage = installErrorMessage;
return this;
}

public Builder commitPendingEditionKey() {
this.currentEditionKey = pendingEditionKey;
return this;
}

public Builder clearCurrentEditionKey() {
this.currentEditionKey = null;
return this;
}

public Builder clearPendingFields() {
this.pendingEditionKey = null;
this.pendingLicense = null;
return this;
}

public Builder clearAutomaticInstallErrorMessage() {
this.installErrorMessage = null;
return this;
}

public State build() {
return new State(this);
}
}
}
}

+ 0
- 23
server/sonar-server/src/main/java/org/sonar/server/edition/package-info.java View File

@@ -1,23 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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.edition;

import javax.annotation.ParametersAreNonnullByDefault;

+ 0
- 126
server/sonar-server/src/main/java/org/sonar/server/edition/ws/ApplyLicenseAction.java View File

@@ -1,126 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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.edition.ws;

import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
import org.sonar.api.utils.log.Loggers;
import org.sonar.server.edition.EditionManagementState;
import org.sonar.server.edition.License;
import org.sonar.server.edition.MutableEditionManagementState;
import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.license.LicenseCommit;
import org.sonar.server.platform.WebServer;
import org.sonar.server.plugins.edition.EditionInstaller;
import org.sonar.server.user.UserSession;
import org.sonar.server.ws.WsUtils;
import org.sonarqube.ws.Editions;

import static com.google.common.base.Preconditions.checkState;

public class ApplyLicenseAction implements EditionsWsAction {
private static final String PARAM_LICENSE = "license";

private final UserSession userSession;
private final MutableEditionManagementState editionManagementState;
private final EditionInstaller editionInstaller;
private final WebServer webServer;
@CheckForNull
private final LicenseCommit licenseCommit;

public ApplyLicenseAction(UserSession userSession, MutableEditionManagementState editionManagementState,
EditionInstaller editionInstaller, WebServer webServer) {
this(userSession, editionManagementState, editionInstaller, webServer, null);
}

public ApplyLicenseAction(UserSession userSession, MutableEditionManagementState editionManagementState,
EditionInstaller editionInstaller, WebServer webServer, @Nullable LicenseCommit licenseCommit) {
this.userSession = userSession;
this.editionManagementState = editionManagementState;
this.editionInstaller = editionInstaller;
this.webServer = webServer;
this.licenseCommit = licenseCommit;
}

@Override
public void define(WebService.NewController controller) {
WebService.NewAction action = controller.createAction("apply_license")
.setSince("6.7")
.setPost(true)
.setDescription("Apply changes to SonarQube to match the specified license." +
" Clear error message of previous automatic install of an edition, if there is any." +
" Require 'Administer System' permission.")
.setResponseExample(getClass().getResource("example-edition-apply_license.json"))
.setHandler(this);

action.createParam(PARAM_LICENSE)
.setRequired(true)
.setSince("6.7")
.setDescription("the license");
}

@Override
public void handle(Request request, Response response) throws Exception {
userSession.checkLoggedIn().checkIsSystemAdministrator();

if (editionManagementState.getPendingInstallationStatus() != EditionManagementState.PendingStatus.NONE) {
throw BadRequestException.create("Can't apply a license when applying one is already in progress");
}

String licenseParam = request.mandatoryParam(PARAM_LICENSE);
License newLicense = License.parse(licenseParam).orElseThrow(() -> BadRequestException.create("The license provided is invalid"));

if (!webServer.isStandalone()) {
checkState(licenseCommit != null, "LicenseCommit instance not found. License-manager plugin should be installed.");
setLicenseWithoutInstall(newLicense);
} else if (editionInstaller.requiresInstallationChange(newLicense.getPluginKeys())) {
editionInstaller.install(newLicense);
} else {
checkState(licenseCommit != null,
"Can't decide edition does not require install if LicenseCommit instance is null. " +
"License-manager plugin should be installed.");
setLicenseWithoutInstall(newLicense);
}

WsUtils.writeProtobuf(buildResponse(), request, response);
}

private void setLicenseWithoutInstall(License newLicense) {
try {
licenseCommit.update(newLicense.getContent());
editionManagementState.newEditionWithoutInstall(newLicense.getEditionKey());
} catch (IllegalArgumentException e) {
Loggers.get(ApplyLicenseAction.class).error("Failed to commit license", e);
throw BadRequestException.create(e.getMessage());
}
}

private Editions.StatusResponse buildResponse() {
Editions.StatusResponse.Builder builder = Editions.StatusResponse.newBuilder()
.setNextEditionKey(editionManagementState.getPendingEditionKey().orElse(""))
.setCurrentEditionKey(editionManagementState.getCurrentEditionKey().orElse(""))
.setInstallationStatus(Editions.InstallationStatus.valueOf(editionManagementState.getPendingInstallationStatus().name()));
editionManagementState.getInstallErrorMessage().ifPresent(builder::setInstallError);
return builder.build();
}
}

+ 0
- 56
server/sonar-server/src/main/java/org/sonar/server/edition/ws/ClearErrorMessageAction.java View File

@@ -1,56 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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.edition.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.edition.MutableEditionManagementState;
import org.sonar.server.user.UserSession;

public class ClearErrorMessageAction implements EditionsWsAction {
private final UserSession userSession;
private final MutableEditionManagementState editionManagementState;

public ClearErrorMessageAction(UserSession userSession, MutableEditionManagementState editionManagementState) {
this.userSession = userSession;
this.editionManagementState = editionManagementState;
}

@Override
public void define(WebService.NewController controller) {
controller.createAction("clear_error_message")
.setSince("6.7")
.setPost(true)
.setDescription("Clear error message of last install of an edition (if any). Require 'Administer System' permission.")
.setHandler(this);
}

@Override
public void handle(Request request, Response response) throws Exception {
userSession
.checkLoggedIn()
.checkIsSystemAdministrator();

editionManagementState.clearInstallErrorMessage();

response.noContent();
}
}

+ 0
- 40
server/sonar-server/src/main/java/org/sonar/server/edition/ws/EditionsWs.java View File

@@ -1,40 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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.edition.ws;

import java.util.Arrays;
import org.sonar.api.server.ws.WebService;

public class EditionsWs implements WebService {
private final EditionsWsAction[] actions;

public EditionsWs(EditionsWsAction[] actions) {
this.actions = actions;
}

@Override
public void define(Context context) {
NewController controller = context.createController("api/editions")
.setSince("6.7")
.setDescription("Manage SonarSource commercial editions.");
Arrays.stream(actions).forEach(action -> action.define(controller));
controller.done();
}
}

+ 0
- 28
server/sonar-server/src/main/java/org/sonar/server/edition/ws/EditionsWsAction.java View File

@@ -1,28 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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.edition.ws;

import org.sonar.server.ws.WsAction;

/**
* Marker interface for actions of {@link EditionsWs}.
*/
public interface EditionsWsAction extends WsAction {
}

+ 0
- 73
server/sonar-server/src/main/java/org/sonar/server/edition/ws/FormDataAction.java View File

@@ -1,73 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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.edition.ws;

import org.sonar.api.platform.Server;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.server.user.UserSession;
import org.sonarqube.ws.Editions.FormDataResponse;

import static org.sonar.server.ws.WsUtils.writeProtobuf;

public class FormDataAction implements EditionsWsAction {
private final UserSession userSession;
private final Server server;
private final DbClient dbClient;

public FormDataAction(UserSession userSession, Server server, DbClient dbClient) {
this.userSession = userSession;
this.server = server;
this.dbClient = dbClient;
}

@Override
public void define(WebService.NewController controller) {
controller.createAction("form_data")
.setSince("6.7")
.setPost(false)
.setDescription("Provide data to prefill license request forms: the server ID and the total number of lines of code.")
.setResponseExample(getClass().getResource("example-edition-form_data.json"))
.setInternal(true)
.setHandler(this);
}

@Override
public void handle(Request request, Response response) throws Exception {
userSession
.checkLoggedIn()
.checkIsSystemAdministrator();

FormDataResponse responsePayload = FormDataResponse.newBuilder()
.setNcloc(computeNcloc())
.setServerId(server.getId())
.build();
writeProtobuf(responsePayload, request, response);
}

private long computeNcloc() {
try (DbSession dbSession = dbClient.openSession(false)) {
return dbClient.liveMeasureDao().sumNclocOfBiggestLongLivingBranch(dbSession);
}
}
}

+ 0
- 127
server/sonar-server/src/main/java/org/sonar/server/edition/ws/PreviewAction.java View File

@@ -1,127 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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.edition.ws;

import java.util.Optional;
import javax.annotation.Nullable;
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.edition.EditionManagementState;
import org.sonar.server.edition.License;
import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.platform.WebServer;
import org.sonar.server.plugins.edition.EditionInstaller;
import org.sonar.server.user.UserSession;
import org.sonar.server.ws.WsUtils;
import org.sonarqube.ws.Editions;

import static java.util.Optional.ofNullable;
import static org.sonarqube.ws.Editions.PreviewStatus.AUTOMATIC_INSTALL;
import static org.sonarqube.ws.Editions.PreviewStatus.MANUAL_INSTALL;
import static org.sonarqube.ws.Editions.PreviewStatus.NO_INSTALL;

public class PreviewAction implements EditionsWsAction {
private static final String PARAM_LICENSE = "license";

private final UserSession userSession;
private final EditionManagementState editionManagementState;
private final EditionInstaller editionInstaller;
private final WebServer webServer;

public PreviewAction(UserSession userSession, EditionManagementState editionManagementState, EditionInstaller editionInstaller,
WebServer webServer) {
this.userSession = userSession;
this.editionManagementState = editionManagementState;
this.editionInstaller = editionInstaller;
this.webServer = webServer;
}

@Override
public void define(WebService.NewController controller) {
WebService.NewAction action = controller.createAction("preview")
.setSince("6.7")
.setPost(true)
.setDescription("Preview the changes to SonarQube to match the specified license. Requires 'Administer System' permission.")
.setResponseExample(getClass().getResource("example-edition-preview.json"))
.setHandler(this);

action.createParam(PARAM_LICENSE)
.setRequired(true)
.setSince("6.7")
.setDescription("the license");
}

@Override
public void handle(Request request, Response response) throws Exception {
userSession.checkLoggedIn().checkIsSystemAdministrator();

if (editionManagementState.getPendingInstallationStatus() != EditionManagementState.PendingStatus.NONE) {
throw BadRequestException.create("Can't apply a license when applying one is already in progress");
}

String licenseParam = request.mandatoryParam(PARAM_LICENSE);
if (licenseParam.isEmpty()) {
throw new IllegalArgumentException(String.format("The '%s' parameter is empty", PARAM_LICENSE));
}
License newLicense = License.parse(licenseParam).orElseThrow(() -> BadRequestException.create("The license provided is invalid"));

NextState nextState = computeNextState(newLicense);
WsUtils.writeProtobuf(buildResponse(nextState), request, response);
}

private static Editions.PreviewResponse buildResponse(NextState nextState) {
return Editions.PreviewResponse.newBuilder()
.setNextEditionKey(nextState.getPendingEditionKey().orElse(""))
.setPreviewStatus(nextState.getPreviewStatus())
.build();
}

private NextState computeNextState(License newLicense) {
if (!webServer.isStandalone() || !editionInstaller.requiresInstallationChange(newLicense.getPluginKeys())) {
return new NextState(newLicense.getEditionKey(), NO_INSTALL);
// this won't refresh the update center (uses cached state). Preview is called while typing (must be fast)
// and anyway the status is refreshed when arriving at the marketplace page.
} else if (editionInstaller.isOffline()) {
return new NextState(newLicense.getEditionKey(), MANUAL_INSTALL);
} else {
return new NextState(newLicense.getEditionKey(), AUTOMATIC_INSTALL);
}
}

private static final class NextState {
private final String pendingEditionKey;
private final Editions.PreviewStatus previewStatus;

private NextState(@Nullable String pendingEditionKey, Editions.PreviewStatus previewStatus) {
this.pendingEditionKey = pendingEditionKey;
this.previewStatus = previewStatus;
}

Optional<String> getPendingEditionKey() {
return ofNullable(pendingEditionKey);
}

Editions.PreviewStatus getPreviewStatus() {
return previewStatus;
}
}

}

+ 0
- 63
server/sonar-server/src/main/java/org/sonar/server/edition/ws/StatusAction.java View File

@@ -1,63 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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.edition.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.edition.EditionManagementState;
import org.sonar.server.user.UserSession;
import org.sonar.server.ws.WsUtils;
import org.sonarqube.ws.Editions;

public class StatusAction implements EditionsWsAction {
private final UserSession userSession;
private final EditionManagementState editionManagementState;

public StatusAction(UserSession userSession, EditionManagementState editionManagementState) {
this.userSession = userSession;
this.editionManagementState = editionManagementState;
}

@Override
public void define(WebService.NewController controller) {
controller.createAction("status")
.setSince("6.7")
.setPost(false)
.setDescription("Provide status of SonarSource commercial edition of the current SonarQube. Requires 'Administer System' permission.")
.setResponseExample(getClass().getResource("example-edition-status.json"))
.setHandler(this);
}

@Override
public void handle(Request request, Response response) throws Exception {
userSession
.checkLoggedIn()
.checkIsSystemAdministrator();

Editions.StatusResponse.Builder responseBuilder = Editions.StatusResponse.newBuilder()
.setCurrentEditionKey(editionManagementState.getCurrentEditionKey().orElse(""))
.setNextEditionKey(editionManagementState.getPendingEditionKey().orElse(""))
.setInstallationStatus(Editions.InstallationStatus.valueOf(editionManagementState.getPendingInstallationStatus().name()));
editionManagementState.getInstallErrorMessage().ifPresent(responseBuilder::setInstallError);

WsUtils.writeProtobuf(responseBuilder.build(), request, response);
}
}

+ 0
- 66
server/sonar-server/src/main/java/org/sonar/server/edition/ws/UninstallAction.java View File

@@ -1,66 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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.edition.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.edition.EditionManagementState.PendingStatus;
import org.sonar.server.edition.MutableEditionManagementState;
import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.plugins.edition.EditionInstaller;
import org.sonar.server.user.UserSession;

import static org.sonar.server.edition.EditionManagementState.PendingStatus.NONE;
import static org.sonar.server.edition.EditionManagementState.PendingStatus.UNINSTALL_IN_PROGRESS;

public class UninstallAction implements EditionsWsAction {
private final UserSession userSession;
private final MutableEditionManagementState mutableEditionManagementState;
private final EditionInstaller editionInstaller;

public UninstallAction(UserSession userSession, MutableEditionManagementState mutableEditionManagementState, EditionInstaller editionInstaller) {
this.userSession = userSession;
this.mutableEditionManagementState = mutableEditionManagementState;
this.editionInstaller = editionInstaller;
}

@Override
public void define(WebService.NewController controller) {
controller.createAction("uninstall")
.setSince("6.7")
.setPost(true)
.setDescription("Uninstall the currently installed edition. Requires 'Administer System' permission.")
.setHandler(this);
}

@Override
public void handle(Request request, Response response) throws Exception {
userSession.checkLoggedIn().checkIsSystemAdministrator();
PendingStatus status = mutableEditionManagementState.getPendingInstallationStatus();
if (status != NONE && status != UNINSTALL_IN_PROGRESS) {
throw BadRequestException.create("Uninstall of the current edition is not allowed when install of an edition is in progress");
}

editionInstaller.uninstall();
mutableEditionManagementState.uninstall();
response.noContent();
}
}

+ 0
- 23
server/sonar-server/src/main/java/org/sonar/server/edition/ws/package-info.java View File

@@ -1,23 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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.edition.ws;

import javax.annotation.ParametersAreNonnullByDefault;

+ 0
- 37
server/sonar-server/src/main/java/org/sonar/server/license/LicenseCommit.java View File

@@ -1,37 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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.license;

import org.sonar.api.ExtensionPoint;

@ExtensionPoint
public interface LicenseCommit {
/**
* Installs the specified license on SonarQube.
*
* @throws IllegalArgumentException if new license is invalid or cannot be read
*/
void update(String newLicense);

/**
* Remove any license installed on SonarQube.
*/
void delete();
}

+ 0
- 23
server/sonar-server/src/main/java/org/sonar/server/license/package-info.java View File

@@ -1,23 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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.license;

import javax.annotation.ParametersAreNonnullByDefault;

+ 0
- 18
server/sonar-server/src/main/java/org/sonar/server/platform/ServerFileSystem.java View File

@@ -27,12 +27,6 @@ import java.io.File;
*/
public interface ServerFileSystem {

/**
* Directory that contains the persisted data to be kept on restarts and upgrades.
* @return an existing directory
*/
File getDataDir();

/**
* Root directory of the server installation
* @return an existing directory
@@ -58,13 +52,6 @@ public interface ServerFileSystem {
*/
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 a directory which may or not exist
@@ -85,9 +72,4 @@ public interface ServerFileSystem {
*/
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();
}

+ 2
- 20
server/sonar-server/src/main/java/org/sonar/server/platform/ServerFileSystemImpl.java View File

@@ -36,18 +36,15 @@ public class ServerFileSystemImpl implements ServerFileSystem, org.sonar.api.pla

private final File homeDir;
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(PATH_HOME.getKey()).get());
this.tempDir = new File(config.get(PATH_TEMP.getKey()).get());
this.dataDir = new File(config.get(PATH_DATA.getKey()).get());
this.deployDir = new File(this.dataDir, TomcatContexts.WEB_DEPLOY_PATH_RELATIVE_TO_DATA_DIR);
File dataDir = new File(config.get(PATH_DATA.getKey()).get());
this.deployDir = new File(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
@@ -70,11 +67,6 @@ public class ServerFileSystemImpl implements ServerFileSystem, org.sonar.api.pla
return tempDir;
}

@Override
public File getDataDir() {
return dataDir;
}

@Override
public File getDeployedPluginsDir() {
return new File(deployDir, "plugins");
@@ -85,11 +77,6 @@ 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");
@@ -105,9 +92,4 @@ public class ServerFileSystemImpl implements ServerFileSystem, org.sonar.api.pla
return uninstallDir;
}

@Override
public File getEditionUninstalledPluginsDir() {
return editionUninstallDir;
}

}

+ 1
- 19
server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java View File

@@ -32,6 +32,7 @@ import org.sonar.ce.CeModule;
import org.sonar.ce.notification.ReportAnalysisFailureNotificationModule;
import org.sonar.ce.settings.ProjectConfigurationFactory;
import org.sonar.core.component.DefaultResourceTypes;
import org.sonar.core.extension.CoreExtensionsInstaller;
import org.sonar.core.platform.ComponentContainer;
import org.sonar.core.timemachine.Periods;
import org.sonar.server.authentication.AuthenticationModule;
@@ -56,8 +57,6 @@ import org.sonar.server.debt.DebtRulesXMLImporter;
import org.sonar.server.duplication.ws.DuplicationsParser;
import org.sonar.server.duplication.ws.DuplicationsWs;
import org.sonar.server.duplication.ws.ShowResponseBuilder;
import org.sonar.server.edition.EditionsWsModule;
import org.sonar.server.edition.FinalizeEditionChange;
import org.sonar.server.email.ws.EmailsWsModule;
import org.sonar.server.es.IndexCreator;
import org.sonar.server.es.IndexDefinitions;
@@ -67,7 +66,6 @@ import org.sonar.server.es.metadata.EsDbCompatibilityImpl;
import org.sonar.server.es.metadata.MetadataIndex;
import org.sonar.server.es.metadata.MetadataIndexDefinition;
import org.sonar.server.event.NewAlerts;
import org.sonar.core.extension.CoreExtensionsInstaller;
import org.sonar.server.favorite.FavoriteModule;
import org.sonar.server.health.NodeHealthModule;
import org.sonar.server.issue.AddTagsAction;
@@ -138,10 +136,6 @@ 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;
@@ -284,12 +278,6 @@ public class PlatformLevel4 extends PlatformLevel {
IndexDefinitions.class,
WebPagesFilter.class,

// edition
EditionInstaller.class,
EditionPluginDownloader.class,
EditionInstallerExecutor.class,
EditionPluginUninstaller.class,

// batch
BatchWsModule.class,

@@ -556,9 +544,6 @@ public class PlatformLevel4 extends PlatformLevel {
CeModule.class,
CeWsModule.class,

// SonarSource editions
EditionsWsModule.class,

InternalPropertiesImpl.class,
ProjectConfigurationFactory.class,

@@ -582,9 +567,6 @@ public class PlatformLevel4 extends PlatformLevel {
add(TelemetryDataLoader.class);
addIfStartupLeader(TelemetryDaemon.class, TelemetryClient.class);

// edition
addIfStartupLeader(FinalizeEditionChange.class);

// system info
addIfCluster(WebSystemInfoModule.forClusterMode()).otherwiseAdd(WebSystemInfoModule.forStandaloneMode());


+ 0
- 91
server/sonar-server/src/main/java/org/sonar/server/plugins/AbstractPluginUninstaller.java View File

@@ -1,91 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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<PluginInfo> getUninstalledPlugins() {
return listJarFiles(uninstallDir)
.stream()
.map(PluginInfo::create)
.collect(MoreCollectors.toList());
}

private static Collection<File> 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));
}
}

}

+ 64
- 2
server/sonar-server/src/main/java/org/sonar/server/plugins/PluginUninstaller.java View File

@@ -19,10 +19,72 @@
*/
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 org.sonar.server.platform.ServerFileSystem;

public class PluginUninstaller extends AbstractPluginUninstaller {
import static java.lang.String.format;
import static org.apache.commons.io.FileUtils.forceMkdir;

public class PluginUninstaller implements Startable {
private static final String PLUGIN_EXTENSION = "jar";
private final ServerPluginRepository serverPluginRepository;
private final File uninstallDir;

public PluginUninstaller(ServerPluginRepository serverPluginRepository, ServerFileSystem fs) {
super(serverPluginRepository, fs.getUninstalledPluginsDir());
this.serverPluginRepository = serverPluginRepository;
this.uninstallDir = fs.getUninstalledPluginsDir();
}

private static Collection<File> listJarFiles(File dir) {
if (dir.exists()) {
return FileUtils.listFiles(dir, new String[] {PLUGIN_EXTENSION}, false);
}
return Collections.emptyList();
}

@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<PluginInfo> getUninstalledPlugins() {
return listJarFiles(uninstallDir)
.stream()
.map(PluginInfo::create)
.collect(MoreCollectors.toList());
}

private void ensurePluginIsInstalled(String key) {
if (!serverPluginRepository.hasPlugin(key)) {
throw new IllegalArgumentException(format("Plugin [%s] is not installed", key));
}
}
}

+ 0
- 9
server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginRepository.java View File

@@ -102,7 +102,6 @@ public class ServerPluginRepository implements PluginRepository, Startable {
public void start() {
loadPreInstalledPlugins();
moveDownloadedPlugins();
moveDownloadedEditionPlugins();
unloadIncompatiblePlugins();
logInstalledPlugins();
loadInstances();
@@ -149,14 +148,6 @@ public class ServerPluginRepository implements PluginRepository, Startable {
}
}

private void moveDownloadedEditionPlugins() {
if (fs.getEditionDownloadedPluginsDir().exists()) {
for (File sourceFile : listJarFiles(fs.getEditionDownloadedPluginsDir())) {
overrideAndRegisterPlugin(sourceFile);
}
}
}

private void registerPluginInfo(PluginInfo info) {
String pluginKey = info.getKey();
if (blacklistedPluginKeys.contains(pluginKey)) {

+ 0
- 149
server/sonar-server/src/main/java/org/sonar/server/plugins/edition/EditionInstaller.java View File

@@ -1,149 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Semaphore;
import java.util.stream.Collectors;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
import org.sonar.core.platform.PluginInfo;
import org.sonar.server.edition.License;
import org.sonar.server.edition.MutableEditionManagementState;
import org.sonar.server.plugins.ServerPluginRepository;
import org.sonar.server.plugins.UpdateCenterMatrixFactory;
import org.sonar.updatecenter.common.UpdateCenter;

public class EditionInstaller {
private static final Logger LOG = Loggers.get(EditionInstaller.class);

private final Semaphore semaphore = new Semaphore(1);
private final EditionInstallerExecutor executor;
private final EditionPluginDownloader editionPluginDownloader;
private final EditionPluginUninstaller editionPluginUninstaller;
private final ServerPluginRepository pluginRepository;
private final UpdateCenterMatrixFactory updateCenterMatrixFactory;
private final MutableEditionManagementState editionManagementState;

public EditionInstaller(EditionPluginDownloader editionDownloader, EditionPluginUninstaller editionPluginUninstaller,
ServerPluginRepository pluginRepository, EditionInstallerExecutor executor, UpdateCenterMatrixFactory updateCenterMatrixFactory,
MutableEditionManagementState editionManagementState) {
this.editionPluginDownloader = editionDownloader;
this.editionPluginUninstaller = editionPluginUninstaller;
this.pluginRepository = pluginRepository;
this.executor = executor;
this.updateCenterMatrixFactory = updateCenterMatrixFactory;
this.editionManagementState = editionManagementState;
}

/**
* Refreshes the update center, and submits in a executor a task to download all the needed plugins (asynchronously).
* If the update center is disabled or if we are offline, the task is not submitted.
*
* @throws IllegalStateException if an installation is already in progress
*/
public void install(License newLicense) {
if (semaphore.tryAcquire()) {
try {
Optional<UpdateCenter> updateCenter = updateCenterMatrixFactory.getUpdateCenter(true);
if (!updateCenter.isPresent()) {
LOG.info("Installation of edition '{}' needs to be done manually", newLicense.getEditionKey());
editionManagementState.startManualInstall(newLicense);
return;
}
editionManagementState.startAutomaticInstall(newLicense);
executor.execute(() -> asyncInstall(newLicense, updateCenter.get()));
} catch (RuntimeException e) {
semaphore.release();
throw e;
}
} else {
throw new IllegalStateException("Another installation of an edition is already running");
}
}

public void uninstall() {
Map<String, PluginInfo> pluginInfosByKeys = pluginRepository.getPluginInfosByKeys();
Set<String> pluginsToRemove = pluginsToRemove(Collections.emptySet(), pluginInfosByKeys.values());
uninstallPlugins(pluginsToRemove);
}

/**
* Check if the update center is disabled or unreachable. It uses the cached status (it doesn't refresh),
* to be a cost-free check.
*/
public boolean isOffline() {
return !updateCenterMatrixFactory.getUpdateCenter(false).isPresent();
}

public boolean requiresInstallationChange(Set<String> editionPluginKeys) {
Map<String, PluginInfo> pluginInfosByKeys = pluginRepository.getPluginInfosByKeys();

return !pluginsToInstall(editionPluginKeys, pluginInfosByKeys.keySet()).isEmpty()
|| !pluginsToRemove(editionPluginKeys, pluginInfosByKeys.values()).isEmpty();
}

private void asyncInstall(License newLicense, UpdateCenter updateCenter) {
try {
Set<String> editionPluginKeys = newLicense.getPluginKeys();
Map<String, PluginInfo> pluginInfosByKeys = pluginRepository.getPluginInfosByKeys();
Set<String> pluginsToRemove = pluginsToRemove(editionPluginKeys, pluginInfosByKeys.values());
Set<String> pluginsToInstall = pluginsToInstall(editionPluginKeys, pluginInfosByKeys.keySet());

LOG.info("Installing edition '{}', download: {}, remove: {}",
newLicense.getEditionKey(), pluginsToInstall, pluginsToRemove);

editionPluginDownloader.downloadEditionPlugins(pluginsToInstall, updateCenter);
uninstallPlugins(pluginsToRemove);
editionManagementState.automaticInstallReady();
} catch (Throwable t) {
LOG.error("Failed to install edition {} with plugins {}", newLicense.getEditionKey(), newLicense.getPluginKeys(), t);
editionManagementState.installFailed(t.getMessage());
} finally {
semaphore.release();
}
}

private void uninstallPlugins(Set<String> pluginsToRemove) {
pluginsToRemove.stream().forEach(editionPluginUninstaller::uninstall);
}

private static Set<String> pluginsToInstall(Set<String> editionPluginKeys, Set<String> installedPluginKeys) {
return editionPluginKeys.stream()
.filter(p -> !installedPluginKeys.contains(p))
.collect(Collectors.toSet());
}

private static Set<String> pluginsToRemove(Set<String> editionPluginKeys, Collection<PluginInfo> installedPluginInfos) {
Set<String> installedCommercialPluginKeys = installedPluginInfos.stream()
.filter(EditionBundledPlugins::isEditionBundled)
.map(PluginInfo::getKey)
.collect(Collectors.toSet());

return installedCommercialPluginKeys.stream()
.filter(p -> !editionPluginKeys.contains(p))
.collect(Collectors.toSet());
}

}

+ 0
- 45
server/sonar-server/src/main/java/org/sonar/server/plugins/edition/EditionInstallerExecutor.java View File

@@ -1,45 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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<ExecutorService> {
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);
}
}

+ 0
- 108
server/sonar-server/src/main/java/org/sonar/server/plugins/edition/EditionPluginDownloader.java View File

@@ -1,108 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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 java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashSet;
import java.util.Set;
import org.sonar.api.utils.HttpDownloader;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
import org.sonar.core.util.FileUtils;
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.toFile;
import static org.apache.commons.lang.StringUtils.substringAfterLast;

public class EditionPluginDownloader {
private static final Logger LOG = Loggers.get(EditionPluginDownloader.class);
private static final String PLUGIN_EXTENSION = "jar";

private final Path tmpDir;
private final Path downloadDir;
private final HttpDownloader downloader;

public EditionPluginDownloader(HttpDownloader downloader, ServerFileSystem fileSystem) {
this.downloadDir = fileSystem.getEditionDownloadedPluginsDir().toPath();
this.downloader = downloader;
this.tmpDir = downloadDir.resolveSibling(downloadDir.getFileName() + "_tmp");
}

public void downloadEditionPlugins(Set<String> pluginKeys, UpdateCenter updateCenter) {
try {
Set<Release> pluginsToInstall = new HashSet<>();
for (String pluginKey : pluginKeys) {
pluginsToInstall.addAll(updateCenter.findInstallablePlugins(pluginKey, Version.create("")));
}

FileUtils.deleteDirectory(tmpDir);
Files.createDirectories(tmpDir);

for (Release r : pluginsToInstall) {
download(r);
}

FileUtils.deleteDirectory(downloadDir);
Files.move(tmpDir, downloadDir);
} catch (IOException e) {
throw new IllegalStateException(e.getMessage(), e);
} finally {
FileUtils.deleteQuietly(tmpDir);
}
}

protected void download(Release release) {
try {
LOG.info("Downloading plugin: {}", release.getArtifact().getKey());
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());
Files.copy(file.toPath(), tmpDir.resolve(file.getName()));
} else {
String filename = substringAfterLast(uri.getPath(), "/");
if (!filename.endsWith("." + PLUGIN_EXTENSION)) {
filename = release.getKey() + "-" + release.getVersion() + "." + PLUGIN_EXTENSION;
}
Path targetFile = tmpDir.resolve(filename);
downloader.download(uri, targetFile.toFile());
}
}
}

+ 0
- 31
server/sonar-server/src/main/java/org/sonar/server/plugins/edition/EditionPluginUninstaller.java View File

@@ -1,31 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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());
}
}

+ 0
- 5
server/sonar-server/src/main/resources/org/sonar/server/edition/ws/example-edition-apply_license.json View File

@@ -1,5 +0,0 @@
{
"currentEditionKey": "",
"installationStatus": "AUTOMATIC_IN_PROGRESS",
"nextEditionKey": "developer-edition"
}

+ 0
- 4
server/sonar-server/src/main/resources/org/sonar/server/edition/ws/example-edition-form_data.json View File

@@ -1,4 +0,0 @@
{
"serverId": "uuid",
"ncloc": 12345
}

+ 0
- 4
server/sonar-server/src/main/resources/org/sonar/server/edition/ws/example-edition-preview.json View File

@@ -1,4 +0,0 @@
{
"nextEditionKey": "developer-edition",
"previewStatus": "AUTOMATIC_INSTALL"
}

+ 0
- 5
server/sonar-server/src/main/resources/org/sonar/server/edition/ws/example-edition-status.json View File

@@ -1,5 +0,0 @@
{
"currentEditionKey": "",
"installationStatus": "AUTOMATIC_READY",
"nextEditionKey": "developer-edition"
}

+ 0
- 39
server/sonar-server/src/test/java/org/sonar/server/edition/EditionsWsModuleTest.java View File

@@ -1,39 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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.edition;

import org.junit.Test;
import org.sonar.core.platform.ComponentContainer;

import static org.assertj.core.api.Assertions.assertThat;
import static org.sonar.core.platform.ComponentContainer.COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER;

public class EditionsWsModuleTest {
private EditionsWsModule underTest = new EditionsWsModule();

@Test
public void verify_component_count() {
ComponentContainer container = new ComponentContainer();
underTest.configure(container);

assertThat(container.getPicoContainer().getComponentAdapters())
.hasSize(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 8);
}
}

+ 0
- 275
server/sonar-server/src/test/java/org/sonar/server/edition/FinalizeEditionChangeTest.java View File

@@ -1,275 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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.edition;

import java.util.Optional;
import org.apache.commons.lang.RandomStringUtils;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonar.api.utils.log.LogTester;
import org.sonar.api.utils.log.LoggerLevel;
import org.sonar.server.license.LicenseCommit;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.doThrow;
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;
import static org.sonar.server.edition.EditionManagementState.PendingStatus.AUTOMATIC_IN_PROGRESS;
import static org.sonar.server.edition.EditionManagementState.PendingStatus.AUTOMATIC_READY;
import static org.sonar.server.edition.EditionManagementState.PendingStatus.MANUAL_IN_PROGRESS;
import static org.sonar.server.edition.EditionManagementState.PendingStatus.NONE;
import static org.sonar.server.edition.EditionManagementState.PendingStatus.UNINSTALL_IN_PROGRESS;

public class FinalizeEditionChangeTest {
@Rule
public ExpectedException expectedException = ExpectedException.none();
@Rule
public LogTester logTester = new LogTester();

private MutableEditionManagementState editionManagementState = mock(MutableEditionManagementState.class);
private LicenseCommit licenseCommit = mock(LicenseCommit.class);
private FinalizeEditionChange underTest = new FinalizeEditionChange(editionManagementState);
private FinalizeEditionChange underTestWithLicenseCommit = new FinalizeEditionChange(editionManagementState, licenseCommit);

@Test
public void start_clears_error_message_when_status_is_NONE_without_LicenseCommit() {
when(editionManagementState.getPendingInstallationStatus()).thenReturn(NONE);

underTest.start();

verify(editionManagementState).getPendingInstallationStatus();
verify(editionManagementState).clearInstallErrorMessage();
verifyNoMoreInteractions(editionManagementState);
}

@Test
public void start_clears_error_message_when_status_is_NONE_with_LicenseCommit() {
when(editionManagementState.getPendingInstallationStatus()).thenReturn(NONE);

underTestWithLicenseCommit.start();

verify(editionManagementState).getPendingInstallationStatus();
verify(editionManagementState).clearInstallErrorMessage();
verifyNoMoreInteractions(editionManagementState);
verifyZeroInteractions(licenseCommit);
}

@Test
public void start_clears_status_when_status_is_AUTOMATIC_READY_and_no_LicenseCommit_is_available() {
when(editionManagementState.getPendingInstallationStatus()).thenReturn(AUTOMATIC_READY);

underTest.start();

verify(editionManagementState).getPendingInstallationStatus();
verify(editionManagementState).finalizeInstallation("Edition installation didn't complete. Some plugins were not installed.");

verifyNoMoreInteractions(editionManagementState);
verifyZeroInteractions(licenseCommit);
assertThat(logTester.logs()).hasSize(1);
assertThat(logTester.logs(LoggerLevel.WARN))
.containsOnly("Edition installation didn't complete. Some plugins were not installed.");
}

@Test
public void start_clears_status_when_status_is_AUTOMATIC_READY_and_license_is_not_available() {
when(editionManagementState.getPendingInstallationStatus()).thenReturn(AUTOMATIC_READY);
when(editionManagementState.getPendingLicense()).thenReturn(Optional.empty());

underTestWithLicenseCommit.start();

verify(editionManagementState).getPendingInstallationStatus();
verify(editionManagementState).finalizeInstallation("Edition installation didn't complete. License was not found.");
verify(editionManagementState).getPendingLicense();

verifyNoMoreInteractions(editionManagementState);
verifyZeroInteractions(licenseCommit);
assertThat(logTester.logs()).hasSize(1);
assertThat(logTester.logs(LoggerLevel.WARN))
.containsOnly("Edition installation didn't complete. License was not found.");
}

@Test
public void start_commit_license_and_finalizeInstallation_in_editionManagementState_when_status_is_AUTOMATIC_READY_and_LicenseCommit_is_available() {
when(editionManagementState.getPendingInstallationStatus()).thenReturn(AUTOMATIC_READY);
String license = RandomStringUtils.randomAlphanumeric(20);
when(editionManagementState.getPendingLicense()).thenReturn(Optional.of(license));

underTestWithLicenseCommit.start();

verify(editionManagementState).getPendingInstallationStatus();
verify(editionManagementState).getPendingLicense();
verify(editionManagementState).finalizeInstallation(null);
verifyNoMoreInteractions(editionManagementState);
verify(licenseCommit).update(license);
verifyNoMoreInteractions(licenseCommit);
}

@Test
public void start_commit_license_and_finalizeInstallation_with_error_in_editionManagementState_when_status_is_AUTOMATIC_READY_and_license_is_invalid() {
when(editionManagementState.getPendingInstallationStatus()).thenReturn(AUTOMATIC_READY);
String license = RandomStringUtils.randomAlphanumeric(20);
when(editionManagementState.getPendingLicense()).thenReturn(Optional.of(license));
doThrow(new IllegalArgumentException("Faking an IAE because of an invalid license"))
.when(licenseCommit)
.update(license);

underTestWithLicenseCommit.start();

verify(editionManagementState).getPendingInstallationStatus();
verify(editionManagementState).getPendingLicense();
verify(editionManagementState).finalizeInstallation("Edition installation didn't complete. License is not valid. Please set a new license.");
verifyNoMoreInteractions(editionManagementState);
verify(licenseCommit).update(license);
verifyNoMoreInteractions(licenseCommit);
assertThat(logTester.logs()).hasSize(1);
assertThat(logTester.logs(LoggerLevel.WARN))
.containsOnly("Edition installation didn't complete. License is not valid. Please set a new license.");
}

@Test
public void start_clears_status_when_status_is_MANUAL_IN_PROGRESS_and_no_LicenseCommit_is_available() {
when(editionManagementState.getPendingInstallationStatus()).thenReturn(MANUAL_IN_PROGRESS);

underTest.start();

verify(editionManagementState).getPendingInstallationStatus();
verify(editionManagementState).finalizeInstallation("Edition installation didn't complete. Some plugins were not installed.");
verifyNoMoreInteractions(editionManagementState);
verifyZeroInteractions(licenseCommit);
assertThat(logTester.logs()).hasSize(1);
assertThat(logTester.logs(LoggerLevel.WARN))
.containsOnly("Edition installation didn't complete. Some plugins were not installed.");
}

@Test
public void start_commit_license_and_finalizeInstallation_in_editionManagementState_when_status_is_MANUAL_IN_PROGRESS_and_LicenseCommit_is_available() {
when(editionManagementState.getPendingInstallationStatus()).thenReturn(MANUAL_IN_PROGRESS);
String license = RandomStringUtils.randomAlphanumeric(20);
when(editionManagementState.getPendingLicense()).thenReturn(Optional.of(license));

underTestWithLicenseCommit.start();

verify(editionManagementState).getPendingInstallationStatus();
verify(editionManagementState).getPendingLicense();
verify(editionManagementState).finalizeInstallation(null);
verifyNoMoreInteractions(editionManagementState);
verify(licenseCommit).update(license);
verifyNoMoreInteractions(licenseCommit);
}

@Test
public void start_commit_license_and_finalizeInstallation_with_error_in_editionManagementState_when_status_is_MANUAL_IN_PROGRESS_and_license_is_invalid() {
when(editionManagementState.getPendingInstallationStatus()).thenReturn(MANUAL_IN_PROGRESS);
String license = RandomStringUtils.randomAlphanumeric(20);
when(editionManagementState.getPendingLicense()).thenReturn(Optional.of(license));
doThrow(new IllegalArgumentException("Faking an IAE because of an invalid license"))
.when(licenseCommit)
.update(license);

underTestWithLicenseCommit.start();

verify(editionManagementState).getPendingInstallationStatus();
verify(editionManagementState).getPendingLicense();
verify(editionManagementState).finalizeInstallation("Edition installation didn't complete. License is not valid. Please set a new license.");
verifyNoMoreInteractions(editionManagementState);
verify(licenseCommit).update(license);
verifyNoMoreInteractions(licenseCommit);
assertThat(logTester.logs()).hasSize(1);
assertThat(logTester.logs(LoggerLevel.WARN))
.containsOnly("Edition installation didn't complete. License is not valid. Please set a new license.");
}

@Test
public void start_put_editionManagement_set_in_automaticInstallError_when_status_is_AUTOMATIC_PROGRESS_and_no_LicenseCommit_is_available() {
when(editionManagementState.getPendingInstallationStatus()).thenReturn(AUTOMATIC_IN_PROGRESS);

underTest.start();

verify(editionManagementState).getPendingInstallationStatus();
verify(editionManagementState).installFailed("SonarQube was restarted before asynchronous installation of edition completed");
verifyNoMoreInteractions(editionManagementState);
verifyZeroInteractions(licenseCommit);
}

@Test
public void start_put_editionManagement_set_in_automaticInstallError_when_status_is_AUTOMATIC_PROGRESS_and_LicenseCommit_is_available() {
when(editionManagementState.getPendingInstallationStatus()).thenReturn(AUTOMATIC_IN_PROGRESS);

underTestWithLicenseCommit.start();

verify(editionManagementState).getPendingInstallationStatus();
verify(editionManagementState).installFailed("SonarQube was restarted before asynchronous installation of edition completed");
verifyNoMoreInteractions(editionManagementState);
verifyZeroInteractions(licenseCommit);
}

@Test
public void stop_should_remove_license_if_uninstalling_and_LicenseCommit_is_available() {
when(editionManagementState.getPendingInstallationStatus()).thenReturn(UNINSTALL_IN_PROGRESS);

underTestWithLicenseCommit.stop();

assertThat(logTester.logs()).hasSize(1);
assertThat(logTester.logs(LoggerLevel.DEBUG))
.containsOnly("Removing license");
verify(licenseCommit).delete();
verifyNoMoreInteractions(licenseCommit);
verify(editionManagementState).getPendingInstallationStatus();
verifyNoMoreInteractions(editionManagementState);
}

@Test
public void stop_should_log_if_uninstalling_and_LicenseCommit_is_not_available() {
when(editionManagementState.getPendingInstallationStatus()).thenReturn(UNINSTALL_IN_PROGRESS);

underTest.stop();

assertThat(logTester.logs()).hasSize(1);
assertThat(logTester.logs(LoggerLevel.WARN))
.containsOnly("License Manager plugin not found - cannot remove the license");
verify(editionManagementState).getPendingInstallationStatus();
verifyNoMoreInteractions(editionManagementState);
}

@Test
public void should_commit_uninstall() {
when(editionManagementState.getPendingInstallationStatus()).thenReturn(EditionManagementState.PendingStatus.UNINSTALL_IN_PROGRESS);

underTest.start();

verify(editionManagementState).getPendingInstallationStatus();
verify(editionManagementState).finalizeInstallation(null);
verifyNoMoreInteractions(editionManagementState);
}

@Test
public void should_fail_uninstall_if_license_commit_is_present() {
when(editionManagementState.getPendingInstallationStatus()).thenReturn(EditionManagementState.PendingStatus.UNINSTALL_IN_PROGRESS);

expectedException.expect(IllegalStateException.class);
expectedException.expectMessage("License Manager plugin is still present");

underTestWithLicenseCommit.start();
}
}

+ 0
- 150
server/sonar-server/src/test/java/org/sonar/server/edition/LicenseTest.java View File

@@ -1,150 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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.edition;

import com.google.common.collect.ImmutableSet;
import java.io.IOException;
import java.io.StringWriter;
import java.util.Base64;
import java.util.Collections;
import java.util.Optional;
import java.util.Properties;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

import static org.assertj.core.api.Assertions.assertThat;

public class LicenseTest {
@Rule
public ExpectedException expectedException = ExpectedException.none();

@Test
public void constructor_fails_if_editionKey_is_null() {
expectedException.expect(NullPointerException.class);
expectedException.expectMessage("editionKey can't be null");

new License(null, Collections.emptyList(), "content");
}

@Test
public void constructor_fails_if_editionKey_is_empty() {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("editionKey can't be empty");

new License("", Collections.emptyList(), "content");
}

@Test
public void constructor_fails_if_plugins_is_null() {
expectedException.expect(NullPointerException.class);

new License("edition-key", null, "content");
}

@Test
public void constructor_fails_if_content_is_null() {
expectedException.expect(NullPointerException.class);
expectedException.expectMessage("content can't be null");

new License("edition-key", Collections.emptyList(), null);
}

@Test
public void constructor_fails_if_content_is_empty() {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("content can't be empty");

new License("edition-key", Collections.emptyList(), "");
}

@Test
public void parse_returns_empty_if_license_is_invalid_string() {
assertThat(License.parse("trash")).isEmpty();
}

@Test
public void parse_succeeds() throws IOException {
Properties props = new Properties();
props.setProperty("Plugins", "plugin1,plugin2");
props.setProperty("Edition", "dev");
StringWriter writer = new StringWriter();
props.store(writer, "");

byte[] encoded = Base64.getEncoder().encode(writer.toString().getBytes());

Optional<License> license = License.parse(new String(encoded));
assertThat(license).isPresent();
assertThat(license.get().getEditionKey()).isEqualTo("dev");
assertThat(license.get().getPluginKeys()).containsOnly("plugin1", "plugin2");
}

@Test
public void parse_is_empty_if_license_has_no_plugin() throws IOException {
Properties props = new Properties();
props.setProperty("Plugins", "");
props.setProperty("Edition", "dev");
StringWriter writer = new StringWriter();
props.store(writer, "");

byte[] encoded = Base64.getEncoder().encode(writer.toString().getBytes());

Optional<License> license = License.parse(new String(encoded));
assertThat(license).isEmpty();
}

@Test
public void parse_is_empty_if_license_has_empty_edition_key() throws IOException {
Properties props = new Properties();
props.setProperty("Plugins", "p1");
props.setProperty("Edition", "");
StringWriter writer = new StringWriter();
props.store(writer, "");

byte[] encoded = Base64.getEncoder().encode(writer.toString().getBytes());

Optional<License> license = License.parse(new String(encoded));
assertThat(license).isEmpty();
}

@Test
public void parse_is_empty_if_license_has_no_edition_key() throws IOException {
Properties props = new Properties();
props.setProperty("Plugins", "plugin1,plugin2");
StringWriter writer = new StringWriter();
props.store(writer, "");

byte[] encoded = Base64.getEncoder().encode(writer.toString().getBytes());

Optional<License> license = License.parse(new String(encoded));
assertThat(license).isEmpty();
}

@Test
public void verify_getters() {
ImmutableSet<String> pluginKeys = ImmutableSet.of("a", "b", "c");

License underTest = new License("edition-key", pluginKeys, "content");

assertThat(underTest.getEditionKey()).isEqualTo("edition-key");
assertThat(underTest.getPluginKeys()).isEqualTo(pluginKeys);
assertThat(underTest.getContent()).isEqualTo("content");
}
}

+ 0
- 1311
server/sonar-server/src/test/java/org/sonar/server/edition/StandaloneEditionManagementStateImplTest.java
File diff suppressed because it is too large
View File


+ 0
- 356
server/sonar-server/src/test/java/org/sonar/server/edition/ws/ApplyLicenseActionTest.java View File

@@ -1,356 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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.edition.ws;

import com.tngtech.java.junit.dataprovider.DataProvider;
import com.tngtech.java.junit.dataprovider.DataProviderRunner;
import com.tngtech.java.junit.dataprovider.UseDataProvider;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Base64;
import java.util.Optional;
import java.util.Properties;
import javax.annotation.Nullable;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.sonar.api.server.ws.WebService;
import org.sonar.server.edition.EditionManagementState;
import org.sonar.server.edition.EditionManagementState.PendingStatus;
import org.sonar.server.edition.License;
import org.sonar.server.edition.MutableEditionManagementState;
import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.exceptions.UnauthorizedException;
import org.sonar.server.license.LicenseCommit;
import org.sonar.server.platform.WebServer;
import org.sonar.server.plugins.edition.EditionInstaller;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.ws.TestRequest;
import org.sonar.server.ws.TestResponse;
import org.sonar.server.ws.WsActionTester;
import org.sonar.test.JsonAssert;
import org.sonarqube.ws.MediaTypes;
import org.sonarqube.ws.Editions;
import org.sonarqube.ws.Editions.StatusResponse;

import static java.util.Collections.singleton;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import static org.sonar.server.edition.EditionManagementState.PendingStatus.AUTOMATIC_IN_PROGRESS;
import static org.sonar.server.edition.EditionManagementState.PendingStatus.NONE;

@RunWith(DataProviderRunner.class)
public class ApplyLicenseActionTest {
private static final String PARAM_LICENSE = "license";
private static final String PENDING_EDITION_NAME = "developer-edition";

@Rule
public ExpectedException expectedException = ExpectedException.none();
@Rule
public UserSessionRule userSessionRule = UserSessionRule.standalone();

private EditionInstaller editionInstaller = mock(EditionInstaller.class);
private MutableEditionManagementState mutableEditionManagementState = mock(MutableEditionManagementState.class);
private LicenseCommit licenseCommit = mock(LicenseCommit.class);
private WebServer webServer = mock(WebServer.class);
private ApplyLicenseAction underTest = new ApplyLicenseAction(userSessionRule, mutableEditionManagementState, editionInstaller,
webServer, licenseCommit);
private WsActionTester actionTester = new WsActionTester(underTest);

@Test
public void verify_definition() {
WebService.Action def = actionTester.getDef();

assertThat(def.key()).isEqualTo("apply_license");
assertThat(def.since()).isEqualTo("6.7");
assertThat(def.isPost()).isTrue();
assertThat(def.isInternal()).isFalse();
assertThat(def.description()).isNotEmpty();
assertThat(def.params()).hasSize(1);

WebService.Param licenseParam = def.param("license");
assertThat(licenseParam.isRequired()).isTrue();
assertThat(licenseParam.description()).isNotEmpty();
}

@Test
public void request_fails_if_user_not_logged_in() {
userSessionRule.anonymous();
TestRequest request = actionTester.newRequest();

expectedException.expect(UnauthorizedException.class);
expectedException.expectMessage("Authentication is required");

request.execute();
}

@Test
public void request_fails_if_user_is_not_system_administer() {
userSessionRule.logIn();
TestRequest request = actionTester.newRequest();

expectedException.expect(ForbiddenException.class);
expectedException.expectMessage("Insufficient privileges");

request.execute();
}

@Test
public void request_fails_if_license_param_is_not_provided() {
userSessionRule.logIn().setSystemAdministrator();
when(mutableEditionManagementState.getPendingInstallationStatus()).thenReturn(NONE);
TestRequest request = actionTester.newRequest();

expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("The 'license' parameter is missing");

request.execute();
}

@Test
@UseDataProvider("notNonePendingInstallationStatuses")
public void request_fails_with_BadRequestException_is_pendingStatus_is_not_NONE(EditionManagementState.PendingStatus notNone) {
userSessionRule.logIn().setSystemAdministrator();
when(mutableEditionManagementState.getCurrentEditionKey()).thenReturn(Optional.empty());
when(mutableEditionManagementState.getPendingEditionKey()).thenReturn(Optional.empty());
when(mutableEditionManagementState.getPendingInstallationStatus()).thenReturn(notNone);
TestRequest request = actionTester.newRequest()
.setParam(PARAM_LICENSE, "foo");

expectedException.expect(BadRequestException.class);
expectedException.expectMessage("Can't apply a license when applying one is already in progress");

request.execute();
}

@Test
public void request_fails_with_BadRequestException_if_license_is_invalid() {
userSessionRule.logIn().setSystemAdministrator();
TestRequest request = actionTester.newRequest()
.setParam(PARAM_LICENSE, "invalid");
when(mutableEditionManagementState.getPendingInstallationStatus()).thenReturn(NONE);

expectedException.expect(BadRequestException.class);
expectedException.expectMessage("The license provided is invalid");
request.execute();
}

@Test
public void request_fails_with_ISE_if_is_cluster_and_license_plugin_is_not_installed() throws IOException {
underTest = new ApplyLicenseAction(userSessionRule, mutableEditionManagementState, editionInstaller, webServer, null);
actionTester = new WsActionTester(underTest);
userSessionRule.logIn().setSystemAdministrator();

when(mutableEditionManagementState.getPendingInstallationStatus()).thenReturn(NONE);
when(webServer.isStandalone()).thenReturn(false);

TestRequest request = actionTester.newRequest().setParam(PARAM_LICENSE, createLicenseParam("dev", "plugin1"));

expectedException.expect(IllegalStateException.class);
expectedException.expectMessage("License-manager plugin should be installed");
request.execute();
}

@Test
public void request_fails_with_ISE_if_no_change_needed_but_license_plugin_is_not_installed() throws IOException {
underTest = new ApplyLicenseAction(userSessionRule, mutableEditionManagementState, editionInstaller, webServer, null);
actionTester = new WsActionTester(underTest);
userSessionRule.logIn().setSystemAdministrator();

setPendingLicense(NONE);
when(webServer.isStandalone()).thenReturn(true);

TestRequest request = actionTester.newRequest().setParam(PARAM_LICENSE, createLicenseParam("dev", "plugin1"));

expectedException.expect(IllegalStateException.class);
expectedException.expectMessage("Can't decide edition does not require install if LicenseCommit instance is null");
request.execute();
}

@Test
public void always_apply_license_when_is_cluster() throws IOException {
userSessionRule.logIn().setSystemAdministrator();
when(webServer.isStandalone()).thenReturn(false);
setPendingLicense(NONE);

TestRequest request = actionTester.newRequest()
.setMediaType(MediaTypes.PROTOBUF)
.setParam(PARAM_LICENSE, createLicenseParam(PENDING_EDITION_NAME, "plugin1"));

TestResponse response = request.execute();
assertResponse(response, PENDING_EDITION_NAME, "", NONE);
verify(mutableEditionManagementState).newEditionWithoutInstall(PENDING_EDITION_NAME);
verifyZeroInteractions(editionInstaller);
}

@Test
public void verify_example() throws IOException {
userSessionRule.logIn().setSystemAdministrator();
setPendingLicense(AUTOMATIC_IN_PROGRESS, null);

TestRequest request = actionTester.newRequest()
.setParam(PARAM_LICENSE, createLicenseParam("dev", "plugin1"));

JsonAssert.assertJson(request.execute().getInput()).isSimilarTo(actionTester.getDef().responseExampleAsString());
}

@Test
public void apply_without_need_to_install() throws IOException {
userSessionRule.logIn().setSystemAdministrator();
setPendingLicense(NONE);
when(editionInstaller.requiresInstallationChange(singleton("plugin1"))).thenReturn(false);
String base64License = createLicenseParam(PENDING_EDITION_NAME, "plugin1");
when(webServer.isStandalone()).thenReturn(true);

TestRequest request = actionTester.newRequest()
.setMediaType(MediaTypes.PROTOBUF)
.setParam(PARAM_LICENSE, base64License);

TestResponse response = request.execute();

assertResponse(response, PENDING_EDITION_NAME, "", NONE);
verify(mutableEditionManagementState).newEditionWithoutInstall(PENDING_EDITION_NAME);
verify(licenseCommit).update(base64License);
}

@Test
public void execute_throws_BadRequestException_if_license_validation_fails_when_there_is_no_need_to_install() throws IOException {
userSessionRule.logIn().setSystemAdministrator();
setPendingLicense(NONE, null);
when(editionInstaller.requiresInstallationChange(singleton("plugin1"))).thenReturn(false);
String base64License = createLicenseParam(PENDING_EDITION_NAME, "plugin1");
TestRequest request = actionTester.newRequest()
.setMediaType(MediaTypes.PROTOBUF)
.setParam(PARAM_LICENSE, base64License);
IllegalArgumentException fakeValidationError = new IllegalArgumentException("Faking failed validation of license on update call");
doThrow(fakeValidationError)
.when(licenseCommit)
.update(base64License);

expectedException.expect(BadRequestException.class);
expectedException.expectMessage(fakeValidationError.getMessage());

request.execute();
}

@Test
public void apply_offline() throws IOException {
userSessionRule.logIn().setSystemAdministrator();
setPendingLicense(PendingStatus.MANUAL_IN_PROGRESS);
when(editionInstaller.requiresInstallationChange(singleton("plugin1"))).thenReturn(true);
when(webServer.isStandalone()).thenReturn(true);

TestRequest request = actionTester.newRequest()
.setMediaType(MediaTypes.PROTOBUF)
.setParam(PARAM_LICENSE, createLicenseParam(PENDING_EDITION_NAME, "plugin1"));

TestResponse response = request.execute();

assertResponse(response, PENDING_EDITION_NAME, "", PendingStatus.MANUAL_IN_PROGRESS);
verify(mutableEditionManagementState, times(0)).startManualInstall(any(License.class));
verify(mutableEditionManagementState, times(0)).startAutomaticInstall(any(License.class));
}

@Test
public void apply_successfully_auto_installation() throws IOException {
userSessionRule.logIn().setSystemAdministrator();
setPendingLicense(PendingStatus.AUTOMATIC_IN_PROGRESS);
when(editionInstaller.requiresInstallationChange(singleton("plugin1"))).thenReturn(true);
when(webServer.isStandalone()).thenReturn(true);

TestRequest request = actionTester.newRequest()
.setMediaType(MediaTypes.PROTOBUF)
.setParam(PARAM_LICENSE, createLicenseParam(PENDING_EDITION_NAME, "plugin1"));

TestResponse response = request.execute();

assertResponse(response, PENDING_EDITION_NAME, "", PendingStatus.AUTOMATIC_IN_PROGRESS);
verify(mutableEditionManagementState, times(0)).startAutomaticInstall(any(License.class));
verify(mutableEditionManagementState, times(0)).startManualInstall(any(License.class));
}

@Test
public void returns_auto_install_fails_instantly() throws IOException {
userSessionRule.logIn().setSystemAdministrator();
String errorMessage = "error! an error!";
setPendingLicense(PendingStatus.NONE, errorMessage);
when(editionInstaller.requiresInstallationChange(singleton("plugin1"))).thenReturn(true);
when(mutableEditionManagementState.getInstallErrorMessage()).thenReturn(Optional.of(errorMessage));

TestRequest request = actionTester.newRequest()
.setMediaType(MediaTypes.PROTOBUF)
.setParam(PARAM_LICENSE, createLicenseParam(PENDING_EDITION_NAME, "plugin1"));

TestResponse response = request.execute();

StatusResponse parsedResponse = Editions.StatusResponse.parseFrom(response.getInputStream());
assertThat(parsedResponse.getInstallError()).isEqualTo(errorMessage);
}

private void assertResponse(TestResponse response, String expectedNextEditionKey, String expectedEditionKey,
PendingStatus expectedPendingStatus) throws IOException {
StatusResponse parsedResponse = Editions.StatusResponse.parseFrom(response.getInputStream());
assertThat(parsedResponse.getCurrentEditionKey()).isEqualTo(expectedEditionKey);
assertThat(parsedResponse.getNextEditionKey()).isEqualTo(expectedNextEditionKey);
assertThat(parsedResponse.getInstallationStatus()).isEqualTo(Editions.InstallationStatus.valueOf(expectedPendingStatus.toString()));
}

private void setPendingLicense(PendingStatus pendingStatus) {
setPendingLicense(pendingStatus, null);
}

private void setPendingLicense(PendingStatus pendingStatus, @Nullable String errorMessage) {
when(mutableEditionManagementState.getCurrentEditionKey()).thenReturn(Optional.empty());
when(mutableEditionManagementState.getPendingEditionKey()).thenReturn(Optional.of(PENDING_EDITION_NAME));
when(mutableEditionManagementState.getPendingInstallationStatus())
.thenReturn(NONE)
.thenReturn(pendingStatus);
when(mutableEditionManagementState.getInstallErrorMessage()).thenReturn(Optional.ofNullable(errorMessage));
}

private static String createLicenseParam(String editionKey, String... pluginKeys) throws IOException {
Properties props = new Properties();
props.setProperty("Plugins", String.join(",", pluginKeys));
props.setProperty("Edition", editionKey);
StringWriter writer = new StringWriter();
props.store(writer, "");

byte[] encoded = Base64.getEncoder().encode(writer.toString().getBytes());
return new String(encoded, StandardCharsets.UTF_8);
}

@DataProvider
public static Object[][] notNonePendingInstallationStatuses() {
return Arrays.stream(EditionManagementState.PendingStatus.values())
.filter(s -> s != NONE)
.map(s -> new Object[] {s})
.toArray(Object[][]::new);
}
}

+ 0
- 90
server/sonar-server/src/test/java/org/sonar/server/edition/ws/ClearErrorMessageActionTest.java View File

@@ -1,90 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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.edition.ws;

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.mockito.Mockito;
import org.sonar.api.server.ws.WebService;
import org.sonar.server.edition.MutableEditionManagementState;
import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.exceptions.UnauthorizedException;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.ws.TestRequest;
import org.sonar.server.ws.WsActionTester;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.verify;

public class ClearErrorMessageActionTest {
@Rule
public UserSessionRule userSessionRule = UserSessionRule.standalone();
@Rule
public ExpectedException expectedException = ExpectedException.none();

private MutableEditionManagementState editionManagementState = Mockito.mock(MutableEditionManagementState.class);
private ClearErrorMessageAction underTest = new ClearErrorMessageAction(userSessionRule, editionManagementState);
private WsActionTester actionTester = new WsActionTester(underTest);

@Test
public void verify_definition() {
WebService.Action def = actionTester.getDef();

assertThat(def.key()).isEqualTo("clear_error_message");
assertThat(def.since()).isEqualTo("6.7");
assertThat(def.isPost()).isTrue();
assertThat(def.isInternal()).isFalse();
assertThat(def.description()).isNotEmpty();
assertThat(def.responseExample()).isNull();
assertThat(def.params()).isEmpty();
}

@Test
public void request_fails_if_user_not_logged_in() {
userSessionRule.anonymous();
TestRequest request = actionTester.newRequest();

expectedException.expect(UnauthorizedException.class);
expectedException.expectMessage("Authentication is required");

request.execute();
}

@Test
public void request_fails_if_user_is_not_system_administer() {
userSessionRule.logIn();
TestRequest request = actionTester.newRequest();

expectedException.expect(ForbiddenException.class);
expectedException.expectMessage("Insufficient privileges");

request.execute();
}

@Test
public void request_clears_errorMessage_from_editionManagement_state() {
userSessionRule.logIn().setSystemAdministrator();

actionTester.newRequest().execute();

verify(editionManagementState).clearInstallErrorMessage();
}
}

+ 0
- 130
server/sonar-server/src/test/java/org/sonar/server/edition/ws/FormDataActionTest.java View File

@@ -1,130 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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.edition.ws;

import com.tngtech.java.junit.dataprovider.DataProviderRunner;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.sonar.api.platform.Server;
import org.sonar.api.server.ws.WebService;
import org.sonar.db.DbClient;
import org.sonar.db.DbTester;
import org.sonar.db.component.ComponentDto;
import org.sonar.db.metric.MetricDto;
import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.exceptions.UnauthorizedException;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.ws.TestRequest;
import org.sonar.server.ws.WsActionTester;
import org.sonarqube.ws.Editions.FormDataResponse;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.sonar.api.measures.CoreMetrics.NCLOC_KEY;
import static org.sonar.api.measures.Metric.ValueType.INT;
import static org.sonar.test.JsonAssert.assertJson;

@RunWith(DataProviderRunner.class)
public class FormDataActionTest {

@Rule
public ExpectedException expectedException = ExpectedException.none();
@Rule
public UserSessionRule userSessionRule = UserSessionRule.standalone();
@Rule
public DbTester db = DbTester.create();

private Server server = mock(Server.class);
private DbClient dbClient = db.getDbClient();
private FormDataAction underTest = new FormDataAction(userSessionRule, server, dbClient);

private WsActionTester ws = new WsActionTester(underTest);

@Test
public void definition() {
WebService.Action def = ws.getDef();

assertThat(def.key()).isEqualTo("form_data");
assertThat(def.since()).isEqualTo("6.7");
assertThat(def.isPost()).isFalse();
assertThat(def.isInternal()).isTrue();
assertThat(def.description()).isNotEmpty();
assertThat(def.params()).isEmpty();
}

@Test
public void request_fails_if_user_not_logged_in() {
userSessionRule.anonymous();
TestRequest request = ws.newRequest();

expectedException.expect(UnauthorizedException.class);
expectedException.expectMessage("Authentication is required");

request.execute();
}

@Test
public void request_fails_if_user_is_not_system_administer() {
userSessionRule.logIn();
TestRequest request = ws.newRequest();

expectedException.expect(ForbiddenException.class);
expectedException.expectMessage("Insufficient privileges");

request.execute();
}

@Test
public void json_example() {
userSessionRule.logIn().setSystemAdministrator();
when(server.getId()).thenReturn("uuid");
setNcloc(12345L);

String result = ws.newRequest().execute().getInput();

assertJson(result).isSimilarTo(ws.getDef().responseExampleAsString());
}

@Test
public void returns_server_id_and_nloc() {
userSessionRule.logIn().setSystemAdministrator();
when(server.getId()).thenReturn("myserver");
long ncloc = 256L;
setNcloc(ncloc);

FormDataResponse expectedResponse = FormDataResponse.newBuilder()
.setServerId("myserver")
.setNcloc(ncloc)
.build();

FormDataResponse result = ws.newRequest().executeProtobuf(FormDataResponse.class);

assertThat(result).isEqualTo(expectedResponse);
}

private void setNcloc(double ncloc) {
ComponentDto project = db.components().insertMainBranch();
MetricDto nclocMetric = db.measures().insertMetric(m -> m.setValueType(INT.toString()).setKey(NCLOC_KEY));
db.measures().insertLiveMeasure(project, nclocMetric, m -> m.setValue(ncloc));
}
}

+ 0
- 260
server/sonar-server/src/test/java/org/sonar/server/edition/ws/PreviewActionTest.java View File

@@ -1,260 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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.edition.ws;

import com.tngtech.java.junit.dataprovider.DataProvider;
import com.tngtech.java.junit.dataprovider.DataProviderRunner;
import com.tngtech.java.junit.dataprovider.UseDataProvider;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Base64;
import java.util.Collections;
import java.util.Properties;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.sonar.api.server.ws.WebService;
import org.sonar.server.edition.EditionManagementState;
import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.exceptions.UnauthorizedException;
import org.sonar.server.platform.WebServer;
import org.sonar.server.plugins.edition.EditionInstaller;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.ws.TestRequest;
import org.sonar.server.ws.TestResponse;
import org.sonar.server.ws.WsActionTester;
import org.sonar.test.JsonAssert;
import org.sonarqube.ws.Editions;
import org.sonarqube.ws.MediaTypes;
import org.sonarqube.ws.Editions.PreviewResponse;
import org.sonarqube.ws.Editions.PreviewStatus;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.sonar.server.edition.EditionManagementState.PendingStatus.NONE;

@RunWith(DataProviderRunner.class)
public class PreviewActionTest {
private static final String PARAM_LICENSE = "license";

@Rule
public ExpectedException expectedException = ExpectedException.none();
@Rule
public UserSessionRule userSessionRule = UserSessionRule.standalone();

private EditionManagementState editionManagementState = mock(EditionManagementState.class);
private EditionInstaller editionInstaller = mock(EditionInstaller.class);
private WebServer webServer = mock(WebServer.class);
private PreviewAction underTest = new PreviewAction(userSessionRule, editionManagementState, editionInstaller, webServer);
private WsActionTester actionTester = new WsActionTester(underTest);

@Test
public void verify_definition() {
WebService.Action def = actionTester.getDef();

assertThat(def.key()).isEqualTo("preview");
assertThat(def.since()).isEqualTo("6.7");
assertThat(def.isPost()).isTrue();
assertThat(def.isInternal()).isFalse();
assertThat(def.description()).isNotEmpty();
assertThat(def.params()).hasSize(1);

WebService.Param licenseParam = def.param("license");
assertThat(licenseParam.isRequired()).isTrue();
assertThat(licenseParam.description()).isNotEmpty();
}

@Test
public void request_fails_if_user_not_logged_in() {
userSessionRule.anonymous();
TestRequest request = actionTester.newRequest();

expectedException.expect(UnauthorizedException.class);
expectedException.expectMessage("Authentication is required");

request.execute();
}

@Test
public void request_fails_if_user_is_not_system_administer() {
userSessionRule.logIn();
TestRequest request = actionTester.newRequest();

expectedException.expect(ForbiddenException.class);
expectedException.expectMessage("Insufficient privileges");

request.execute();
}

@Test
public void request_fails_if_license_param_is_not_provided() {
userSessionRule.logIn().setSystemAdministrator();
when(editionManagementState.getPendingInstallationStatus()).thenReturn(NONE);
TestRequest request = actionTester.newRequest();

expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("The 'license' parameter is missing");

request.execute();
}

@Test
public void request_fails_if_license_param_is_empty() {
userSessionRule.logIn().setSystemAdministrator();
when(editionManagementState.getPendingInstallationStatus()).thenReturn(NONE);
TestRequest request = actionTester.newRequest()
.setParam(PARAM_LICENSE, "");

expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("The 'license' parameter is empty");

request.execute();
}

@Test
public void request_fails_if_license_param_is_invalid() {
userSessionRule.logIn().setSystemAdministrator();
when(editionManagementState.getPendingInstallationStatus()).thenReturn(NONE);
TestRequest request = actionTester.newRequest()
.setParam(PARAM_LICENSE, "foo");

expectedException.expect(BadRequestException.class);
expectedException.expectMessage("The license provided is invalid");

request.execute();
}

@Test
@UseDataProvider("notNonePendingInstallationStatuses")
public void request_fails_with_BadRequestException_is_pendingStatus_is_not_NONE(EditionManagementState.PendingStatus notNone) {
userSessionRule.logIn().setSystemAdministrator();
when(editionManagementState.getPendingInstallationStatus()).thenReturn(notNone);
TestRequest request = actionTester.newRequest()
.setParam(PARAM_LICENSE, "foo");

expectedException.expect(BadRequestException.class);
expectedException.expectMessage("Can't apply a license when applying one is already in progress");

request.execute();
}

@Test
public void verify_example() throws IOException {
userSessionRule.logIn().setSystemAdministrator();
when(webServer.isStandalone()).thenReturn(true);
when(editionManagementState.getPendingInstallationStatus()).thenReturn(NONE);
when(editionInstaller.requiresInstallationChange(Collections.singleton("plugin1"))).thenReturn(true);
when(editionInstaller.isOffline()).thenReturn(false);

TestRequest request = actionTester.newRequest()
.setParam(PARAM_LICENSE, createLicenseParam("developer-edition", "plugin1"));

JsonAssert.assertJson(request.execute().getInput()).isSimilarTo(actionTester.getDef().responseExampleAsString());
}

@Test
public void license_requires_no_installation() throws IOException {
when(webServer.isStandalone()).thenReturn(true);
userSessionRule.logIn().setSystemAdministrator();
when(editionManagementState.getPendingInstallationStatus()).thenReturn(NONE);
when(editionInstaller.requiresInstallationChange(Collections.singleton("plugin1"))).thenReturn(false);

TestRequest request = actionTester.newRequest()
.setMediaType(MediaTypes.PROTOBUF)
.setParam(PARAM_LICENSE, createLicenseParam("developer-edition", "plugin1"));

assertResponse(request.execute(), "developer-edition", PreviewStatus.NO_INSTALL);
}

@Test
public void cluster_require_no_installation() throws IOException {
when(webServer.isStandalone()).thenReturn(false);
userSessionRule.logIn().setSystemAdministrator();
when(editionManagementState.getPendingInstallationStatus()).thenReturn(NONE);
when(editionInstaller.requiresInstallationChange(Collections.singleton("plugin1"))).thenReturn(false);

TestRequest request = actionTester.newRequest()
.setMediaType(MediaTypes.PROTOBUF)
.setParam(PARAM_LICENSE, createLicenseParam("developer-edition", "plugin1"));

assertResponse(request.execute(), "developer-edition", PreviewStatus.NO_INSTALL);
}

@Test
public void license_will_result_in_auto_install() throws IOException {
userSessionRule.logIn().setSystemAdministrator();
when(webServer.isStandalone()).thenReturn(true);
when(editionManagementState.getPendingInstallationStatus()).thenReturn(NONE);
when(editionInstaller.requiresInstallationChange(Collections.singleton("plugin1"))).thenReturn(true);
when(editionInstaller.isOffline()).thenReturn(false);

TestRequest request = actionTester.newRequest()
.setMediaType(MediaTypes.PROTOBUF)
.setParam(PARAM_LICENSE, createLicenseParam("developer-edition", "plugin1"));

assertResponse(request.execute(), "developer-edition", PreviewStatus.AUTOMATIC_INSTALL);
}

@Test
public void license_will_result_in_manual_install() throws IOException {
userSessionRule.logIn().setSystemAdministrator();
when(webServer.isStandalone()).thenReturn(true);
when(editionManagementState.getPendingInstallationStatus()).thenReturn(NONE);
when(editionInstaller.requiresInstallationChange(Collections.singleton("plugin1"))).thenReturn(true);
when(editionInstaller.isOffline()).thenReturn(true);

TestRequest request = actionTester.newRequest()
.setMediaType(MediaTypes.PROTOBUF)
.setParam(PARAM_LICENSE, createLicenseParam("developer-edition", "plugin1"));

assertResponse(request.execute(), "developer-edition", PreviewStatus.MANUAL_INSTALL);
}

private void assertResponse(TestResponse response, String expectedNextEditionKey, PreviewStatus expectedPreviewStatus) throws IOException {
PreviewResponse parsedResponse = Editions.PreviewResponse.parseFrom(response.getInputStream());
assertThat(parsedResponse.getPreviewStatus()).isEqualTo(expectedPreviewStatus);
assertThat(parsedResponse.getNextEditionKey()).isEqualTo(expectedNextEditionKey);
}

private static String createLicenseParam(String editionKey, String... pluginKeys) throws IOException {
Properties props = new Properties();
props.setProperty("Plugins", String.join(",", pluginKeys));
props.setProperty("Edition", editionKey);
StringWriter writer = new StringWriter();
props.store(writer, "");

byte[] encoded = Base64.getEncoder().encode(writer.toString().getBytes());
return new String(encoded, StandardCharsets.UTF_8);
}

@DataProvider
public static Object[][] notNonePendingInstallationStatuses() {
return Arrays.stream(EditionManagementState.PendingStatus.values())
.filter(s -> s != NONE)
.map(s -> new Object[] {s})
.toArray(Object[][]::new);
}

}

+ 0
- 132
server/sonar-server/src/test/java/org/sonar/server/edition/ws/StatusActionTest.java View File

@@ -1,132 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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.edition.ws;

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonar.api.server.ws.WebService;
import org.sonar.server.edition.EditionManagementState;
import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.exceptions.UnauthorizedException;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.ws.TestRequest;
import org.sonar.server.ws.WsActionTester;
import org.sonar.test.JsonAssert;

import static java.util.Optional.empty;
import static java.util.Optional.of;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.sonar.server.edition.EditionManagementState.PendingStatus.NONE;

public class StatusActionTest {
@Rule
public UserSessionRule userSessionRule = UserSessionRule.standalone();
@Rule
public ExpectedException expectedException = ExpectedException.none();

private EditionManagementState editionManagementState = mock(EditionManagementState.class);
private StatusAction underTest = new StatusAction(userSessionRule, editionManagementState);
private WsActionTester actionTester = new WsActionTester(underTest);

@Test
public void verify_definition() {
WebService.Action def = actionTester.getDef();
assertThat(def.key()).isEqualTo("status");
assertThat(def.since()).isEqualTo("6.7");
assertThat(def.isPost()).isFalse();
assertThat(def.description()).isNotEmpty();
assertThat(def.params()).isEmpty();
}

@Test
public void request_fails_if_user_not_logged_in() {
userSessionRule.anonymous();
TestRequest request = actionTester.newRequest();

expectedException.expect(UnauthorizedException.class);
expectedException.expectMessage("Authentication is required");

request.execute();
}

@Test
public void request_fails_if_user_is_not_system_administer() {
userSessionRule.logIn();
TestRequest request = actionTester.newRequest();

expectedException.expect(ForbiddenException.class);
expectedException.expectMessage("Insufficient privileges");

request.execute();
}

@Test
public void verify_example() {
userSessionRule.logIn().setSystemAdministrator();
when(editionManagementState.getCurrentEditionKey()).thenReturn(empty());
when(editionManagementState.getPendingEditionKey()).thenReturn(of("developer-edition"));
when(editionManagementState.getPendingInstallationStatus()).thenReturn(EditionManagementState.PendingStatus.AUTOMATIC_READY);
when(editionManagementState.getInstallErrorMessage()).thenReturn(empty());

TestRequest request = actionTester.newRequest();

JsonAssert.assertJson(request.execute().getInput()).isSimilarTo(actionTester.getDef().responseExampleAsString());
}

@Test
public void response_contains_optional_fields_as_empty_string() {
userSessionRule.logIn().setSystemAdministrator();
when(editionManagementState.getCurrentEditionKey()).thenReturn(empty());
when(editionManagementState.getPendingEditionKey()).thenReturn(empty());
when(editionManagementState.getPendingInstallationStatus()).thenReturn(NONE);
when(editionManagementState.getInstallErrorMessage()).thenReturn(empty());

TestRequest request = actionTester.newRequest();

JsonAssert.assertJson(request.execute().getInput())
.isSimilarTo("{" +
" \"currentEditionKey\": \"\"," +
" \"installationStatus\": \"NONE\"," +
" \"nextEditionKey\": \"\"" +
"}");
}

@Test
public void response_contains_automaticInstallError_when_present() {
userSessionRule.logIn().setSystemAdministrator();
when(editionManagementState.getCurrentEditionKey()).thenReturn(empty());
when(editionManagementState.getPendingEditionKey()).thenReturn(empty());
when(editionManagementState.getPendingInstallationStatus()).thenReturn(NONE);
String errorMessage = "an error! oh god, an error!";
when(editionManagementState.getInstallErrorMessage()).thenReturn(of(errorMessage));
TestRequest request = actionTester.newRequest();

JsonAssert.assertJson(request.execute().getInput())
.isSimilarTo("{" +
" \"currentEditionKey\": \"\"," +
" \"installationStatus\": \"NONE\"," +
" \"nextEditionKey\": \"\"," +
" \"installError\": \"" + errorMessage + "\"" +
"}");
}
}

+ 0
- 137
server/sonar-server/src/test/java/org/sonar/server/edition/ws/UninstallActionTest.java View File

@@ -1,137 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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.edition.ws;

import com.tngtech.java.junit.dataprovider.DataProvider;
import com.tngtech.java.junit.dataprovider.DataProviderRunner;
import com.tngtech.java.junit.dataprovider.UseDataProvider;
import java.util.Arrays;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.sonar.api.server.ws.WebService;
import org.sonar.server.edition.EditionManagementState;
import org.sonar.server.edition.MutableEditionManagementState;
import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.exceptions.UnauthorizedException;
import org.sonar.server.plugins.edition.EditionInstaller;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.ws.TestRequest;
import org.sonar.server.ws.TestResponse;
import org.sonar.server.ws.WsActionTester;

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;
import static org.sonar.server.edition.EditionManagementState.PendingStatus.NONE;
import static org.sonar.server.edition.EditionManagementState.PendingStatus.UNINSTALL_IN_PROGRESS;

@RunWith(DataProviderRunner.class)
public class UninstallActionTest {
@Rule
public ExpectedException expectedException = ExpectedException.none();
@Rule
public UserSessionRule userSessionRule = UserSessionRule.standalone();

private EditionInstaller editionInstaller = mock(EditionInstaller.class);
private MutableEditionManagementState mutableEditionManagementState = mock(MutableEditionManagementState.class);
private UninstallAction action = new UninstallAction(userSessionRule, mutableEditionManagementState, editionInstaller);
private WsActionTester actionTester = new WsActionTester(action);

@Test
public void check_definition() {
WebService.Action def = actionTester.getDef();

assertThat(def.key()).isEqualTo("uninstall");
assertThat(def.since()).isEqualTo("6.7");
assertThat(def.isPost()).isTrue();
assertThat(def.isInternal()).isFalse();
assertThat(def.responseExampleAsString()).isNull();
assertThat(def.description()).isNotEmpty();
assertThat(def.params()).isEmpty();
}

@Test
public void request_fails_if_user_not_logged_in() {
userSessionRule.anonymous();
TestRequest request = actionTester.newRequest();

expectedException.expect(UnauthorizedException.class);
expectedException.expectMessage("Authentication is required");

request.execute();
}

@Test
public void request_fails_if_user_is_not_system_administer() {
userSessionRule.logIn();
TestRequest request = actionTester.newRequest();

expectedException.expect(ForbiddenException.class);
expectedException.expectMessage("Insufficient privileges");

request.execute();
}

@Test
@UseDataProvider("notNoneOrUninstallStatuses")
public void request_fails_if_current_status_is_not_none_nor_uninstall(EditionManagementState.PendingStatus notNoneOrUninstall) {
userSessionRule.logIn().setSystemAdministrator();
when(mutableEditionManagementState.getPendingInstallationStatus()).thenReturn(notNoneOrUninstall);

expectedException.expect(BadRequestException.class);
expectedException.expectMessage("Uninstall of the current edition is not allowed when install of an edition");
actionTester.newRequest().execute();
}

@Test
public void successful_edition_uninstall() {
userSessionRule.logIn().setSystemAdministrator();
when(mutableEditionManagementState.getPendingInstallationStatus()).thenReturn(NONE);

TestResponse execute = actionTester.newRequest().execute();
assertThat(execute.getStatus()).isEqualTo(204);
verify(editionInstaller).uninstall();
verify(mutableEditionManagementState).uninstall();
}

@Test
public void successful_edition_uninstall_when_state_is_already_uninstall_in_progress() {
userSessionRule.logIn().setSystemAdministrator();
when(mutableEditionManagementState.getPendingInstallationStatus()).thenReturn(UNINSTALL_IN_PROGRESS);

TestResponse execute = actionTester.newRequest().execute();
assertThat(execute.getStatus()).isEqualTo(204);
verify(editionInstaller).uninstall();
verify(mutableEditionManagementState).uninstall();
}

@DataProvider
public static Object[][] notNoneOrUninstallStatuses() {
return Arrays.stream(EditionManagementState.PendingStatus.values())
.filter(s -> s != NONE)
.filter(s -> s != UNINSTALL_IN_PROGRESS)
.map(s -> new Object[] {s})
.toArray(Object[][]::new);
}
}

+ 0
- 35
server/sonar-server/src/test/java/org/sonar/server/plugins/edition/EditionInstallerExecutorTest.java View File

@@ -1,35 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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();
}
}

+ 0
- 293
server/sonar-server/src/test/java/org/sonar/server/plugins/edition/EditionInstallerTest.java View File

@@ -1,293 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.sonar.api.utils.log.LogTester;
import org.sonar.api.utils.log.LoggerLevel;
import org.sonar.core.platform.PluginInfo;
import org.sonar.server.edition.License;
import org.sonar.server.edition.MutableEditionManagementState;
import org.sonar.server.plugins.ServerPluginRepository;
import org.sonar.server.plugins.UpdateCenterMatrixFactory;
import org.sonar.updatecenter.common.UpdateCenter;

import static java.util.Collections.singleton;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anySetOf;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;

public class EditionInstallerTest {
@Rule
public LogTester logTester = new LogTester();

private static final String PLUGIN_KEY = "key";

private EditionPluginDownloader downloader = mock(EditionPluginDownloader.class);
private EditionPluginUninstaller uninstaller = mock(EditionPluginUninstaller.class);
private UpdateCenterMatrixFactory updateCenterMatrixFactory = mock(UpdateCenterMatrixFactory.class);
private ServerPluginRepository pluginRepository = mock(ServerPluginRepository.class);
private UpdateCenter updateCenter = mock(UpdateCenter.class);
private MutableEditionManagementState editionManagementState = mock(MutableEditionManagementState.class);

private EditionInstallerExecutor synchronousExecutor = new EditionInstallerExecutor() {
public void execute(Runnable r) {
r.run();
}
};
private EditionInstallerExecutor mockedExecutor = mock(EditionInstallerExecutor.class);

private EditionInstaller underTestSynchronousExecutor = new EditionInstaller(downloader, uninstaller, pluginRepository, synchronousExecutor, updateCenterMatrixFactory,
editionManagementState);
private EditionInstaller underTestMockedExecutor = new EditionInstaller(downloader, uninstaller, pluginRepository, mockedExecutor, updateCenterMatrixFactory,
editionManagementState);

@Before
public void setUp() {
when(updateCenterMatrixFactory.getUpdateCenter(anyBoolean())).thenReturn(Optional.of(updateCenter));
}

@Test
public void launch_task_download_plugins() {
underTestSynchronousExecutor.install(licenseWithPluginKeys(PLUGIN_KEY));

verify(downloader).downloadEditionPlugins(singleton(PLUGIN_KEY), updateCenter);
}

@Test
public void editionManagementState_is_changed_before_running_background_thread() {
PluginInfo commercial1 = createPluginInfo("p1", true);
PluginInfo commercial2 = createPluginInfo("p2", true);
PluginInfo open1 = createPluginInfo("p3", false);
mockPluginRepository(commercial1, commercial2, open1);
License newLicense = licenseWithPluginKeys("p1", "p4");

underTestMockedExecutor.install(newLicense);

verify(editionManagementState).startAutomaticInstall(newLicense);
verify(editionManagementState, times(0)).automaticInstallReady();
verifyNoMoreInteractions(editionManagementState);
ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
verify(mockedExecutor).execute(runnableCaptor.capture());

reset(editionManagementState);
runnableCaptor.getValue().run();

verify(editionManagementState).automaticInstallReady();
verifyNoMoreInteractions(editionManagementState);
}

@Test
public void editionManagementState_is_changed_to_automatic_failure_when_read_existing_plugins_fails() {
RuntimeException fakeException = new RuntimeException("Faking getPluginInfosByKeys throwing an exception");
when(pluginRepository.getPluginInfosByKeys())
.thenThrow(fakeException);
License newLicense = licenseWithPluginKeys("p1", "p4");

underTestSynchronousExecutor.install(newLicense);

verifyMoveToAutomaticFailureAndLogsError(newLicense, fakeException.getMessage());
}

@Test
public void editionManagementState_is_changed_to_automatic_failure_when_downloader_fails() {
PluginInfo commercial1 = createPluginInfo("p1", true);
PluginInfo commercial2 = createPluginInfo("p2", true);
PluginInfo open1 = createPluginInfo("p3", false);
mockPluginRepository(commercial1, commercial2, open1);
RuntimeException fakeException = new RuntimeException("Faking downloadEditionPlugins throwing an exception");
doThrow(fakeException)
.when(downloader)
.downloadEditionPlugins(anySetOf(String.class), any(UpdateCenter.class));
License newLicense = licenseWithPluginKeys("p1", "p4");

underTestSynchronousExecutor.install(newLicense);

verifyMoveToAutomaticFailureAndLogsError(newLicense, fakeException.getMessage());
}

@Test
public void editionManagementState_is_changed_to_automatic_failure_when_uninstaller_fails() {
PluginInfo commercial1 = createPluginInfo("p1", true);
PluginInfo commercial2 = createPluginInfo("p2", true);
PluginInfo open1 = createPluginInfo("p3", false);
mockPluginRepository(commercial1, commercial2, open1);
RuntimeException fakeException = new RuntimeException("Faking uninstall throwing an exception");
doThrow(fakeException)
.when(uninstaller)
.uninstall(anyString());
License newLicense = licenseWithPluginKeys("p1", "p4");

underTestSynchronousExecutor.install(newLicense);

verifyMoveToAutomaticFailureAndLogsError(newLicense, fakeException.getMessage());
}

@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);
License newLicense = licenseWithPluginKeys("p1", "p4");

underTestSynchronousExecutor.install(newLicense);

verify(editionManagementState).startAutomaticInstall(newLicense);
verify(editionManagementState).automaticInstallReady();
verifyNoMoreInteractions(editionManagementState);
verify(downloader).downloadEditionPlugins(singleton("p4"), updateCenter);
verify(uninstaller).uninstall("p2");
verifyNoMoreInteractions(uninstaller);
verifyNoMoreInteractions(downloader);
assertThat(logTester.logs()).containsOnly("Installing edition 'edition-key', download: [p4], remove: [p2]");

}

@Test
public void uninstall_commercial_plugins() {
PluginInfo commercial1 = createPluginInfo("p1", true);
PluginInfo commercial2 = createPluginInfo("p2", true);
PluginInfo open1 = createPluginInfo("p3", false);
mockPluginRepository(commercial1, commercial2, open1);

underTestSynchronousExecutor.uninstall();

verify(uninstaller).uninstall("p2");
verify(uninstaller).uninstall("p1");

verifyNoMoreInteractions(uninstaller);
verifyZeroInteractions(downloader);
}

@Test
public void move_to_manualInstall_state_when_offline() {
mockPluginRepository(createPluginInfo("p1", true));
synchronousExecutor = mock(EditionInstallerExecutor.class);
when(updateCenterMatrixFactory.getUpdateCenter(true)).thenReturn(Optional.absent());
underTestSynchronousExecutor = new EditionInstaller(downloader, uninstaller, pluginRepository, synchronousExecutor, updateCenterMatrixFactory, editionManagementState);
License newLicense = licenseWithPluginKeys("p1");

underTestSynchronousExecutor.install(newLicense);

verifyZeroInteractions(synchronousExecutor);
verifyZeroInteractions(uninstaller);
verifyZeroInteractions(downloader);
verify(editionManagementState).startManualInstall(newLicense);
verifyNoMoreInteractions(editionManagementState);
assertThat(logTester.logs()).containsOnly("Installation of edition 'edition-key' needs to be done manually");
}

@Test
public void is_offline() {
when(updateCenterMatrixFactory.getUpdateCenter(false)).thenReturn(Optional.absent());
assertThat(underTestSynchronousExecutor.isOffline()).isTrue();
}

@Test
public void is_not_offline() {
assertThat(underTestSynchronousExecutor.isOffline()).isFalse();
}

@Test
public void requires_installation_change() {
PluginInfo commercial1 = createPluginInfo("p1", true);
PluginInfo commercial2 = createPluginInfo("p2", true);
PluginInfo open1 = createPluginInfo("p3", false);
mockPluginRepository(commercial1, commercial2, open1);

Set<String> editionPlugins = new HashSet<>();
editionPlugins.add("p1");
editionPlugins.add("p4");

boolean flag = underTestSynchronousExecutor.requiresInstallationChange(editionPlugins);

assertThat(flag).isTrue();
verifyZeroInteractions(downloader);
verifyZeroInteractions(uninstaller);
verifyZeroInteractions(editionManagementState);
}

@Test
public void does_not_require_installation_change() {
PluginInfo commercial1 = createPluginInfo("p1", true);
PluginInfo commercial2 = createPluginInfo("p2", true);
PluginInfo open1 = createPluginInfo("p3", false);
mockPluginRepository(commercial1, commercial2, open1);

Set<String> editionPlugins = new HashSet<>();
editionPlugins.add("p1");
editionPlugins.add("p2");

boolean flag = underTestSynchronousExecutor.requiresInstallationChange(editionPlugins);

assertThat(flag).isFalse();
verifyZeroInteractions(downloader);
verifyZeroInteractions(uninstaller);
verifyZeroInteractions(editionManagementState);
}

private void mockPluginRepository(PluginInfo... installedPlugins) {
Map<String, PluginInfo> pluginsByKey = Arrays.stream(installedPlugins).collect(uniqueIndex(PluginInfo::getKey));
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;
}

private static License licenseWithPluginKeys(String... pluginKeys) {
return new License("edition-key", Arrays.asList(pluginKeys), "foo");
}

private void verifyMoveToAutomaticFailureAndLogsError(License newLicense, String expectedErrorMessage) {
verify(editionManagementState).startAutomaticInstall(newLicense);
verify(editionManagementState).installFailed(expectedErrorMessage);
verifyNoMoreInteractions(editionManagementState);
assertThat(logTester.logs(LoggerLevel.ERROR))
.containsOnly("Failed to install edition " + newLicense.getEditionKey() + " with plugins " + newLicense.getPluginKeys());
}

}

+ 0
- 133
server/sonar-server/src/test/java/org/sonar/server/plugins/edition/EditionPluginDownloaderTest.java View File

@@ -1,133 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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.collect.ImmutableList;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.List;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.sonar.api.utils.HttpDownloader;
import org.sonar.api.utils.log.LogTester;
import org.sonar.server.platform.ServerFileSystem;
import org.sonar.updatecenter.common.Plugin;
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.junit.Assert.fail;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

public class EditionPluginDownloaderTest {
@Rule
public LogTester logTester = new LogTester();

@Rule
public TemporaryFolder temp = new TemporaryFolder();

private ServerFileSystem fs = mock(ServerFileSystem.class);
private HttpDownloader httpDownloader = mock(HttpDownloader.class);
private UpdateCenter updateCenter = mock(UpdateCenter.class);

private File downloadDir;
private File tmpDir;
private EditionPluginDownloader downloader;

@Before
public void setUp() throws IOException {
downloadDir = temp.newFolder("download");
tmpDir = new File(downloadDir.getParentFile(), downloadDir.getName() + "_tmp");
when(fs.getEditionDownloadedPluginsDir()).thenReturn(downloadDir);
downloader = new EditionPluginDownloader(httpDownloader, fs);
}

@Test
public void download_plugin_to_tmp() throws URISyntaxException {
List<Release> releases = ImmutableList.of(createRelease("plugin1", "1.0", "http://host/plugin1.jar"),
createRelease("plugin2", "1.0", "http://host/plugin2.jar"));

when(updateCenter.findInstallablePlugins("plugins", Version.create(""))).thenReturn(releases);
downloader.downloadEditionPlugins(Collections.singleton("plugins"), updateCenter);

verify(httpDownloader).download(new URI("http://host/plugin1.jar"), new File(tmpDir, "plugin1.jar"));
verify(httpDownloader).download(new URI("http://host/plugin2.jar"), new File(tmpDir, "plugin2.jar"));

assertThat(logTester.logs()).containsOnly("Downloading plugin: plugin1", "Downloading plugin: plugin2");
assertThat(downloadDir).isDirectory();
assertThat(tmpDir).doesNotExist();
}
@Test
public void download_plugin_to_tmp_with_file_uri() throws IOException {
File plugin1 = temp.newFile("plugin1.jar");
File plugin2 = temp.newFile("plugin2.jar");

List<Release> releases = ImmutableList.of(createRelease("plugin1", "1.0", plugin1.toURI().toString()),
createRelease("plugin2", "1.0", plugin2.toURI().toString()));

when(updateCenter.findInstallablePlugins("plugins", Version.create(""))).thenReturn(releases);
downloader.downloadEditionPlugins(Collections.singleton("plugins"), updateCenter);

assertThat(logTester.logs()).containsOnly("Downloading plugin: plugin1", "Downloading plugin: plugin2");
assertThat(downloadDir).isDirectory();
assertThat(tmpDir).doesNotExist();
}

@Test
public void dont_write_download_dir_if_download_fails() throws URISyntaxException {
List<Release> releases = ImmutableList.of(createRelease("plugin1", "1.0", "http://host/plugin1.jar"),
createRelease("plugin2", "1.0", "http://host/plugin2.jar"));

doThrow(new IllegalStateException("error")).when(httpDownloader).download(new URI("http://host/plugin1.jar"), new File(tmpDir, "plugin1.jar"));

when(updateCenter.findInstallablePlugins("plugins", Version.create(""))).thenReturn(releases);

try {
downloader.downloadEditionPlugins(Collections.singleton("plugins"), updateCenter);
fail("expecting exception");
} catch (IllegalStateException e) {

}

verify(httpDownloader).download(new URI("http://host/plugin1.jar"), new File(tmpDir, "plugin1.jar"));

assertThat(downloadDir.list()).isEmpty();
assertThat(tmpDir).doesNotExist();
}

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);
when(release.getArtifact()).thenReturn(Plugin.factory(key));
return release;
}
}

+ 0
- 52
server/sonar-server/src/test/java/org/sonar/server/plugins/edition/EditionPluginUninstallerTest.java View File

@@ -1,52 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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();
}
}

+ 0
- 143
sonar-plugin-api/src/main/java/org/sonar/api/config/License.java View File

@@ -1,143 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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.api.config;

import java.io.IOException;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.sonar.api.utils.DateUtils;

/**
* SonarSource license. This class aims to extract metadata but not to validate or - of course -
* to generate license
*
* @since 3.0
*/
public final class License {
private String product;
private String organization;
private String expirationDate;
private String type;
private String server;
private Map<String, String> additionalProperties;

private License(Map<String, String> properties) {
this.additionalProperties = new HashMap<>(properties);
product = StringUtils.defaultString(get("Product", properties), get("Plugin", properties));
organization = StringUtils.defaultString(get("Organisation", properties), get("Name", properties));
expirationDate = StringUtils.defaultString(get("Expiration", properties), get("Expires", properties));
type = get("Type", properties);
server = get("Server", properties);
// SONAR-4340 Don't expose Digest and Obeo properties
additionalProperties.remove("Digest");
additionalProperties.remove("Obeo");
}

private String get(String key, Map<String, String> properties) {
additionalProperties.remove(key);
return properties.get(key);
}

/**
* Get additional properties available on this license (like threshold conditions)
* @since 3.6
*/
public Map<String, String> additionalProperties() {
return additionalProperties;
}

@Nullable
public String getProduct() {
return product;
}

@Nullable
public String getOrganization() {
return organization;
}

@Nullable
public String getExpirationDateAsString() {
return expirationDate;
}

@Nullable
public Date getExpirationDate() {
return DateUtils.parseDateQuietly(expirationDate);
}

public boolean isExpired() {
return isExpired(new Date());
}

boolean isExpired(Date now) {
Date date = getExpirationDate();
if (date == null) {
return false;
}
// SONAR-6079 include last day
Calendar cal = Calendar.getInstance();
cal.setTime(date);
cal.add(Calendar.DAY_OF_MONTH, 1);
cal.add(Calendar.SECOND, -1);
return now.after(cal.getTime());
}

@Nullable
public String getType() {
return type;
}

@Nullable
public String getServer() {
return server;
}

public static License readBase64(String base64) {
return readPlainText(new String(Base64.decodeBase64(base64.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8));
}

static License readPlainText(String data) {
Map<String, String> props = new HashMap<>();
try {
List<String> lines = IOUtils.readLines(new StringReader(data));
for (String line : lines) {
if (StringUtils.isNotBlank(line) && line.indexOf(':') > 0) {
String key = StringUtils.substringBefore(line, ":");
String value = StringUtils.substringAfter(line, ":");
props.put(StringUtils.trimToEmpty(key), StringUtils.trimToEmpty(value));
}
}

} catch (IOException e) {
// silently ignore
}
return new License(props);
}
}

+ 0
- 161
sonar-plugin-api/src/test/java/org/sonar/api/config/LicenseTest.java View File

@@ -1,161 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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.api.config;

import org.apache.commons.codec.binary.Base64;
import org.junit.Test;
import org.sonar.api.utils.DateUtils;

import java.util.Calendar;
import java.util.TimeZone;

import static org.assertj.core.api.Assertions.assertThat;

public class LicenseTest {

private static final String V2_FORMAT = "Foo: bar\n" +
"Organisation: ABC \n" +
"Server: 12345 \n" +
"Product: SQALE\n" +
" Expiration: 2012-05-18 \n" +
"Type: EVALUATION \n" +
"Other: field\n";

private static final String V1_FORMAT = "Foo: bar\n" +
"Name: ABC \n" +
"Plugin: SQALE\n" +
" Expires: 2012-05-18 \n" +
"Other: field\n" +
"Digest: abcdef\n" +
"Obeo: obeo\n";

@Test
public void readPlainTest() {
License license = License.readPlainText(V2_FORMAT);

assertThat(license.getOrganization()).isEqualTo("ABC");
assertThat(license.getServer()).isEqualTo("12345");
assertThat(license.getProduct()).isEqualTo("SQALE");
assertThat(license.getExpirationDateAsString()).isEqualTo("2012-05-18");
assertThat(license.getType()).isEqualTo("EVALUATION");
}

@Test
public void readPlainText_empty_fields() {
License license = License.readPlainText("");

assertThat(license.getOrganization()).isNull();
assertThat(license.getServer()).isNull();
assertThat(license.getProduct()).isNull();
assertThat(license.getExpirationDateAsString()).isNull();
assertThat(license.getExpirationDate()).isNull();
assertThat(license.getType()).isNull();
}

@Test
public void readPlainText_not_valid_input() {
License license = License.readPlainText("old pond ... a frog leaps in water’s sound");

assertThat(license.getOrganization()).isNull();
assertThat(license.getServer()).isNull();
assertThat(license.getProduct()).isNull();
assertThat(license.getExpirationDateAsString()).isNull();
assertThat(license.getExpirationDate()).isNull();
assertThat(license.getType()).isNull();
}

@Test
public void readPlainTest_version_1() {
License license = License.readPlainText(V1_FORMAT);

assertThat(license.getOrganization()).isEqualTo("ABC");
assertThat(license.getServer()).isNull();
assertThat(license.getProduct()).isEqualTo("SQALE");
assertThat(license.getExpirationDateAsString()).isEqualTo("2012-05-18");
assertThat(license.getType()).isNull();
}

@Test
public void readBase64() {
License license = License.readBase64(new String(Base64.encodeBase64(V2_FORMAT.getBytes())));

assertThat(license.getOrganization()).isEqualTo("ABC");
assertThat(license.getServer()).isEqualTo("12345");
assertThat(license.getProduct()).isEqualTo("SQALE");
assertThat(license.getExpirationDateAsString()).isEqualTo("2012-05-18");
assertThat(license.getType()).isEqualTo("EVALUATION");
}

@Test
public void trimBeforeReadingBase64() {
String encodedKeyWithTrailingWhiteSpaces = "Rm9vOiBiYXIKT3JnYW5pc2F0aW9uOiBBQkMgClNlcnZlcjogMTIzND \n" +
"UgICAKUHJvZHVjdDogU1FBTEUKICBFeHBpcmF0aW9uOiAyMDEyLTA1 \n" +
"LTE4ICAKVHlwZTogIEVWQUxVQVRJT04gICAKT3RoZXI6IGZpZWxkCg==\n";

License license = License.readBase64(new String(encodedKeyWithTrailingWhiteSpaces.getBytes()));

assertThat(license.getOrganization()).isEqualTo("ABC");
assertThat(license.getServer()).isEqualTo("12345");
assertThat(license.getProduct()).isEqualTo("SQALE");
assertThat(license.getExpirationDateAsString()).isEqualTo("2012-05-18");
assertThat(license.getType()).isEqualTo("EVALUATION");
}

@Test
public void readBase64_not_base64() {
License license = License.readBase64("çé '123$@");

assertThat(license.getOrganization()).isNull();
assertThat(license.getServer()).isNull();
assertThat(license.getProduct()).isNull();
assertThat(license.getExpirationDateAsString()).isNull();
assertThat(license.getExpirationDate()).isNull();
assertThat(license.getType()).isNull();
}

@Test
public void isExpired() {
License license = License.readPlainText(V2_FORMAT);

assertThat(license.isExpired(DateUtils.parseDate("2011-01-01"))).isFalse();
Calendar sameDay = Calendar.getInstance(TimeZone.getDefault());
sameDay.setTime(DateUtils.parseDate("2012-05-18"));
assertThat(license.isExpired(sameDay.getTime())).isFalse();
sameDay.set(Calendar.HOUR_OF_DAY, 15);
assertThat(license.isExpired(sameDay.getTime())).isFalse();
sameDay.set(Calendar.HOUR_OF_DAY, 23);
sameDay.set(Calendar.MINUTE, 59);
sameDay.set(Calendar.SECOND, 59);
assertThat(license.isExpired(sameDay.getTime())).isFalse();
// The day after
sameDay.add(Calendar.SECOND, 1);
assertThat(license.isExpired(sameDay.getTime())).isTrue();
assertThat(license.isExpired(DateUtils.parseDate("2013-06-23"))).isTrue();
}

@Test
public void otherProperties() {
License license = License.readPlainText(V2_FORMAT);

assertThat(license.additionalProperties().get("Other")).isEqualTo("field");
assertThat(license.additionalProperties().containsKey("Digest")).isFalse();
assertThat(license.additionalProperties().containsKey("Obeo")).isFalse();
}
}

+ 0
- 8
sonar-ws/src/main/java/org/sonarqube/ws/client/DefaultWsClient.java View File

@@ -27,7 +27,6 @@ import org.sonarqube.ws.client.ce.CeService;
import org.sonarqube.ws.client.components.ComponentsService;
import org.sonarqube.ws.client.custommeasures.CustomMeasuresService;
import org.sonarqube.ws.client.duplications.DuplicationsService;
import org.sonarqube.ws.client.editions.EditionsService;
import org.sonarqube.ws.client.emails.EmailsService;
import org.sonarqube.ws.client.favorites.FavoritesService;
import org.sonarqube.ws.client.favourites.FavouritesService;
@@ -86,7 +85,6 @@ class DefaultWsClient implements WsClient {
private final ComponentsService componentsService;
private final CustomMeasuresService customMeasuresService;
private final DuplicationsService duplicationsService;
private final EditionsService editionsService;
private final EmailsService emailsService;
private final FavoritesService favoritesService;
private final FavouritesService favouritesService;
@@ -138,7 +136,6 @@ class DefaultWsClient implements WsClient {
this.componentsService = new ComponentsService(wsConnector);
this.customMeasuresService = new CustomMeasuresService(wsConnector);
this.duplicationsService = new DuplicationsService(wsConnector);
this.editionsService = new EditionsService(wsConnector);
this.emailsService = new EmailsService(wsConnector);
this.favoritesService = new FavoritesService(wsConnector);
this.favouritesService = new FavouritesService(wsConnector);
@@ -218,11 +215,6 @@ class DefaultWsClient implements WsClient {
return duplicationsService;
}

@Override
public EditionsService editions() {
return editionsService;
}

@Override
public EmailsService emails() {
return emailsService;

+ 0
- 3
sonar-ws/src/main/java/org/sonarqube/ws/client/WsClient.java View File

@@ -27,7 +27,6 @@ import org.sonarqube.ws.client.ce.CeService;
import org.sonarqube.ws.client.components.ComponentsService;
import org.sonarqube.ws.client.custommeasures.CustomMeasuresService;
import org.sonarqube.ws.client.duplications.DuplicationsService;
import org.sonarqube.ws.client.editions.EditionsService;
import org.sonarqube.ws.client.emails.EmailsService;
import org.sonarqube.ws.client.favorites.FavoritesService;
import org.sonarqube.ws.client.favourites.FavouritesService;
@@ -104,8 +103,6 @@ public interface WsClient {

DuplicationsService duplications();

EditionsService editions();

EmailsService emails();

FavoritesService favorites();

+ 0
- 47
sonar-ws/src/main/java/org/sonarqube/ws/client/editions/ApplyLicenseRequest.java View File

@@ -1,47 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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.sonarqube.ws.client.editions;

import java.util.List;
import javax.annotation.Generated;

/**
* This is part of the internal API.
* This is a POST request.
* @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/editions/apply_license">Further information about this action online (including a response example)</a>
* @since 6.7
*/
@Generated("sonar-ws-generator")
public class ApplyLicenseRequest {

private String license;

/**
* This is a mandatory parameter.
*/
public ApplyLicenseRequest setLicense(String license) {
this.license = license;
return this;
}

public String getLicense() {
return license;
}
}

+ 0
- 125
sonar-ws/src/main/java/org/sonarqube/ws/client/editions/EditionsService.java View File

@@ -1,125 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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.sonarqube.ws.client.editions;

import java.util.stream.Collectors;
import javax.annotation.Generated;
import org.sonarqube.ws.MediaTypes;
import org.sonarqube.ws.client.BaseService;
import org.sonarqube.ws.client.GetRequest;
import org.sonarqube.ws.client.PostRequest;
import org.sonarqube.ws.client.WsConnector;
import org.sonarqube.ws.Editions.FormDataResponse;
import org.sonarqube.ws.Editions.PreviewResponse;
import org.sonarqube.ws.Editions.StatusResponse;

/**
* @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/editions">Further information about this web service online</a>
*/
@Generated("sonar-ws-generator")
public class EditionsService extends BaseService {

public EditionsService(WsConnector wsConnector) {
super(wsConnector, "api/editions");
}

/**
*
* This is part of the internal API.
* This is a POST request.
* @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/editions/apply_license">Further information about this action online (including a response example)</a>
* @since 6.7
*/
public String applyLicense(ApplyLicenseRequest request) {
return call(
new PostRequest(path("apply_license"))
.setParam("license", request.getLicense())
.setMediaType(MediaTypes.JSON)
).content();
}

/**
*
* This is part of the internal API.
* This is a POST request.
* @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/editions/clear_error_message">Further information about this action online (including a response example)</a>
* @since 6.7
*/
public void clearErrorMessage() {
call(
new PostRequest(path("clear_error_message"))
.setMediaType(MediaTypes.JSON)
).content();
}

/**
*
* This is part of the internal API.
* This is a GET request.
* @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/editions/form_data">Further information about this action online (including a response example)</a>
* @since 6.7
*/
public FormDataResponse formData() {
return call(
new GetRequest(path("form_data")),
FormDataResponse.parser());
}

/**
*
* This is part of the internal API.
* This is a POST request.
* @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/editions/preview">Further information about this action online (including a response example)</a>
* @since 6.7
*/
public PreviewResponse preview(PreviewRequest request) {
return call(
new PostRequest(path("preview"))
.setParam("license", request.getLicense()),
PreviewResponse.parser());
}

/**
*
* This is part of the internal API.
* This is a GET request.
* @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/editions/status">Further information about this action online (including a response example)</a>
* @since 6.7
*/
public StatusResponse status() {
return call(
new GetRequest(path("status")),
StatusResponse.parser());
}

/**
*
* This is part of the internal API.
* This is a POST request.
* @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/editions/uninstall">Further information about this action online (including a response example)</a>
* @since 6.7
*/
public void uninstall() {
call(
new PostRequest(path("uninstall"))
.setMediaType(MediaTypes.JSON)
).content();
}
}

+ 0
- 47
sonar-ws/src/main/java/org/sonarqube/ws/client/editions/PreviewRequest.java View File

@@ -1,47 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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.sonarqube.ws.client.editions;

import java.util.List;
import javax.annotation.Generated;

/**
* This is part of the internal API.
* This is a POST request.
* @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/editions/preview">Further information about this action online (including a response example)</a>
* @since 6.7
*/
@Generated("sonar-ws-generator")
public class PreviewRequest {

private String license;

/**
* This is a mandatory parameter.
*/
public PreviewRequest setLicense(String license) {
this.license = license;
return this;
}

public String getLicense() {
return license;
}
}

+ 0
- 26
sonar-ws/src/main/java/org/sonarqube/ws/client/editions/package-info.java View File

@@ -1,26 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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
@Generated("sonar-ws-generator")
package org.sonarqube.ws.client.editions;

import javax.annotation.ParametersAreNonnullByDefault;
import javax.annotation.Generated;


+ 0
- 60
sonar-ws/src/main/protobuf/ws-editions.proto View File

@@ -1,60 +0,0 @@
// SonarQube, open source software quality management tool.
// Copyright (C) 2008-2016 SonarSource
// mailto:contact AT sonarsource DOT com
//
// SonarQube 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.
//
// SonarQube 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.

syntax = "proto2";

package sonarqube.ws.editions;

option java_package = "org.sonarqube.ws";
option java_outer_classname = "Editions";
option optimize_for = SPEED;

// GET api/editions/status
// POST api/editions/apply_license
message StatusResponse {
optional string currentEditionKey = 1;
optional InstallationStatus installationStatus = 2;
optional string nextEditionKey = 3;
optional string installError = 4;
}

enum InstallationStatus {
NONE = 0;
AUTOMATIC_IN_PROGRESS = 1;
AUTOMATIC_READY = 2;
MANUAL_IN_PROGRESS = 3;
UNINSTALL_IN_PROGRESS = 4;
}

// POST api/editions/preview
message PreviewResponse {
optional string nextEditionKey = 1;
optional PreviewStatus previewStatus = 2;
}

enum PreviewStatus {
NO_INSTALL = 0;
AUTOMATIC_INSTALL = 1;
MANUAL_INSTALL = 2;
}

// POST api/editions/form_data
message FormDataResponse {
optional string serverId = 1;
optional int64 ncloc = 2;
}

Loading…
Cancel
Save