]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9126 Allow preventing projects analysis on a organization
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Tue, 25 Apr 2017 08:47:37 +0000 (10:47 +0200)
committerJulien Lancelot <julien.lancelot@sonarsource.com>
Wed, 26 Apr 2017 14:34:31 +0000 (16:34 +0200)
server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java
server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/LoadReportAnalysisMetadataHolderStep.java
server/sonar-server/src/main/java/org/sonar/server/organization/BillingValidations.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/organization/BillingValidationsExtension.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/organization/BillingValidationsProxy.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/organization/BillingValidationsProxyImpl.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/LoadReportAnalysisMetadataHolderStepTest.java
server/sonar-server/src/test/java/org/sonar/server/organization/BillingValidationsProxyTest.java [new file with mode: 0644]

index d0600dc7e0d6f8cc3f73dd4ec18a2d240bcc33f1..90fe0838bb24d935b526897b7e98fae5ffeb895a 100644 (file)
@@ -96,6 +96,7 @@ import org.sonar.server.notification.NotificationCenter;
 import org.sonar.server.notification.NotificationService;
 import org.sonar.server.notification.email.AlertsEmailTemplate;
 import org.sonar.server.notification.email.EmailNotificationChannel;
+import org.sonar.server.organization.BillingValidationsProxyImpl;
 import org.sonar.server.organization.DefaultOrganizationProviderImpl;
 import org.sonar.server.permission.GroupPermissionChanger;
 import org.sonar.server.permission.PermissionTemplateService;
@@ -289,6 +290,7 @@ public class ComputeEngineContainerImpl implements ComputeEngineContainer {
       ResourceTypes.class,
       DefaultResourceTypes.get(),
       Periods.class,
+      BillingValidationsProxyImpl.class,
 
       // quality profile
       ActiveRuleIndexer.class,
index 89a1da47da4ff9abff60e2d3ee70f9f479a4856c..edf0062e3945f7e8a5df41e79f7435efbe277ce8 100644 (file)
@@ -88,7 +88,7 @@ public class ComputeEngineContainerImplTest {
     assertThat(picoContainer.getComponentAdapters())
       .hasSize(
         CONTAINER_ITSELF
-          + 73 // level 4
+          + 74 // level 4
           + 4 // content of CeConfigurationModule
           + 5 // content of CeQueueModule
           + 3 // content of CeHttpModule
index fd8f7cb451d54c2528c45406b43d0f309e691058..c89f97140d11fd980ef8ca2e5132b15276c272d5 100644 (file)
@@ -38,6 +38,9 @@ import org.sonar.server.computation.task.projectanalysis.analysis.MutableAnalysi
 import org.sonar.server.computation.task.projectanalysis.analysis.Organization;
 import org.sonar.server.computation.task.projectanalysis.batch.BatchReportReader;
 import org.sonar.server.computation.task.step.ComputationStep;
+import org.sonar.server.organization.BillingValidations;
+import org.sonar.server.organization.BillingValidations.BillingValidationsException;
+import org.sonar.server.organization.BillingValidationsProxy;
 import org.sonar.server.organization.DefaultOrganizationProvider;
 import org.sonar.server.qualityprofile.QualityProfile;
 
@@ -66,14 +69,16 @@ public class LoadReportAnalysisMetadataHolderStep implements ComputationStep {
   private final MutableAnalysisMetadataHolder mutableAnalysisMetadataHolder;
   private final DefaultOrganizationProvider defaultOrganizationProvider;
   private final DbClient dbClient;
+  private final BillingValidations billingValidations;
 
   public LoadReportAnalysisMetadataHolderStep(CeTask ceTask, BatchReportReader reportReader, MutableAnalysisMetadataHolder mutableAnalysisMetadataHolder,
-    DefaultOrganizationProvider defaultOrganizationProvider, DbClient dbClient) {
+    DefaultOrganizationProvider defaultOrganizationProvider, DbClient dbClient, BillingValidationsProxy billingValidations) {
     this.ceTask = ceTask;
     this.reportReader = reportReader;
     this.mutableAnalysisMetadataHolder = mutableAnalysisMetadataHolder;
     this.defaultOrganizationProvider = defaultOrganizationProvider;
     this.dbClient = dbClient;
+    this.billingValidations = billingValidations;
   }
 
   @Override
@@ -84,6 +89,7 @@ public class LoadReportAnalysisMetadataHolderStep implements ComputationStep {
     checkProjectKeyConsistency(reportMetadata);
     Organization organization = toOrganization(ceTask.getOrganizationUuid());
     checkOrganizationKeyConsistency(reportMetadata, organization);
+    checkOrganizationCanExecuteAnalysis(organization);
     checkQualityProfilesConsistency(reportMetadata, organization);
 
     mutableAnalysisMetadataHolder.setRootComponentRef(reportMetadata.getRootComponentRef());
@@ -146,6 +152,14 @@ public class LoadReportAnalysisMetadataHolderStep implements ComputationStep {
     }
   }
 
+  private void checkOrganizationCanExecuteAnalysis(Organization organization) {
+    try {
+      billingValidations.checkOnProjectAnalysis(new BillingValidations.Organization(organization.getKey(), organization.getUuid()));
+    } catch (BillingValidationsException e) {
+      throw MessageException.of(e.getMessage());
+    }
+  }
+
   private String resolveReportOrganizationKey(@Nullable String organizationKey) {
     if (reportBelongsToDefaultOrganization(organizationKey)) {
       return defaultOrganizationProvider.get().getKey();
diff --git a/server/sonar-server/src/main/java/org/sonar/server/organization/BillingValidations.java b/server/sonar-server/src/main/java/org/sonar/server/organization/BillingValidations.java
new file mode 100644 (file)
index 0000000..1c524ac
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * 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.organization;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Available checks that will be done by the billing plugin.
+ * When the billing plugin is not loaded, no check will be done.
+ * This is not the interface that should be implemented by the plugin, but {@link BillingValidationsExtension}
+ */
+public interface BillingValidations {
+
+  /**
+   * @throws BillingValidationsException when projects analysis on organization is not allowed
+   */
+  void checkOnProjectAnalysis(Organization organization);
+
+  class Organization {
+    private final String key;
+    private final String uuid;
+
+    public Organization(String key, String uuid) {
+      this.key = requireNonNull(key, "Organization key cannot be null");
+      this.uuid = requireNonNull(uuid, "Organization uuid cannot be null");
+    }
+
+    public String getKey() {
+      return key;
+    }
+
+    public String getUuid() {
+      return uuid;
+    }
+  }
+
+  class BillingValidationsException extends RuntimeException {
+    public BillingValidationsException(String message) {
+      super(message);
+    }
+
+    /**
+     * Does not fill in the stack trace
+     *
+     * @see java.lang.Throwable#fillInStackTrace()
+     */
+    @Override
+    public synchronized Throwable fillInStackTrace() {
+      return this;
+    }
+
+    @Override
+    public String toString() {
+      return getMessage();
+    }
+
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/organization/BillingValidationsExtension.java b/server/sonar-server/src/main/java/org/sonar/server/organization/BillingValidationsExtension.java
new file mode 100644 (file)
index 0000000..0b70671
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * 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.organization;
+
+import org.sonar.api.ce.ComputeEngineSide;
+import org.sonar.api.server.ServerSide;
+
+/**
+ * The billing plugin must implement this interface
+ */
+@ServerSide
+@ComputeEngineSide
+public interface BillingValidationsExtension extends BillingValidations {
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/organization/BillingValidationsProxy.java b/server/sonar-server/src/main/java/org/sonar/server/organization/BillingValidationsProxy.java
new file mode 100644 (file)
index 0000000..f0b5219
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * 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.organization;
+
+/**
+ * The goal of this class is to handle the 2 different use case :
+ * - The billing plugin exists, the proxy will redirect method calls to the plugin
+ * - No billing plugin, every methods won't do anything
+ */
+public interface BillingValidationsProxy extends BillingValidations {
+
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/organization/BillingValidationsProxyImpl.java b/server/sonar-server/src/main/java/org/sonar/server/organization/BillingValidationsProxyImpl.java
new file mode 100644 (file)
index 0000000..c091e34
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * 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.organization;
+
+public class BillingValidationsProxyImpl implements BillingValidationsProxy {
+
+  private final BillingValidationsExtension billingValidationsExtension;
+
+  public BillingValidationsProxyImpl(BillingValidationsExtension billingValidationsExtension) {
+    this.billingValidationsExtension = billingValidationsExtension;
+  }
+
+  // Used when no plugin is providing the extension
+  public BillingValidationsProxyImpl() {
+    this.billingValidationsExtension = null;
+  }
+
+  @Override
+  public void checkOnProjectAnalysis(Organization organization) {
+    if (billingValidationsExtension == null) {
+      return;
+    }
+    billingValidationsExtension.checkOnProjectAnalysis(organization);
+  }
+}
index a979638fdfd50427651ede0182f5e42c4e220486..294a3dbde4c71934a21fd1a0ab29d0fe0c9d3b4e 100644 (file)
@@ -84,6 +84,7 @@ import org.sonar.server.metric.CoreCustomMetrics;
 import org.sonar.server.metric.DefaultMetricFinder;
 import org.sonar.server.metric.ws.MetricsWsModule;
 import org.sonar.server.notification.NotificationModule;
+import org.sonar.server.organization.BillingValidationsProxyImpl;
 import org.sonar.server.organization.OrganizationCreationImpl;
 import org.sonar.server.organization.OrganizationValidationImpl;
 import org.sonar.server.organization.ws.OrganizationsWsModule;
@@ -256,6 +257,7 @@ public class PlatformLevel4 extends PlatformLevel {
       OrganizationValidationImpl.class,
       OrganizationCreationImpl.class,
       OrganizationsWsModule.class,
+      BillingValidationsProxyImpl.class,
 
       // quality profile
       DefinedQProfileRepositoryImpl.class,
index 8ae21842cc78e02bd817ab1f2126e709b56ad026..bc0a286f29e1858188e2a89e3bf2a3327f05e5d6 100644 (file)
@@ -23,6 +23,7 @@ import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
+import org.mockito.ArgumentCaptor;
 import org.sonar.api.utils.MessageException;
 import org.sonar.api.utils.System2;
 import org.sonar.ce.queue.CeTask;
@@ -34,11 +35,17 @@ import org.sonar.server.computation.task.projectanalysis.analysis.MutableAnalysi
 import org.sonar.server.computation.task.projectanalysis.analysis.Organization;
 import org.sonar.server.computation.task.projectanalysis.batch.BatchReportReaderRule;
 import org.sonar.server.computation.task.step.ComputationStep;
+import org.sonar.server.organization.BillingValidations;
+import org.sonar.server.organization.BillingValidations.BillingValidationsException;
+import org.sonar.server.organization.BillingValidationsProxy;
 import org.sonar.server.organization.DefaultOrganizationProvider;
 import org.sonar.server.organization.TestDefaultOrganizationProvider;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.any;
+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 LoadReportAnalysisMetadataHolderStepTest {
@@ -58,6 +65,7 @@ public class LoadReportAnalysisMetadataHolderStepTest {
 
   private DbClient dbClient = dbTester.getDbClient();
   private DefaultOrganizationProvider defaultOrganizationProvider = TestDefaultOrganizationProvider.from(dbTester);
+  private BillingValidationsProxy billingValidations = mock(BillingValidationsProxy.class);
   private ComputationStep underTest;
 
   @Before
@@ -308,8 +316,40 @@ public class LoadReportAnalysisMetadataHolderStepTest {
     underTest.execute();
   }
 
+  @Test
+  public void execute_fails_with_MessageException_when_organization_is_not_allowed_to_execute_analysis() {
+    OrganizationDto organization = dbTester.organizations().insert();
+    reportReader.setMetadata(newBatchReportBuilder()
+      .setOrganizationKey(organization.getKey())
+      .build());
+    ComputationStep underTest = createStep(createCeTask(PROJECT_KEY, organization.getUuid()));
+    doThrow(new BillingValidationsException("This organization cannot execute project analysis")).when(billingValidations)
+      .checkOnProjectAnalysis(any(BillingValidations.Organization.class));
+
+    expectedException.expect(MessageException.class);
+    expectedException.expectMessage("This organization cannot execute project analysis");
+
+    underTest.execute();
+  }
+
+  @Test
+  public void execute_does_no_fails_when_organization_is_allowed_to_execute_analysis() {
+    OrganizationDto organization = dbTester.organizations().insert();
+    reportReader.setMetadata(newBatchReportBuilder()
+      .setOrganizationKey(organization.getKey())
+      .build());
+    ComputationStep underTest = createStep(createCeTask(PROJECT_KEY, organization.getUuid()));
+
+    underTest.execute();
+
+    ArgumentCaptor<BillingValidations.Organization> argumentCaptor = ArgumentCaptor.forClass(BillingValidations.Organization.class);
+    verify(billingValidations).checkOnProjectAnalysis(argumentCaptor.capture());
+    assertThat(argumentCaptor.getValue().getKey()).isEqualTo(organization.getKey());
+    assertThat(argumentCaptor.getValue().getUuid()).isEqualTo(organization.getUuid());
+  }
+
   private LoadReportAnalysisMetadataHolderStep createStep(CeTask ceTask) {
-    return new LoadReportAnalysisMetadataHolderStep(ceTask, reportReader, analysisMetadataHolder, defaultOrganizationProvider, dbClient);
+    return new LoadReportAnalysisMetadataHolderStep(ceTask, reportReader, analysisMetadataHolder, defaultOrganizationProvider, dbClient, billingValidations);
   }
 
   private static ScannerReport.Metadata.Builder newBatchReportBuilder() {
diff --git a/server/sonar-server/src/test/java/org/sonar/server/organization/BillingValidationsProxyTest.java b/server/sonar-server/src/test/java/org/sonar/server/organization/BillingValidationsProxyTest.java
new file mode 100644 (file)
index 0000000..649a35c
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonar.server.organization;
+
+import org.junit.Test;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.sonar.server.organization.BillingValidations.Organization;
+
+public class BillingValidationsProxyTest {
+
+  private static String ORGANIZATION_KEY = "ORGANIZATION_KEY";
+  private static String ORGANIZATION_UUID = "ORGANIZATION_UUID";
+
+  private BillingValidationsExtension billingValidationsExtension = mock(BillingValidationsExtension.class);
+
+  private BillingValidationsProxyImpl underTest;
+
+  @Test
+  public void checkOnProjectAnalysis_calls_extension_when_available() {
+    underTest = new BillingValidationsProxyImpl(billingValidationsExtension);
+
+    Organization organization = new Organization(ORGANIZATION_KEY, ORGANIZATION_UUID);
+    underTest.checkOnProjectAnalysis(organization);
+
+    verify(billingValidationsExtension).checkOnProjectAnalysis(organization);
+  }
+
+  @Test
+  public void checkOnProjectAnalysis_does_nothing_when_no_extension_available() {
+    underTest = new BillingValidationsProxyImpl();
+
+    Organization organization = new Organization(ORGANIZATION_KEY, ORGANIZATION_UUID);
+    underTest.checkOnProjectAnalysis(organization);
+
+    verifyZeroInteractions(billingValidationsExtension);
+  }
+
+}