@@ -50,6 +50,7 @@ dependencies { | |||
testImplementation 'org.assertj:assertj-core' | |||
testImplementation 'org.assertj:assertj-guava' | |||
testImplementation 'org.hamcrest:hamcrest-all' | |||
testImplementation 'org.mockito:mockito-core' | |||
testImplementation project(':sonar-plugin-api-impl') | |||
testImplementation testFixtures(project(':server:sonar-server-common')) | |||
} |
@@ -49,6 +49,7 @@ import org.sonar.db.ce.CeTaskCharacteristicDto; | |||
import org.sonar.db.ce.DeleteIf; | |||
import org.sonar.db.component.ComponentDto; | |||
import org.sonar.db.user.UserDto; | |||
import org.sonar.server.platform.WebServer; | |||
import org.sonar.server.property.InternalProperties; | |||
import static com.google.common.base.Preconditions.checkState; | |||
@@ -68,11 +69,13 @@ public class CeQueueImpl implements CeQueue { | |||
private final System2 system2; | |||
private final DbClient dbClient; | |||
private final UuidFactory uuidFactory; | |||
protected final WebServer webServer; | |||
public CeQueueImpl(System2 system2, DbClient dbClient, UuidFactory uuidFactory) { | |||
public CeQueueImpl(System2 system2, DbClient dbClient, UuidFactory uuidFactory, WebServer webServer) { | |||
this.system2 = system2; | |||
this.dbClient = dbClient; | |||
this.uuidFactory = uuidFactory; | |||
this.webServer = webServer; | |||
} | |||
@Override | |||
@@ -243,6 +246,7 @@ public class CeQueueImpl implements CeQueue { | |||
private void cancelImpl(DbSession dbSession, CeQueueDto q) { | |||
CeActivityDto activityDto = new CeActivityDto(q); | |||
activityDto.setNodeName(webServer.getNodeName().orElse(null)); | |||
activityDto.setStatus(CeActivityDto.Status.CANCELED); | |||
remove(dbSession, q, activityDto); | |||
} | |||
@@ -251,13 +255,14 @@ public class CeQueueImpl implements CeQueue { | |||
public void fail(DbSession dbSession, CeQueueDto task, @Nullable String errorType, @Nullable String errorMessage) { | |||
checkState(IN_PROGRESS.equals(task.getStatus()), "Task is not in-progress and can't be marked as failed [uuid=%s]", task.getUuid()); | |||
CeActivityDto activityDto = new CeActivityDto(task); | |||
activityDto.setNodeName(webServer.getNodeName().orElse(null)); | |||
activityDto.setStatus(CeActivityDto.Status.FAILED); | |||
activityDto.setErrorType(errorType); | |||
activityDto.setErrorMessage(errorMessage); | |||
updateExecutionFields(activityDto); | |||
remove(dbSession, task, activityDto); | |||
} | |||
protected long updateExecutionFields(CeActivityDto activityDto) { | |||
Long startedAt = activityDto.getStartedAt(); | |||
if (startedAt == null) { |
@@ -43,6 +43,7 @@ import org.sonar.db.component.ComponentDto; | |||
import org.sonar.db.component.ComponentTesting; | |||
import org.sonar.db.user.UserDto; | |||
import org.sonar.db.user.UserTesting; | |||
import org.sonar.server.platform.WebServer; | |||
import static com.google.common.collect.ImmutableList.of; | |||
import static java.util.Arrays.asList; | |||
@@ -52,12 +53,15 @@ import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.assertj.core.api.Assertions.assertThatThrownBy; | |||
import static org.assertj.core.api.Assertions.catchThrowable; | |||
import static org.assertj.core.api.Assertions.tuple; | |||
import static org.mockito.Mockito.mock; | |||
import static org.mockito.Mockito.when; | |||
import static org.sonar.ce.queue.CeQueue.SubmitOption.UNIQUE_QUEUE_PER_MAIN_COMPONENT; | |||
public class CeQueueImplTest { | |||
private static final String WORKER_UUID = "workerUuid"; | |||
private static final long NOW = 1_450_000_000_000L; | |||
private static final String NODE_NAME = "nodeName1"; | |||
private System2 system2 = new TestSystem2().setNow(NOW); | |||
@@ -68,7 +72,9 @@ public class CeQueueImplTest { | |||
private UuidFactory uuidFactory = new SequenceUuidFactory(); | |||
private CeQueue underTest = new CeQueueImpl(system2, db.getDbClient(), uuidFactory); | |||
private WebServer nodeInformationProvider = mock(WebServer.class); | |||
private CeQueue underTest = new CeQueueImpl(system2, db.getDbClient(), uuidFactory, nodeInformationProvider); | |||
@Test | |||
public void submit_returns_task_populated_from_CeTaskSubmit_and_creates_CeQueue_row() { | |||
@@ -382,11 +388,37 @@ public class CeQueueImplTest { | |||
underTest.cancel(db.getSession(), queueDto); | |||
Optional<CeActivityDto> activity = db.getDbClient().ceActivityDao().selectByUuid(db.getSession(), task.getUuid()); | |||
Optional<CeActivityDto> activity = findCeActivityDtoInDb(task); | |||
assertThat(activity).isPresent(); | |||
assertThat(activity.get().getStatus()).isEqualTo(CeActivityDto.Status.CANCELED); | |||
} | |||
@Test | |||
public void cancel_pending_whenNodeNameProvided_setItInCeActivity() { | |||
when(nodeInformationProvider.getNodeName()).thenReturn(Optional.of(NODE_NAME)); | |||
CeTask task = submit(CeTaskTypes.REPORT, newComponent(randomAlphabetic(12))); | |||
CeQueueDto queueDto = db.getDbClient().ceQueueDao().selectByUuid(db.getSession(), task.getUuid()).get(); | |||
underTest.cancel(db.getSession(), queueDto); | |||
Optional<CeActivityDto> activity = findCeActivityDtoInDb(task); | |||
assertThat(activity).isPresent(); | |||
assertThat(activity.get().getNodeName()).isEqualTo(NODE_NAME); | |||
} | |||
@Test | |||
public void cancel_pending_whenNodeNameNOtProvided_setNulInCeActivity() { | |||
when(nodeInformationProvider.getNodeName()).thenReturn(Optional.empty()); | |||
CeTask task = submit(CeTaskTypes.REPORT, newComponent(randomAlphabetic(12))); | |||
CeQueueDto queueDto = db.getDbClient().ceQueueDao().selectByUuid(db.getSession(), task.getUuid()).get(); | |||
underTest.cancel(db.getSession(), queueDto); | |||
Optional<CeActivityDto> activity = findCeActivityDtoInDb(task); | |||
assertThat(activity).isPresent(); | |||
assertThat(activity.get().getNodeName()).isNull(); | |||
} | |||
@Test | |||
public void fail_to_cancel_if_in_progress() { | |||
CeTask task = submit(CeTaskTypes.REPORT, newComponent(randomAlphabetic(11))); | |||
@@ -408,11 +440,11 @@ public class CeQueueImplTest { | |||
int canceledCount = underTest.cancelAll(); | |||
assertThat(canceledCount).isEqualTo(2); | |||
Optional<CeActivityDto> ceActivityInProgress = db.getDbClient().ceActivityDao().selectByUuid(db.getSession(), pendingTask1.getUuid()); | |||
Optional<CeActivityDto> ceActivityInProgress = findCeActivityDtoInDb(pendingTask1); | |||
assertThat(ceActivityInProgress.get().getStatus()).isEqualTo(CeActivityDto.Status.CANCELED); | |||
Optional<CeActivityDto> ceActivityPending1 = db.getDbClient().ceActivityDao().selectByUuid(db.getSession(), pendingTask2.getUuid()); | |||
Optional<CeActivityDto> ceActivityPending1 = findCeActivityDtoInDb(pendingTask2); | |||
assertThat(ceActivityPending1.get().getStatus()).isEqualTo(CeActivityDto.Status.CANCELED); | |||
Optional<CeActivityDto> ceActivityPending2 = db.getDbClient().ceActivityDao().selectByUuid(db.getSession(), inProgressTask.getUuid()); | |||
Optional<CeActivityDto> ceActivityPending2 = findCeActivityDtoInDb(inProgressTask); | |||
assertThat(ceActivityPending2).isNotPresent(); | |||
} | |||
@@ -430,7 +462,7 @@ public class CeQueueImplTest { | |||
@Test | |||
public void pauseWorkers_marks_workers_as_pausing_if_some_tasks_in_progress() { | |||
CeTask task = submit(CeTaskTypes.REPORT, newComponent(randomAlphabetic(12))); | |||
db.getDbClient().ceQueueDao().tryToPeek(session, task.getUuid(), WORKER_UUID); | |||
db.getDbClient().ceQueueDao().tryToPeek(session, task.getUuid(), WORKER_UUID); | |||
// task is in-progress | |||
assertThat(underTest.getWorkersPauseStatus()).isEqualTo(CeQueue.WorkersPauseStatus.RESUMED); | |||
@@ -477,13 +509,31 @@ public class CeQueueImplTest { | |||
underTest.fail(db.getSession(), queueDto, "TIMEOUT", "Failed on timeout"); | |||
Optional<CeActivityDto> activity = db.getDbClient().ceActivityDao().selectByUuid(db.getSession(), task.getUuid()); | |||
Optional<CeActivityDto> activity = findCeActivityDtoInDb(task); | |||
assertThat(activity).isPresent(); | |||
assertThat(activity.get().getStatus()).isEqualTo(CeActivityDto.Status.FAILED); | |||
assertThat(activity.get().getErrorType()).isEqualTo("TIMEOUT"); | |||
assertThat(activity.get().getErrorMessage()).isEqualTo("Failed on timeout"); | |||
assertThat(activity.get().getExecutedAt()).isEqualTo(NOW); | |||
assertThat(activity.get().getWorkerUuid()).isEqualTo(WORKER_UUID); | |||
assertThat(activity.get().getNodeName()).isNull(); | |||
} | |||
@Test | |||
public void fail_in_progress_task_whenNodeNameProvided_setsItInCeActivityDto() { | |||
when(nodeInformationProvider.getNodeName()).thenReturn(Optional.of(NODE_NAME)); | |||
CeTask task = submit(CeTaskTypes.REPORT, newComponent(randomAlphabetic(12))); | |||
CeQueueDto queueDto = db.getDbClient().ceQueueDao().tryToPeek(db.getSession(), task.getUuid(), WORKER_UUID).get(); | |||
underTest.fail(db.getSession(), queueDto, "TIMEOUT", "Failed on timeout"); | |||
Optional<CeActivityDto> activity = findCeActivityDtoInDb(task); | |||
assertThat(activity).isPresent(); | |||
assertThat(activity.get().getNodeName()).isEqualTo(NODE_NAME); | |||
} | |||
private Optional<CeActivityDto> findCeActivityDtoInDb(CeTask task) { | |||
return db.getDbClient().ceActivityDao().selectByUuid(db.getSession(), task.getUuid()); | |||
} | |||
@Test |
@@ -47,6 +47,7 @@ import org.sonar.db.ce.CeQueueDao; | |||
import org.sonar.db.ce.CeQueueDto; | |||
import org.sonar.db.ce.CeTaskCharacteristicDto; | |||
import org.sonar.db.component.ComponentDto; | |||
import org.sonar.server.platform.WebServer; | |||
import static com.google.common.base.Preconditions.checkArgument; | |||
import static java.lang.String.format; | |||
@@ -65,8 +66,8 @@ public class InternalCeQueueImpl extends CeQueueImpl implements InternalCeQueue | |||
private final NextPendingTaskPicker nextPendingTaskPicker; | |||
public InternalCeQueueImpl(System2 system2, DbClient dbClient, UuidFactory uuidFactory, CEQueueStatus queueStatus, | |||
ComputeEngineStatus computeEngineStatus, NextPendingTaskPicker nextPendingTaskPicker) { | |||
super(system2, dbClient, uuidFactory); | |||
ComputeEngineStatus computeEngineStatus, NextPendingTaskPicker nextPendingTaskPicker, WebServer webServer) { | |||
super(system2, dbClient, uuidFactory, webServer); | |||
this.dbClient = dbClient; | |||
this.queueStatus = queueStatus; | |||
this.computeEngineStatus = computeEngineStatus; | |||
@@ -113,6 +114,7 @@ public class InternalCeQueueImpl extends CeQueueImpl implements InternalCeQueue | |||
CeQueueDto queueDto = dbClient.ceQueueDao().selectByUuid(dbSession, task.getUuid()) | |||
.orElseThrow(() -> new IllegalStateException("Task does not exist anymore: " + task)); | |||
CeActivityDto activityDto = new CeActivityDto(queueDto); | |||
activityDto.setNodeName(webServer.getNodeName().orElse(null)); | |||
activityDto.setStatus(status); | |||
executionTimeInMs = updateExecutionFields(activityDto); | |||
updateTaskResult(activityDto, taskResult); | |||
@@ -176,6 +178,7 @@ public class InternalCeQueueImpl extends CeQueueImpl implements InternalCeQueue | |||
List<CeQueueDto> wornOutTasks = dbClient.ceQueueDao().selectWornout(dbSession); | |||
wornOutTasks.forEach(queueDto -> { | |||
CeActivityDto activityDto = new CeActivityDto(queueDto); | |||
activityDto.setNodeName(webServer.getNodeName().orElse(null)); | |||
activityDto.setStatus(CeActivityDto.Status.CANCELED); | |||
updateExecutionFields(activityDto); | |||
remove(dbSession, queueDto, activityDto); |
@@ -48,6 +48,7 @@ import org.sonar.db.ce.CeTaskTypes; | |||
import org.sonar.db.component.ComponentDto; | |||
import org.sonar.db.component.ComponentTesting; | |||
import org.sonar.db.user.UserDto; | |||
import org.sonar.server.platform.WebServer; | |||
import static java.util.Arrays.asList; | |||
import static java.util.Collections.emptyMap; | |||
@@ -68,6 +69,7 @@ public class InternalCeQueueImplTest { | |||
private static final String AN_ANALYSIS_UUID = "U1"; | |||
private static final String WORKER_UUID_1 = "worker uuid 1"; | |||
private static final String WORKER_UUID_2 = "worker uuid 2"; | |||
private static final String NODE_NAME = "nodeName1"; | |||
private System2 system2 = new AlwaysIncreasingSystem2(); | |||
@@ -81,13 +83,15 @@ public class InternalCeQueueImplTest { | |||
private ComputeEngineStatus computeEngineStatus = mock(ComputeEngineStatus.class); | |||
private Configuration config = mock(Configuration.class); | |||
private NextPendingTaskPicker nextPendingTaskPicker = new NextPendingTaskPicker(config, db.getDbClient()); | |||
private WebServer nodeInformationProvider = mock(WebServer.class); | |||
private InternalCeQueue underTest = new InternalCeQueueImpl(system2, db.getDbClient(), uuidFactory, queueStatus, | |||
computeEngineStatus, nextPendingTaskPicker); | |||
computeEngineStatus, nextPendingTaskPicker, nodeInformationProvider); | |||
@Before | |||
public void setUp() { | |||
when(config.getBoolean(any())).thenReturn(Optional.of(false)); | |||
when(computeEngineStatus.getStatus()).thenReturn(STARTED); | |||
when(nodeInformationProvider.getNodeName()).thenReturn(Optional.of(NODE_NAME)); | |||
} | |||
@Test | |||
@@ -210,6 +214,32 @@ public class InternalCeQueueImplTest { | |||
assertThat(history.get().getAnalysisUuid()).isEqualTo("U1"); | |||
} | |||
@Test | |||
public void remove_sets_nodeName_in_CeActivity_when_nodeInformationProvider_defines_node_name() { | |||
when(nodeInformationProvider.getNodeName()).thenReturn(Optional.of(NODE_NAME)); | |||
CeTask task = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1")); | |||
Optional<CeTask> peek = underTest.peek(WORKER_UUID_2, true); | |||
underTest.remove(peek.get(), CeActivityDto.Status.SUCCESS, newTaskResult(AN_ANALYSIS_UUID), null); | |||
Optional<CeActivityDto> history = db.getDbClient().ceActivityDao().selectByUuid(db.getSession(), task.getUuid()); | |||
assertThat(history).isPresent(); | |||
assertThat(history.get().getNodeName()).isEqualTo(NODE_NAME); | |||
} | |||
@Test | |||
public void remove_do_not_set_nodeName_in_CeActivity_when_nodeInformationProvider_does_not_define_node_name() { | |||
when(nodeInformationProvider.getNodeName()).thenReturn(Optional.empty()); | |||
CeTask task = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1")); | |||
Optional<CeTask> peek = underTest.peek(WORKER_UUID_2, true); | |||
underTest.remove(peek.get(), CeActivityDto.Status.SUCCESS, newTaskResult(AN_ANALYSIS_UUID), null); | |||
Optional<CeActivityDto> history = db.getDbClient().ceActivityDao().selectByUuid(db.getSession(), task.getUuid()); | |||
assertThat(history).isPresent(); | |||
assertThat(history.get().getNodeName()).isNull(); | |||
} | |||
@Test | |||
public void remove_saves_error_message_and_stacktrace_when_exception_is_provided() { | |||
Throwable error = new NullPointerException("Fake NPE to test persistence to DB"); | |||
@@ -248,7 +278,7 @@ public class InternalCeQueueImplTest { | |||
db.getDbClient().ceQueueDao().deleteByUuid(db.getSession(), task.getUuid()); | |||
db.commit(); | |||
InternalCeQueueImpl underTest = new InternalCeQueueImpl(system2, db.getDbClient(), null, queueStatus, null, null); | |||
InternalCeQueueImpl underTest = new InternalCeQueueImpl(system2, db.getDbClient(), null, queueStatus, null, null, nodeInformationProvider); | |||
try { | |||
underTest.remove(task, CeActivityDto.Status.SUCCESS, null, null); | |||
@@ -265,7 +295,7 @@ public class InternalCeQueueImplTest { | |||
CeTask task = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1")); | |||
db.getDbClient().ceQueueDao().deleteByUuid(db.getSession(), task.getUuid()); | |||
db.commit(); | |||
InternalCeQueueImpl underTest = new InternalCeQueueImpl(system2, db.getDbClient(), null, queueStatusMock, null, null); | |||
InternalCeQueueImpl underTest = new InternalCeQueueImpl(system2, db.getDbClient(), null, queueStatusMock, null, null, nodeInformationProvider); | |||
try { | |||
underTest.remove(task, CeActivityDto.Status.FAILED, null, null); | |||
@@ -277,16 +307,19 @@ public class InternalCeQueueImplTest { | |||
@Test | |||
public void cancelWornOuts_does_not_update_queueStatus() { | |||
CEQueueStatus queueStatusMock = mock(CEQueueStatus.class); | |||
CeTask task = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1")); | |||
db.executeUpdateSql("update ce_queue set status = 'PENDING', started_at = 123 where uuid = '" + task.getUuid() + "'"); | |||
db.commit(); | |||
InternalCeQueueImpl underTest = new InternalCeQueueImpl(system2, db.getDbClient(), null, queueStatusMock, null, null); | |||
InternalCeQueueImpl underTest = new InternalCeQueueImpl(system2, db.getDbClient(), null, queueStatusMock, null, null, nodeInformationProvider); | |||
underTest.cancelWornOuts(); | |||
assertThat(db.getDbClient().ceActivityDao().selectByUuid(db.getSession(), task.getUuid())).isPresent(); | |||
Optional<CeActivityDto> ceActivityDto = db.getDbClient().ceActivityDao().selectByUuid(db.getSession(), task.getUuid()); | |||
assertThat(ceActivityDto).isPresent(); | |||
assertThat(ceActivityDto.get().getNodeName()).isEqualTo(NODE_NAME); | |||
verifyNoInteractions(queueStatusMock); | |||
} | |||
@@ -30,7 +30,8 @@ import static java.lang.String.format; | |||
public class CeActivityDto { | |||
private static final int MAX_SIZE_ERROR_MESSAGE = 1000; | |||
private static final int ERROR_MESSAGE_MAX_SIZE = 1000; | |||
private static final int NODE_NAME_MAX_SIZE = 100; | |||
public enum Status { | |||
SUCCESS, FAILED, CANCELED | |||
@@ -105,6 +106,8 @@ public class CeActivityDto { | |||
*/ | |||
private int warningCount = 0; | |||
private String nodeName; | |||
CeActivityDto() { | |||
// required for MyBatis | |||
} | |||
@@ -287,7 +290,7 @@ public class CeActivityDto { | |||
} | |||
public CeActivityDto setErrorMessage(@Nullable String errorMessage) { | |||
this.errorMessage = ensureNotTooBig(removeCharZeros(errorMessage), MAX_SIZE_ERROR_MESSAGE); | |||
this.errorMessage = ensureNotTooBig(removeCharZeros(errorMessage), ERROR_MESSAGE_MAX_SIZE); | |||
return this; | |||
} | |||
@@ -331,10 +334,21 @@ public class CeActivityDto { | |||
return this; | |||
} | |||
@CheckForNull | |||
public String getNodeName() { | |||
return nodeName; | |||
} | |||
public CeActivityDto setNodeName(@Nullable String nodeName) { | |||
this.nodeName = ensureNotTooBig(nodeName, NODE_NAME_MAX_SIZE); | |||
return this; | |||
} | |||
@Override | |||
public String toString() { | |||
return "CeActivityDto{" + | |||
"uuid='" + uuid + '\'' + | |||
", nodeName='" + nodeName + '\'' + | |||
", componentUuid='" + componentUuid + '\'' + | |||
", mainComponentUuid='" + mainComponentUuid + '\'' + | |||
", analysisUuid='" + analysisUuid + '\'' + |
@@ -21,6 +21,7 @@ | |||
<sql id="ceActivityColumns"> | |||
ca.uuid, | |||
ca.node_name as nodeName, | |||
ca.task_type as taskType, | |||
ca.component_uuid as componentUuid, | |||
ca.main_component_uuid as mainComponentUuid, | |||
@@ -175,6 +176,7 @@ | |||
<insert id="insert" parameterType="org.sonar.db.ce.CeActivityDto" useGeneratedKeys="false"> | |||
insert into ce_activity ( | |||
uuid, | |||
node_name, | |||
component_uuid, | |||
main_component_uuid, | |||
analysis_uuid, | |||
@@ -199,6 +201,7 @@ | |||
) | |||
values ( | |||
#{uuid,jdbcType=VARCHAR}, | |||
#{nodeName,jdbcType=VARCHAR}, | |||
#{componentUuid,jdbcType=VARCHAR}, | |||
#{mainComponentUuid,jdbcType=VARCHAR}, | |||
#{analysisUuid,jdbcType=VARCHAR}, |
@@ -136,7 +136,8 @@ CREATE TABLE "CE_ACTIVITY"( | |||
"ERROR_TYPE" CHARACTER VARYING(20), | |||
"WORKER_UUID" CHARACTER VARYING(40), | |||
"CREATED_AT" BIGINT NOT NULL, | |||
"UPDATED_AT" BIGINT NOT NULL | |||
"UPDATED_AT" BIGINT NOT NULL, | |||
"NODE_NAME" CHARACTER VARYING(100) | |||
); | |||
ALTER TABLE "CE_ACTIVITY" ADD CONSTRAINT "PK_CE_ACTIVITY" PRIMARY KEY("UUID"); | |||
CREATE INDEX "CE_ACTIVITY_COMPONENT" ON "CE_ACTIVITY"("COMPONENT_UUID" NULLS FIRST); |
@@ -71,6 +71,7 @@ public class CeActivityDaoTest { | |||
private static final String COMPONENT_1 = randomAlphabetic(14); | |||
private static final long INITIAL_TIME = 1_450_000_000_000L; | |||
private static final String NODE_NAME = "node1"; | |||
private final TestSystem2 system2 = new TestSystem2().setNow(INITIAL_TIME); | |||
@@ -93,6 +94,7 @@ public class CeActivityDaoTest { | |||
assertThat(saved).isPresent(); | |||
CeActivityDto dto = saved.get(); | |||
assertThat(dto.getUuid()).isEqualTo("TASK_1"); | |||
assertThat(dto.getNodeName()).isEqualTo(NODE_NAME); | |||
assertThat(dto.getMainComponentUuid()).isEqualTo(MAINCOMPONENT_1); | |||
assertThat(dto.getComponentUuid()).isEqualTo(COMPONENT_1); | |||
assertThat(dto.getStatus()).isEqualTo(SUCCESS); | |||
@@ -858,6 +860,7 @@ public class CeActivityDaoTest { | |||
CeQueueDto ceQueueDto = db.getDbClient().ceQueueDao().selectByUuid(dbSession, uuid).get(); | |||
CeActivityDto dto = new CeActivityDto(ceQueueDto); | |||
dto.setNodeName(NODE_NAME); | |||
dto.setStatus(status); | |||
dto.setStartedAt(1_500_000_000_000L); | |||
dto.setExecutedAt(1_500_000_000_500L); |
@@ -26,6 +26,7 @@ import java.util.Random; | |||
import org.junit.Test; | |||
import org.junit.runner.RunWith; | |||
import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic; | |||
import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.assertj.core.api.Assertions.assertThatThrownBy; | |||
@@ -33,6 +34,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; | |||
@RunWith(DataProviderRunner.class) | |||
public class CeActivityDtoTest { | |||
private static final String STR_40_CHARS = "0123456789012345678901234567890123456789"; | |||
private static final String STR_100_CHARS = randomAlphabetic(100); | |||
private CeActivityDto underTest = new CeActivityDto(); | |||
@Test | |||
@@ -99,6 +101,21 @@ public class CeActivityDtoTest { | |||
.hasMessage("Value is too long for column CE_ACTIVITY.MAIN_COMPONENT_UUID: " + str_41_chars); | |||
} | |||
@Test | |||
public void setNodeName_accepts_null_empty_and_string_100_chars_or_less() { | |||
underTest.setNodeName(null); | |||
underTest.setNodeName(""); | |||
underTest.setNodeName("bar"); | |||
underTest.setNodeName(STR_100_CHARS); | |||
assertThat(underTest.getNodeName()).isEqualTo(STR_100_CHARS); | |||
} | |||
@Test | |||
public void setNodeName_ifMoreThan100chars_truncates() { | |||
underTest.setNodeName(STR_100_CHARS + "This should be truncated"); | |||
assertThat(underTest.getNodeName()).isEqualTo(STR_100_CHARS); | |||
} | |||
@Test | |||
@UseDataProvider("stringsWithChar0") | |||
public void setStacktrace_filters_out_char_zero(String withChar0, String expected) { |
@@ -37,6 +37,7 @@ import org.sonar.server.platform.db.migration.version.v95.DbVersion95; | |||
import org.sonar.server.platform.db.migration.version.v96.DbVersion96; | |||
import org.sonar.server.platform.db.migration.version.v97.DbVersion97; | |||
import org.sonar.server.platform.db.migration.version.v98.DbVersion98; | |||
import org.sonar.server.platform.db.migration.version.v99.DbVersion99; | |||
public class MigrationConfigurationModule extends Module { | |||
@Override | |||
@@ -54,6 +55,7 @@ public class MigrationConfigurationModule extends Module { | |||
DbVersion96.class, | |||
DbVersion97.class, | |||
DbVersion98.class, | |||
DbVersion99.class, | |||
// migration steps | |||
MigrationStepRegistryImpl.class, |
@@ -0,0 +1,51 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2022 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.db.migration.version.v99; | |||
import com.google.common.annotations.VisibleForTesting; | |||
import java.sql.Connection; | |||
import java.sql.SQLException; | |||
import org.sonar.db.Database; | |||
import org.sonar.db.DatabaseUtils; | |||
import org.sonar.server.platform.db.migration.def.VarcharColumnDef; | |||
import org.sonar.server.platform.db.migration.sql.AddColumnsBuilder; | |||
import org.sonar.server.platform.db.migration.step.DdlChange; | |||
class AddNodeNameColumnToCeActivityTable extends DdlChange { | |||
@VisibleForTesting | |||
static final String TABLE_NAME = "ce_activity"; | |||
@VisibleForTesting | |||
static final String COLUMN_NAME = "node_name"; | |||
public AddNodeNameColumnToCeActivityTable(Database db) { | |||
super(db); | |||
} | |||
@Override | |||
public void execute(Context context) throws SQLException { | |||
try (Connection c = getDatabase().getDataSource().getConnection()) { | |||
if (!DatabaseUtils.tableColumnExists(c, TABLE_NAME, COLUMN_NAME)) { | |||
context.execute(new AddColumnsBuilder(getDialect(), TABLE_NAME) | |||
.addColumn(VarcharColumnDef.newVarcharColumnDefBuilder(COLUMN_NAME).setLimit(100).setIsNullable(true).build()) | |||
.build()); | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,31 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2022 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.db.migration.version.v99; | |||
import org.sonar.server.platform.db.migration.step.MigrationStepRegistry; | |||
import org.sonar.server.platform.db.migration.version.DbVersion; | |||
public class DbVersion99 implements DbVersion { | |||
@Override | |||
public void addSteps(MigrationStepRegistry registry) { | |||
registry | |||
.add(6800, "Add node_name column to ce_activity table", AddNodeNameColumnToCeActivityTable.class); | |||
} | |||
} |
@@ -0,0 +1,53 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2022 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.db.migration.version.v99; | |||
import java.sql.SQLException; | |||
import java.sql.Types; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.sonar.db.CoreDbTester; | |||
import static org.sonar.server.platform.db.migration.version.v99.AddNodeNameColumnToCeActivityTable.COLUMN_NAME; | |||
import static org.sonar.server.platform.db.migration.version.v99.AddNodeNameColumnToCeActivityTable.TABLE_NAME; | |||
public class AddNodeNameColumnToCeActivityTableTest { | |||
@Rule | |||
public final CoreDbTester db = CoreDbTester.createForSchema(AddNodeNameColumnToCeActivityTableTest.class, "schema.sql"); | |||
private final AddNodeNameColumnToCeActivityTable underTest = new AddNodeNameColumnToCeActivityTable(db.database()); | |||
@Test | |||
public void migration_should_add_column() throws SQLException { | |||
db.assertColumnDoesNotExist(TABLE_NAME, COLUMN_NAME); | |||
underTest.execute(); | |||
db.assertColumnDefinition(TABLE_NAME, COLUMN_NAME, Types.VARCHAR, null, true); | |||
} | |||
@Test | |||
public void migration_should_be_reentrant() throws SQLException { | |||
db.assertColumnDoesNotExist(TABLE_NAME, COLUMN_NAME); | |||
underTest.execute(); | |||
// re-entrant | |||
underTest.execute(); | |||
db.assertColumnDefinition(TABLE_NAME, COLUMN_NAME, Types.VARCHAR, null, true); | |||
} | |||
} |
@@ -0,0 +1,40 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2022 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.db.migration.version.v99; | |||
import org.junit.Test; | |||
import static org.sonar.server.platform.db.migration.version.DbVersionTestUtils.verifyMigrationNotEmpty; | |||
import static org.sonar.server.platform.db.migration.version.DbVersionTestUtils.verifyMinimumMigrationNumber; | |||
public class DbVersion99Test { | |||
private final DbVersion99 underTest = new DbVersion99(); | |||
@Test | |||
public void migrationNumber_starts_at_6800() { | |||
verifyMinimumMigrationNumber(underTest, 6800); | |||
} | |||
@Test | |||
public void verify_migration_is_not_empty() { | |||
verifyMigrationNotEmpty(underTest); | |||
} | |||
} |
@@ -0,0 +1,25 @@ | |||
CREATE TABLE "CE_ACTIVITY"( | |||
"UUID" CHARACTER VARYING(40) NOT NULL, | |||
"TASK_TYPE" CHARACTER VARYING(15) NOT NULL, | |||
"MAIN_COMPONENT_UUID" CHARACTER VARYING(40), | |||
"COMPONENT_UUID" CHARACTER VARYING(40), | |||
"STATUS" CHARACTER VARYING(15) NOT NULL, | |||
"MAIN_IS_LAST" BOOLEAN NOT NULL, | |||
"MAIN_IS_LAST_KEY" CHARACTER VARYING(55) NOT NULL, | |||
"IS_LAST" BOOLEAN NOT NULL, | |||
"IS_LAST_KEY" CHARACTER VARYING(55) NOT NULL, | |||
"SUBMITTER_UUID" CHARACTER VARYING(255), | |||
"SUBMITTED_AT" BIGINT NOT NULL, | |||
"STARTED_AT" BIGINT, | |||
"EXECUTED_AT" BIGINT, | |||
"EXECUTION_COUNT" INTEGER NOT NULL, | |||
"EXECUTION_TIME_MS" BIGINT, | |||
"ANALYSIS_UUID" CHARACTER VARYING(50), | |||
"ERROR_MESSAGE" CHARACTER VARYING(1000), | |||
"ERROR_STACKTRACE" CHARACTER LARGE OBJECT, | |||
"ERROR_TYPE" CHARACTER VARYING(20), | |||
"WORKER_UUID" CHARACTER VARYING(40), | |||
"CREATED_AT" BIGINT NOT NULL, | |||
"UPDATED_AT" BIGINT NOT NULL | |||
); |
@@ -19,6 +19,7 @@ | |||
*/ | |||
package org.sonar.server.platform; | |||
import java.util.Optional; | |||
import org.sonar.api.ce.ComputeEngineSide; | |||
import org.sonar.api.server.ServerSide; | |||
@@ -27,7 +28,7 @@ import org.sonar.api.server.ServerSide; | |||
public interface WebServer { | |||
/** | |||
* WebServer is standalone when property {@link org.sonar.process.ProcessProperties.Property#CLUSTER_ENABLED} is {@code false} or | |||
* Node is standalone when property {@link org.sonar.process.ProcessProperties.Property#CLUSTER_ENABLED} is {@code false} or | |||
* undefined. | |||
*/ | |||
boolean isStandalone(); | |||
@@ -40,4 +41,6 @@ public interface WebServer { | |||
*/ | |||
boolean isStartupLeader(); | |||
Optional<String> getNodeName(); | |||
} |
@@ -19,24 +19,29 @@ | |||
*/ | |||
package org.sonar.server.platform; | |||
import java.util.Optional; | |||
import org.sonar.api.config.Configuration; | |||
import org.sonar.api.utils.log.Loggers; | |||
import static org.sonar.process.ProcessProperties.Property.CLUSTER_ENABLED; | |||
import static org.sonar.process.ProcessProperties.Property.CLUSTER_NODE_NAME; | |||
import static org.sonar.process.ProcessProperties.Property.CLUSTER_WEB_STARTUP_LEADER; | |||
public class WebServerImpl implements WebServer { | |||
private final boolean clusterEnabled; | |||
private final boolean startupLeader; | |||
private final String nodeName; | |||
public WebServerImpl(Configuration config) { | |||
this.clusterEnabled = config.getBoolean(CLUSTER_ENABLED.getKey()).orElse(false); | |||
if (this.clusterEnabled) { | |||
this.startupLeader = config.getBoolean(CLUSTER_WEB_STARTUP_LEADER.getKey()).orElse(false); | |||
this.nodeName = config.get(CLUSTER_NODE_NAME.getKey()).orElse(CLUSTER_NODE_NAME.getDefaultValue()); | |||
Loggers.get(WebServerImpl.class).info("Cluster enabled (startup {})", startupLeader ? "leader" : "follower"); | |||
} else { | |||
this.startupLeader = true; | |||
this.nodeName = null; | |||
} | |||
} | |||
@@ -49,4 +54,9 @@ public class WebServerImpl implements WebServer { | |||
public boolean isStartupLeader() { | |||
return startupLeader; | |||
} | |||
@Override | |||
public Optional<String> getNodeName() { | |||
return Optional.ofNullable(nodeName); | |||
} | |||
} |
@@ -69,4 +69,40 @@ public class WebServerImplTest { | |||
assertThat(underTest.isStartupLeader()).isFalse(); | |||
} | |||
@Test | |||
public void getNodeName_whenNotACluster_isEmpty() { | |||
settings.setProperty("sonar.cluster.enabled", "false"); | |||
settings.setProperty("sonar.cluster.node.name", "nameIgnored"); | |||
WebServerImpl underTest = new WebServerImpl(settings.asConfig()); | |||
assertThat(underTest.getNodeName()).isEmpty(); | |||
} | |||
@Test | |||
public void getNodeName_whenClusterAndNameNotDefined_fallbacksToDefaultName() { | |||
settings.setProperty("sonar.cluster.enabled", "true"); | |||
settings.removeProperty("sonar.cluster.node.name"); | |||
WebServerImpl underTest = new WebServerImpl(settings.asConfig()); | |||
assertThat(underTest.getNodeName()).isNotEmpty(); | |||
String nodeNameFirstCallToGetNodeName = underTest.getNodeName().get(); | |||
assertThat(nodeNameFirstCallToGetNodeName).startsWith("sonarqube-"); | |||
String nodeNameSecondCallToGetNodeName = underTest.getNodeName().get(); | |||
assertThat(nodeNameFirstCallToGetNodeName).isEqualTo(nodeNameSecondCallToGetNodeName); | |||
} | |||
@Test | |||
public void getNodeName_whenClusterAndNameDefined_returnName() { | |||
String nodeName = "nodeName1"; | |||
settings.setProperty("sonar.cluster.enabled", "true"); | |||
settings.setProperty("sonar.cluster.node.name", nodeName); | |||
WebServerImpl underTest = new WebServerImpl(settings.asConfig()); | |||
assertThat(underTest.getNodeName()).isNotEmpty(); | |||
assertThat(underTest.getNodeName().get()).startsWith(nodeName); | |||
} | |||
} |
@@ -47,7 +47,8 @@ public class ServerIdManager implements Startable { | |||
private final SonarRuntime runtime; | |||
private final WebServer webServer; | |||
public ServerIdManager(ServerIdChecksum serverIdChecksum, ServerIdFactory serverIdFactory, DbClient dbClient, SonarRuntime runtime, WebServer webServer) { | |||
public ServerIdManager(ServerIdChecksum serverIdChecksum, ServerIdFactory serverIdFactory, | |||
DbClient dbClient, SonarRuntime runtime, WebServer webServer) { | |||
this.serverIdChecksum = serverIdChecksum; | |||
this.serverIdFactory = serverIdFactory; | |||
this.dbClient = dbClient; |
@@ -107,6 +107,7 @@ public class TaskFormatter { | |||
builder.setId(dto.getUuid()); | |||
builder.setStatus(Ce.TaskStatus.valueOf(dto.getStatus().name())); | |||
builder.setType(dto.getTaskType()); | |||
ofNullable(dto.getNodeName()).ifPresent(builder::setNodeName); | |||
ofNullable(dto.getComponentUuid()).ifPresent(uuid -> setComponent(builder, uuid, cache).setComponentId(uuid)); | |||
String analysisUuid = dto.getAnalysisUuid(); | |||
ofNullable(analysisUuid).ifPresent(builder::setAnalysisId); |
@@ -29,10 +29,12 @@ import org.sonar.db.DbClient; | |||
import org.sonar.db.DbTester; | |||
import org.sonar.db.ce.CeQueueDto; | |||
import org.sonar.db.component.ComponentDto; | |||
import org.sonar.server.platform.WebServer; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.assertj.core.api.Assertions.assertThatThrownBy; | |||
import static org.assertj.core.api.Assertions.tuple; | |||
import static org.mockito.Mockito.mock; | |||
public class ExportSubmitterImplTest { | |||
@@ -43,7 +45,7 @@ public class ExportSubmitterImplTest { | |||
public DbTester db = DbTester.create(system2); | |||
private final DbClient dbClient = db.getDbClient(); | |||
private final CeQueue ceQueue = new CeQueueImpl(system2, db.getDbClient(), UuidFactoryFast.getInstance()); | |||
private final CeQueue ceQueue = new CeQueueImpl(system2, db.getDbClient(), UuidFactoryFast.getInstance(), mock(WebServer.class)); | |||
private final ExportSubmitterImpl underTest = new ExportSubmitterImpl(ceQueue, dbClient); | |||
@@ -84,6 +84,7 @@ import static org.sonar.server.ce.ws.CeWsParameters.PARAM_TYPE; | |||
public class ActivityActionTest { | |||
private static final long EXECUTED_AT = System2.INSTANCE.now(); | |||
private static final String NODE_NAME = "nodeName1"; | |||
@Rule | |||
public UserSessionRule userSession = UserSessionRule.standalone(); | |||
@@ -111,6 +112,7 @@ public class ActivityActionTest { | |||
Task task = activityResponse.getTasks(0); | |||
assertThat(task.getId()).isEqualTo("T2"); | |||
assertThat(task.getStatus()).isEqualTo(Ce.TaskStatus.FAILED); | |||
assertThat(task.getNodeName()).isEqualTo(NODE_NAME); | |||
assertThat(task.getComponentId()).isEqualTo(project2.uuid()); | |||
assertThat(task.hasAnalysisId()).isFalse(); | |||
assertThat(task.getExecutionTimeMs()).isEqualTo(500L); | |||
@@ -118,6 +120,7 @@ public class ActivityActionTest { | |||
task = activityResponse.getTasks(1); | |||
assertThat(task.getId()).isEqualTo("T1"); | |||
assertThat(task.getNodeName()).isEqualTo(NODE_NAME); | |||
assertThat(task.getStatus()).isEqualTo(Ce.TaskStatus.SUCCESS); | |||
assertThat(task.getComponentId()).isEqualTo(project1.uuid()); | |||
assertThat(task.getWarningCount()).isZero(); | |||
@@ -678,6 +681,7 @@ public class ActivityActionTest { | |||
activityDto.setStatus(status); | |||
activityDto.setExecutionTimeMs(500L); | |||
activityDto.setExecutedAt(EXECUTED_AT); | |||
activityDto.setNodeName(NODE_NAME); | |||
activityDto.setAnalysisUuid(analysis == null ? null : analysis.getUuid()); | |||
db.getDbClient().ceActivityDao().insert(db.getSession(), activityDto); | |||
db.commit(); |
@@ -36,12 +36,14 @@ import org.sonar.db.ce.CeQueueDto; | |||
import org.sonar.db.ce.CeTaskTypes; | |||
import org.sonar.db.component.ComponentDto; | |||
import org.sonar.server.exceptions.ForbiddenException; | |||
import org.sonar.server.platform.WebServer; | |||
import org.sonar.server.tester.UserSessionRule; | |||
import org.sonar.server.ws.WsActionTester; | |||
import static java.util.Collections.emptyMap; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.assertj.core.api.Assertions.assertThatThrownBy; | |||
import static org.mockito.Mockito.mock; | |||
public class CancelActionTest { | |||
@@ -51,7 +53,7 @@ public class CancelActionTest { | |||
public DbTester db = DbTester.create(); | |||
private System2 system2 = new TestSystem2(); | |||
private CeQueue queue = new CeQueueImpl(system2, db.getDbClient(), UuidFactoryFast.getInstance()); | |||
private CeQueue queue = new CeQueueImpl(system2, db.getDbClient(), UuidFactoryFast.getInstance(), mock(WebServer.class)); | |||
private CancelAction underTest = new CancelAction(userSession, db.getDbClient(), queue); | |||
private WsActionTester tester = new WsActionTester(underTest); |
@@ -49,6 +49,7 @@ import static org.sonar.db.ce.CeQueueTesting.makeInProgress; | |||
public class TaskFormatterTest { | |||
private static final String NODE_NAME = "nodeName1"; | |||
@Rule | |||
public DbTester db = DbTester.create(System2.INSTANCE); | |||
@@ -187,6 +188,7 @@ public class TaskFormatterTest { | |||
assertThat(wsTask.getType()).isEqualTo(CeTaskTypes.REPORT); | |||
assertThat(wsTask.getId()).isEqualTo("UUID"); | |||
assertThat(wsTask.getNodeName()).isEqualTo(NODE_NAME); | |||
assertThat(wsTask.getStatus()).isEqualTo(Ce.TaskStatus.FAILED); | |||
assertThat(wsTask.getSubmittedAt()).isEqualTo(DateUtils.formatDateTime(new Date(1_450_000_000_000L))); | |||
assertThat(wsTask.getSubmitterLogin()).isEqualTo(user.getLogin()); | |||
@@ -282,6 +284,7 @@ public class TaskFormatterTest { | |||
testActivityDto.setWarningCount(warningCount); | |||
return testActivityDto | |||
.setStatus(status) | |||
.setNodeName(NODE_NAME) | |||
.setExecutionTimeMs(500L) | |||
.setAnalysisUuid("U1"); | |||
} |
@@ -134,6 +134,7 @@ message Task { | |||
optional string pullRequestTitle = 25; | |||
optional int32 warningCount = 26; | |||
repeated string warnings = 27; | |||
optional string nodeName = 28; | |||
} | |||
enum TaskStatus { |