3 * Copyright (C) 2009-2022 SonarSource SA
4 * mailto:info AT sonarsource DOT com
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 3 of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 package org.sonar.server.platform.serverid;
22 import com.tngtech.java.junit.dataprovider.DataProviderRunner;
23 import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
24 import org.junit.After;
25 import org.junit.Rule;
26 import org.junit.Test;
27 import org.junit.runner.RunWith;
28 import org.sonar.api.CoreProperties;
29 import org.sonar.api.SonarEdition;
30 import org.sonar.api.SonarQubeSide;
31 import org.sonar.api.internal.SonarRuntimeImpl;
32 import org.sonar.api.utils.System2;
33 import org.sonar.api.utils.Version;
34 import org.sonar.core.platform.ServerId;
35 import org.sonar.db.DbClient;
36 import org.sonar.db.DbSession;
37 import org.sonar.db.DbTester;
38 import org.sonar.db.property.PropertyDto;
39 import org.sonar.server.platform.WebServer;
40 import org.sonar.server.property.InternalProperties;
42 import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
43 import static org.assertj.core.api.Assertions.assertThat;
44 import static org.assertj.core.api.Assertions.assertThatThrownBy;
45 import static org.assertj.core.api.Assertions.fail;
46 import static org.mockito.ArgumentMatchers.any;
47 import static org.mockito.Mockito.mock;
48 import static org.mockito.Mockito.verify;
49 import static org.mockito.Mockito.when;
50 import static org.sonar.api.SonarQubeSide.COMPUTE_ENGINE;
51 import static org.sonar.api.SonarQubeSide.SERVER;
52 import static org.sonar.core.platform.ServerId.DATABASE_ID_LENGTH;
53 import static org.sonar.core.platform.ServerId.NOT_UUID_DATASET_ID_LENGTH;
54 import static org.sonar.core.platform.ServerId.UUID_DATASET_ID_LENGTH;
56 @RunWith(DataProviderRunner.class)
57 public class ServerIdManagerTest {
59 private static final ServerId WITH_DATABASE_ID_SERVER_ID = ServerId.of(randomAlphanumeric(DATABASE_ID_LENGTH), randomAlphanumeric(NOT_UUID_DATASET_ID_LENGTH));
60 private static final String CHECKSUM_1 = randomAlphanumeric(12);
63 public final DbTester dbTester = DbTester.create(System2.INSTANCE);
65 private final ServerIdChecksum serverIdChecksum = mock(ServerIdChecksum.class);
66 private final ServerIdFactory serverIdFactory = mock(ServerIdFactory.class);
67 private final DbClient dbClient = dbTester.getDbClient();
68 private final DbSession dbSession = dbTester.getSession();
69 private final WebServer webServer = mock(WebServer.class);
70 private ServerIdManager underTest;
73 public void tearDown() {
74 if (underTest != null) {
80 public void web_leader_persists_new_server_id_if_missing() {
81 mockCreateNewServerId();
82 mockChecksumOf(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1);
83 when(webServer.isStartupLeader()).thenReturn(true);
88 verifyCreateNewServerIdFromScratch();
92 public void web_leader_persists_new_server_id_if_value_is_empty() {
94 mockCreateNewServerId();
95 mockChecksumOf(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1);
96 when(webServer.isStartupLeader()).thenReturn(true);
100 verifyDb(CHECKSUM_1);
101 verifyCreateNewServerIdFromScratch();
105 public void web_leader_keeps_existing_server_id_if_valid() {
106 insertServerId(WITH_DATABASE_ID_SERVER_ID);
107 insertChecksum(CHECKSUM_1);
108 mockChecksumOf(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1);
109 when(webServer.isStartupLeader()).thenReturn(true);
113 verifyDb(CHECKSUM_1);
117 public void web_leader_creates_server_id_from_current_serverId_with_databaseId_if_checksum_fails() {
118 ServerId currentServerId = ServerId.of(randomAlphanumeric(DATABASE_ID_LENGTH), randomAlphanumeric(UUID_DATASET_ID_LENGTH));
119 insertServerId(currentServerId);
120 insertChecksum("does_not_match_WITH_DATABASE_ID_SERVER_ID");
121 mockChecksumOf(currentServerId, "matches_WITH_DATABASE_ID_SERVER_ID");
122 mockCreateNewServerIdFrom(currentServerId);
123 mockChecksumOf(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1);
124 when(webServer.isStartupLeader()).thenReturn(true);
128 verifyDb(CHECKSUM_1);
129 verifyCreateNewServerIdFrom(currentServerId);
133 public void web_leader_generates_missing_checksum_for_current_serverId_with_databaseId() {
134 insertServerId(WITH_DATABASE_ID_SERVER_ID);
135 mockChecksumOf(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1);
136 when(webServer.isStartupLeader()).thenReturn(true);
140 verifyDb(CHECKSUM_1);
144 public void web_follower_does_not_fail_if_server_id_matches_checksum() {
145 insertServerId(WITH_DATABASE_ID_SERVER_ID);
146 insertChecksum(CHECKSUM_1);
147 mockChecksumOf(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1);
148 when(webServer.isStartupLeader()).thenReturn(false);
153 verifyDb(CHECKSUM_1);
157 public void web_follower_fails_if_server_id_is_missing() {
158 when(webServer.isStartupLeader()).thenReturn(false);
160 expectMissingServerIdException(() -> test(SERVER));
164 public void web_follower_fails_if_server_id_is_empty() {
166 when(webServer.isStartupLeader()).thenReturn(false);
168 expectEmptyServerIdException(() -> test(SERVER));
172 public void web_follower_fails_if_checksum_does_not_match() {
173 String dbChecksum = "boom";
174 insertServerId(WITH_DATABASE_ID_SERVER_ID);
175 insertChecksum(dbChecksum);
176 mockChecksumOf(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1);
177 when(webServer.isStartupLeader()).thenReturn(false);
181 fail("An ISE should have been raised");
182 } catch (IllegalStateException e) {
183 assertThat(e.getMessage()).isEqualTo("Server ID is invalid");
185 verifyDb(dbChecksum);
190 public void compute_engine_does_not_fail_if_server_id_is_valid() {
191 insertServerId(WITH_DATABASE_ID_SERVER_ID);
192 insertChecksum(CHECKSUM_1);
193 mockChecksumOf(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1);
195 test(COMPUTE_ENGINE);
198 verifyDb(CHECKSUM_1);
202 public void compute_engine_fails_if_server_id_is_missing() {
203 expectMissingServerIdException(() -> test(COMPUTE_ENGINE));
207 public void compute_engine_fails_if_server_id_is_empty() {
210 expectEmptyServerIdException(() -> test(COMPUTE_ENGINE));
214 public void compute_engine_fails_if_server_id_is_invalid() {
215 String dbChecksum = "boom";
216 insertServerId(WITH_DATABASE_ID_SERVER_ID);
217 insertChecksum(dbChecksum);
218 mockChecksumOf(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1);
222 fail("An ISE should have been raised");
223 } catch (IllegalStateException e) {
224 assertThat(e.getMessage()).isEqualTo("Server ID is invalid");
226 verifyDb(dbChecksum);
230 private void expectEmptyServerIdException(ThrowingCallable callback) {
231 assertThatThrownBy(callback)
232 .isInstanceOf(IllegalStateException.class)
233 .hasMessage("Property sonar.core.id is empty in database");
236 private void expectMissingServerIdException(ThrowingCallable callback) {
237 assertThatThrownBy(callback)
238 .isInstanceOf(IllegalStateException.class)
239 .hasMessage("Property sonar.core.id is missing in database");
242 private void verifyDb(String expectedChecksum) {
243 assertThat(dbClient.propertiesDao().selectGlobalProperty(dbSession, CoreProperties.SERVER_ID))
244 .extracting(PropertyDto::getValue)
245 .isEqualTo(ServerIdManagerTest.WITH_DATABASE_ID_SERVER_ID.toString());
246 assertThat(dbClient.internalPropertiesDao().selectByKey(dbSession, InternalProperties.SERVER_ID_CHECKSUM))
247 .hasValue(expectedChecksum);
250 private void mockCreateNewServerId() {
251 when(serverIdFactory.create()).thenReturn(WITH_DATABASE_ID_SERVER_ID);
252 when(serverIdFactory.create(any())).thenThrow(new IllegalStateException("new ServerId should not be created from current server id"));
255 private void mockCreateNewServerIdFrom(ServerId currentServerId) {
256 when(serverIdFactory.create()).thenThrow(new IllegalStateException("new ServerId should be created from current server id"));
257 when(serverIdFactory.create(currentServerId)).thenReturn(ServerIdManagerTest.WITH_DATABASE_ID_SERVER_ID);
260 private void verifyCreateNewServerIdFromScratch() {
261 verify(serverIdFactory).create();
264 private void verifyCreateNewServerIdFrom(ServerId currentServerId) {
265 verify(serverIdFactory).create(currentServerId);
268 private void mockChecksumOf(ServerId serverId, String checksum1) {
269 when(serverIdChecksum.computeFor(serverId.toString())).thenReturn(checksum1);
272 private void insertServerId(ServerId serverId) {
273 insertServerId(serverId.toString());
276 private void insertServerId(String serverId) {
277 dbClient.propertiesDao().saveProperty(dbSession, new PropertyDto().setKey(CoreProperties.SERVER_ID).setValue(serverId),
278 null, null, null, null);
282 private void insertChecksum(String value) {
283 dbClient.internalPropertiesDao().save(dbSession, InternalProperties.SERVER_ID_CHECKSUM, value);
287 private void test(SonarQubeSide side) {
288 underTest = new ServerIdManager(serverIdChecksum, serverIdFactory, dbClient, SonarRuntimeImpl
289 .forSonarQube(Version.create(6, 7), side, SonarEdition.COMMUNITY), webServer);