]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9721 Opt-out of telemetry with a property
authorTeryk Bellahsene <teryk.bellahsene@sonarsource.com>
Tue, 22 Aug 2017 14:33:47 +0000 (16:33 +0200)
committerTeryk Bellahsene <teryk@users.noreply.github.com>
Wed, 30 Aug 2017 14:24:53 +0000 (16:24 +0200)
server/sonar-server/src/main/java/org/sonar/server/telemetry/TelemetryClient.java
server/sonar-server/src/main/java/org/sonar/server/telemetry/TelemetryDaemon.java
server/sonar-server/src/main/java/org/sonar/server/telemetry/TelemetryProperties.java
server/sonar-server/src/test/java/org/sonar/server/telemetry/TelemetryDaemonTest.java
sonar-application/src/main/assembly/conf/sonar.properties
tests/src/test/java/org/sonarqube/tests/telemetry/TelemetryTest.java

index d318eef39e9ef3528169b6b4f145839035e35610..ac3a6c4252933358fb85515c57a6d633bcb30a7f 100644 (file)
@@ -28,10 +28,13 @@ import okhttp3.Request;
 import okhttp3.RequestBody;
 import org.sonar.api.config.Configuration;
 import org.sonar.api.server.ServerSide;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
 
 @ServerSide
 public class TelemetryClient {
   private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
+  private static final Logger LOG = Loggers.get(TelemetryClient.class);
 
   private final OkHttpClient okHttpClient;
   private final TelemetryUrl serverUrl;
@@ -50,6 +53,19 @@ public class TelemetryClient {
     }
   }
 
+  void optOut(String json) {
+    Request.Builder request = new Request.Builder();
+    request.url(serverUrl.get());
+    RequestBody body = RequestBody.create(JSON, json);
+    request.delete(body);
+
+    try {
+      okHttpClient.newCall(request.build()).execute();
+    } catch (IOException e) {
+      LOG.debug("Error when sending opt-out usage statistics: %s", e.getMessage());
+    }
+  }
+
   private Request buildHttpRequest(String json) {
     Request.Builder request = new Request.Builder();
     request.url(serverUrl.get());
@@ -57,4 +73,5 @@ public class TelemetryClient {
     request.post(body);
     return request.build();
   }
+
 }
index d32b773bcd6fe1bcf3de721236020b8daa40992b..c303529b94e76aa592764cdbb9100a776409f82d 100644 (file)
@@ -30,19 +30,26 @@ import org.sonar.api.config.Configuration;
 import org.sonar.api.platform.Server;
 import org.sonar.api.server.ServerSide;
 import org.sonar.api.utils.System2;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
 import org.sonar.api.utils.text.JsonWriter;
 import org.sonar.server.property.InternalProperties;
 
 import static org.sonar.api.utils.DateUtils.formatDate;
 import static org.sonar.api.utils.DateUtils.parseDate;
+import static org.sonar.server.telemetry.TelemetryProperties.PROP_ENABLE;
+import static org.sonar.server.telemetry.TelemetryProperties.PROP_URL;
 
 @ServerSide
 public class TelemetryDaemon implements Startable {
   private static final String THREAD_NAME_PREFIX = "sq-telemetry-service-";
   private static final int SEVEN_DAYS = 7 * 24 * 60 * 60 * 1_000;
   private static final String I_PROP_LAST_PING = "sonar.telemetry.lastPing";
+  private static final String I_PROP_OPT_OUT = "sonar.telemetry.optOut";
+  private static final Logger LOG = Loggers.get(TelemetryDaemon.class);
 
   private final TelemetryClient telemetryClient;
+  private final Configuration config;
   private final InternalProperties internalProperties;
   private final Server server;
   private final System2 system2;
@@ -50,16 +57,38 @@ public class TelemetryDaemon implements Startable {
 
   private ScheduledExecutorService executorService;
 
-  public TelemetryDaemon(TelemetryClient telemetryClient, InternalProperties internalProperties, Server server, System2 system2, Configuration config) {
+  public TelemetryDaemon(TelemetryClient telemetryClient, Configuration config, InternalProperties internalProperties, Server server, System2 system2) {
     this.telemetryClient = telemetryClient;
+    this.config = config;
+    this.frequencyInSeconds = new TelemetryFrequency(config);
     this.internalProperties = internalProperties;
     this.server = server;
-    this.frequencyInSeconds = new TelemetryFrequency(config);
     this.system2 = system2;
   }
 
   @Override
   public void start() {
+    boolean isTelemetryActivated = config.getBoolean(PROP_ENABLE).orElseThrow(() -> new IllegalStateException(String.format("Setting '%s' must be provided.", PROP_URL)));
+    if (!internalProperties.read(I_PROP_OPT_OUT).isPresent()) {
+      if (!isTelemetryActivated) {
+        StringWriter json = new StringWriter();
+        try (JsonWriter writer = JsonWriter.of(json)) {
+          writer.beginObject();
+          writer.prop("id", server.getId());
+          writer.endObject();
+        }
+        telemetryClient.optOut(json.toString());
+        internalProperties.write(I_PROP_OPT_OUT, String.valueOf(system2.now()));
+        LOG.info("Sharing of SonarQube statistics is disabled.");
+      } else {
+        internalProperties.write(I_PROP_OPT_OUT, null);
+      }
+    }
+
+    if (!isTelemetryActivated) {
+      return;
+    }
+    LOG.info("Sharing of SonarQube statistics is enabled.");
     executorService = Executors.newSingleThreadScheduledExecutor(
       new ThreadFactoryBuilder()
         .setNameFormat(THREAD_NAME_PREFIX + "%d")
index a77a67fa67c66ee2a1fc74d9f64a651d3aa9b14e..90a5f4379860b8e7f739aeaf821033eac9599af7 100644 (file)
@@ -25,6 +25,11 @@ import org.sonar.api.Property;
 
 @Properties({
   @Property(
+    key = TelemetryProperties.PROP_ENABLE,
+    defaultValue = "true",
+    name = "Share SonarQube statistics",
+    global = false),
+   @Property(
     key = TelemetryProperties.PROP_FREQUENCY,
     // 6 hours in seconds
     defaultValue = "21600",
@@ -37,6 +42,7 @@ import org.sonar.api.Property;
     global = false)
 })
 public class TelemetryProperties {
+  static final String PROP_ENABLE = "sonar.telemetry.enable";
   static final String PROP_FREQUENCY = "sonar.telemetry.frequency";
   static final String PROP_URL = "sonar.telemetry.url";
 }
index 62011777a395c33e32daeee0945d3796ade2c599..3d0d66436384516d0c5bdd33e04388917c473c0c 100644 (file)
@@ -55,7 +55,7 @@ public class TelemetryDaemonTest {
     settings = new MapSettings(new PropertyDefinitions(TelemetryProperties.class));
     system2.setNow(System.currentTimeMillis());
 
-    underTest = new TelemetryDaemon(client, internalProperties, server, system2, settings.asConfig());
+    underTest = new TelemetryDaemon(client, settings.asConfig(), internalProperties, server, system2);
   }
 
   @Test
@@ -63,7 +63,7 @@ public class TelemetryDaemonTest {
     settings.setProperty("sonar.telemetry.frequency", "1");
     underTest.start();
 
-    verify(client, timeout(2_000).atLeastOnce()).send(anyString());
+    verify(client, timeout(1_000).atLeastOnce()).send(anyString());
   }
 
   @Test
@@ -73,7 +73,6 @@ public class TelemetryDaemonTest {
     long sevenDaysAgo = now - (ONE_DAY * 7L);
     internalProperties.write("sonar.telemetry.lastPing", String.valueOf(sixDaysAgo));
     settings.setProperty("sonar.telemetry.frequency", "1");
-    underTest = new TelemetryDaemon(client, internalProperties, server, system2, settings.asConfig());
     underTest.start();
     verify(client, timeout(1_000).never()).send(anyString());
     internalProperties.write("sonar.telemetry.lastPing", String.valueOf(sevenDaysAgo));
@@ -114,7 +113,19 @@ public class TelemetryDaemonTest {
 
     underTest.start();
 
-    verify(client, timeout(2_000)).send(anyString());
+    verify(client, timeout(1_000).atLeastOnce()).send(anyString());
     assertThat(internalProperties.read("sonar.telemetry.lastPing").get()).isEqualTo(String.valueOf(today));
   }
+
+  @Test
+  public void opt_out_sent_once() {
+    settings.setProperty("sonar.telemetry.frequency", "1");
+    settings.setProperty("sonar.telemetry.enable", "false");
+    underTest.start();
+    underTest.start();
+
+
+    verify(client, timeout(1_000).never()).send(anyString());
+    verify(client, timeout(1_000).times(1)).optOut(anyString());
+  }
 }
index dbc2994af1bd7e01f10a615b25a2fe0495408125..4b5de0da3ecc5713d4f83dad18f030e03f5ebccf 100644 (file)
 #sonar.path.data=data
 #sonar.path.temp=temp
 
+# Telemetry - Share anonymous SonarQube statistics
+# By sharing anonymous SonarQube statistics, you help us understand how SonarQube is used so we can improve the product to work even better for you.
+# We don't collect source code or IP addresses. And we don't share the data with anyone else.
+# To see an example of the data shared: login as a global administrator, call the WS api/system/info and check the Statistics field.
+#sonar.telemetry.enable=true
+
 
 #--------------------------------------------------------------------------------------------------
 # DEVELOPMENT - only for developers
index c6b20b58fb7bebcdbd4ee34e23c551ec37413002..e65fb8651805ec1e6f0482df13f30d7c0ea51e6f 100644 (file)
@@ -70,4 +70,25 @@ public class TelemetryTest {
 
     orchestrator.stop();
   }
+
+  @Test
+  public void opt_out_of_telemetry() throws Exception {
+    String serverId = randomAlphanumeric(40);
+    orchestrator = Orchestrator.builderEnv()
+      .addPlugin(xooPlugin())
+      .setServerProperty("sonar.telemetry.enable", "false")
+      .setServerProperty("sonar.telemetry.url", url)
+      .setServerProperty("sonar.telemetry.frequency", "1")
+      .setServerProperty("sonar.core.id", serverId)
+      .build();
+    orchestrator.start();
+
+    RecordedRequest request = server.takeRequest(1, TimeUnit.SECONDS);
+
+    assertThat(request.getMethod()).isEqualTo("DELETE");
+    assertThat(request.getBody().readUtf8()).contains(serverId);
+    assertThat(request.getHeader(HttpHeaders.USER_AGENT)).contains("SonarQube");
+
+    orchestrator.stop();
+  }
 }