From a49db7d096955fd4586a000ef8c8ff8b2ae5a405 Mon Sep 17 00:00:00 2001 From: =?utf8?q?S=C3=A9bastien=20Lesaint?= Date: Thu, 12 Oct 2017 16:50:31 +0200 Subject: [PATCH] SONAR-9940 add WS api/editions/apply_license (mocked implementation) edition key is the whole license if license contains string "manual", the state will be changed to MANUAL_IN_PROGRESS if license contains string "done", the license will be applies without requiring a restart otherwise state will go to AUTOMATIC_IN_PROGRESS --- .../server/edition/EditionsWsModule.java | 2 + .../server/edition/ws/ApplyLicenseAction.java | 88 +++++++++++ .../ws/example-edition-apply_license.json | 5 + .../server/edition/EditionsWsModuleTest.java | 2 +- .../edition/ws/ApplyLicenseActionTest.java | 149 ++++++++++++++++++ sonar-ws/src/main/protobuf/ws-editions.proto | 1 + 6 files changed, 246 insertions(+), 1 deletion(-) create mode 100644 server/sonar-server/src/main/java/org/sonar/server/edition/ws/ApplyLicenseAction.java create mode 100644 server/sonar-server/src/main/resources/org/sonar/server/edition/ws/example-edition-apply_license.json create mode 100644 server/sonar-server/src/test/java/org/sonar/server/edition/ws/ApplyLicenseActionTest.java diff --git a/server/sonar-server/src/main/java/org/sonar/server/edition/EditionsWsModule.java b/server/sonar-server/src/main/java/org/sonar/server/edition/EditionsWsModule.java index fb05d1834c1..0a1ca5d30f7 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/edition/EditionsWsModule.java +++ b/server/sonar-server/src/main/java/org/sonar/server/edition/EditionsWsModule.java @@ -20,6 +20,7 @@ package org.sonar.server.edition; import org.sonar.core.platform.Module; +import org.sonar.server.edition.ws.ApplyLicenseAction; import org.sonar.server.edition.ws.EditionsWs; import org.sonar.server.edition.ws.StatusAction; @@ -29,6 +30,7 @@ public class EditionsWsModule extends Module { add( StandaloneEditionManagementStateImpl.class, StatusAction.class, + ApplyLicenseAction.class, EditionsWs.class); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/edition/ws/ApplyLicenseAction.java b/server/sonar-server/src/main/java/org/sonar/server/edition/ws/ApplyLicenseAction.java new file mode 100644 index 00000000000..75dc8356053 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/edition/ws/ApplyLicenseAction.java @@ -0,0 +1,88 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.edition.ws; + +import java.util.Collections; +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.edition.MutableEditionManagementState; +import org.sonar.server.exceptions.BadRequestException; +import org.sonar.server.user.UserSession; +import org.sonar.server.ws.WsUtils; +import org.sonarqube.ws.WsEditions; + +public class ApplyLicenseAction implements EditionsWsAction { + private static final String PARAM_LICENSE = "license"; + + private UserSession userSession; + private MutableEditionManagementState editionManagementState; + + public ApplyLicenseAction(UserSession userSession, MutableEditionManagementState editionManagementState) { + this.userSession = userSession; + this.editionManagementState = editionManagementState; + } + + @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. 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 license = request.mandatoryParam(PARAM_LICENSE); + License newLicense = new License(license, Collections.emptyList(), license); + if (license.contains("manual")) { + editionManagementState.startManualInstall(newLicense); + } else if (license.contains("done")) { + editionManagementState.newEditionWithoutInstall(newLicense.getEditionKey()); + } else { + editionManagementState.startAutomaticInstall(newLicense); + } + + WsUtils.writeProtobuf(buildResponse(), request, response); + } + + private WsEditions.StatusResponse buildResponse() { + return WsEditions.StatusResponse.newBuilder() + .setNextEditionKey(editionManagementState.getPendingEditionKey().orElse("")) + .setCurrentEditionKey(editionManagementState.getCurrentEditionKey().orElse("")) + .setInstallationStatus(WsEditions.InstallationStatus.valueOf(editionManagementState.getPendingInstallationStatus().name())) + .build(); + } +} diff --git a/server/sonar-server/src/main/resources/org/sonar/server/edition/ws/example-edition-apply_license.json b/server/sonar-server/src/main/resources/org/sonar/server/edition/ws/example-edition-apply_license.json new file mode 100644 index 00000000000..507a64db677 --- /dev/null +++ b/server/sonar-server/src/main/resources/org/sonar/server/edition/ws/example-edition-apply_license.json @@ -0,0 +1,5 @@ +{ + "currentEditionKey": "", + "installationStatus": "AUTOMATIC_IN_PROGRESS", + "nextEditionKey": "developer-edition" +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/edition/EditionsWsModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/edition/EditionsWsModuleTest.java index c4fae543a77..116a4f4f2cd 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/edition/EditionsWsModuleTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/edition/EditionsWsModuleTest.java @@ -34,6 +34,6 @@ public class EditionsWsModuleTest { underTest.configure(container); assertThat(container.getPicoContainer().getComponentAdapters()) - .hasSize(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 3); + .hasSize(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 4); } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/edition/ws/ApplyLicenseActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/edition/ws/ApplyLicenseActionTest.java new file mode 100644 index 00000000000..4cd01c677ac --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/edition/ws/ApplyLicenseActionTest.java @@ -0,0 +1,149 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.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 java.util.Optional; +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.tester.UserSessionRule; +import org.sonar.server.ws.TestRequest; +import org.sonar.server.ws.WsActionTester; +import org.sonar.test.JsonAssert; + +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.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"; + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + @Rule + public UserSessionRule userSessionRule = UserSessionRule.standalone(); + + private MutableEditionManagementState mutableEditionManagementState = mock(MutableEditionManagementState.class); + private ApplyLicenseAction underTest = new ApplyLicenseAction(userSessionRule, mutableEditionManagementState); + 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 verify_example() { + userSessionRule.logIn().setSystemAdministrator(); + when(mutableEditionManagementState.getCurrentEditionKey()).thenReturn(Optional.empty()); + when(mutableEditionManagementState.getPendingEditionKey()).thenReturn(Optional.of("developer-edition")); + when(mutableEditionManagementState.getPendingInstallationStatus()) + .thenReturn(NONE) + .thenReturn(AUTOMATIC_IN_PROGRESS); + + TestRequest request = actionTester.newRequest() + .setParam(PARAM_LICENSE, "foo"); + + JsonAssert.assertJson(request.execute().getInput()).isSimilarTo(actionTester.getDef().responseExampleAsString()); + } + + @DataProvider + public static Object[][] notNonePendingInstallationStatuses() { + return Arrays.stream(EditionManagementState.PendingStatus.values()) + .filter(s -> s != NONE) + .map(s -> new Object[] {s}) + .toArray(Object[][]::new); + } +} diff --git a/sonar-ws/src/main/protobuf/ws-editions.proto b/sonar-ws/src/main/protobuf/ws-editions.proto index 9e486715aae..8f173acc923 100644 --- a/sonar-ws/src/main/protobuf/ws-editions.proto +++ b/sonar-ws/src/main/protobuf/ws-editions.proto @@ -25,6 +25,7 @@ option java_outer_classname = "WsEditions"; option optimize_for = SPEED; // GET api/editions/status +// POST api/editions/apply_license message StatusResponse { optional string currentEditionKey = 1; optional InstallationStatus installationStatus = 2; -- 2.39.5