Browse Source

SONAR-19084 Add telemetry information (managed instance provider)

tags/10.1.0.73491
Aurelien Poscia 1 year ago
parent
commit
f47bed3cff

+ 10
- 7
server/sonar-server-common/src/main/java/org/sonar/server/telemetry/TelemetryData.java View File

@@ -43,7 +43,7 @@ public class TelemetryData {
private final Long installationDate;
private final String installationVersion;
private final boolean inDocker;
private final boolean isScimEnabled;
private final ManagedInstanceInformation managedInstanceInformation;
private final List<UserTelemetryDto> users;
private final List<Project> projects;
private final List<ProjectStatistics> projectStatistics;
@@ -63,7 +63,6 @@ public class TelemetryData {
installationDate = builder.installationDate;
installationVersion = builder.installationVersion;
inDocker = builder.inDocker;
isScimEnabled = builder.isScimEnabled;
users = builder.users;
projects = builder.projects;
projectStatistics = builder.projectStatistics;
@@ -71,6 +70,7 @@ public class TelemetryData {
hasUnanalyzedC = builder.hasUnanalyzedC;
hasUnanalyzedCpp = builder.hasUnanalyzedCpp;
customSecurityConfigs = requireNonNullElse(builder.customSecurityConfigs, Set.of());
managedInstanceInformation = builder.managedInstanceInformation;
}

public String getServerId() {
@@ -113,8 +113,8 @@ public class TelemetryData {
return inDocker;
}

public boolean isScimEnabled() {
return isScimEnabled;
public ManagedInstanceInformation getManagedInstanceInformation() {
return managedInstanceInformation;
}

public Optional<Boolean> hasUnanalyzedC() {
@@ -160,7 +160,7 @@ public class TelemetryData {
private Long installationDate;
private String installationVersion;
private boolean inDocker = false;
private boolean isScimEnabled;
private ManagedInstanceInformation managedInstanceInformation;
private Boolean hasUnanalyzedC;
private Boolean hasUnanalyzedCpp;
private Set<String> customSecurityConfigs;
@@ -248,8 +248,8 @@ public class TelemetryData {
return this;
}

public Builder setIsScimEnabled(boolean isEnabled) {
this.isScimEnabled = isEnabled;
Builder setManagedInstanceInformation(ManagedInstanceInformation managedInstanceInformation) {
this.managedInstanceInformation = managedInstanceInformation;
return this;
}

@@ -283,6 +283,9 @@ public class TelemetryData {
record QualityGate(String uuid, String caycStatus) {
}

record ManagedInstanceInformation(boolean isManaged, @Nullable String provider) {
}

public static class ProjectStatistics {
private final String projectUuid;
private final Long branchCount;

+ 46
- 39
server/sonar-server-common/src/main/java/org/sonar/server/telemetry/TelemetryDataJsonWriter.java View File

@@ -36,9 +36,10 @@ import static org.sonar.api.utils.DateUtils.DATETIME_FORMAT;
public class TelemetryDataJsonWriter {

@VisibleForTesting
static final String SCIM_PROPERTY = "scim";
static final String MANAGED_INSTANCE_PROPERTY = "managedInstanceInformation";

private static final String LANGUAGE_PROPERTY = "language";
private static final String VERSION = "version";

private final List<TelemetryExtension> extensions;

@@ -49,64 +50,62 @@ public class TelemetryDataJsonWriter {
this.system2 = system2;
}

public void writeTelemetryData(JsonWriter json, TelemetryData statistics) {
public void writeTelemetryData(JsonWriter json, TelemetryData telemetryData) {
json.beginObject();
json.prop("id", statistics.getServerId());
json.prop("version", statistics.getVersion());
json.prop("messageSequenceNumber", statistics.getMessageSequenceNumber());
json.prop("id", telemetryData.getServerId());
json.prop(VERSION, telemetryData.getVersion());
json.prop("messageSequenceNumber", telemetryData.getMessageSequenceNumber());
json.prop("localTimestamp", toUtc(system2.now()));
statistics.getEdition().ifPresent(e -> json.prop("edition", e.name().toLowerCase(Locale.ENGLISH)));
json.prop("defaultQualityGate", statistics.getDefaultQualityGate());
telemetryData.getEdition().ifPresent(e -> json.prop("edition", e.name().toLowerCase(Locale.ENGLISH)));
json.prop("defaultQualityGate", telemetryData.getDefaultQualityGate());
json.name("database");
json.beginObject();
json.prop("name", statistics.getDatabase().name());
json.prop("version", statistics.getDatabase().version());
json.prop("name", telemetryData.getDatabase().name());
json.prop(VERSION, telemetryData.getDatabase().version());
json.endObject();
json.name("plugins");
json.beginArray();
statistics.getPlugins().forEach((plugin, version) -> {
telemetryData.getPlugins().forEach((plugin, version) -> {
json.beginObject();
json.prop("name", plugin);
json.prop("version", version);
json.prop(VERSION, version);
json.endObject();
});
json.endArray();

if (!statistics.getCustomSecurityConfigs().isEmpty()) {
if (!telemetryData.getCustomSecurityConfigs().isEmpty()) {
json.name("customSecurityConfig");
json.beginArray();
json.values(statistics.getCustomSecurityConfigs());
json.values(telemetryData.getCustomSecurityConfigs());
json.endArray();
}

statistics.hasUnanalyzedC().ifPresent(hasUnanalyzedC -> json.prop("hasUnanalyzedC", hasUnanalyzedC));
statistics.hasUnanalyzedCpp().ifPresent(hasUnanalyzedCpp -> json.prop("hasUnanalyzedCpp", hasUnanalyzedCpp));
telemetryData.hasUnanalyzedC().ifPresent(hasUnanalyzedC -> json.prop("hasUnanalyzedC", hasUnanalyzedC));
telemetryData.hasUnanalyzedCpp().ifPresent(hasUnanalyzedCpp -> json.prop("hasUnanalyzedCpp", hasUnanalyzedCpp));

if (statistics.getInstallationDate() != null) {
json.prop("installationDate", toUtc(statistics.getInstallationDate()));
if (telemetryData.getInstallationDate() != null) {
json.prop("installationDate", toUtc(telemetryData.getInstallationDate()));
}
if (statistics.getInstallationVersion() != null) {
json.prop("installationVersion", statistics.getInstallationVersion());
if (telemetryData.getInstallationVersion() != null) {
json.prop("installationVersion", telemetryData.getInstallationVersion());
}
json.prop("docker", statistics.isInDocker());

json.prop(SCIM_PROPERTY, statistics.isScimEnabled());

writeUserData(json, statistics);
writeProjectData(json, statistics);
writeProjectStatsData(json, statistics);
writeQualityGates(json, statistics);
json.prop("docker", telemetryData.isInDocker());

writeUserData(json, telemetryData);
writeProjectData(json, telemetryData);
writeProjectStatsData(json, telemetryData);
writeQualityGates(json, telemetryData);
writeManagedInstanceInformation(json, telemetryData.getManagedInstanceInformation());
extensions.forEach(e -> e.write(json));

json.endObject();
}

private static void writeUserData(JsonWriter json, TelemetryData statistics) {
if (statistics.getUserTelemetries() != null) {
private static void writeUserData(JsonWriter json, TelemetryData telemetryData) {
if (telemetryData.getUserTelemetries() != null) {
json.name("users");
json.beginArray();
statistics.getUserTelemetries().forEach(user -> {
telemetryData.getUserTelemetries().forEach(user -> {
json.beginObject();
json.prop("userUuid", DigestUtils.sha3_224Hex(user.getUuid()));
json.prop("status", user.isActive() ? "active" : "inactive");
@@ -126,11 +125,11 @@ public class TelemetryDataJsonWriter {
}
}

private static void writeProjectData(JsonWriter json, TelemetryData statistics) {
if (statistics.getProjects() != null) {
private static void writeProjectData(JsonWriter json, TelemetryData telemetryData) {
if (telemetryData.getProjects() != null) {
json.name("projects");
json.beginArray();
statistics.getProjects().forEach(project -> {
telemetryData.getProjects().forEach(project -> {
json.beginObject();
json.prop("projectUuid", project.projectUuid());
if (project.lastAnalysis() != null) {
@@ -144,11 +143,11 @@ public class TelemetryDataJsonWriter {
}
}

private static void writeProjectStatsData(JsonWriter json, TelemetryData statistics) {
if (statistics.getProjectStatistics() != null) {
private static void writeProjectStatsData(JsonWriter json, TelemetryData telemetryData) {
if (telemetryData.getProjectStatistics() != null) {
json.name("projects-general-stats");
json.beginArray();
statistics.getProjectStatistics().forEach(project -> {
telemetryData.getProjectStatistics().forEach(project -> {
json.beginObject();
json.prop("projectUuid", project.getProjectUuid());
json.prop("branchCount", project.getBranchCount());
@@ -168,11 +167,11 @@ public class TelemetryDataJsonWriter {
}
}

private static void writeQualityGates(JsonWriter json, TelemetryData statistics) {
if (statistics.getQualityGates() != null) {
private static void writeQualityGates(JsonWriter json, TelemetryData telemetryData) {
if (telemetryData.getQualityGates() != null) {
json.name("quality-gates");
json.beginArray();
statistics.getQualityGates().forEach(qualityGate -> {
telemetryData.getQualityGates().forEach(qualityGate -> {
json.beginObject();
json.prop("uuid", qualityGate.uuid());
json.prop("caycStatus", qualityGate.caycStatus());
@@ -182,6 +181,14 @@ public class TelemetryDataJsonWriter {
}
}

private static void writeManagedInstanceInformation(JsonWriter json, TelemetryData.ManagedInstanceInformation provider) {
json.name(MANAGED_INSTANCE_PROPERTY);
json.beginObject();
json.prop("isManaged", provider.isManaged());
json.prop("provider", provider.isManaged() ? provider.provider() : null);
json.endObject();
}

@NotNull
private static String toUtc(long date) {
return DateTimeFormatter.ofPattern(DATETIME_FORMAT)

+ 33
- 7
server/sonar-server-common/src/test/java/org/sonar/server/telemetry/TelemetryDataJsonWriterTest.java View File

@@ -43,13 +43,11 @@ import org.sonar.core.telemetry.TelemetryExtension;
import org.sonar.core.util.stream.MoreCollectors;
import org.sonar.db.user.UserTelemetryDto;

import static java.lang.String.format;
import static java.util.stream.Collectors.joining;
import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
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.telemetry.TelemetryDataJsonWriter.SCIM_PROPERTY;
import static org.sonar.test.JsonAssert.assertJson;

@RunWith(DataProviderRunner.class)
@@ -233,16 +231,43 @@ public class TelemetryDataJsonWriterTest {
""".formatted(isInDocker));
}

@DataProvider
public static Object[][] getManagedInstanceData() {
return new Object[][] {
{true, "scim"},
{true, "github"},
{true, "gitlab"},
{false, null},
};
}

@Test
@UseDataProvider("getFeatureFlagEnabledStates")
public void write_scim_feature_flag(boolean isScimEnabled) {
@UseDataProvider("getManagedInstanceData")
public void writeTelemetryData_encodesCorrectlyManagedInstanceInformation(boolean isManaged, String provider) {
TelemetryData data = telemetryBuilder()
.setIsScimEnabled(isScimEnabled)
.setManagedInstanceInformation(new TelemetryData.ManagedInstanceInformation(isManaged, provider))
.build();

String json = writeTelemetryData(data);

assertJson(json).isSimilarTo("{" + format(" \"%s\":", SCIM_PROPERTY) + isScimEnabled + "}");
if (isManaged) {
assertJson(json).isSimilarTo("""
{
"managedInstanceInformation": {
"isManaged": true,
"provider": "%s"
}
}
""".formatted(provider));
} else {
assertJson(json).isSimilarTo("""
{
"managedInstanceInformation": {
"isManaged": false
}
}
""");
}
}

@Test
@@ -478,6 +503,7 @@ public class TelemetryDataJsonWriterTest {
.setVersion("bar")
.setMessageSequenceNumber(1L)
.setPlugins(Collections.emptyMap())
.setManagedInstanceInformation(new TelemetryData.ManagedInstanceInformation(false, null))
.setDatabase(new TelemetryData.Database("H2", "11"));
}

@@ -530,7 +556,7 @@ public class TelemetryDataJsonWriterTest {
@DataProvider
public static Object[][] allEditions() {
return Arrays.stream(EditionProvider.Edition.values())
.map(t -> new Object[]{t})
.map(t -> new Object[] {t})
.toArray(Object[][]::new);
}


+ 14
- 2
server/sonar-webserver-core/src/main/java/org/sonar/server/telemetry/TelemetryDataLoaderImpl.java View File

@@ -53,6 +53,7 @@ import org.sonar.db.measure.ProjectLocDistributionDto;
import org.sonar.db.metric.MetricDto;
import org.sonar.db.qualitygate.ProjectQgateAssociationDto;
import org.sonar.db.qualitygate.QualityGateDto;
import org.sonar.server.management.ManagedInstanceService;
import org.sonar.server.platform.DockerSupport;
import org.sonar.server.property.InternalProperties;
import org.sonar.server.qualitygate.QualityGateCaycChecker;
@@ -101,11 +102,13 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader {
private final DockerSupport dockerSupport;
private final QualityGateCaycChecker qualityGateCaycChecker;
private final QualityGateFinder qualityGateFinder;
private final ManagedInstanceService managedInstanceService;

@Inject
public TelemetryDataLoaderImpl(Server server, DbClient dbClient, PluginRepository pluginRepository,
PlatformEditionProvider editionProvider, InternalProperties internalProperties, Configuration configuration,
DockerSupport dockerSupport, QualityGateCaycChecker qualityGateCaycChecker, QualityGateFinder qualityGateFinder) {
DockerSupport dockerSupport, QualityGateCaycChecker qualityGateCaycChecker, QualityGateFinder qualityGateFinder,
ManagedInstanceService managedInstanceService) {
this.server = server;
this.dbClient = dbClient;
this.pluginRepository = pluginRepository;
@@ -115,6 +118,7 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader {
this.dockerSupport = dockerSupport;
this.qualityGateCaycChecker = qualityGateCaycChecker;
this.qualityGateFinder = qualityGateFinder;
this.managedInstanceService = managedInstanceService;
}

private static Database loadDatabaseMetadata(DbSession dbSession) {
@@ -157,13 +161,15 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader {
installationDateProperty.ifPresent(s -> data.setInstallationDate(Long.valueOf(s)));
Optional<String> installationVersionProperty = internalProperties.read(InternalProperties.INSTALLATION_VERSION);


return data
.setInstallationVersion(installationVersionProperty.orElse(null))
.setInDocker(dockerSupport.isRunningInDocker())
.setIsScimEnabled(isScimEnabled())
.setManagedInstanceInformation(buildManagedInstanceInformation())
.build();
}


private void resolveUnanalyzedLanguageCode(TelemetryData.Builder data, DbSession dbSession) {
long numberOfUnanalyzedCMeasures = dbClient.liveMeasureDao().countProjectsHavingMeasure(dbSession, UNANALYZED_C_KEY);
long numberOfUnanalyzedCppMeasures = dbClient.liveMeasureDao().countProjectsHavingMeasure(dbSession, UNANALYZED_CPP_KEY);
@@ -358,4 +364,10 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader {
private boolean isScimEnabled() {
return this.internalProperties.read(SCIM_PROPERTY_ENABLED).map(Boolean::parseBoolean).orElse(false);
}

private TelemetryData.ManagedInstanceInformation buildManagedInstanceInformation() {
String provider = managedInstanceService.isInstanceExternallyManaged() ? managedInstanceService.getProviderName() : null;
return new TelemetryData.ManagedInstanceInformation(managedInstanceService.isInstanceExternallyManaged(), provider);
}

}

+ 22
- 9
server/sonar-webserver-core/src/test/java/org/sonar/server/telemetry/TelemetryDataLoaderImplTest.java View File

@@ -52,6 +52,7 @@ import org.sonar.db.qualitygate.QualityGateDto;
import org.sonar.db.user.UserDbTester;
import org.sonar.db.user.UserDto;
import org.sonar.db.user.UserTelemetryDto;
import org.sonar.server.management.ManagedInstanceService;
import org.sonar.server.platform.DockerSupport;
import org.sonar.server.property.InternalProperties;
import org.sonar.server.property.MapInternalProperties;
@@ -86,7 +87,6 @@ import static org.sonar.db.component.BranchType.BRANCH;
import static org.sonar.server.metric.UnanalyzedLanguageMetrics.UNANALYZED_CPP_KEY;
import static org.sonar.server.metric.UnanalyzedLanguageMetrics.UNANALYZED_C_KEY;
import static org.sonar.server.qualitygate.QualityGateCaycStatus.NON_COMPLIANT;
import static org.sonar.server.telemetry.TelemetryDataLoaderImpl.SCIM_PROPERTY_ENABLED;

@RunWith(DataProviderRunner.class)
public class TelemetryDataLoaderImplTest {
@@ -104,11 +104,12 @@ public class TelemetryDataLoaderImplTest {
private final QualityGateCaycChecker qualityGateCaycChecker = mock(QualityGateCaycChecker.class);
private final QualityGateFinder qualityGateFinder = new QualityGateFinder(db.getDbClient());
private final InternalProperties internalProperties = spy(new MapInternalProperties());
private final ManagedInstanceService managedInstanceService = mock(ManagedInstanceService.class);

private final TelemetryDataLoader communityUnderTest = new TelemetryDataLoaderImpl(server, db.getDbClient(), pluginRepository, editionProvider,
internalProperties, configuration, dockerSupport, qualityGateCaycChecker, qualityGateFinder);
internalProperties, configuration, dockerSupport, qualityGateCaycChecker, qualityGateFinder, managedInstanceService);
private final TelemetryDataLoader commercialUnderTest = new TelemetryDataLoaderImpl(server, db.getDbClient(), pluginRepository, editionProvider,
internalProperties, configuration, dockerSupport, qualityGateCaycChecker, qualityGateFinder);
internalProperties, configuration, dockerSupport, qualityGateCaycChecker, qualityGateFinder, managedInstanceService);

private QualityGateDto builtInDefaultQualityGate;
private MetricDto bugsDto;
@@ -440,14 +441,16 @@ public class TelemetryDataLoaderImplTest {
}

@Test
@UseDataProvider("getScimFeatureStatues")
public void detect_scim_feature_status(String isEnabled) {
db.components().insertPublicProject().getMainBranchComponent();
when(internalProperties.read(SCIM_PROPERTY_ENABLED)).thenReturn(Optional.ofNullable(isEnabled));
@UseDataProvider("getManagedInstanceData")
public void managedInstanceData_containsCorrectInformation(boolean isManaged, String provider) {
when(managedInstanceService.isInstanceExternallyManaged()).thenReturn(isManaged);
when(managedInstanceService.getProviderName()).thenReturn(provider);

TelemetryData data = communityUnderTest.load();
TelemetryData data = commercialUnderTest.load();

assertThat(data.isScimEnabled()).isEqualTo(Boolean.parseBoolean(isEnabled));
TelemetryData.ManagedInstanceInformation managedInstance = data.getManagedInstanceInformation();
assertThat(managedInstance.isManaged()).isEqualTo(isManaged);
assertThat(managedInstance.provider()).isEqualTo(provider);
}

@Test
@@ -483,4 +486,14 @@ public class TelemetryDataLoaderImplTest {
result.add(null);
return result;
}

@DataProvider
public static Object[][] getManagedInstanceData() {
return new Object[][]{
{true, "scim"},
{true, "github"},
{true, "gitlab"},
{false, null},
};
}
}

Loading…
Cancel
Save