import org.sonar.api.utils.log.Profiler;
import org.sonar.batch.protocol.output.BatchReportReader;
import org.sonar.core.component.ComponentDto;
+import org.sonar.core.component.SnapshotDto;
import org.sonar.core.computation.db.AnalysisReportDto;
import org.sonar.core.persistence.DbSession;
import org.sonar.core.persistence.MyBatis;
import org.sonar.server.computation.step.ComputationSteps;
import org.sonar.server.db.DbClient;
+import javax.annotation.Nullable;
import java.io.File;
import java.io.IOException;
import static org.sonar.api.utils.DateUtils.formatDateTimeNullSafe;
import static org.sonar.api.utils.DateUtils.longToDate;
+import static org.sonar.core.computation.db.AnalysisReportDto.Status.*;
public class ComputationService implements ServerComponent {
Profiler profiler = Profiler.create(LOG).startDebug(String.format(
"Analysis of project %s (report %d)", item.dto.getProjectKey(), item.dto.getId()));
- ComponentDto project = loadProject(item);
+ ComponentDto project = null;
+
try {
+ project = loadProject(item);
File reportDir = extractReportInDir(item);
BatchReportReader reader = new BatchReportReader(reportDir);
+ if (isSnapshotMissing(item, reader.readMetadata().getSnapshotId())) {
+ return;
+ }
ComputationContext context = new ComputationContext(reader, project);
for (ComputationStep step : steps.orderedSteps()) {
if (ArrayUtils.contains(step.supportedProjectQualifiers(), context.getProject().qualifier())) {
stepProfiler.stopDebug();
}
}
- item.dto.succeed();
-
+ item.dto.setStatus(SUCCESS);
} catch (Throwable e) {
- item.dto.fail();
+ item.dto.setStatus(FAILED);
throw Throwables.propagate(e);
} finally {
item.dto.setFinishedAt(system.now());
}
}
- private ComponentDto loadProject(ReportQueue.Item queueItem) {
+ private ComponentDto loadProject(ReportQueue.Item item) {
DbSession session = dbClient.openSession(false);
try {
- return dbClient.componentDao().getByKey(session, queueItem.dto.getProjectKey());
+ return dbClient.componentDao().getByKey(session, item.dto.getProjectKey());
} finally {
MyBatis.closeQuietly(session);
}
}
- private void saveActivity(AnalysisReportDto report, ComponentDto project) {
+ private boolean isSnapshotMissing(ReportQueue.Item item, long snapshotId) {
+ DbSession session = dbClient.openSession(false);
+ try {
+ SnapshotDto snapshot = dbClient.snapshotDao().getNullableByKey(session, snapshotId);
+ if (snapshot == null) {
+ item.dto.setStatus(CANCELLED);
+ LOG.info("Processing of report #{} is canceled because it was submitted while another report of the same project was already being processed.", item.dto.getId());
+ LOG.debug("The snapshot ID #{} provided by the report #{} does not exist anymore.", snapshotId, item.dto.getId());
+ }
+ return snapshot==null;
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+
+ }
+
+ private void saveActivity(AnalysisReportDto report, @Nullable ComponentDto project) {
Activity activity = new Activity();
activity.setType(Activity.Type.ANALYSIS_REPORT);
activity.setAction("LOG_ANALYSIS_REPORT");
activity
.setData("key", String.valueOf(report.getId()))
- .setData("projectKey", project.key())
- .setData("projectName", project.name())
- .setData("projectUuid", project.uuid())
+ .setData("projectKey", report.getProjectKey())
.setData("status", String.valueOf(report.getStatus()))
.setData("submittedAt", formatDateTimeNullSafe(longToDate(report.getCreatedAt())))
.setData("startedAt", formatDateTimeNullSafe(longToDate(report.getStartedAt())))
.setData("finishedAt", formatDateTimeNullSafe(longToDate(report.getFinishedAt())));
+ if (project != null) {
+ activity
+ .setData("projectName", project.name())
+ .setData("projectUuid", project.uuid());
+ }
activityService.save(activity);
}
}
import org.sonar.core.persistence.DbSession;
import org.sonar.core.persistence.MyBatis;
import org.sonar.process.ProcessConstants;
+import org.sonar.server.computation.db.AnalysisReportDao;
import org.sonar.server.db.DbClient;
import javax.annotation.CheckForNull;
import static org.sonar.core.computation.db.AnalysisReportDto.Status.PENDING;
public class ReportQueue implements ServerComponent {
-
- public static class Item {
- public final AnalysisReportDto dto;
- public final File zipFile;
-
- public Item(AnalysisReportDto dto, File zipFile) {
- this.dto = dto;
- this.zipFile = zipFile;
- }
- }
-
private final DbClient dbClient;
private final Settings settings;
try {
checkThatProjectExistsInDatabase(projectKey, session);
- // save report data on file. Directory is created if it does not exist yet.
- FileUtils.copyInputStreamToFile(reportData, file);
+ saveReportOnDisk(reportData, file);
+ AnalysisReportDto dto = saveReportMetadataInDatabase(projectKey, uuid, session);
- // add report metadata to the queue
- AnalysisReportDto dto = new AnalysisReportDto()
- .setProjectKey(projectKey)
- .setStatus(PENDING)
- .setUuid(uuid);
- dbClient.analysisReportDao().insert(session, dto);
- session.commit();
return new Item(dto, file);
-
} catch (Exception e) {
FileUtils.deleteQuietly(file);
throw new IllegalStateException("Fail to store analysis report of project " + projectKey, e);
}
}
+ private AnalysisReportDto saveReportMetadataInDatabase(String projectKey, String uuid, DbSession session) {
+ AnalysisReportDto dto = new AnalysisReportDto()
+ .setProjectKey(projectKey)
+ .setStatus(PENDING)
+ .setUuid(uuid);
+ dao().insert(session, dto);
+ session.commit();
+ return dto;
+ }
+
+ private AnalysisReportDao dao() {
+ return dbClient.analysisReportDao();
+ }
+
+ private void saveReportOnDisk(InputStream reportData, File file) throws IOException {
+ FileUtils.copyInputStreamToFile(reportData, file);
+ }
+
private void checkThatProjectExistsInDatabase(String projectKey, DbSession session) {
dbClient.componentDao().getByKey(session, projectKey);
}
DbSession session = dbClient.openSession(false);
try {
FileUtils.deleteQuietly(item.zipFile);
- dbClient.analysisReportDao().delete(session, item.dto.getId());
+ dao().delete(session, item.dto.getId());
session.commit();
} finally {
MyBatis.closeQuietly(session);
public Item pop() {
DbSession session = dbClient.openSession(false);
try {
- AnalysisReportDto dto = dbClient.analysisReportDao().pop(session);
+ AnalysisReportDto dto = dao().pop(session);
if (dto != null) {
File file = reportFileForUuid(dto.getUuid());
if (file.exists()) {
return new Item(dto, file);
}
Loggers.get(getClass()).error("Analysis report not found: " + file.getAbsolutePath());
- dbClient.analysisReportDao().delete(session, dto.getId());
+ dao().delete(session, dto.getId());
session.commit();
}
return null;
}
}
- public List<AnalysisReportDto> findByProjectKey(String projectKey) {
+ public List<AnalysisReportDto> selectByProjectKey(String projectKey) {
DbSession session = dbClient.openSession(false);
try {
- return dbClient.analysisReportDao().selectByProjectKey(session, projectKey);
+ return dao().selectByProjectKey(session, projectKey);
} finally {
MyBatis.closeQuietly(session);
}
DbSession session = dbClient.openSession(false);
try {
- dbClient.analysisReportDao().truncate(session);
+ dao().truncate(session);
session.commit();
} finally {
MyBatis.closeQuietly(session);
public void resetToPendingStatus() {
DbSession session = dbClient.openSession(false);
try {
- dbClient.analysisReportDao().resetAllToPendingStatus(session);
+ dao().resetAllToPendingStatus(session);
session.commit();
} finally {
MyBatis.closeQuietly(session);
public List<AnalysisReportDto> all() {
DbSession session = dbClient.openSession(false);
try {
- return dbClient.analysisReportDao().selectAll(session);
+ return dao().selectAll(session);
} finally {
MyBatis.closeQuietly(session);
}
private File reportFileForUuid(String uuid) {
return new File(reportsDir(), String.format("%s.zip", uuid));
}
+
+ public static class Item {
+ public final AnalysisReportDto dto;
+ public final File zipFile;
+
+ public Item(AnalysisReportDto dto, File zipFile) {
+ this.dto = dto;
+ this.zipFile = zipFile;
+ }
+ }
}
import org.sonar.api.utils.internal.JUnitTempFolder;
import org.sonar.api.utils.log.LogTester;
import org.sonar.api.utils.log.LoggerLevel;
-import org.sonar.batch.protocol.output.BatchReportWriter;
import org.sonar.batch.protocol.output.BatchReport;
+import org.sonar.batch.protocol.output.BatchReportWriter;
import org.sonar.core.computation.db.AnalysisReportDto;
+import org.sonar.core.computation.db.AnalysisReportDto.Status;
import org.sonar.core.persistence.DbTester;
import org.sonar.server.activity.Activity;
import org.sonar.server.activity.ActivityService;
import org.sonar.server.component.db.ComponentDao;
+import org.sonar.server.component.db.SnapshotDao;
import org.sonar.server.computation.step.ComputationStep;
import org.sonar.server.computation.step.ComputationSteps;
import org.sonar.server.db.DbClient;
public class ComputationServiceTest {
+ private static final long ANY_SNAPSHOT_ID = 54987654231L;
@ClassRule
public static DbTester dbTester = new DbTester();
-
@Rule
public JUnitTempFolder tempFolder = new JUnitTempFolder();
-
@Rule
public LogTester logTester = new LogTester();
-
ComputationStep projectStep1 = mockStep(Qualifiers.PROJECT);
ComputationStep projectStep2 = mockStep(Qualifiers.PROJECT);
ComputationStep viewStep = mockStep(Qualifiers.VIEW);
@Before
public void setUp() throws Exception {
- DbClient dbClient = new DbClient(dbTester.database(), dbTester.myBatis(), new ComponentDao());
+ DbClient dbClient = new DbClient(dbTester.database(), dbTester.myBatis(), new ComponentDao(), new SnapshotDao(system));
sut = new ComputationService(dbClient, steps, activityService, tempFolder, system);
// db contains project with key "P1"
// view step is not supposed to be executed
when(steps.orderedSteps()).thenReturn(Arrays.asList(projectStep1, projectStep2, viewStep));
- AnalysisReportDto dto = AnalysisReportDto.newForTests(1L).setProjectKey("P1").setUuid("U1");
+ AnalysisReportDto dto = newDefaultReport();
File zip = generateZip();
sut.process(new ReportQueue.Item(dto, zip));
// report is integrated -> status is set to SUCCESS
- assertThat(dto.getStatus()).isEqualTo(AnalysisReportDto.Status.SUCCESS);
+ assertThat(dto.getStatus()).isEqualTo(Status.SUCCESS);
assertThat(dto.getFinishedAt()).isNotNull();
// one info log at the end
verify(activityService).save(any(Activity.class));
}
+ private AnalysisReportDto newDefaultReport() {
+ return AnalysisReportDto.newForTests(1L).setProjectKey("P1").setUuid("U1").setStatus(Status.PENDING);
+ }
+
@Test
public void debug_logs() throws Exception {
logTester.setLevel(LoggerLevel.DEBUG);
- AnalysisReportDto dto = AnalysisReportDto.newForTests(1L).setProjectKey("P1").setUuid("U1");
+ AnalysisReportDto dto = newDefaultReport();
File zip = generateZip();
sut.process(new ReportQueue.Item(dto, zip));
@Test
public void fail_if_corrupted_zip() throws Exception {
- AnalysisReportDto dto = AnalysisReportDto.newForTests(1L).setProjectKey("P1").setUuid("U1");
+ AnalysisReportDto dto = newDefaultReport();
File zip = tempFolder.newFile();
FileUtils.write(zip, "not a file");
fail();
} catch (IllegalStateException e) {
assertThat(e.getMessage()).startsWith("Fail to unzip " + zip.getAbsolutePath() + " into ");
- assertThat(dto.getStatus()).isEqualTo(AnalysisReportDto.Status.FAILED);
+ assertThat(dto.getStatus()).isEqualTo(Status.FAILED);
assertThat(dto.getFinishedAt()).isNotNull();
}
}
when(steps.orderedSteps()).thenReturn(Arrays.asList(projectStep1));
doThrow(new IllegalStateException("pb")).when(projectStep1).execute(any(ComputationContext.class));
- AnalysisReportDto dto = AnalysisReportDto.newForTests(1L).setProjectKey("P1").setUuid("U1");
+ AnalysisReportDto dto = newDefaultReport();
File zip = generateZip();
try {
fail();
} catch (IllegalStateException e) {
assertThat(e.getMessage()).isEqualTo("pb");
- assertThat(dto.getStatus()).isEqualTo(AnalysisReportDto.Status.FAILED);
+ assertThat(dto.getStatus()).isEqualTo(Status.FAILED);
assertThat(dto.getFinishedAt()).isNotNull();
}
}
+ @Test
+ public void analysis_cancelled_when_snapshot_not_found() throws Exception {
+ AnalysisReportDto report = newDefaultReport();
+ File zip = generateZip(ANY_SNAPSHOT_ID);
+ logTester.setLevel(LoggerLevel.DEBUG);
+
+ sut.process(new ReportQueue.Item(report, zip));
+
+ assertThat(report.getStatus()).isEqualTo(Status.CANCELLED);
+ assertThat(logTester.logs()).contains(
+ String.format("Processing of report #%s is canceled because it was submitted while another report of the same project was already being processed.", report.getId()));
+ assertThat(logTester.logs()).contains(String.format("The snapshot ID #%s provided by the report #%s does not exist anymore.", ANY_SNAPSHOT_ID, report.getId()));
+ }
+
private ComputationStep mockStep(String... qualifiers) {
ComputationStep step = mock(ComputationStep.class);
when(step.supportedProjectQualifiers()).thenReturn(qualifiers);
}
private File generateZip() throws IOException {
+ return generateZip(110L);
+ }
+
+ private File generateZip(long snapshotId) throws IOException {
File dir = tempFolder.newDir();
BatchReportWriter writer = new BatchReportWriter(dir);
writer.writeMetadata(BatchReport.Metadata.newBuilder()
.setRootComponentRef(1)
.setProjectKey("PROJECT_KEY")
.setAnalysisDate(150000000L)
+ .setSnapshotId(snapshotId)
.build());
File zip = tempFolder.newFile();
ZipUtils.zipDir(dir, zip);
import org.sonar.test.DbTests;
import java.io.File;
-import java.io.IOException;
import java.io.InputStream;
import java.util.List;
static final long NOW = 1_500_000_000_000L;
@Rule
- public DbTester dbTester = new DbTester();
+ public DbTester db = new DbTester();
@Rule
public TemporaryFolder temp = new TemporaryFolder();
settings.setProperty(ProcessConstants.PATH_DATA, dataDir.getAbsolutePath());
when(system.now()).thenReturn(NOW);
- DbClient dbClient = new DbClient(dbTester.database(), dbTester.myBatis(), new ComponentDao(), new AnalysisReportDao(system));
+ DbClient dbClient = new DbClient(db.database(), db.myBatis(), new ComponentDao(), new AnalysisReportDao(system));
sut = new ReportQueue(dbClient, settings);
try (DbSession session = dbClient.openSession(false)) {
}
@Test
- public void add_report_to_queue() throws IOException {
+ public void add_report_to_queue() throws Exception {
// must:
// 1. insert metadata in db
// 2. copy report content to directory /data/analysis
assertThat(item.dto.getUuid()).isNotEmpty();
assertThat(item.dto.getId()).isGreaterThan(0L);
- List<AnalysisReportDto> reports = sut.findByProjectKey("P1");
+ List<AnalysisReportDto> reports = sut.selectByProjectKey("P1");
assertThat(reports).hasSize(1);
AnalysisReportDto report = reports.get(0);
@Test
public void find_by_project_key() throws Exception {
sut.add("P1", generateData());
- assertThat(sut.findByProjectKey("P1")).hasSize(1).extracting("projectKey").containsExactly("P1");
- assertThat(sut.findByProjectKey("P2")).isEmpty();
+ assertThat(sut.selectByProjectKey("P1")).hasSize(1).extracting("projectKey").containsExactly("P1");
+ assertThat(sut.selectByProjectKey("P2")).isEmpty();
}
@Test
@Test
public void remove() {
ReportQueue.Item item = sut.add("P1", generateData());
- assertThat(dbTester.countRowsOfTable("analysis_reports")).isEqualTo(1);
+ assertThat(db.countRowsOfTable("analysis_reports")).isEqualTo(1);
sut.remove(item);
- assertThat(dbTester.countRowsOfTable("analysis_reports")).isEqualTo(0);
+ assertThat(db.countRowsOfTable("analysis_reports")).isEqualTo(0);
assertThat(item.zipFile).doesNotExist();
}
assertThat(sut.pop()).isNull();
// table sanitized
- assertThat(dbTester.countRowsOfTable("analysis_reports")).isEqualTo(0);
+ assertThat(db.countRowsOfTable("analysis_reports")).isEqualTo(0);
}
@Test
sut.clear();
- assertThat(dbTester.countRowsOfTable("analysis_reports")).isEqualTo(0);
+ assertThat(db.countRowsOfTable("analysis_reports")).isEqualTo(0);
assertThat(analysisDir()).doesNotExist();
}
+ @Test
+ public void clear_do_not_fail_when_directory_do_not_exist() throws Exception {
+ sut.clear();
+ sut.clear();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void add_on_non_existent_project() throws Exception {
+ sut.add("UNKNOWN_PROJECT_KEY", generateData());
+ }
+
@Test
public void reset_to_pending_status() throws Exception {
// 2 pending
<dataset>
<projects id="10" kee="P1" qualifier="TRK"/>
+ <snapshots
+ id="110" project_id="10" parent_snapshot_id="[null]" root_project_id="10" root_snapshot_id="[null]"
+ purge_status="[null]" period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]" period3_mode="[null]"
+ period3_param="[null]" period3_date="[null]" period4_mode="[null]" period4_param="[null]"
+ period4_date="[null]" period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ scope="PRJ" qualifier="TRK" created_at="1225544280000" build_date="1225544280000" version="[null]" path=""
+ status="P" islast="false" depth="0"/>
</dataset>
return report;
}
- public void setId(Long id) {
- this.id = id;
- }
-
public String getProjectKey() {
return projectKey;
}
return this;
}
- public void fail() {
- this.status = Status.FAILED;
- }
-
- public void succeed() {
- this.status = Status.SUCCESS;
- }
-
public String getUuid() {
return uuid;
}
return id;
}
+ public void setId(Long id) {
+ this.id = id;
+ }
+
@Override
public String toString() {
return Objects.toStringHelper(this)
}
public static enum Status {
- PENDING, WORKING, SUCCESS, FAILED;
-
- public boolean isInFinalState() {
- return SUCCESS.equals(this) || FAILED.equals(this);
- }
+ PENDING, WORKING, SUCCESS, FAILED, CANCELLED
}
}