Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrated code lifecycle: Save and update build job to database at the start of the process #10001

Open
wants to merge 30 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
d812605
track status
BBesrour Dec 2, 2024
1f8c1cd
Merge branch 'develop' into feature/integrated-code-lifecycle/track-b…
BBesrour Dec 12, 2024
ed2fbd6
add timeout option
BBesrour Dec 12, 2024
e47c5ef
filter
BBesrour Dec 12, 2024
e2d7b48
statistics
BBesrour Dec 13, 2024
68a983e
statistics
BBesrour Dec 13, 2024
30ed711
fix tests
BBesrour Dec 13, 2024
2b10bf6
fix style
BBesrour Dec 13, 2024
4baf8a8
tests and style
BBesrour Dec 13, 2024
0218ae2
fix
BBesrour Dec 13, 2024
b595fa6
add submission date
BBesrour Dec 13, 2024
1314bbd
fix client tests
BBesrour Dec 13, 2024
922ab66
fix server tests
BBesrour Dec 13, 2024
53578fa
fix server tests
BBesrour Dec 13, 2024
01bd6ea
Merge branch 'develop' into feature/integrated-code-lifecycle/track-b…
BBesrour Dec 13, 2024
2d2f7e5
Merge branch 'develop' into feature/integrated-code-lifecycle/track-b…
BBesrour Dec 14, 2024
a9f4e2f
Update LocalCIIntegrationTest.java
BBesrour Dec 16, 2024
e3df977
Merge branch 'develop' into feature/integrated-code-lifecycle/track-b…
BBesrour Dec 17, 2024
5aac75c
fix conflicts
BBesrour Dec 17, 2024
d3d9d56
feedback
BBesrour Dec 17, 2024
79b6c0d
Merge branch 'develop' into feature/integrated-code-lifecycle/track-b…
BBesrour Dec 27, 2024
267489e
Merge branch 'develop' into feature/integrated-code-lifecycle/track-b…
BBesrour Jan 3, 2025
ab23ba7
Merge branch 'develop' into feature/integrated-code-lifecycle/track-b…
BBesrour Jan 7, 2025
2faba15
Merge branch 'develop' into feature/integrated-code-lifecycle/track-b…
BBesrour Jan 14, 2025
3e74bec
fix
BBesrour Jan 14, 2025
0572b41
Merge branch 'develop' into feature/integrated-code-lifecycle/track-b…
BBesrour Jan 20, 2025
2914939
Merge branch 'develop' into feature/integrated-code-lifecycle/track-b…
BBesrour Jan 20, 2025
2f1f4fd
fix tests
BBesrour Jan 20, 2025
de7efc1
Merge branch 'develop' into feature/integrated-code-lifecycle/track-b…
BBesrour Jan 22, 2025
b423aa9
Merge branch 'develop' into feature/integrated-code-lifecycle/track-b…
BBesrour Jan 22, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@

import com.fasterxml.jackson.annotation.JsonInclude;

import de.tum.cit.aet.artemis.programming.domain.build.BuildStatus;

@JsonInclude(JsonInclude.Include.NON_EMPTY)
public record BuildJobsStatisticsDTO(long totalBuilds, long successfulBuilds, long failedBuilds, long cancelledBuilds) {
public record BuildJobsStatisticsDTO(long totalBuilds, long successfulBuilds, long failedBuilds, long cancelledBuilds, long timeOutBuilds, long missingBuilds) {

/**
* Create a BuildJobsStatisticsDTO from a list of BuildJobResultCountDTOs.
Expand All @@ -20,19 +18,21 @@ public static BuildJobsStatisticsDTO of(List<BuildJobResultCountDTO> resultCount
long successfulBuilds = 0;
long failedBuilds = 0;
long cancelledBuilds = 0;
// Switch case would cause an error in the testDTOImplementations test
long timeOutBuilds = 0;
long missingBuilds = 0;
long otherBuilds = 0;

for (BuildJobResultCountDTO resultCountDTO : resultCountDTOList) {
if (resultCountDTO.status() == BuildStatus.SUCCESSFUL) {
successfulBuilds += resultCountDTO.count();
}
else if (resultCountDTO.status() == BuildStatus.FAILED || resultCountDTO.status() == BuildStatus.ERROR) {
failedBuilds += resultCountDTO.count();
}
else if (resultCountDTO.status() == BuildStatus.CANCELLED) {
cancelledBuilds += resultCountDTO.count();
switch (resultCountDTO.status()) {
case SUCCESSFUL -> successfulBuilds += resultCountDTO.count();
case FAILED, ERROR -> failedBuilds += resultCountDTO.count();
case CANCELLED -> cancelledBuilds += resultCountDTO.count();
case TIMEOUT -> timeOutBuilds += resultCountDTO.count();
case MISSING -> missingBuilds += resultCountDTO.count();
default -> otherBuilds += resultCountDTO.count();
}
}
totalBuilds = successfulBuilds + failedBuilds + cancelledBuilds;
return new BuildJobsStatisticsDTO(totalBuilds, successfulBuilds, failedBuilds, cancelledBuilds);
totalBuilds = successfulBuilds + failedBuilds + cancelledBuilds + timeOutBuilds + missingBuilds + otherBuilds;
return new BuildJobsStatisticsDTO(totalBuilds, successfulBuilds, failedBuilds, cancelledBuilds, timeOutBuilds, missingBuilds);
BBesrour marked this conversation as resolved.
Show resolved Hide resolved
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
*/
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public record FinishedBuildJobDTO(String id, String name, String buildAgentAddress, long participationId, long courseId, long exerciseId, BuildStatus status,
RepositoryType repositoryType, String repositoryName, RepositoryType triggeredByPushTo, ZonedDateTime buildStartDate, ZonedDateTime buildCompletionDate, String commitHash,
ResultDTO submissionResult) {
RepositoryType repositoryType, String repositoryName, RepositoryType triggeredByPushTo, ZonedDateTime buildSubmissionDate, ZonedDateTime buildStartDate,
ZonedDateTime buildCompletionDate, String commitHash, ResultDTO submissionResult) {

/**
* A DTO representing a result
Expand Down Expand Up @@ -65,6 +65,6 @@ public static FinishedBuildJobDTO of(BuildJob buildJob) {

return new FinishedBuildJobDTO(buildJob.getBuildJobId(), buildJob.getName(), buildJob.getBuildAgentAddress(), buildJob.getParticipationId(), buildJob.getCourseId(),
buildJob.getExerciseId(), buildJob.getBuildStatus(), buildJob.getRepositoryType(), buildJob.getRepositoryName(), buildJob.getTriggeredByPushTo(),
buildJob.getBuildStartDate(), buildJob.getBuildCompletionDate(), buildJob.getCommitHash(), resultDTO);
buildJob.getBuildSubmissionDate(), buildJob.getBuildStartDate(), buildJob.getBuildCompletionDate(), buildJob.getCommitHash(), resultDTO);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -177,10 +177,17 @@ public CompletableFuture<BuildResult> executeBuildJob(BuildJobQueueItem buildJob
}
else {
finishBuildJobExceptionally(buildJobItem.id(), containerName, e);

final String msg;
if (e instanceof TimeoutException) {
msg = "Build job with id " + buildJobItem.id() + " was timed out";
logTimedOutBuildJob(buildJobItem, buildJobTimeoutSeconds);
}
throw new CompletionException(e);
else {
msg = "Build job with id " + buildJobItem.id() + " failed";
}

throw new CompletionException(msg, e);
}
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -449,12 +449,22 @@ private void processBuild(BuildJobQueueItem buildJob) {
BuildJobQueueItem job;
BuildStatus status;

if (!(ex.getCause() instanceof CancellationException) || !ex.getMessage().equals("Build job with id " + buildJob.id() + " was cancelled.")) {
status = BuildStatus.FAILED;
log.error("Error while processing build job: {}", buildJob, ex);
String cancelledMsg = "Build job with id " + buildJob.id() + " was cancelled.";
String timeoutMsg = "Build job with id " + buildJob.id() + " was timed out";
BBesrour marked this conversation as resolved.
Show resolved Hide resolved
Throwable cause = ex.getCause();
String errorMessage = ex.getMessage();

if ((cause instanceof TimeoutException) || errorMessage.equals(timeoutMsg)) {
status = BuildStatus.TIMEOUT;
log.info("Build job with id {} was timed out", buildJob.id());
}
else {
else if ((cause instanceof CancellationException) && errorMessage.equals(cancelledMsg)) {
status = BuildStatus.CANCELLED;
log.info("Build job with id {} was cancelled", buildJob.id());
}
else {
status = BuildStatus.FAILED;
log.error("Error while processing build job: {}", buildJob, ex);
}

job = new BuildJobQueueItem(buildJob, completionDate, status);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ public class BuildJob extends DomainObject {
@Column(name = "build_agent_address")
private String buildAgentAddress;

@Column(name = "build_submission_date")
private ZonedDateTime buildSubmissionDate;

@Column(name = "build_start_date")
private ZonedDateTime buildStartDate;

Expand Down Expand Up @@ -89,6 +92,7 @@ public BuildJob(BuildJobQueueItem queueItem, BuildStatus buildStatus, Result res
this.participationId = queueItem.participationId();
this.result = result;
this.buildAgentAddress = queueItem.buildAgent().memberAddress();
this.buildSubmissionDate = queueItem.jobTimingInfo().submissionDate();
this.buildStartDate = queueItem.jobTimingInfo().buildStartDate();
this.buildCompletionDate = queueItem.jobTimingInfo().buildCompletionDate();
this.repositoryType = queueItem.repositoryInfo().repositoryType();
Expand Down Expand Up @@ -157,6 +161,14 @@ public void setBuildAgentAddress(String buildAgentAddress) {
this.buildAgentAddress = buildAgentAddress;
}

public ZonedDateTime getBuildSubmissionDate() {
return buildSubmissionDate;
}

public void setBuildSubmissionDate(ZonedDateTime buildSubmissionDate) {
this.buildSubmissionDate = buildSubmissionDate;
}

public ZonedDateTime getBuildStartDate() {
return buildStartDate;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@
* FAILED: the build failed
* ERROR: the build produced an error
* CANCELED: the build was canceled
* QUEUED: the build is queued
* BUILDING: the build is currently building
* MISSING: the build is missing (i.e. it was not found in the queue, not being built or not finished)
*/

public enum BuildStatus {
SUCCESSFUL, FAILED, ERROR, CANCELLED
SUCCESSFUL, FAILED, ERROR, CANCELLED, QUEUED, BUILDING, TIMEOUT, MISSING
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import de.tum.cit.aet.artemis.buildagent.dto.BuildJobResultCountDTO;
import de.tum.cit.aet.artemis.buildagent.dto.DockerImageBuild;
Expand All @@ -39,8 +41,8 @@ public interface BuildJobRepository extends ArtemisJpaRepository<BuildJob, Long>
LEFT JOIN Course c ON b.courseId = c.id
WHERE (:buildStatus IS NULL OR b.buildStatus = :buildStatus)
AND (:buildAgentAddress IS NULL OR b.buildAgentAddress = :buildAgentAddress)
AND (CAST(:startDate AS string) IS NULL OR b.buildStartDate >= :startDate)
AND (CAST(:endDate AS string) IS NULL OR b.buildStartDate <= :endDate)
AND (CAST(:startDate AS string) IS NULL OR b.buildSubmissionDate >= :startDate)
BBesrour marked this conversation as resolved.
Show resolved Hide resolved
AND (CAST(:endDate AS string) IS NULL OR b.buildSubmissionDate <= :endDate)
BBesrour marked this conversation as resolved.
Show resolved Hide resolved
AND (:searchTerm IS NULL OR (b.repositoryName LIKE %:searchTerm% OR c.title LIKE %:searchTerm%))
AND (:courseId IS NULL OR b.courseId = :courseId)
AND (:durationLower IS NULL OR (b.buildCompletionDate - b.buildStartDate) >= :durationLower)
Expand Down Expand Up @@ -77,7 +79,7 @@ Page<Long> findIdsByFilterCriteria(@Param("buildStatus") BuildStatus buildStatus
COUNT(b.buildStatus)
)
FROM BuildJob b
WHERE b.buildStartDate >= :fromDateTime
WHERE b.buildSubmissionDate >= :fromDateTime
AND (:courseId IS NULL OR b.courseId = :courseId)
GROUP BY b.buildStatus
""")
Expand Down Expand Up @@ -120,4 +122,37 @@ SELECT COUNT(b)
WHERE b.exerciseId = :exerciseId AND b.buildStatus = 'SUCCESSFUL'
""")
long fetchSuccessfulBuildJobCountByExerciseId(@Param("exerciseId") Long exerciseId);

@Transactional
@Modifying
@Query("UPDATE BuildJob b SET b.buildStatus = :newStatus WHERE b.buildJobId = :buildJobId")
void updateBuildJobStatus(@Param("buildJobId") String buildJobId, @Param("newStatus") BuildStatus newStatus);

/**
* Update the build job status and set the build start date if it is not set yet. The buildStartDate is required to calculate the statistics and the correctly display in the
* build overview.
* This is used to update missing jobs that do not have a build start date yet.
*
* @param buildJobId the build job id
* @param newStatus the new build status
* @param buildStartDate the build start date
*/
@Transactional
@Modifying
@Query("""
UPDATE BuildJob b
SET b.buildStatus = :newStatus,
b.buildStartDate = CASE WHEN b.buildStartDate IS NULL THEN :buildStartDate ELSE b.buildStartDate END
WHERE b.buildJobId = :buildJobId
""")
void updateBuildJobStatusWithBuildStartDate(@Param("buildJobId") String buildJobId, @Param("newStatus") BuildStatus newStatus,
@Param("buildStartDate") ZonedDateTime buildStartDate);

/**
* Find all build jobs with the given build status.
*
* @param statuses the list of build statuses
* @return the list of build jobs
*/
List<BuildJob> findAllByBuildStatusIn(List<BuildStatus> statuses);
}
Loading
Loading