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: Always create submission results for failed build jobs #8534

Merged
merged 15 commits into from
May 3, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
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 @@ -92,7 +92,7 @@ public BuildJob(LocalCIBuildJobQueueItem queueItem, BuildStatus buildStatus, Res
this.buildCompletionDate = queueItem.jobTimingInfo().buildCompletionDate();
this.repositoryType = queueItem.repositoryInfo().repositoryType();
this.repositoryName = queueItem.repositoryInfo().repositoryName();
this.commitHash = queueItem.buildConfig().commitHash();
this.commitHash = queueItem.buildConfig().commitHashToBuild();
this.retryCount = queueItem.retryCount();
this.priority = queueItem.priority();
this.triggeredByPushTo = queueItem.repositoryInfo().triggeredByPushTo();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,72 +138,63 @@ public void processResult() {
}
result = programmingExerciseGradingService.processNewProgrammingExerciseResult(participation, buildResult);

if (result != null) {
programmingMessagingService.notifyUserAboutNewResult(result, participation);
addResultToBuildAgentsRecentBuildJobs(buildJob, result);
}
else {
programmingMessagingService.notifyUserAboutSubmissionError((Participation) participation,
new BuildTriggerWebsocketError("Result could not be processed", participation.getId()));
}
}
else {
log.warn("Participation with id {} has been deleted. Cancelling the processing of the build result.", buildJob.participationId());
}
}
finally {
// save build job to database
savedBuildJob = saveFinishedBuildJob(buildJob, BuildStatus.SUCCESSFUL, result);
}
}
else {
if (ex.getCause() instanceof CancellationException && ex.getMessage().equals("Build job with id " + buildJob.id() + " was cancelled.")) {
if (ex != null) {
if (ex.getCause() instanceof CancellationException && ex.getMessage().equals("Build job with id " + buildJob.id() + " was cancelled.")) {
savedBuildJob = saveFinishedBuildJob(buildJob, BuildStatus.CANCELLED, result);
}
else {
log.error("Error while processing build job: {}", buildJob, ex);
savedBuildJob = saveFinishedBuildJob(buildJob, BuildStatus.FAILED, result);
}
}
laurenzfb marked this conversation as resolved.
Show resolved Hide resolved
else {
savedBuildJob = saveFinishedBuildJob(buildJob, BuildStatus.SUCCESSFUL, result);
}

if (participationOptional.isPresent()) {
ProgrammingExerciseParticipation participation = (ProgrammingExerciseParticipation) participationOptional.get();
programmingMessagingService.notifyUserAboutSubmissionError((Participation) participation,
new BuildTriggerWebsocketError("Build job was cancelled", participation.getId()));
}

savedBuildJob = saveFinishedBuildJob(buildJob, BuildStatus.CANCELLED, null);
if (result != null) {
programmingMessagingService.notifyUserAboutNewResult(result, participation);
addResultToBuildAgentsRecentBuildJobs(buildJob, result);
}
else {
programmingMessagingService.notifyUserAboutSubmissionError((Participation) participation,
new BuildTriggerWebsocketError("Result could not be processed", participation.getId()));
}
}
}
else {
log.error("Error while processing build job: {}", buildJob, ex);

if (participationOptional.isPresent()) {
ProgrammingExerciseParticipation participation = (ProgrammingExerciseParticipation) participationOptional.get();
programmingMessagingService.notifyUserAboutSubmissionError((Participation) participation,
new BuildTriggerWebsocketError(ex.getMessage(), participation.getId()));
if (!buildLogs.isEmpty()) {
if (savedBuildJob != null) {
buildLogEntryService.saveBuildLogsToFile(buildLogs, savedBuildJob.getBuildJobId());
}
else {
log.warn("Participation with id {} has been deleted. Cancelling the requeueing of the build job.", buildJob.participationId());
log.warn("Couldn't save build logs as build job {} was not saved", buildJob.id());
laurenzfb marked this conversation as resolved.
Show resolved Hide resolved
}

savedBuildJob = saveFinishedBuildJob(buildJob, BuildStatus.FAILED, null);
}
}

if (!buildLogs.isEmpty()) {
if (savedBuildJob != null) {
buildLogEntryService.saveBuildLogsToFile(buildLogs, savedBuildJob.getBuildJobId());
}
else {
log.warn("Couldn't save build logs as build job {} was not saved", buildJob.id());
}
}

// If the build job is a solution build of a test or auxiliary push, we need to trigger the build of the corresponding template repository
if (isSolutionBuildOfTestOrAuxPush(buildJob)) {
log.debug("Triggering build of template repository for solution build with id {}", buildJob.id());
try {
programmingTriggerService.triggerTemplateBuildAndNotifyUser(buildJob.exerciseId(), buildJob.buildConfig().commitHash(), SubmissionType.TEST,
buildJob.repositoryInfo().triggeredByPushTo());
}
catch (EntityNotFoundException e) {
// Something went wrong while retrieving the template participation.
// At this point, programmingMessagingService.notifyUserAboutSubmissionError() does not work, because the template participation is not available.
// The instructor will see in the UI that no build of the template repository was conducted and will receive an error message when triggering the build manually.
log.error("Something went wrong while triggering the template build for exercise {} after the solution build was finished.", buildJob.exerciseId(), e);
// If the build job is a solution build of a test or auxiliary push, we need to trigger the build of the corresponding template repository
if (isSolutionBuildOfTestOrAuxPush(buildJob)) {
log.debug("Triggering build of template repository for solution build with id {}", buildJob.id());
try {
programmingTriggerService.triggerTemplateBuildAndNotifyUser(buildJob.exerciseId(), buildJob.buildConfig().testCommitHash(), SubmissionType.TEST,
buildJob.repositoryInfo().triggeredByPushTo());
}
catch (EntityNotFoundException e) {
// Something went wrong while retrieving the template participation.
// At this point, programmingMessagingService.notifyUserAboutSubmissionError() does not work, because the template participation is not available.
// The instructor will see in the UI that no build of the template repository was conducted and will receive an error message when triggering the build
// manually.
log.error("Something went wrong while triggering the template build for exercise {} after the solution build was finished.", buildJob.exerciseId(), e);
laurenzfb marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import de.tum.in.www1.artemis.exception.localvc.LocalVCInternalException;
import de.tum.in.www1.artemis.repository.AuxiliaryRepositoryRepository;
import de.tum.in.www1.artemis.repository.SolutionProgrammingExerciseParticipationRepository;
import de.tum.in.www1.artemis.service.connectors.GitService;
import de.tum.in.www1.artemis.service.connectors.aeolus.AeolusResult;
import de.tum.in.www1.artemis.service.connectors.aeolus.AeolusTemplateService;
import de.tum.in.www1.artemis.service.connectors.aeolus.Windfile;
Expand Down Expand Up @@ -66,6 +67,8 @@ public class LocalCITriggerService implements ContinuousIntegrationTriggerServic

private final LocalCIBuildConfigurationService localCIBuildConfigurationService;

private final GitService gitService;

private IQueue<LocalCIBuildJobQueueItem> queue;

private IMap<String, ZonedDateTime> dockerImageCleanupInfo;
Expand All @@ -74,7 +77,7 @@ public LocalCITriggerService(HazelcastInstance hazelcastInstance, AeolusTemplate
ProgrammingLanguageConfiguration programmingLanguageConfiguration, AuxiliaryRepositoryRepository auxiliaryRepositoryRepository,
LocalCIProgrammingLanguageFeatureService programmingLanguageFeatureService, Optional<VersionControlService> versionControlService,
SolutionProgrammingExerciseParticipationRepository solutionProgrammingExerciseParticipationRepository,
LocalCIBuildConfigurationService localCIBuildConfigurationService) {
LocalCIBuildConfigurationService localCIBuildConfigurationService, GitService gitService) {
this.hazelcastInstance = hazelcastInstance;
this.aeolusTemplateService = aeolusTemplateService;
this.programmingLanguageConfiguration = programmingLanguageConfiguration;
Expand All @@ -83,6 +86,7 @@ public LocalCITriggerService(HazelcastInstance hazelcastInstance, AeolusTemplate
this.versionControlService = versionControlService;
this.solutionProgrammingExerciseParticipationRepository = solutionProgrammingExerciseParticipationRepository;
this.localCIBuildConfigurationService = localCIBuildConfigurationService;
this.gitService = gitService;
}

@PostConstruct
Expand All @@ -106,12 +110,31 @@ public void triggerBuild(ProgrammingExerciseParticipation participation) throws
* Add a new build job item containing all relevant information necessary for the execution to the distributed build job queue.
*
* @param participation the participation of the repository which should be built and tested
* @param commitHash the commit hash of the commit that triggers the build. If it is null, the latest commit of the default branch will be built.
* @param commitHashToBuild the commit hash of the commit that triggers the build. If it is null, the latest commit of the default branch will be built.
* @param triggeredByPushTo type of the repository that was pushed to and triggered the build job
* @throws LocalCIException if the build job could not be added to the queue.
*/
@Override
public void triggerBuild(ProgrammingExerciseParticipation participation, String commitHash, RepositoryType triggeredByPushTo) throws LocalCIException {
public void triggerBuild(ProgrammingExerciseParticipation participation, String commitHashToBuild, RepositoryType triggeredByPushTo) throws LocalCIException {

// Commit hash related to the repository that will be tested
String assignmentCommitHash;

// Commit hash related to the test repository
String testCommitHash;

if (triggeredByPushTo == null || triggeredByPushTo.equals(RepositoryType.AUXILIARY)) {
assignmentCommitHash = gitService.getLastCommitHash(participation.getVcsRepositoryUri()).getName();
testCommitHash = gitService.getLastCommitHash(participation.getProgrammingExercise().getVcsTestRepositoryUri()).getName();
}
else if (triggeredByPushTo.equals(RepositoryType.TESTS)) {
assignmentCommitHash = gitService.getLastCommitHash(participation.getVcsRepositoryUri()).getName();
testCommitHash = commitHashToBuild;
}
else {
assignmentCommitHash = commitHashToBuild;
testCommitHash = gitService.getLastCommitHash(participation.getProgrammingExercise().getVcsTestRepositoryUri()).getName();
}
laurenzfb marked this conversation as resolved.
Show resolved Hide resolved

ProgrammingExercise programmingExercise = participation.getProgrammingExercise();

Expand All @@ -128,7 +151,7 @@ public void triggerBuild(ProgrammingExerciseParticipation participation, String

RepositoryInfo repositoryInfo = getRepositoryInfo(participation, triggeredByPushTo);

BuildConfig buildConfig = getBuildConfig(participation, commitHash);
BuildConfig buildConfig = getBuildConfig(participation, commitHashToBuild, assignmentCommitHash, testCommitHash);

LocalCIBuildJobQueueItem buildJobQueueItem = new LocalCIBuildJobQueueItem(buildJobId, participation.getBuildPlanId(), null, participation.getId(), courseId,
programmingExercise.getId(), 0, priority, null, repositoryInfo, jobTimingInfo, buildConfig, null);
Expand Down Expand Up @@ -211,7 +234,7 @@ else if (repositoryTypeOrUserName.equals("solution")) {

}

private BuildConfig getBuildConfig(ProgrammingExerciseParticipation participation, String commitHash) {
private BuildConfig getBuildConfig(ProgrammingExerciseParticipation participation, String commitHashToBuild, String assignmentCommitHash, String testCommitHash) {
String branch;
try {
branch = versionControlService.orElseThrow().getOrRetrieveBranchOfParticipation(participation);
Expand Down Expand Up @@ -244,7 +267,7 @@ private BuildConfig getBuildConfig(ProgrammingExerciseParticipation participatio
// Todo: If build agent does not have access to filesystem, we need to send the build script to the build agent and execute it there.
String buildScript = localCIBuildConfigurationService.createBuildScript(participation);

return new BuildConfig(buildScript, dockerImage, commitHash, branch, programmingLanguage, projectType, staticCodeAnalysisEnabled, sequentialTestRunsEnabled,
testwiseCoverageEnabled, resultPaths);
return new BuildConfig(buildScript, dockerImage, commitHashToBuild, assignmentCommitHash, testCommitHash, branch, programmingLanguage, projectType,
staticCodeAnalysisEnabled, sequentialTestRunsEnabled, testwiseCoverageEnabled, resultPaths);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

import java.io.IOException;
import java.io.StringReader;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
Expand Down Expand Up @@ -76,9 +75,6 @@ public class BuildJobExecutionService {

private final BuildLogsMap buildLogsMap;

@Value("${artemis.version-control.url}")
private URL localVCBaseUrl;

@Value("${artemis.version-control.default-branch:main}")
private String defaultBranch;

Expand Down Expand Up @@ -134,7 +130,7 @@ public LocalCIBuildResult runBuildJob(LocalCIBuildJobQueueItem buildJob, String
LocalVCRepositoryUri testsRepoUri = new LocalVCRepositoryUri(buildJob.repositoryInfo().testRepositoryUri());

// retrieve last commit hash from repositories
String assignmentCommitHash = buildJob.buildConfig().commitHash();
String assignmentCommitHash = buildJob.buildConfig().assignmentCommitHash();
if (assignmentCommitHash == null) {
try {
assignmentCommitHash = gitService.getLastCommitHash(assignmentRepoUri).getName();
Expand All @@ -145,14 +141,16 @@ public LocalCIBuildResult runBuildJob(LocalCIBuildJobQueueItem buildJob, String
throw new LocalCIException(msg, e);
}
}
String testCommitHash;
try {
testCommitHash = gitService.getLastCommitHash(testsRepoUri).getName();
}
catch (EntityNotFoundException e) {
msg = "Could not find last commit hash for test repository " + testsRepoUri.repositorySlug();
buildLogsMap.appendBuildLogEntry(buildJob.id(), msg);
throw new LocalCIException(msg, e);
String testCommitHash = buildJob.buildConfig().testCommitHash();
if (testCommitHash == null) {
try {
testCommitHash = gitService.getLastCommitHash(testsRepoUri).getName();
}
catch (EntityNotFoundException e) {
msg = "Could not find last commit hash for test repository " + testsRepoUri.repositorySlug();
buildLogsMap.appendBuildLogEntry(buildJob.id(), msg);
throw new LocalCIException(msg, e);
}
}

Path assignmentRepositoryPath;
Expand All @@ -161,7 +159,7 @@ public LocalCIBuildResult runBuildJob(LocalCIBuildJobQueueItem buildJob, String
* If this build job is triggered by a push to the test repository, the commit hash reflects changes to the test repository.
* Thus, we do not checkout the commit hash of the test repository in the assignment repository.
*/
if (buildJob.buildConfig().commitHash() != null && !isPushToTestOrAuxRepository) {
if (buildJob.buildConfig().assignmentCommitHash() != null && !isPushToTestOrAuxRepository) {
// Clone the assignment repository into a temporary directory with the name of the commit hash and then checkout the commit hash.
assignmentRepositoryPath = cloneRepository(assignmentRepoUri, assignmentCommitHash, true, buildJob.id());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,11 @@ private void processBuild(LocalCIBuildJobQueueItem buildJob) {
List<BuildLogEntry> buildLogs = buildLogsMap.getBuildLogs(buildJob.id());
buildLogsMap.removeBuildLogs(buildJob.id());

ResultQueueItem resultQueueItem = new ResultQueueItem(null, job, buildLogs, ex);
LocalCIBuildResult failedResult = new LocalCIBuildResult(buildJob.buildConfig().branch(), buildJob.buildConfig().assignmentCommitHash(),
buildJob.buildConfig().testCommitHash(), false);
failedResult.setBuildLogEntries(buildLogs);

krusche marked this conversation as resolved.
Show resolved Hide resolved
ResultQueueItem resultQueueItem = new ResultQueueItem(failedResult, job, buildLogs, ex);
resultQueue.add(resultQueueItem);

processingJobs.remove(buildJob.id());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public record BuildConfig(String buildScript, String dockerImage, String commitHash, String branch, ProgrammingLanguage programmingLanguage, ProjectType projectType,
boolean scaEnabled, boolean sequentialTestRunsEnabled, boolean testwiseCoverageEnabled, List<String> resultPaths) implements Serializable {
public record BuildConfig(String buildScript, String dockerImage, String commitHashToBuild, String assignmentCommitHash, String testCommitHash, String branch,
ProgrammingLanguage programmingLanguage, ProjectType projectType, boolean scaEnabled, boolean sequentialTestRunsEnabled, boolean testwiseCoverageEnabled,
List<String> resultPaths) implements Serializable {
laurenzfb marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,16 @@ public LocalCIBuildResult(String assignmentRepoBranchName, String assignmentRepo
this.staticCodeAnalysisReports = staticCodeAnalysisReports;
}

public LocalCIBuildResult(String assignmentRepoBranchName, String assignmentRepoCommitHash, String testsRepoCommitHash, boolean isBuildSuccessful) {
this.assignmentRepoBranchName = assignmentRepoBranchName;
this.assignmentRepoCommitHash = assignmentRepoCommitHash;
this.testsRepoCommitHash = testsRepoCommitHash;
this.isBuildSuccessful = isBuildSuccessful;
this.buildRunDate = ZonedDateTime.now();
this.jobs = new ArrayList<>();
this.staticCodeAnalysisReports = new ArrayList<>();
}

@Override
public ZonedDateTime getBuildRunDate() {
return buildRunDate;
Expand Down
2 changes: 1 addition & 1 deletion src/main/webapp/app/entities/build-config.model.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export class BuildConfig {
public dockerImage?: string;
public commitHash?: string;
public commitHashToBuild?: string;
public branch?: string;
public programmingLanguage?: string;
public projectType?: string;
Expand Down
Loading
Loading