Browse Source

SONAR-21909 Introduce active version field in upgrades api

tags/10.5.0.89998
OrlovAlexander 1 month ago
parent
commit
1d663f51ea

+ 1
- 1
build.gradle View File

@@ -446,7 +446,7 @@ subprojects {
}
dependency 'org.junit.platform:junit-platform-suite-api:1.10.2'
dependency 'org.junit.platform:junit-platform-suite-engine:1.10.2'
dependency 'org.sonarsource.update-center:sonar-update-center-common:1.31.0.1207'
dependency 'org.sonarsource.update-center:sonar-update-center-common:1.32.0.2424'
dependency("org.springframework:spring-context:${springVersion}") {
exclude 'commons-logging:commons-logging'
}

+ 93
- 0
server/sonar-webserver-webapi/src/main/java/org/sonar/server/platform/ws/ActiveVersionEvaluator.java View File

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

import com.google.common.collect.Lists;
import java.util.Calendar;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.SortedSet;
import org.sonar.api.utils.System2;
import org.sonar.core.platform.SonarQubeVersion;
import org.sonar.updatecenter.common.Release;
import org.sonar.updatecenter.common.UpdateCenter;
import org.sonar.updatecenter.common.Version;

public class ActiveVersionEvaluator {

private static final Comparator<Version> COMPARATOR = Comparator.comparingInt((Version v) -> Integer.parseInt(v.getMajor()))
.thenComparingInt((Version v) -> Integer.parseInt(v.getMinor()));
private final SonarQubeVersion sonarQubeVersion;
private final System2 system2;

public ActiveVersionEvaluator(SonarQubeVersion sonarQubeVersion, System2 system2) {
this.sonarQubeVersion = sonarQubeVersion;
this.system2 = system2;
}

public boolean evaluateIfActiveVersion(UpdateCenter updateCenter) {
Version installedVersion = Version.create(sonarQubeVersion.get().toString());

if (compareWithoutPatchVersion(installedVersion, updateCenter.getSonar().getLtaVersion().getVersion()) == 0) {
return true;
}
SortedSet<Release> allReleases = updateCenter.getSonar().getAllReleases();
if (compareWithoutPatchVersion(installedVersion, updateCenter.getSonar().getPastLtaVersion().getVersion()) == 0) {
Release initialLtaRelease = findInitialVersionOfMajorRelease(allReleases, updateCenter.getSonar().getLtaVersion().getVersion());
Date initialLtaReleaseDate = initialLtaRelease.getDate();
if (initialLtaReleaseDate == null) {
throw new IllegalStateException("Initial Major release date is missing in releases");
}
// date of the latest major release should be within 6 months
Calendar c = Calendar.getInstance();
c.setTime(new Date(system2.now()));
c.add(Calendar.MONTH, -6);

return initialLtaReleaseDate.after(c.getTime());
} else {
return compareWithoutPatchVersion(installedVersion, findPreviousReleaseIgnoringPatch(allReleases).getVersion()) >= 0;
}
}


private static int compareWithoutPatchVersion(Version v1, Version v2) {
return COMPARATOR.compare(v1, v2);
}

private static Release findInitialVersionOfMajorRelease(SortedSet<Release> releases, Version referenceVersion) {
return releases.stream()
.filter(release -> release.getVersion().getMajor().equals(referenceVersion.getMajor())
&& release.getVersion().getMinor().equals(referenceVersion.getMinor()))
.min(Comparator.comparing(r -> Integer.parseInt(r.getVersion().getPatch())))
.orElseThrow(() -> new IllegalStateException("Unable to find initial major release for version " + referenceVersion + " in releases"));
}

private static Release findPreviousReleaseIgnoringPatch(SortedSet<Release> releases) {
Release refRelease = releases.last();
List<Release> sublist = Lists.reverse(releases.stream().toList());
for (Release release : sublist) {
if (compareWithoutPatchVersion(release.getVersion(), refRelease.getVersion()) < 0) {
return release;
}
}
throw new IllegalStateException("Unable to find previous release in releases");
}
}

+ 2
- 1
server/sonar-webserver-webapi/src/main/java/org/sonar/server/platform/ws/SystemWsModule.java View File

@@ -43,7 +43,8 @@ public class SystemWsModule extends Module {
RestartAction.class,
StatusAction.class,
UpgradesAction.class,
SystemWs.class
SystemWs.class,
ActiveVersionEvaluator.class

);
}

+ 36
- 12
server/sonar-webserver-webapi/src/main/java/org/sonar/server/platform/ws/UpgradesAction.java View File

@@ -22,6 +22,7 @@ package org.sonar.server.platform.ws;
import com.google.common.io.Resources;
import java.util.List;
import java.util.Optional;
import org.sonar.api.server.ws.Change;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
@@ -46,6 +47,7 @@ public class UpgradesAction implements SystemWsAction {

private static final String ARRAY_UPGRADES = "upgrades";
private static final String PROPERTY_UPDATE_CENTER_LTS = "latestLTS";
private static final String PROPERTY_UPDATE_CENTER_LTA = "latestLTA";
private static final String PROPERTY_UPDATE_CENTER_REFRESH = "updateCenterRefresh";
private static final String PROPERTY_VERSION = "version";
private static final String PROPERTY_DESCRIPTION = "description";
@@ -68,11 +70,14 @@ public class UpgradesAction implements SystemWsAction {
private static final String PROPERTY_ISSUE_TRACKER_URL = "issueTrackerUrl";
private static final String PROPERTY_EDITION_BUNDLED = "editionBundled";
private static final String PROPERTY_TERMS_AND_CONDITIONS_URL = "termsAndConditionsUrl";
public static final String INSTALLED_VERSION_ACTIVE = "installedVersionActive";

private final UpdateCenterMatrixFactory updateCenterFactory;
private final ActiveVersionEvaluator activeVersionEvaluator;

public UpgradesAction(UpdateCenterMatrixFactory updateCenterFactory) {
public UpgradesAction(UpdateCenterMatrixFactory updateCenterFactory, ActiveVersionEvaluator activeVersionEvaluator) {
this.updateCenterFactory = updateCenterFactory;
this.activeVersionEvaluator = activeVersionEvaluator;
}

private static void writeMetadata(JsonWriter jsonWriter, Release release) {
@@ -96,7 +101,10 @@ public class UpgradesAction implements SystemWsAction {
"is provided in the response.")
.setSince("5.2")
.setHandler(this)
.setResponseExample(Resources.getResource(this.getClass(), "example-upgrades_plugins.json"));
.setResponseExample(Resources.getResource(this.getClass(), "example-upgrades_plugins.json"))
.setChangelog(new Change("10.5", "The field 'ltsVersion' is deprecated from the response"))
.setChangelog(new Change("10.5", "The field 'ltaVersion' is added to indicate the Long-Term Active Version"))
.setChangelog(new Change("10.5", "The field 'installedVersionActive' is added to indicate if the installed version is an active version"));
}

@Override
@@ -110,21 +118,37 @@ public class UpgradesAction implements SystemWsAction {
private void writeResponse(JsonWriter jsonWriter) {
jsonWriter.beginObject();

Optional<UpdateCenter> updateCenter = updateCenterFactory.getUpdateCenter(DO_NOT_FORCE_REFRESH);
writeUpgrades(jsonWriter, updateCenter);
if (updateCenter.isPresent()) {
Release ltsRelease = updateCenter.get().getSonar().getLtsRelease();
if (ltsRelease != null) {
Version ltsVersion = ltsRelease.getVersion();
String latestLTS = String.format("%s.%s", ltsVersion.getMajor(), ltsVersion.getMinor());
jsonWriter.prop(PROPERTY_UPDATE_CENTER_LTS, latestLTS);
}
jsonWriter.propDateTime(PROPERTY_UPDATE_CENTER_REFRESH, updateCenter.get().getDate());
Optional<UpdateCenter> updateCenterOpt = updateCenterFactory.getUpdateCenter(DO_NOT_FORCE_REFRESH);
writeUpgrades(jsonWriter, updateCenterOpt);
if (updateCenterOpt.isPresent()) {
UpdateCenter updateCenter = updateCenterOpt.get();
writeLatestLtsVersion(jsonWriter, updateCenter);
writeLatestLtaVersion(jsonWriter, updateCenter);
jsonWriter.propDateTime(PROPERTY_UPDATE_CENTER_REFRESH, updateCenter.getDate());
jsonWriter.prop(INSTALLED_VERSION_ACTIVE, activeVersionEvaluator.evaluateIfActiveVersion(updateCenter));
}

jsonWriter.endObject();
}

private static void writeLatestLtsVersion(JsonWriter jsonWriter, UpdateCenter updateCenter) {
Release ltsRelease = updateCenter.getSonar().getLtsRelease();
if (ltsRelease != null) {
Version ltsVersion = ltsRelease.getVersion();
String latestLTS = String.format("%s.%s", ltsVersion.getMajor(), ltsVersion.getMinor());
jsonWriter.prop(PROPERTY_UPDATE_CENTER_LTS, latestLTS);
}
}

private static void writeLatestLtaVersion(JsonWriter jsonWriter, UpdateCenter updateCenter) {
Release ltaRelease = updateCenter.getSonar().getLtaVersion();
if (ltaRelease != null) {
Version ltaVersion = ltaRelease.getVersion();
String latestLTA = String.format("%s.%s", ltaVersion.getMajor(), ltaVersion.getMinor());
jsonWriter.prop(PROPERTY_UPDATE_CENTER_LTA, latestLTA);
}
}

private static void writeUpgrades(JsonWriter jsonWriter, Optional<UpdateCenter> updateCenter) {
jsonWriter.name(ARRAY_UPGRADES).beginArray();


+ 2
- 0
server/sonar-webserver-webapi/src/main/resources/org/sonar/server/platform/ws/example-upgrades_plugins.json View File

@@ -37,5 +37,7 @@
}
],
"latestLTS": "8.9",
"latestLTA": "8.9",
"installedVersionActive": true,
"updateCenterRefresh": "2015-04-24T16:08:36+0200"
}

+ 157
- 0
server/sonar-webserver-webapi/src/test/java/org/sonar/server/platform/ws/ActiveVersionEvaluatorTest.java View File

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

import java.util.Calendar;
import java.util.SortedSet;
import java.util.TreeSet;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.sonar.api.utils.DateUtils;
import org.sonar.api.utils.System2;
import org.sonar.core.platform.SonarQubeVersion;
import org.sonar.updatecenter.common.Release;
import org.sonar.updatecenter.common.Sonar;
import org.sonar.updatecenter.common.UpdateCenter;
import org.sonar.updatecenter.common.Version;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.sonar.api.utils.Version.parse;

class ActiveVersionEvaluatorTest {

private final SonarQubeVersion sonarQubeVersion = mock(SonarQubeVersion.class);
private final UpdateCenter updateCenter = mock(UpdateCenter.class);
private static final Sonar sonar = mock(Sonar.class);
private final System2 system2 = mock(System2.class);
private final ActiveVersionEvaluator underTest = new ActiveVersionEvaluator(sonarQubeVersion, system2);

@BeforeEach
void setup() {
when(updateCenter.getSonar()).thenReturn(sonar);
when(updateCenter.getDate()).thenReturn(DateUtils.parseDateTime("2015-04-24T16:08:36+0200"));
when(sonar.getLtaVersion()).thenReturn(new Release(sonar, Version.create("9.9.4")));
when(sonar.getPastLtaVersion()).thenReturn(new Release(sonar, Version.create("8.9.10")));
}

@Test
void evaluateIfActiveVersion_whenInstalledVersionIsLatestLta_shouldReturnActiveVersion() {
when(updateCenter.getSonar().getAllReleases()).thenReturn(getReleases());
when(sonarQubeVersion.get()).thenReturn(parse("9.9.2"));

assertThat(underTest.evaluateIfActiveVersion(updateCenter)).isTrue();
}

@Test
void evaluateIfActiveVersion_whenInstalledVersionIsPastLtaAndWithinSixMonthFromLta_shouldReturnVersionIsActive() {
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.MONTH, -5);
calendar.set(Calendar.DAY_OF_MONTH, 1);

when(sonarQubeVersion.get()).thenReturn(parse("8.9.5"));
SortedSet<Release> releases = getReleases();
releases.stream().filter(r -> r.getVersion().equals(Version.create("9.9"))).findFirst().get().setDate(calendar.getTime());
when(sonar.getAllReleases()).thenReturn(releases);

assertThat(underTest.evaluateIfActiveVersion(updateCenter)).isTrue();
}

@Test
void evaluateIfActiveVersion_whenInstalledVersionIsPastLtaAndAfterSixMonthFromLta_shouldReturnVersionNotActive() {
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.MONTH, -7);
calendar.set(Calendar.DAY_OF_MONTH, 1);

when(system2.now()).thenCallRealMethod();

when(sonarQubeVersion.get()).thenReturn(parse("8.9.5"));
SortedSet<Release> releases = getReleases();
releases.stream().filter(r -> r.getVersion().equals(Version.create("9.9"))).findFirst().get().setDate(calendar.getTime());
when(sonar.getAllReleases()).thenReturn(releases);

assertThat(underTest.evaluateIfActiveVersion(updateCenter)).isFalse();
}

@Test
void evaluateIfActiveVersion_whenInstalledVersionIsPastLtaAndReleaseDateIsMissing_shouldThrowIllegalStateException() {

when(sonarQubeVersion.get()).thenReturn(parse("8.9.5"));
SortedSet<Release> releases = getReleases();
when(sonar.getAllReleases()).thenReturn(releases);

assertThatThrownBy(() -> underTest.evaluateIfActiveVersion(updateCenter))
.isInstanceOf(IllegalStateException.class)
.hasMessageContaining("Initial Major release date is missing in releases");
}

@Test
void evaluateIfActiveVersion_whenNoPreviousReleasesFound_shouldThrowIllegalStateException() {

when(sonarQubeVersion.get()).thenReturn(parse("10.4.1"));
TreeSet<Release> releases = new TreeSet<>();
releases.add(new Release(sonar, Version.create("10.4.1")));
when(sonar.getAllReleases()).thenReturn(releases);

assertThatThrownBy(() -> underTest.evaluateIfActiveVersion(updateCenter))
.isInstanceOf(IllegalStateException.class)
.hasMessageContaining("Unable to find previous release in releases");
}

@Test
void evaluateIfActiveVersion_whenInstalledVersionIsLatestMinusOne_shouldReturnVersionIsActive() {
when(sonarQubeVersion.get()).thenReturn(parse("10.9"));
when(updateCenter.getSonar().getAllReleases()).thenReturn(getReleases());

assertThat(underTest.evaluateIfActiveVersion(updateCenter)).isTrue();
}

@Test
void evaluateIfActiveVersion_whenInstalledVersionIsSnapshot_shouldReturnVersionIsActive() {
when(sonarQubeVersion.get()).thenReturn(parse("10.11-SNAPSHOT"));
when(updateCenter.getSonar().getAllReleases()).thenReturn(getReleases());

assertThat(underTest.evaluateIfActiveVersion(updateCenter)).isTrue();
}

public static SortedSet<Release> getReleases() {
TreeSet<Release> releases = new TreeSet<>();
releases.add(new Release(sonar, Version.create("9.9")));
releases.add(new Release(sonar, Version.create("9.9.1")));
releases.add(new Release(sonar, Version.create("9.9.2")));
releases.add(new Release(sonar, Version.create("9.9.3")));
releases.add(new Release(sonar, Version.create("9.9.4")));
releases.add(new Release(sonar, Version.create("10.0")));
releases.add(new Release(sonar, Version.create("10.1")));
releases.add(new Release(sonar, Version.create("10.2")));
releases.add(new Release(sonar, Version.create("10.2.1")));
releases.add(new Release(sonar, Version.create("10.3")));
releases.add(new Release(sonar, Version.create("10.4")));
releases.add(new Release(sonar, Version.create("10.4.1")));
releases.add(new Release(sonar, Version.create("10.9.1")));
releases.add(new Release(sonar, Version.create("10.10.1")));
releases.add(new Release(sonar, Version.create("10.10.2")));
releases.add(new Release(sonar, Version.create("10.10.3")));
return releases;
}

}

+ 1
- 1
server/sonar-webserver-webapi/src/test/java/org/sonar/server/platform/ws/SystemWsModuleTest.java View File

@@ -29,6 +29,6 @@ public class SystemWsModuleTest {
public void verify_count_of_added_components() {
ListContainer container = new ListContainer();
new SystemWsModule().configure(container);
assertThat(container.getAddedObjects()).hasSize(15);
assertThat(container.getAddedObjects()).hasSize(16);
}
}

+ 33
- 15
server/sonar-webserver-webapi/src/test/java/org/sonar/server/platform/ws/UpgradesActionTest.java View File

@@ -20,10 +20,12 @@
package org.sonar.server.platform.ws;

import java.util.Optional;
import org.junit.Before;
import org.junit.Test;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.sonar.api.server.ws.WebService;
import org.sonar.api.utils.DateUtils;
import org.sonar.api.utils.System2;
import org.sonar.core.platform.SonarQubeVersion;
import org.sonar.server.plugins.UpdateCenterMatrixFactory;
import org.sonar.server.ws.TestResponse;
import org.sonar.server.ws.WsActionTester;
@@ -39,19 +41,25 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.sonar.api.utils.Version.parse;
import static org.sonar.server.platform.ws.ActiveVersionEvaluatorTest.getReleases;
import static org.sonar.test.JsonAssert.assertJson;

public class UpgradesActionTest {
class UpgradesActionTest {
private static final String JSON_EMPTY_UPGRADE_LIST = "{" +
" \"upgrades\":" + "[]" +
"}";

private UpdateCenterMatrixFactory updateCenterFactory = mock(UpdateCenterMatrixFactory.class);
private UpdateCenter updateCenter = mock(UpdateCenter.class);
private Sonar sonar = mock(Sonar.class);
private UpgradesAction underTest = new UpgradesAction(updateCenterFactory);
private final UpdateCenterMatrixFactory updateCenterFactory = mock(UpdateCenterMatrixFactory.class);
private final SonarQubeVersion sonarQubeVersion = mock(SonarQubeVersion.class);
private final UpdateCenter updateCenter = mock(UpdateCenter.class);
private final Sonar sonar = mock(Sonar.class);
private final System2 system2 = mock(System2.class);

private WsActionTester tester = new WsActionTester(underTest);
private final ActiveVersionEvaluator activeVersionEvaluator = new ActiveVersionEvaluator(sonarQubeVersion, system2);
private final UpgradesAction underTest = new UpgradesAction(updateCenterFactory, activeVersionEvaluator);

private final WsActionTester tester = new WsActionTester(underTest);

private static SonarUpdate createSonar_51_update() {
Plugin brandingPlugin = Plugin.factory("branding")
@@ -67,7 +75,8 @@ public class UpgradesActionTest {
Plugin viewsPlugin = Plugin.factory("views")
.setName("Views")
.setCategory("Governance")
.setDescription("Create aggregation trees to group projects. Projects can for instance be grouped by applications, applications by team, teams by department.")
.setDescription("Create aggregation trees to group projects. Projects can for instance be grouped by applications, applications " +
"by team, teams by department.")
.setHomepageUrl("https://redirect.sonarsource.com/plugins/views.html")
.setLicense("Commercial")
.setOrganization("SonarSource")
@@ -88,15 +97,17 @@ public class UpgradesActionTest {
return sonarUpdate;
}

@Before
public void wireMocks() {
@BeforeEach
void setup() {
when(updateCenterFactory.getUpdateCenter(anyBoolean())).thenReturn(Optional.of(updateCenter));
when(updateCenter.getSonar()).thenReturn(sonar);
when(updateCenter.getDate()).thenReturn(DateUtils.parseDateTime("2015-04-24T16:08:36+0200"));
when(sonar.getLtaVersion()).thenReturn(new Release(sonar, Version.create("9.9.4")));
when(sonar.getPastLtaVersion()).thenReturn(new Release(sonar, Version.create("8.9.10")));
}

@Test
public void action_updates_is_defined() {
void action_updates_is_defined() {
WebService.Action def = tester.getDef();

assertThat(def.key()).isEqualTo("upgrades");
@@ -108,14 +119,17 @@ public class UpgradesActionTest {
}

@Test
public void empty_array_is_returned_when_there_is_no_upgrade_available() {
void empty_array_is_returned_when_there_is_no_upgrade_available() {
when(updateCenter.getSonar().getAllReleases()).thenReturn(getReleases());
when(sonarQubeVersion.get()).thenReturn(parse("9.9.2"));

TestResponse response = tester.newRequest().execute();

assertJson(response.getInput()).withStrictArrayOrder().isSimilarTo(JSON_EMPTY_UPGRADE_LIST);
}

@Test
public void empty_array_is_returned_when_update_center_is_unavailable() {
void empty_array_is_returned_when_update_center_is_unavailable() {
when(updateCenterFactory.getUpdateCenter(anyBoolean())).thenReturn(Optional.empty());

TestResponse response = tester.newRequest().execute();
@@ -124,14 +138,18 @@ public class UpgradesActionTest {
}

@Test
public void verify_JSON_response_against_example() {
void verify_JSON_response_against_example() {
SonarUpdate sonarUpdate = createSonar_51_update();
when(sonarQubeVersion.get()).thenReturn(parse("8.9.0"));
when(sonar.getLtsRelease()).thenReturn(new Release(sonar, Version.create("8.9.2")));
when(sonar.getLtaVersion()).thenReturn(new Release(sonar, Version.create("8.9.2")));
when(updateCenter.findSonarUpdates()).thenReturn(of(sonarUpdate));
when(updateCenter.getSonar().getAllReleases()).thenReturn(getReleases());

TestResponse response = tester.newRequest().execute();

assertJson(response.getInput()).withStrictArrayOrder()
.isSimilarTo(tester.getDef().responseExampleAsString());
}

}

Loading…
Cancel
Save