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: Allow Admins to clear Distributed data #10111

Open
wants to merge 10 commits into
base: develop
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -271,4 +271,22 @@ public ResponseEntity<Void> resumeAllBuildAgents() {
localCIBuildJobQueueService.resumeAllBuildAgents();
return ResponseEntity.noContent().build();
}

/**
* {@code PUT /api/admin/clear-distributed-data} : Clear all distributed data.
* This endpoint allows administrators to clear all distributed data. See {@link SharedQueueManagementService#clearDistributedData()}.
*
* <p>
* <strong>Authorization:</strong> This operation requires admin privileges, enforced by {@code @EnforceAdmin}.
* </p>
*
* @return {@link ResponseEntity} with status code 200 (OK) if the distributed data was successfully cleared
* or an appropriate error response if something went wrong
*/
@PutMapping("clear-distributed-data")
public ResponseEntity<Void> clearDistributedData() {
log.debug("REST request to clear distributed data");
localCIBuildJobQueueService.clearDistributedData();
return ResponseEntity.ok().build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import de.tum.cit.aet.artemis.buildagent.dto.BuildAgentInformation;
import de.tum.cit.aet.artemis.buildagent.dto.BuildJobQueueItem;
import de.tum.cit.aet.artemis.buildagent.dto.DockerImageBuild;
import de.tum.cit.aet.artemis.buildagent.dto.ResultQueueItem;
import de.tum.cit.aet.artemis.core.dto.SortingOrder;
import de.tum.cit.aet.artemis.core.dto.pageablesearch.FinishedBuildJobPageableSearchDTO;
import de.tum.cit.aet.artemis.core.service.ProfileService;
Expand All @@ -61,6 +62,8 @@ public class SharedQueueManagementService {

private IQueue<BuildJobQueueItem> queue;

private IQueue<ResultQueueItem> resultQueue;

/**
* Map of build jobs currently being processed across all nodes
*/
Expand Down Expand Up @@ -100,6 +103,7 @@ public void init() {
this.resumeBuildAgentTopic = hazelcastInstance.getTopic("resumeBuildAgentTopic");
this.buildAgentInformation.addEntryListener(new BuildAgentListener(), false);
this.updateBuildAgentCapacity();
this.resultQueue = this.hazelcastInstance.getQueue("buildResultQueue");
}

/**
Expand Down Expand Up @@ -306,6 +310,18 @@ public void cancelAllJobsForParticipation(long participationId) {
}
}

/**
* Clear all build related data from the distributed data structures.
* This method should only be called by an admin user.
*/
public void clearDistributedData() {
queue.clear();
processingJobs.clear();
dockerImageCleanupInfo.clear();
resultQueue.clear();
buildAgentInformation.clear();
}
BBesrour marked this conversation as resolved.
Show resolved Hide resolved

/**
* Get all finished build jobs that match the search criteria.
*
Expand Down Expand Up @@ -407,7 +423,6 @@ private long getBuildJobRemainingDuration(BuildJobQueueItem buildJob, ZonedDateT
return 0;
}
return Duration.between(now, estimatedCompletionDate).toSeconds();

}

private class BuildAgentListener
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">
<span jhiTranslate="artemisApp.buildAgents.clearDistributedData.title"></span>
</h5>
<button type="button" class="btn-close" aria-label="Close" (click)="cancel()"></button>
</div>
<div class="modal-body">
<div>
<div jhiTranslate="artemisApp.buildAgents.clearDistributedData.descriptionFirst"></div>
<div jhiTranslate="artemisApp.buildAgents.clearDistributedData.descriptionSecond"></div>
<div jhiTranslate="artemisApp.buildAgents.clearDistributedData.descriptionThird"></div>
BBesrour marked this conversation as resolved.
Show resolved Hide resolved
</div>
<input type="text" class="form-control" required name="confirm" [(ngModel)]="confirmationText" [pattern]="'CLEAR DATA'" />
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" (click)="cancel()">
<fa-icon [icon]="faTimes" />
<span jhiTranslate="artemisApp.buildQueue.filter.close"></span>
</button>
<button type="button" class="btn btn-danger" (click)="confirm()" [disabled]="!buttonEnabled()">
<fa-icon [icon]="faTrash" />
<span jhiTranslate="artemisApp.buildAgents.clearDistributedData.clearData"></span>
</button>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Component, computed, inject, model } from '@angular/core';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { TranslateDirective } from 'app/shared/language/translate.directive';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { faTimes, faTrash } from '@fortawesome/free-solid-svg-icons';
import { FormsModule } from '@angular/forms';

@Component({
selector: 'jhi-build-agent-clear-distributed-data',
standalone: true,
BBesrour marked this conversation as resolved.
Show resolved Hide resolved
imports: [FaIconComponent, TranslateDirective, FormsModule],
templateUrl: './build-agent-clear-distributed-data.component.html',
})
export class BuildAgentClearDistributedDataComponent {
private activeModal = inject(NgbActiveModal);

confirmationText = model<string>('');

protected readonly faTimes = faTimes;
protected readonly faTrash = faTrash;

private readonly expectedConfirmationText = 'CLEAR DATA';

buttonEnabled = computed(() => this.confirmationText() === this.expectedConfirmationText);

/**
* Closes the modal by dismissing it
*/
cancel() {
this.activeModal.dismiss('cancel');
}

confirm() {
this.activeModal.close(true);
}
}
BBesrour marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">
<span jhiTranslate="artemisApp.buildAgents.pauseAll"></span>
</h5>
<button type="button" class="btn-close" aria-label="Close" (click)="cancel()"></button>
</div>
<div class="modal-body">
<div>
<span jhiTranslate="artemisApp.buildAgents.pauseAllWarning"></span>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" (click)="cancel()">
<fa-icon [icon]="faTimes" />
<span jhiTranslate="artemisApp.buildQueue.filter.close"></span>
</button>
<button type="button" class="btn btn-danger" (click)="confirm()">
<fa-icon [icon]="faPause" />
<span jhiTranslate="artemisApp.buildAgents.pauseAll"></span>
</button>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Component, inject } from '@angular/core';
import { faPause, faTimes } from '@fortawesome/free-solid-svg-icons';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { TranslateDirective } from 'app/shared/language/translate.directive';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';

@Component({
selector: 'jhi-build-agent-pause-all-modal',
standalone: true,
imports: [FaIconComponent, TranslateDirective],
templateUrl: './build-agent-pause-all-modal.component.html',
})
export class BuildAgentPauseAllModalComponent {
private activeModal = inject(NgbActiveModal);

protected readonly faTimes = faTimes;
protected readonly faPause = faPause;

/**
* Closes the modal by dismissing it
*/
cancel() {
this.activeModal.dismiss('cancel');
}

confirm() {
this.activeModal.close(true);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,14 @@ <h3 id="build-agents-heading" jhiTranslate="artemisApp.buildAgents.summary"></h3
<fa-icon [icon]="faPlay" />
<span jhiTranslate="artemisApp.buildAgents.resumeAll"></span>
</button>
<button class="btn btn-danger" (click)="displayPauseBuildAgentModal(content)">
<button class="btn btn-danger" (click)="displayPauseBuildAgentModal()">
<fa-icon [icon]="faPause" />
<span jhiTranslate="artemisApp.buildAgents.pauseAll"></span>
</button>
<button class="btn btn-danger" (click)="displayClearDistributedDataModal()">
<fa-icon [icon]="faTrash" />
<span jhiTranslate="artemisApp.buildAgents.clearDistributedData.title"></span>
</button>
</div>
</div>
<jhi-data-table [showPageSizeDropdown]="false" [showSearchField]="false" entityType="buildAgent" [allEntities]="buildAgents!">
Expand Down Expand Up @@ -128,28 +132,3 @@ <h3 id="build-agents-heading" jhiTranslate="artemisApp.buildAgents.summary"></h3
</jhi-data-table>
}
</div>

<!-- Modal -->
<ng-template #content let-modal>
<div class="modal-header">
<h5 class="modal-title">
<span jhiTranslate="artemisApp.buildAgents.pauseAll"></span>
</h5>
<button type="button" class="btn-close" aria-label="Close" (click)="modal.close()"></button>
</div>
<div class="modal-body">
<div>
<span jhiTranslate="artemisApp.buildAgents.pauseAllWarning"></span>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" (click)="modal.close()">
<fa-icon [icon]="faTimes" />
<span jhiTranslate="artemisApp.buildQueue.filter.close"></span>
</button>
<button type="button" class="btn btn-danger" (click)="pauseAllBuildAgents(modal)">
<fa-icon [icon]="faPause" />
<span jhiTranslate="artemisApp.buildAgents.pauseAll"></span>
</button>
</div>
</ng-template>
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@ import { BuildAgentInformation, BuildAgentStatus } from 'app/entities/programmin
import { JhiWebsocketService } from 'app/core/websocket/websocket.service';
import { BuildAgentsService } from 'app/localci/build-agents/build-agents.service';
import { Subscription } from 'rxjs';
import { faPause, faPlay, faTimes } from '@fortawesome/free-solid-svg-icons';
import { faPause, faPlay, faTimes, faTrash } from '@fortawesome/free-solid-svg-icons';
import { BuildQueueService } from 'app/localci/build-queue/build-queue.service';
import { Router } from '@angular/router';
import { BuildAgent } from 'app/entities/programming/build-agent.model';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
import { AlertService, AlertType } from 'app/core/util/alert.service';
import { ArtemisSharedModule } from 'app/shared/shared.module';
import { ArtemisDataTableModule } from 'app/shared/data-table/data-table.module';
import { NgxDatatableModule } from '@siemens/ngx-datatable';
import { BuildAgentPauseAllModalComponent } from 'app/localci/build-agents/build-agent-summary/build-agent-pause-all-modal/build-agent-pause-all-modal.component';
import { BuildAgentClearDistributedDataComponent } from 'app/localci/build-agents/build-agent-summary/build-agent-clear-distributed-data/build-agent-clear-distributed-data.component';

@Component({
selector: 'jhi-build-agents',
Expand Down Expand Up @@ -40,6 +42,7 @@ export class BuildAgentSummaryComponent implements OnInit, OnDestroy {
protected readonly faTimes = faTimes;
protected readonly faPause = faPause;
protected readonly faPlay = faPlay;
protected readonly faTrash = faTrash;

ngOnInit() {
this.routerLink = this.router.url;
Expand Down Expand Up @@ -98,11 +101,25 @@ export class BuildAgentSummaryComponent implements OnInit, OnDestroy {
}
}

displayPauseBuildAgentModal(modal: any) {
this.modalService.open(modal);
displayPauseBuildAgentModal() {
const modalRef: NgbModalRef = this.modalService.open(BuildAgentPauseAllModalComponent as Component);
modalRef.result.then((result) => {
if (result) {
this.pauseAllBuildAgents();
}
});
}

pauseAllBuildAgents(modal?: any) {
displayClearDistributedDataModal() {
const modalRef: NgbModalRef = this.modalService.open(BuildAgentClearDistributedDataComponent as Component, { size: 'lg' });
modalRef.result.then((result) => {
if (result) {
this.clearDistributedData();
}
});
}

pauseAllBuildAgents() {
this.buildAgentsService.pauseAllBuildAgents().subscribe({
next: () => {
this.load();
Expand All @@ -118,9 +135,6 @@ export class BuildAgentSummaryComponent implements OnInit, OnDestroy {
});
},
});
if (modal) {
modal.close();
}
}

resumeAllBuildAgents() {
Expand All @@ -140,4 +154,21 @@ export class BuildAgentSummaryComponent implements OnInit, OnDestroy {
},
});
}

clearDistributedData() {
this.buildAgentsService.clearDistributedData().subscribe({
next: () => {
this.alertService.addAlert({
type: AlertType.SUCCESS,
message: 'artemisApp.buildAgents.alerts.distributedDataCleared',
});
},
error: () => {
this.alertService.addAlert({
type: AlertType.DANGER,
message: 'artemisApp.buildAgents.alerts.distributedDataClearFailed',
});
},
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,12 @@ export class BuildAgentsService {
}),
);
}

clearDistributedData(): Observable<void> {
return this.http.delete<void>(`${this.adminResourceUrl}/clear-distributed-data`).pipe(
catchError((err) => {
return throwError(() => new Error(`Failed to clear distributed data\n${err.message}`));
}),
);
}
}
13 changes: 11 additions & 2 deletions src/main/webapp/i18n/de/buildAgents.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,22 @@
"buildAgentResumeFailed": "Fortsetzen des Build-Agenten fehlgeschlagen.",
"buildAgentWithoutName": "Der Name des Build-Agenten ist erforderlich.",
"buildAgentsPaused": "Anfrage zum Anhalten aller Build-Agenten erfolgreich gesendet. Die Agenten akzeptieren keine neuen Build-Jobs und werden die aktuellen Build-Jobs entweder ordnungsgemäß beenden oder nach einer konfigurierbaren Anzahl von Sekunden abbrechen.",
"buildAgentsResumed": "Anfrage zum Fortsetzen aller Build-Agenten erfolgreich gesendet. Die Agenten werden wieder neue Build-Jobs annehmen."
"buildAgentsResumed": "Anfrage zum Fortsetzen aller Build-Agenten erfolgreich gesendet. Die Agenten werden wieder neue Build-Jobs annehmen.",
"distributedDataCleared": "Verteilte Daten erfolgreich gelöscht.",
"distributedDataClearFailed": "Löschen der verteilten Daten fehlgeschlagen."
},
"pause": "Anhalten",
"resume": "Fortsetzen",
"pauseAll": "Alle Agenten Anhalten",
"resumeAll": "Alle Agenten fortsetzen",
"pauseAllWarning": "Du bist dabei, alle Build-Agenten anzuhalten. Dies wird verhindern, dass sie neue Build-Jobs verarbeiten.\nBist du sicher, dass du fortfahren möchtest?"
"pauseAllWarning": "Du bist dabei, alle Build-Agenten anzuhalten. Dies wird verhindern, dass sie neue Build-Jobs verarbeiten.\nBist du sicher, dass du fortfahren möchtest?",
"clearDistributedData": {
"title": "Verteilte Daten löschen",
"descriptionFirst": "Diese Aktion wird alle verteilten Daten löschen. Dazu gehören die BuildJob Queue, die processingJobs Map, die DockerImageCleanUp Map, die BuildAgents Map und die BuildJobResults Queue.",
BBesrour marked this conversation as resolved.
Show resolved Hide resolved
"descriptionSecond": "Diese Aktion ist irreversibel und führt zu Datenverlust. Das System benötigt einige Minuten, um wieder in den Normalbetrieb zurückzukehren.",
"descriptionThird": "Wenn du sicher bist, dass du fortfahren möchtest, gib bitte 'CLEAR DATA' in das untenstehende Eingabefeld ein und klicke auf die Schaltfläche 'Daten löschen'.",
"clearData": "Daten löschen"
}
}
}
}
13 changes: 11 additions & 2 deletions src/main/webapp/i18n/en/buildAgents.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,22 @@
"buildAgentResumeFailed": "Failed to resume build agent.",
"buildAgentWithoutName": "Build agent name is required.",
"buildAgentsPaused": "Pause request sent to all build agents. The agents will not accept new build jobs and will gracefully finish the current build jobs or will cancel them after a configurable seconds.",
"buildAgentsResumed": "Resume request sent to all build agents. The agents will start accepting new build jobs."
"buildAgentsResumed": "Resume request sent to all build agents. The agents will start accepting new build jobs.",
"distributedDataCleared": "Distributed data cleared successfully.",
"distributedDataClearFailed": "Failed to clear distributed data."
},
"pause": "Pause",
"resume": "Resume",
"pauseAll": "Pause All Agents",
"resumeAll": "Resume All Agents",
"pauseAllWarning": "You are about to pause all build agents. This will prevent them from processing any new build jobs.\nAre you sure you want to proceed?"
"pauseAllWarning": "You are about to pause all build agents. This will prevent them from processing any new build jobs.\nAre you sure you want to proceed?",
"clearDistributedData": {
"title": "Clear Distributed Data",
"descriptionFirst": "This action will clear all distributed data. This includes the buildJob queue, the processingJobs map, the dockerImageCleanUp map, the buildAgents map, and the buildJobResults queue.",
"descriptionSecond": "This action is irreversible and will lead in data loss. The system will take a few minutes to return to normal operation.",
"descriptionThird": "If you are sure you want to proceed, please type 'CLEAR DATA' in the input field below and click the 'Clear Data' button.",
"clearData": "Clear Data"
}
}
}
}
Loading
Loading