Skip to content

Commit

Permalink
Merge pull request #3463 from mercedes-benz/feature-390-zero-downtime…
Browse files Browse the repository at this point in the history
…-sechub-deployment

Provide zero downtime sechub deployment
  • Loading branch information
de-jcup authored Sep 30, 2024
2 parents 67283f4 + 7744f8b commit d598074
Show file tree
Hide file tree
Showing 106 changed files with 2,578 additions and 593 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ protected AdapterExecutionResult execute(PDSAdapterConfig config, AdapterRuntime

assertThreadNotInterrupted();

/* create and configure current PDS context object */
PDSContext pdsContext = contextFactory.create(config, this, runtimeContext);
handleResilienceConfiguration(pdsContext);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,17 +61,21 @@ protected String createAdapterId() {
@Override
public final AdapterExecutionResult start(C config, AdapterMetaDataCallback callback) throws AdapterException {
AdapterRuntimeContext runtimeContext = new AdapterRuntimeContext();
/*
* callback is from product executor and resolves adapter meta data for the
* product result (which is reused on soft restart of job
*/
runtimeContext.callback = callback;
runtimeContext.metaData = callback.getMetaDataOrNull();

if (runtimeContext.metaData == null) {

/* not reused, we need a complete new PDS job */
runtimeContext.metaData = new AdapterMetaData();
runtimeContext.metaData.adapterVersion = getAdapterVersion();
runtimeContext.type = ExecutionType.INITIAL;

} else {

/* reuse existing PDS job means we have a restart */
runtimeContext.type = ExecutionType.RESTART;

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@
import org.springframework.stereotype.Component;

import com.mercedesbenz.sechub.domain.administration.config.AdministrationConfigService;
import com.mercedesbenz.sechub.sharedkernel.Step;
import com.mercedesbenz.sechub.sharedkernel.messaging.AdministrationConfigMessage;
import com.mercedesbenz.sechub.sharedkernel.messaging.AsynchronMessageHandler;
import com.mercedesbenz.sechub.sharedkernel.messaging.DomainMessage;
import com.mercedesbenz.sechub.sharedkernel.messaging.IsReceivingAsyncMessage;
import com.mercedesbenz.sechub.sharedkernel.messaging.JobMessage;
import com.mercedesbenz.sechub.sharedkernel.messaging.MessageDataKeys;
import com.mercedesbenz.sechub.sharedkernel.messaging.MessageID;
import com.mercedesbenz.sechub.sharedkernel.usecases.other.UseCaseSystemSuspendsJobsWhenSigTermReceived;

@Component
public class JobAdministrationMessageHandler implements AsynchronMessageHandler {
Expand Down Expand Up @@ -44,6 +46,9 @@ public void receiveAsyncMessage(DomainMessage request) {
case JOB_FAILED:
handleJobFailed(request);
break;
case JOB_SUSPENDED:
handleJobSuspended(request);
break;
case JOB_CANCELLATION_RUNNING:
handleJobCancellationRunning(request);
break;
Expand Down Expand Up @@ -86,4 +91,13 @@ private void handleJobFailed(DomainMessage request) {
deleteService.delete(message.getJobUUID());
}

@IsReceivingAsyncMessage(MessageID.JOB_SUSPENDED)
@UseCaseSystemSuspendsJobsWhenSigTermReceived(@Step(number = 7, name = "Administration handles suspended job", description = "Administration domain removes suspended job from its running job list"))
private void handleJobSuspended(DomainMessage request) {
JobMessage message = request.get(MessageDataKeys.JOB_SUSPENDED_DATA);
// we do drop job info - we only hold running and waiting jobs. The suspended
// job will be restarted and appear later again.
deleteService.delete(message.getJobUUID());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ private void updateSchedulerJobInformation(SchedulerMessage status) {
saveStatusEntry(SchedulerStatusEntryKeys.SCHEDULER_JOBS_STARTED, status.getAmountOfJobsStarted());
saveStatusEntry(SchedulerStatusEntryKeys.SCHEDULER_JOBS_CANCEL_REQUESTED, status.getAmountOfJobsCancelRequested());
saveStatusEntry(SchedulerStatusEntryKeys.SCHEDULER_JOBS_CANCELED, status.getAmountOfJobsCanceled());
saveStatusEntry(SchedulerStatusEntryKeys.SCHEDULER_JOBS_SUSPENDED, status.getAmountOfJobsSuspended());
saveStatusEntry(SchedulerStatusEntryKeys.SCHEDULER_JOBS_ENDED, status.getAmountOfJobsEnded());

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ public enum SchedulerStatusEntryKeys implements StatusEntryKey {

SCHEDULER_JOBS_CANCEL_REQUESTED("status.scheduler.jobs.cancel_requested"),

SCHEDULER_JOBS_RESUMING("status.scheduler.jobs.resuming"),

SCHEDULER_JOBS_SUSPENDED("status.scheduler.jobs.suspended"),

SCHEDULER_JOBS_ENDED("status.scheduler.jobs.ended"),

;
Expand Down
206 changes: 93 additions & 113 deletions sechub-api-java/src/main/resources/reduced-openapi3.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ private int compareOnMessageIfTypeIsSame(SecHubMessage otherNotNull) {
if (text == null) {
return -1;
}
if (otherNotNull.text == null) {
return 1;
}
return text.compareTo(otherNotNull.text);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
// SPDX-License-Identifier: MIT
package com.mercedesbenz.sechub.commons.model.job;

import com.mercedesbenz.sechub.commons.core.MustBeKeptStable;

/**
* Represents the execution state of a scheduled SecHub job
* Represents the execution state of a scheduled SecHub job.
*
* Attention: never change existing enum values because they are used for
* persistence as identifiers! Only add new ones!
*
* @author Albert Tregnaghi
*
*/
@MustBeKeptStable
public enum ExecutionState {

INITIALIZING("Initializing. E.g. Workspace has pending uploads etc."),
Expand All @@ -19,7 +25,13 @@ public enum ExecutionState {

CANCELED("The job has been canceled"),

ENDED("Has ended - with failure or success");
SUSPENDED("The job has been suspended and can be resumed by another SecHub instance"),

RESUMING("A former suspended job is resuming"),

ENDED("Has ended - with failure or success"),

;

private String description;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,38 @@

class SecHubMessageTest {

@Test
void compare_with_null_works() {
/* prepare */
SecHubMessage info1 = new SecHubMessage(SecHubMessageType.INFO, "info1");

/* execute */
int compareResult = info1.compareTo(null);

/* test */
assertEquals(1, compareResult);

}

@Test
void compare_with_message_without_text_works() {
/* prepare */
SecHubMessage info1 = new SecHubMessage(SecHubMessageType.INFO, "info1");
SecHubMessage info2 = new SecHubMessage(SecHubMessageType.INFO, null);

/* execute 1 */
int compareResult = info1.compareTo(info2);

/* test 1 */
assertEquals(1, compareResult);

/* execute 2 */
compareResult = info2.compareTo(info1);

/* test 1 */
assertEquals(-1, compareResult);
}

/* Why we need ordering? so results are always same listed when recreated */
@Test
void types_ordering_is_error_warn_than_info_in_a_treeset() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
hide empty fields
hide empty methods

'You can find more examles at https://plantuml.com/class-diagram
'You can find more examples at https://plantuml.com/class-diagram

!include module_sechub_job.puml
!include module_sechub_eventbus.puml
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
@startuml

'Hide empty parts:
hide empty fields
hide empty methods

'You can find more examples at https://plantuml.com/class-diagram

package com.mercedesbenz.sechub.adapter {

interface AdapterMetaDataCallback{
AdapterMetaData getMetaDataOrNull()
persist(AdapterMetaData data)
}

class AdapterMetaData {
setValue(String key, String value)
String getValue(String key)
}

interface Adapter {
AdapterExecutionResult start(C config, AdapterMetaDataCallback callback)
}

class PDSAdapterV1 implements Adapter{

}
}


package com.mercedesbenz.sechub.domain.scan {


class ScanService {
startScan()
}

class ScanJobExecutor{
}

class ProductResult {
UUID getSecHubJobUUID()
UUID getProductExecutorConfigUUID()
}

class AbstractProductExecutionService {
runOnExecutorWithOneConfiguration()
}

interface ProductExecutor {
execute(SecHubExecutionContext context, P param)
}

interface ProductExecutorCallback extends AdapterMetaDataCallback{
ProductResult getProductResult()
}
class ProductExecutorContextFactory
class ProductExecutorCallbackImpl implements ProductExecutorCallback

class ScanJobExecutionRunnable {
}
}



note top of PDSAdapterV1
If meta data is available, the PDS adapter tries to fetch
former PDS job UUID from meta data of product result.

If the PDS job uuid is null, a new PDS job is created, otherwise the
existing PDS job will be reused and the the current state handled.
end note

ProductExecutorContextFactory -> ProductExecutorCallbackImpl : creates
AbstractProductExecutionService -> ProductExecutorContextFactory: calls to create callback instance
AbstractProductExecutionService "1" *-- "many" ProductExecutor
ProductExecutorCallback --> AdapterMetaData : provides

ProductResult <-- AbstractProductExecutionService : loads former product result for same executor and sechub job uuid
PDSAdapterV1 --> AdapterMetaData : stores and reads
PDSAdapterV1 -> AdapterMetaDataCallback: uses

ProductExecutor -> Adapter : uses to communicate

ScanJobExecutionRunnable --> AbstractProductExecutionService: calls multiple implementations

package com.mercedesbenz.sechub.domain.schedule {

class ScheduleMessageHandler {
handleJobRestartRequested()
}

class SchedulerRestartJobService {
restartJob(UUID jobUUID, String ownerEmailAddress)
- markJobAsNewExecutedNow(ScheduleSecHubJob secHubJob)
}

class ScheduleJobLauncherService {
executeJob(ScheduleSecHubJob secHubJob)
}
}


package com.mercedesbenz.sechub.domain.administration {


class JobRestartRequestService{
restartJob(UUID sechubJobUUID)
}
}

class DomainMessageService {
}

JobRestartRequestService -[bold,#blue]> DomainMessageService: (1) REQEUST RESTART JOB (soft)
DomainMessageService -[bold,#blue]> ScheduleMessageHandler : (2) REQEUST RESTART JOB (soft)
ScheduleMessageHandler -[bold,#blue]> SchedulerRestartJobService

SchedulerRestartJobService -[bold,#green]> ScheduleJobLauncherService

ScheduleJobLauncherService -[bold,#green]> DomainMessageService : (3) START_SCAN (synchron)

DomainMessageService -[bold,#green]> ScanService: (4) START_SCAN (synchron)


ScanService -[bold,#green]> ScanJobExecutor

ScanJobExecutor -> ScanJobExecutionRunnable
@enduml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
@startuml

'Hide empty parts:
hide empty fields
hide empty methods

'You can find more examples at https://plantuml.com/class-diagram

package com.mercedesbenz.sechub.domain.schedule {

class SchedulerJobBatchTriggerService {
void triggerExecutionOfNextJob()
}

class SchedulerNextJobResolver {
UUID resolveNextJobUUID();
}

class ScheduleJobMarkerService {
}

class ScheduleResumeJobService {
void resume(ScheduleSecHubJob sechubJob)
}

database DB {
entity ScheduleSecHubJob {
}
}

}


node EventBus {
}

node springcontainer as "Spring boot container" {
}

cloud restartProcess as "Restart job handling" {
}

SchedulerJobBatchTriggerService --> ScheduleJobMarkerService
ScheduleResumeJobService ...> EventBus: REQUEST_JOB_RESTART
restartProcess <. EventBus: REQUEST_JOB_RESTART
SchedulerNextJobResolver <-- ScheduleJobMarkerService
SchedulerNextJobResolver --> ScheduleSecHubJob
SchedulerJobBatchTriggerService --> ScheduleResumeJobService : when RESUMING
ScheduleJobMarkerService ..> ScheduleSecHubJob :updates execution state to RESUMING\nwhen jobs was in state SUSPENDED\n


springcontainer --[#darkgreen,bold]> SchedulerJobBatchTriggerService: scheduled


note top of SchedulerNextJobResolver
At first job uuids of
suspended jobs are resolved.

If no suspended job shall be executed,
the selected schedule strategy is used
to resolve the next job.
end note

@enduml
Loading

0 comments on commit d598074

Please sign in to comment.