"STATUS" VARCHAR(15) NOT NULL,
"SUBMITTER_LOGIN" VARCHAR(255) NULL,
"WORKER_UUID" VARCHAR(40) NULL,
- "EXECUTION_COUNT" INTEGER NULL,
+ "EXECUTION_COUNT" INTEGER NOT NULL,
"STARTED_AT" BIGINT NULL,
"CREATED_AT" BIGINT NOT NULL,
"UPDATED_AT" BIGINT NOT NULL
public CeQueueDto insert(DbSession session, CeQueueDto dto) {
if (dto.getCreatedAt() == 0L || dto.getUpdatedAt() == 0L) {
- dto.setCreatedAt(system2.now());
- dto.setUpdatedAt(system2.now());
+ long now = system2.now();
+ dto.setCreatedAt(now);
+ dto.setUpdatedAt(now);
}
mapper(session).insert(dto);
*/
package org.sonar.db.ce;
-import com.google.common.base.MoreObjects;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
private String componentUuid;
private Status status;
private String submitterLogin;
+ /**
+ * UUID of the worker that is executing, or of the last worker that executed, the current task.
+ */
+ private String workerUuid;
+ /**
+ * This counter is incremented by 1 each time the tasks switches to status {@link Status#IN_PROGRESS IN_PROGRESS}.
+ */
+ private int executionCount = 0;
private Long startedAt;
private long createdAt;
private long updatedAt;
return this;
}
+ public String getWorkerUuid() {
+ return workerUuid;
+ }
+
+ public CeQueueDto setWorkerUuid(@Nullable String workerUuid) {
+ checkArgument(workerUuid == null || workerUuid.length() <= 40, "worker uuid is too long: %s", workerUuid);
+ this.workerUuid = workerUuid;
+ return this;
+ }
+
+ public int getExecutionCount() {
+ return executionCount;
+ }
+
+ public CeQueueDto setExecutionCount(int executionCount) {
+ checkArgument(executionCount >= 0, "execution count can't be < 0");
+ this.executionCount = executionCount;
+ return this;
+ }
+
@CheckForNull
public Long getStartedAt() {
return startedAt;
@Override
public String toString() {
- return MoreObjects.toStringHelper(this)
- .add("uuid", uuid)
- .add("taskType", taskType)
- .add("componentUuid", componentUuid)
- .add("status", status)
- .add("submitterLogin", submitterLogin)
- .add("startedAt", startedAt)
- .add("createdAt", createdAt)
- .add("updatedAt", updatedAt)
- .toString();
+ return "CeQueueDto{" +
+ "uuid='" + uuid + '\'' +
+ ", taskType='" + taskType + '\'' +
+ ", componentUuid='" + componentUuid + '\'' +
+ ", status=" + status +
+ ", submitterLogin='" + submitterLogin + '\'' +
+ ", workerUuid='" + workerUuid + '\'' +
+ ", executionCount=" + executionCount +
+ ", startedAt=" + startedAt +
+ ", createdAt=" + createdAt +
+ ", updatedAt=" + updatedAt +
+ '}';
}
@Override
cq.component_uuid as componentUuid,
cq.status as status,
cq.submitter_login as submitterLogin,
+ cq.worker_uuid as workerUuid,
+ cq.execution_count as executionCount,
cq.started_at as startedAt,
cq.created_at as createdAt,
cq.updated_at as updatedAt
component_uuid,
status,
submitter_login,
+ worker_uuid,
+ execution_count,
started_at,
created_at,
updated_at
#{componentUuid,jdbcType=VARCHAR},
#{status,jdbcType=VARCHAR},
#{submitterLogin,jdbcType=VARCHAR},
+ #{workerUuid,jdbcType=VARCHAR},
+ #{executionCount,jdbcType=INTEGER},
#{startedAt,jdbcType=BIGINT},
#{createdAt,jdbcType=BIGINT},
#{updatedAt,jdbcType=BIGINT}
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.stream.Stream;
import javax.annotation.Nullable;
import org.junit.Rule;
import org.junit.Test;
+import org.mockito.Mockito;
+import org.sonar.api.utils.System2;
import org.sonar.api.utils.internal.TestSystem2;
import org.sonar.db.DbTester;
import static com.google.common.collect.Lists.newArrayList;
import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
import static org.sonar.db.ce.CeQueueDto.Status.IN_PROGRESS;
import static org.sonar.db.ce.CeQueueDto.Status.PENDING;
import static org.sonar.db.ce.CeQueueTesting.newCeQueueDto;
private static final String TASK_UUID_2 = "TASK_2";
private static final String COMPONENT_UUID_1 = "PROJECT_1";
private static final String COMPONENT_UUID_2 = "PROJECT_2";
- public static final String TASK_UUID_3 = "TASK_3";
+ private static final String TASK_UUID_3 = "TASK_3";
+ private static final String SELECT_QUEUE_UUID_AND_STATUS_QUERY = "select uuid,status from ce_queue";
+ private static final String SUBMITTER_LOGIN = "henri";
+ private static final String WORKER_UUID = "worker uuid";
+ private static final int EXECUTION_COUNT = 42;
private TestSystem2 system2 = new TestSystem2().setNow(INIT_TIME);
public DbTester db = DbTester.create(system2);
private CeQueueDao underTest = new CeQueueDao(system2);
- private static final String SELECT_QUEUE_UUID_AND_STATUS_QUERY = "select uuid,status from ce_queue";
@Test
- public void test_insert() {
- insert(TASK_UUID_1, COMPONENT_UUID_1, PENDING);
+ public void insert_populates_createdAt_and_updateAt_from_System2_with_same_value_if_any_is_not_set() {
+ System2 system2 = mock(System2.class);
+ CeQueueDao ceQueueDao = new CeQueueDao(system2);
+ long now = 1_334_333L;
+ CeQueueDto dto = new CeQueueDto()
+ .setTaskType(CeTaskTypes.REPORT)
+ .setComponentUuid(COMPONENT_UUID_1)
+ .setStatus(PENDING)
+ .setSubmitterLogin(SUBMITTER_LOGIN)
+ .setWorkerUuid(WORKER_UUID)
+ .setExecutionCount(EXECUTION_COUNT);
+
+ mockSystem2ForSingleCall(system2, now);
+ ceQueueDao.insert(db.getSession(), dto.setUuid(TASK_UUID_1));
+ mockSystem2ForSingleCall(system2, now);
+ ceQueueDao.insert(db.getSession(), dto.setUuid(TASK_UUID_2).setCreatedAt(8_000_999L).setUpdatedAt(0));
+ mockSystem2ForSingleCall(system2, now);
+ ceQueueDao.insert(db.getSession(), dto.setUuid(TASK_UUID_3).setCreatedAt(0).setUpdatedAt(8_000_999L));
+ mockSystem2ForSingleCall(system2, now);
+ String uuid4 = "uuid 4";
+ ceQueueDao.insert(db.getSession(), dto.setUuid(uuid4).setCreatedAt(6_888_777L).setUpdatedAt(8_000_999L));
+ db.getSession().commit();
+
+ Stream.of(TASK_UUID_1, TASK_UUID_2, TASK_UUID_3)
+ .forEach(uuid -> {
+ CeQueueDto saved = underTest.selectByUuid(db.getSession(), uuid).get();
+ assertThat(saved.getUuid()).isEqualTo(uuid);
+ assertThat(saved.getTaskType()).isEqualTo(CeTaskTypes.REPORT);
+ assertThat(saved.getComponentUuid()).isEqualTo(COMPONENT_UUID_1);
+ assertThat(saved.getStatus()).isEqualTo(PENDING);
+ assertThat(saved.getSubmitterLogin()).isEqualTo(SUBMITTER_LOGIN);
+ assertThat(saved.getWorkerUuid()).isEqualTo(WORKER_UUID);
+ assertThat(saved.getExecutionCount()).isEqualTo(EXECUTION_COUNT);
+ assertThat(saved.getCreatedAt()).isEqualTo(now);
+ assertThat(saved.getUpdatedAt()).isEqualTo(now);
+ assertThat(saved.getStartedAt()).isNull();
+ });
+ CeQueueDto saved = underTest.selectByUuid(db.getSession(), uuid4).get();
+ assertThat(saved.getUuid()).isEqualTo(uuid4);
+ assertThat(saved.getTaskType()).isEqualTo(CeTaskTypes.REPORT);
+ assertThat(saved.getComponentUuid()).isEqualTo(COMPONENT_UUID_1);
+ assertThat(saved.getStatus()).isEqualTo(PENDING);
+ assertThat(saved.getSubmitterLogin()).isEqualTo(SUBMITTER_LOGIN);
+ assertThat(saved.getWorkerUuid()).isEqualTo(WORKER_UUID);
+ assertThat(saved.getExecutionCount()).isEqualTo(EXECUTION_COUNT);
+ assertThat(saved.getCreatedAt()).isEqualTo(6_888_777L);
+ assertThat(saved.getUpdatedAt()).isEqualTo(8_000_999L);
+ assertThat(saved.getStartedAt()).isNull();
+ }
- Optional<CeQueueDto> saved = underTest.selectByUuid(db.getSession(), TASK_UUID_1);
- assertThat(saved.isPresent()).isTrue();
+ private void mockSystem2ForSingleCall(System2 system2, long now) {
+ Mockito.reset(system2);
+ when(system2.now()).thenReturn(now).thenThrow(new IllegalStateException("now should be called only once"));
}
@Test
assertThat(saved.getComponentUuid()).isEqualTo(COMPONENT_UUID_1);
assertThat(saved.getStatus()).isEqualTo(PENDING);
assertThat(saved.getSubmitterLogin()).isEqualTo("henri");
+ assertThat(saved.getWorkerUuid()).isNull();
+ assertThat(saved.getExecutionCount()).isEqualTo(0);
assertThat(saved.getCreatedAt()).isEqualTo(INIT_TIME);
assertThat(saved.getUpdatedAt()).isEqualTo(INIT_TIME);
assertThat(saved.getStartedAt()).isNull();
}
private static Map<String, Object> rowMap(String uuid, CeQueueDto.Status status) {
- return ImmutableMap.<String, Object>of("UUID", uuid, "STATUS", status.name());
+ return ImmutableMap.of("UUID", uuid, "STATUS", status.name());
}
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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.db.ce;
+
+import java.util.Random;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+public class CeQueueDtoTest {
+ private static final String STR_15_CHARS = "012345678901234";
+ private static final String STR_40_CHARS = "0123456789012345678901234567890123456789";
+ private static final String STR_255_CHARS = STR_40_CHARS + STR_40_CHARS + STR_40_CHARS + STR_40_CHARS
+ + STR_40_CHARS + STR_40_CHARS + STR_15_CHARS;
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private CeQueueDto underTest = new CeQueueDto();
+
+ @Test
+ public void setComponentUuid_accepts_null_empty_and_string_40_chars_or_less() {
+ underTest.setComponentUuid(null);
+ underTest.setComponentUuid("");
+ underTest.setComponentUuid("bar");
+ underTest.setComponentUuid(STR_40_CHARS);
+ }
+
+ @Test
+ public void setComponentUuid_throws_IAE_if_value_is_41_chars() {
+ String str_41_chars = STR_40_CHARS + "a";
+
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Value of component UUID is too long: " + str_41_chars);
+
+ underTest.setComponentUuid(str_41_chars);
+ }
+
+ @Test
+ public void setTaskType_throws_NPE_if_argument_is_null() {
+ expectedException.expect(NullPointerException.class);
+
+ underTest.setTaskType(null);
+ }
+
+ @Test
+ public void setTaskType_accepts_empty_and_string_15_chars_or_less() {
+ underTest.setTaskType("");
+ underTest.setTaskType("bar");
+ underTest.setTaskType(STR_15_CHARS);
+ }
+
+ @Test
+ public void setTaskType_throws_IAE_if_value_is_41_chars() {
+ String str_16_chars = STR_15_CHARS + "a";
+
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Value of task type is too long: " + str_16_chars);
+
+ underTest.setTaskType(str_16_chars);
+ }
+
+ @Test
+ public void setSubmitterLogin_accepts_null_empty_and_string_255_chars_or_less() {
+ underTest.setSubmitterLogin(null);
+ underTest.setSubmitterLogin("");
+ underTest.setSubmitterLogin("bar");
+ underTest.setSubmitterLogin(STR_255_CHARS);
+ }
+
+ @Test
+ public void setSubmitterLogin_throws_IAE_if_value_is_41_chars() {
+ String str_256_chars = STR_255_CHARS + "a";
+
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Value of submitter login is too long: " + str_256_chars);
+
+ underTest.setSubmitterLogin(str_256_chars);
+ }
+
+ @Test
+ public void setWorkerUuid_accepts_null_empty_and_string_40_chars_or_less() {
+ underTest.setWorkerUuid(null);
+ underTest.setWorkerUuid("");
+ underTest.setWorkerUuid("bar");
+ underTest.setWorkerUuid(STR_40_CHARS);
+ }
+
+ @Test
+ public void setWorkerUuid_throws_IAE_if_value_is_41_chars() {
+ String str_41_chars = STR_40_CHARS + "a";
+
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("worker uuid is too long: " + str_41_chars);
+
+ underTest.setWorkerUuid(str_41_chars);
+ }
+
+ @Test
+ public void setExecutionCount_throws_IAE_if_value_is_less_than_0() {
+ int lessThanZero = -1-(Math.abs(new Random().nextInt()));
+
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("execution count can't be < 0");
+
+ underTest.setExecutionCount(lessThanZero);
+ }
+}
import java.sql.SQLException;
import org.sonar.db.Database;
-import org.sonar.server.platform.db.migration.def.IntegerColumnDef;
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;
+import static org.sonar.server.platform.db.migration.def.IntegerColumnDef.newIntegerColumnDefBuilder;
+
public class AddCeQueueWorkerUuidAndExecutionCount extends DdlChange {
private static final String TABLE_CE_QUEUE = "ce_queue";
.setLimit(VarcharColumnDef.UUID_SIZE)
.setIsNullable(true)
.build())
- .addColumn(IntegerColumnDef.newIntegerColumnDefBuilder()
+ .addColumn(newIntegerColumnDefBuilder()
.setColumnName("execution_count")
.setIsNullable(true)
.build())
.add(1627, "Delete permission templates linked to removed users", DeletePermissionTemplatesLinkedToRemovedUsers.class)
;
.add(1628, "Add columns CE_QUEUE.WORKER_UUID and EXECUTION_COUNT", AddCeQueueWorkerUuidAndExecutionCount.class);
+ .add(1629, "Make CE_QUEUE.EXECUTION_COUNT not nullable", MakeCeQueueExecutionCountNotNullable.class);
}
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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.v64;
+
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.server.platform.db.migration.sql.AlterColumnsBuilder;
+import org.sonar.server.platform.db.migration.step.DdlChange;
+
+import static org.sonar.server.platform.db.migration.def.IntegerColumnDef.newIntegerColumnDefBuilder;
+
+public class MakeCeQueueExecutionCountNotNullable extends DdlChange {
+
+ private static final String TABLE_CE_QUEUE = "ce_queue";
+
+ public MakeCeQueueExecutionCountNotNullable(Database db) {
+ super(db);
+ }
+
+ @Override
+ public void execute(Context context) throws SQLException {
+ context.execute("update ce_queue set execution_count = 0 where execution_count is null");
+
+ context.execute(new AlterColumnsBuilder(getDialect(), TABLE_CE_QUEUE)
+ .updateColumn(newIntegerColumnDefBuilder()
+ .setColumnName("execution_count")
+ .setIsNullable(false)
+ .build())
+ .build());
+ }
+}
@Test
public void verify_migration_count() {
- verifyMigrationCount(underTest, 28);
+ verifyMigrationCount(underTest, 29);
}
-
}
+
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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.v64;
+
+import java.sql.SQLException;
+import java.sql.Types;
+import java.util.List;
+import java.util.Random;
+import java.util.stream.Stream;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.core.util.stream.MoreCollectors;
+import org.sonar.db.CoreDbTester;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class MakeCeQueueExecutionCountNotNullableTest {
+
+ private static final String TABLE_CE_QUEUE = "ce_queue";
+
+ @Rule
+ public CoreDbTester db = CoreDbTester.createForSchema(MakeCeQueueExecutionCountNotNullableTest.class, "ce_queue.sql");
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private MakeCeQueueExecutionCountNotNullable underTest = new MakeCeQueueExecutionCountNotNullable(db.database());
+
+ @Test
+ public void execute_makes_column_execution_count_not_nullable_when_table_is_empty() throws SQLException {
+ underTest.execute();
+
+ verifyColumnDefinition();
+ }
+
+ @Test
+ public void execute_set_column_execution_count_to_0_and_not_nullable_no_matter_status_of_the_task() throws SQLException {
+ insertCeQueue("u1", Status.IN_PROGRESS);
+ insertCeQueue("u2", Status.PENDING);
+
+ underTest.execute();
+
+ verifyColumnDefinition();
+ assertThat(getUuidsForExecutionCount(0)).containsOnly("u1", "u2");
+ assertThat(getUuidsForExecutionCount(1)).isEmpty();
+ }
+
+ private List<Object> getUuidsForExecutionCount(int executionCount) {
+ return db.select("select uuid as \"UUID\" from ce_queue where execution_count=" + executionCount)
+ .stream()
+ .flatMap(row -> Stream.of(row.get("UUID")))
+ .collect(MoreCollectors.toList());
+ }
+
+ private void verifyColumnDefinition() {
+ db.assertColumnDefinition(TABLE_CE_QUEUE, "execution_count", Types.INTEGER, null, false);
+ }
+
+ private void insertCeQueue(String uuid, Status status) {
+ db.executeInsert(TABLE_CE_QUEUE,
+ "UUID", uuid,
+ "TASK_TYPE", uuid + "_type",
+ "STATUS", status.name(),
+ "CREATED_AT", new Random().nextLong() + "",
+ "UPDATED_AT", new Random().nextLong() + "");
+ }
+
+ public enum Status {
+ PENDING, IN_PROGRESS
+ }
+}
--- /dev/null
+CREATE TABLE "CE_QUEUE" (
+ "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "UUID" VARCHAR(40) NOT NULL,
+ "TASK_TYPE" VARCHAR(15) NOT NULL,
+ "COMPONENT_UUID" VARCHAR(40) NULL,
+ "STATUS" VARCHAR(15) NOT NULL,
+ "SUBMITTER_LOGIN" VARCHAR(255) NULL,
+ "WORKER_UUID" VARCHAR(40) NULL,
+ "EXECUTION_COUNT" INTEGER NULL,
+ "STARTED_AT" BIGINT NULL,
+ "CREATED_AT" BIGINT NOT NULL,
+ "UPDATED_AT" BIGINT NOT NULL
+);
+CREATE UNIQUE INDEX "CE_QUEUE_UUID" ON "CE_QUEUE" ("UUID");
+CREATE INDEX "CE_QUEUE_COMPONENT_UUID" ON "CE_QUEUE" ("COMPONENT_UUID");
+CREATE INDEX "CE_QUEUE_STATUS" ON "CE_QUEUE" ("STATUS");