From 683f52090361740461e8559833041061adbb2d96 Mon Sep 17 00:00:00 2001 From: Albert Tregnaghi Date: Wed, 25 Sep 2024 17:37:39 +0200 Subject: [PATCH] Implemented resume handling #390 --- .../scheduler/SchedulerStatusEntryKeys.java | 2 + .../src/main/resources/reduced-openapi3.json | 206 ++++++++---------- .../commons/model/job/ExecutionState.java | 6 +- .../sechub/docgen/util/DocGeneratorUtil.java | 5 +- .../sechub/integrationtest/api/AsUser.java | 46 +++- .../api/IntegrationTestSetup.java | 6 +- .../sechub/integrationtest/api/TestAPI.java | 38 ++-- ...SigTermSimulationJobScenario11IntTest.java | 52 +++-- ...JobUsecasesEventTraceScenario4IntTest.java | 8 +- ...anceHasBeenStartedNotificationService.java | 3 +- .../SerecoProductResultTransformer.java | 3 +- ...ntegrationTestSchedulerRestController.java | 6 +- .../schedule/ScheduleJobMarkerService.java | 27 ++- .../schedule/ScheduleResumeJobService.java | 49 +++++ .../SchedulerJobBatchTriggerService.java | 15 +- .../ScheduleCipherPoolCleanupService.java | 6 +- .../job/SecHubJobRepositoryCustom.java | 11 +- .../schedule/job/SecHubJobRepositoryImpl.java | 29 ++- .../FirstComeFirstServeSchedulerStrategy.java | 17 +- ...ojectAndModuleGroupAtSameTimeStrategy.java | 17 +- ...lyOneScanPerProjectAtSameTimeStrategy.java | 17 +- .../strategy/SchedulerNextJobResolver.java | 79 +++++++ .../schedule/strategy/SchedulerStrategy.java | 6 +- ...ry.java => SchedulerStrategyProvider.java} | 4 +- .../domain/schedule/job/JobCreator.java | 5 + .../job/SecHubJobRepositoryDBTest.java | 123 +++++++++++ ...stComeFirstServeSchedulerStrategyTest.java | 11 +- ...tAndModuleGroupAtSameTimeStrategyTest.java | 16 +- ...eScanPerProjectAtSameTimeStrategyTest.java | 16 +- .../SchedulerNextJobResolverTest.java | 114 ++++++++++ ...ava => SchedulerStrategyProviderTest.java} | 6 +- .../server/SecHubServerFlywayFactory.java | 3 +- .../server/SecHubSystemPropertyInjector.java | 3 +- .../ScheduleJobMarkerServiceTest.java | 41 ++-- .../DocumentationScopeConstants.java | 21 ++ .../sechub/sharedkernel/MustBeDocumented.java | 16 +- .../storage/S3PropertiesSetup.java | 27 +-- .../storage/SharedVolumePropertiesSetup.java | 3 +- 38 files changed, 761 insertions(+), 302 deletions(-) create mode 100644 sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/ScheduleResumeJobService.java create mode 100644 sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/strategy/SchedulerNextJobResolver.java rename sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/strategy/{SchedulerStrategyFactory.java => SchedulerStrategyProvider.java} (97%) create mode 100644 sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/strategy/SchedulerNextJobResolverTest.java rename sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/strategy/{SchedulerStrategyFactoryTest.java => SchedulerStrategyProviderTest.java} (96%) create mode 100644 sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/DocumentationScopeConstants.java diff --git a/sechub-administration/src/main/java/com/mercedesbenz/sechub/domain/administration/scheduler/SchedulerStatusEntryKeys.java b/sechub-administration/src/main/java/com/mercedesbenz/sechub/domain/administration/scheduler/SchedulerStatusEntryKeys.java index 1ed3b3a216..2e2a4de99e 100644 --- a/sechub-administration/src/main/java/com/mercedesbenz/sechub/domain/administration/scheduler/SchedulerStatusEntryKeys.java +++ b/sechub-administration/src/main/java/com/mercedesbenz/sechub/domain/administration/scheduler/SchedulerStatusEntryKeys.java @@ -18,6 +18,8 @@ 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"), diff --git a/sechub-api-java/src/main/resources/reduced-openapi3.json b/sechub-api-java/src/main/resources/reduced-openapi3.json index 192b2d741e..d44eb27cf5 100644 --- a/sechub-api-java/src/main/resources/reduced-openapi3.json +++ b/sechub-api-java/src/main/resources/reduced-openapi3.json @@ -305,119 +305,57 @@ "title": "FalsePositives", "type": "object", "properties": { - "falsePositives": { + "projectData": { "type": "array", - "description": "Job data list containing false positive setup based on former jobs", + "description": "Porject data list containing false positive setup for the project", "items": { "type": "object", "properties": { - "metaData": { + "webScan": { "type": "object", "properties": { - "severity": { - "type": "string", - "description": "Severity of origin report entry marked as false positive" - }, - "code": { - "type": "object", - "properties": { - "start": { - "type": "object", - "properties": { - "sourceCode": { - "type": "string", - "description": "source code" - }, - "relevantPart": { - "type": "string", - "description": "relevant part of source vulnerability" - }, - "location": { - "type": "string", - "description": "location of code" - } - }, - "description": "entry point" - }, - "end": { - "type": "object", - "properties": { - "sourceCode": { - "type": "string", - "description": "source code" - }, - "relevantPart": { - "type": "string", - "description": "relevant part of source vulnerability" - }, - "location": { - "type": "string", - "description": "location of code" - } - }, - "description": "end point (sink)" - } - }, - "description": "Code part. Only available for scan type 'codeScan'" - }, - "owasp": { - "type": "string", - "description": "OWASP At least this field must be set for web scans when no cwe identifier is defined." - }, "cweId": { "type": "number", - "description": "CWE (common weakness enumeration). For code scans this is always set." - }, - "cveId": { - "type": "string", - "description": "CVE (common vulnerability and exposures). For infra scans this is always set." - }, - "name": { - "type": "string", - "description": "Name of origin finding marked as false positive" - }, - "scanType": { - "type": "string", - "description": "Scan type - e.g. codeScan" - } - }, - "description": "Meta data for this false positive" - }, - "jobData": { - "type": "object", - "properties": { - "jobUUID": { - "type": "string", - "description": "SecHub job uuid where finding was" + "description": "Defines a CWE ID for false positives which occur during webscans. This is mandatory, but can be empty. If it is not specified it matches the findings with no CWE IDs." }, - "findingId": { - "type": "number", - "description": "SecHub finding identifier - identifies problem inside the job which shall be markeda as a false positive. *ATTENTION*: at the moment only code scan false positive handling is supported. Infra and web scan findings will lead to a non accepted error!" + "methods": { + "type": "array", + "description": "Defines a list of (HTTP) methods for false positives which occur during webscans. This is optional and if nothing is specified, the entry applies to all methods.", + "items": { + "oneOf": [ + { + "type": "object" + }, + { + "type": "boolean" + }, + { + "type": "string" + }, + { + "type": "number" + } + ] + } }, - "comment": { + "urlPattern": { "type": "string", - "description": "A comment from author describing why this was marked as a false positive" + "description": "Defines a url pattern for false positives which occur during webscans. Can be used with wildcards like '*.host.com'. Each entry must contain more than just wildcards, '*.*.*' or '*' are not allowed." } }, - "description": "Job data parts, can be used as key to identify false positives" + "description": "Defines a section for false positives which occur during webscans." }, - "created": { + "comment": { "type": "string", - "description": "Creation timestamp" + "description": "A comment describing why this is a false positive." }, - "author": { + "id": { "type": "string", - "description": "User id of author who created false positive" + "description": "Identifier which is used to update or remove the respective false positive entry." } } } - } - } - }, - "FalsePositivesForJob": { - "title": "FalsePositivesForJob", - "type": "object", - "properties": { + }, "apiVersion": { "type": "string", "description": "The api version, currently only 1.0 is supported" @@ -434,7 +372,7 @@ }, "findingId": { "type": "number", - "description": "SecHub finding identifier - identifies problem inside the job which shall be markeda as a false positive. *ATTENTION*: at the moment only code scan false positive handling is supported. Infra and web scan findings will lead to a non accepted error!" + "description": "SecHub finding identifier - identifies problem inside the job which shall be markeda as a false positive." }, "comment": { "type": "string", @@ -445,7 +383,7 @@ }, "type": { "type": "string", - "description": "The type of the json content. Currently only accepted value is 'falsePositiveJobDataList'." + "description": "The type of the json content. Currently only accepted value is 'falsePositiveDataList' but we also still accept the deprecated type 'falsePositiveJobDataList'." } } }, @@ -1035,6 +973,19 @@ "webScan": { "type": "object", "properties": { + "maxScanDuration": { + "type": "object", + "properties": { + "duration": { + "type": "number", + "description": "Duration of the scan as integer" + }, + "unit": { + "type": "string", + "description": "Unit of the duration. Possible values are: millisecond(s), second(s), minute(s), hour(s), day(s)" + } + } + }, "headers": { "type": "array", "description": "List of HTTP headers. Can be used for authentication or anything else.", @@ -1070,19 +1021,6 @@ } } }, - "maxScanDuration": { - "type": "object", - "properties": { - "duration": { - "type": "number", - "description": "Duration of the scan as integer" - }, - "unit": { - "type": "string", - "description": "Unit of the duration. Possible values are: millisecond(s), second(s), minute(s), hour(s), day(s)" - } - } - }, "clientCertificate": { "type": "object", "properties": { @@ -1683,7 +1621,7 @@ "201": { "description": "201", "content": { - "application/json": { + "text/plain;charset=UTF-8": { "schema": { "$ref": "#/components/schemas/ExecutorConfigurationId" } @@ -3196,6 +3134,48 @@ } } }, + "/api/project/{projectId}/false-positive/project-data/{id}": { + "delete": { + "tags": [ + "project" + ], + "summary": "User unmarks existing false positive project data definitons", + "description": "User unmarks existing false positive project data definitons", + "operationId": "userUnmarksFalsePositivesProjectData", + "parameters": [ + { + "name": "projectId", + "in": "path", + "description": "The project id", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "id", + "in": "path", + "description": "Identifier which is used to remove the respective false positive entry.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "204" + } + }, + "security": [ + { + "basic": [ + + ] + } + ] + } + }, "/api/project/{projectId}/false-positive/{jobUUID}/{findingId}": { "delete": { "tags": [ @@ -3290,9 +3270,9 @@ "tags": [ "project" ], - "summary": "User marks false positives for finished sechub job", - "description": "User marks false positives for finished sechub job", - "operationId": "userMarksFalsePositivesForJob", + "summary": "User marks false positives", + "description": "User marks false positives", + "operationId": "userMarksFalsePositives", "parameters": [ { "name": "projectId", @@ -3308,7 +3288,7 @@ "content": { "application/json;charset=UTF-8": { "schema": { - "$ref": "#/components/schemas/FalsePositivesForJob" + "$ref": "#/components/schemas/FalsePositives" } } } diff --git a/sechub-commons-model/src/main/java/com/mercedesbenz/sechub/commons/model/job/ExecutionState.java b/sechub-commons-model/src/main/java/com/mercedesbenz/sechub/commons/model/job/ExecutionState.java index 9a4c11ce2f..d25e0dc161 100644 --- a/sechub-commons-model/src/main/java/com/mercedesbenz/sechub/commons/model/job/ExecutionState.java +++ b/sechub-commons-model/src/main/java/com/mercedesbenz/sechub/commons/model/job/ExecutionState.java @@ -27,7 +27,11 @@ public enum ExecutionState { SUSPENDED("The job has been suspended and can be resumed by another SecHub instance"), - ENDED("Has ended - with failure or success"); + RESUMING("A former suspended job is resuming"), + + ENDED("Has ended - with failure or success"), + + ; private String description; diff --git a/sechub-doc/src/main/java/com/mercedesbenz/sechub/docgen/util/DocGeneratorUtil.java b/sechub-doc/src/main/java/com/mercedesbenz/sechub/docgen/util/DocGeneratorUtil.java index ce3eb44ec7..6e4208fe27 100644 --- a/sechub-doc/src/main/java/com/mercedesbenz/sechub/docgen/util/DocGeneratorUtil.java +++ b/sechub-doc/src/main/java/com/mercedesbenz/sechub/docgen/util/DocGeneratorUtil.java @@ -10,6 +10,7 @@ import com.mercedesbenz.sechub.docgen.DocAnnotationData; import com.mercedesbenz.sechub.pds.PDSMustBeDocumented; +import com.mercedesbenz.sechub.sharedkernel.DocumentationScopeConstants; import com.mercedesbenz.sechub.sharedkernel.MustBeDocumented; public class DocGeneratorUtil { @@ -27,7 +28,7 @@ public static DocAnnotationData buildDataForMustBeDocumented(MustBeDocumented in buildSpringScheduledParts(data, element); /* when class name shall be used... */ - if (MustBeDocumented.SCOPE_USE_DEFINED_CLASSNAME_LOWERCASED.equals(data.scope)) { + if (DocumentationScopeConstants.SCOPE_USE_DEFINED_CLASSNAME_LOWERCASED.equals(data.scope)) { data.scope = toCamelOne(fetchClass(element)).toLowerCase(); } return data; @@ -42,7 +43,7 @@ public static DocAnnotationData buildDataForPDSMustBeDocumented(PDSMustBeDocumen buildSpringScheduledParts(data, element); /* when class name shall be used... */ - if (MustBeDocumented.SCOPE_USE_DEFINED_CLASSNAME_LOWERCASED.equals(data.scope)) { + if (DocumentationScopeConstants.SCOPE_USE_DEFINED_CLASSNAME_LOWERCASED.equals(data.scope)) { data.scope = toCamelOne(fetchClass(element)).toLowerCase(); } return data; diff --git a/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/api/AsUser.java b/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/api/AsUser.java index cead2c830d..1e26c38bf5 100644 --- a/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/api/AsUser.java +++ b/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/api/AsUser.java @@ -36,9 +36,11 @@ import com.mercedesbenz.sechub.commons.mapping.MappingData; import com.mercedesbenz.sechub.commons.model.JSONConverter; import com.mercedesbenz.sechub.commons.model.SecHubConfigurationModel; +import com.mercedesbenz.sechub.commons.model.TrafficLight; import com.mercedesbenz.sechub.domain.scan.project.FalsePositiveProjectData; import com.mercedesbenz.sechub.integrationtest.JSONTestSupport; import com.mercedesbenz.sechub.integrationtest.internal.IntegrationTestContext; +import com.mercedesbenz.sechub.integrationtest.internal.IntegrationTestExampleConstants; import com.mercedesbenz.sechub.integrationtest.internal.IntegrationTestFileSupport; import com.mercedesbenz.sechub.integrationtest.internal.IntegrationTestTemplateFile; import com.mercedesbenz.sechub.integrationtest.internal.SecHubClientExecutor.ExecutionResult; @@ -965,12 +967,52 @@ public ProjectFalsePositivesDefinition startFalsePositiveDefinition(TestProject return new ProjectFalsePositivesDefinition(project); } - public UUID triggerAsyncPDSCodeScanWithDifferentDataSections(TestProject project) { + /** + * This triggers a PDS code scan in asynchronous way. It will use + * {@link IntegrationTestExampleConstants#PATH_TO_ZIPFILE_WITH_DIFFERENT_DATA_SECTIONS}. + * It will map wanted traffic light to corresponding reference ids:
+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Traffic lightused reference-id
GREENlow-id
YELLOWmedium-id
REDcritical-id
+ * Please look at + * {@link IntegrationTestExampleConstants#PATH_TO_ZIPFILE_WITH_DIFFERENT_DATA_SECTIONS} + * for the finding details. + * + * @param project - test project + * @param wantedTrafficLight - which kind of report is wanted + * @return uuid of SecHub job + */ + public UUID triggerAsyncPDSCodeScanWithWantedTrafficLightResult(TestProject project, TrafficLight wantedTrafficLight) { /* @formatter:off */ + String referenceId = switch (wantedTrafficLight) { + case GREEN -> "low-id"; + case YELLOW -> "medium-id"; + case RED -> "critical-id"; + default -> { + throw new IllegalArgumentException("Traffic light type: "+wantedTrafficLight+" is not supported by this test helper method!"); + } + }; + + UUID jobUUID = createCodeScanWithTemplate( IntegrationTestTemplateFile.CODE_SCAN_3_SOURCES_DATA_ONE_REFERENCE, project, NOT_MOCKED, - TemplateData.builder().addReferenceId("files-a").build()); + TemplateData.builder().addReferenceId(referenceId).build()); uploadSourcecode(project, jobUUID, PATH_TO_ZIPFILE_WITH_DIFFERENT_DATA_SECTIONS). approveJob(project, jobUUID); diff --git a/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/api/IntegrationTestSetup.java b/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/api/IntegrationTestSetup.java index f1680c112d..0273e82711 100644 --- a/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/api/IntegrationTestSetup.java +++ b/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/api/IntegrationTestSetup.java @@ -30,7 +30,8 @@ import ch.qos.logback.classic.Level; public class IntegrationTestSetup implements TestRule { - // please keep this private static field at top - reason: initialize setup support as soon as possible... + // please keep this private static field at top - reason: initialize setup + // support as soon as possible... private static final LocalDeveloperFileSetupSupport support = LocalDeveloperFileSetupSupport.INSTANCE; public static final String SECHUB_INTEGRATIONTEST_ONLY_NECESSARY_TESTS_FOR_DOCUMENTATION = "sechub.integrationtest.only.necessary4documentation"; public static final String SECHUB_INTEGRATIONTEST_NEVER_NECESSARY_TESTS_FOR_DOCUMENTATION = "sechub.integrationtest.never.necessary4documentation"; @@ -166,8 +167,7 @@ public void evaluate() throws Throwable { Assume.assumeTrue(message, false); } } else { - integrationTestEnabled = support.isAlwaysSecHubIntegrationTestRunning() - || Boolean.getBoolean(SECHUB_INTEGRATIONTEST_RUNNING); + integrationTestEnabled = support.isAlwaysSecHubIntegrationTestRunning() || Boolean.getBoolean(SECHUB_INTEGRATIONTEST_RUNNING); if (!integrationTestEnabled) { String message = "Skipped test scenario '" + scenario.getName() + "'\nReason: not in integration test mode.\nDefine -D" + SECHUB_INTEGRATIONTEST_RUNNING + "=true to enable integration tests!"; diff --git a/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/api/TestAPI.java b/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/api/TestAPI.java index 865896e9ae..a5e40b9fb9 100644 --- a/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/api/TestAPI.java +++ b/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/api/TestAPI.java @@ -357,7 +357,7 @@ public boolean runAndReturnTrueWhenSuccesfulImpl() throws Exception { public static TestSecHubJobStatus getSecHubJobStatus(TestProject project, UUID jobUUID, TestUser asUser) { String status = as(asUser).getJobStatus(project.getProjectId(), jobUUID); - LOG.info(">>>>>>>>>JOB:STATUS:" + status); + LOG.info(" => Job status: {}", status); TestSecHubJobStatus jobStatus = TestSecHubJobStatus.fromJSON(status); return jobStatus; } @@ -443,12 +443,10 @@ public static void waitForJobRunning(TestProject project, int timeOutInSeconds, LOG.info("wait for job running project:{}, job:{}, timeToWaitInMillis{}, timeOutInSeconds:{}", project.getProjectId(), jobUUID, timeToWaitInMillis, timeOutInSeconds); - executeUntilSuccessOrTimeout(new AbstractTestExecutable(SUPER_ADMIN, timeOutInSeconds, timeToWaitInMillis, HttpClientErrorException.class) { + executeUntilSuccessOrTimeout(new AbstractTestExecutable(SUPER_ADMIN, timeOutInSeconds, HttpClientErrorException.class) { @Override public boolean runAndReturnTrueWhenSuccesfulImpl() throws Exception { - String status = as(getUser()).getJobStatus(project.getProjectId(), jobUUID); - LOG.info(">>>>>>>>>JOB:STATUS:" + status); - return status.contains("STARTED"); + return containsStatus(getUser(), project, jobUUID, "STARTED"); } }); } @@ -465,13 +463,24 @@ public static void waitForJobStatusCancelRequestedOrCanceled(TestProject project executeUntilSuccessOrTimeout(new AbstractTestExecutable(SUPER_ADMIN, 5, HttpClientErrorException.class) { @Override public boolean runAndReturnTrueWhenSuccesfulImpl() throws Exception { - String status = as(getUser()).getJobStatus(project.getProjectId(), jobUUID); - LOG.info(">>>>>>>>>JOB:STATUS:" + status); - return status.contains("CANCEL_REQUESTED") || status.contains("CANCELED"); + return containsStatus(getUser(), project, jobUUID, "CANCEL_REQUESTED", "CANCELED"); } }); } + private static boolean containsStatus(TestUser user, TestProject project, UUID jobUUID, String... acceptedContainedStatus) { + String status = as(user).getJobStatus(project.getProjectId(), jobUUID); + LOG.info(">>>>>>>>>JOB:STATUS:" + status); + + for (String accepted : acceptedContainedStatus) { + if (status.contains(accepted)) { + return true; + } + } + + return false; + } + /** * Waits for sechub job being finally canceled - after 5 seconds time out is * reached @@ -489,9 +498,7 @@ public static void waitForJobStatusCanceled(TestProject project, UUID jobUUID, b executeUntilSuccessOrTimeout(new AbstractTestExecutable(SUPER_ADMIN, 5, runnable, HttpClientErrorException.class) { @Override public boolean runAndReturnTrueWhenSuccesfulImpl() throws Exception { - String status = as(getUser()).getJobStatus(project.getProjectId(), jobUUID); - LOG.info(">>>>>>>>>JOB:STATUS:" + status); - return status.contains("CANCELED"); + return containsStatus(getUser(), project, jobUUID, "CANCELED"); } }); } @@ -505,13 +512,10 @@ public boolean runAndReturnTrueWhenSuccesfulImpl() throws Exception { */ public static void waitForJobStatusSuspended(TestProject project, UUID jobUUID) { LOG.info("wait for job status is 'suspended'. project:{}, job:{}", project.getProjectId(), jobUUID); - executeUntilSuccessOrTimeout(new AbstractTestExecutable(SUPER_ADMIN, 5, HttpClientErrorException.class) { @Override public boolean runAndReturnTrueWhenSuccesfulImpl() throws Exception { - String status = as(getUser()).getJobStatus(project.getProjectId(), jobUUID); - LOG.info(">>>>>>>>>JOB:STATUS:" + status); - return status.contains("SUSPENDED"); + return containsStatus(getUser(), project, jobUUID, "SUSPENDED"); } }); } @@ -528,9 +532,7 @@ public static void waitForJobStatusFailed(TestProject project, UUID jobUUID) { executeUntilSuccessOrTimeout(new AbstractTestExecutable(SUPER_ADMIN, 5, HttpClientErrorException.class) { @Override public boolean runAndReturnTrueWhenSuccesfulImpl() throws Exception { - String status = as(getUser()).getJobStatus(project.getProjectId(), jobUUID); - LOG.info(">>>>>>>>>JOB:STATUS:" + status); - return status.contains("FAILED"); + return containsStatus(getUser(), project, jobUUID, "FAILED"); } }); } diff --git a/sechub-integrationtest/src/test/java/com/mercedesbenz/sechub/integrationtest/scenario11/SigTermSimulationJobScenario11IntTest.java b/sechub-integrationtest/src/test/java/com/mercedesbenz/sechub/integrationtest/scenario11/SigTermSimulationJobScenario11IntTest.java index 9e63eb4776..a9d56aaf6f 100644 --- a/sechub-integrationtest/src/test/java/com/mercedesbenz/sechub/integrationtest/scenario11/SigTermSimulationJobScenario11IntTest.java +++ b/sechub-integrationtest/src/test/java/com/mercedesbenz/sechub/integrationtest/scenario11/SigTermSimulationJobScenario11IntTest.java @@ -13,11 +13,12 @@ import org.junit.Test; import org.junit.rules.Timeout; +import com.mercedesbenz.sechub.commons.model.TrafficLight; import com.mercedesbenz.sechub.commons.pds.data.PDSJobStatusState; import com.mercedesbenz.sechub.integrationtest.api.IntegrationTestSetup; -import com.mercedesbenz.sechub.integrationtest.api.TestAPI; import com.mercedesbenz.sechub.integrationtest.api.TestProject; import com.mercedesbenz.sechub.integrationtest.api.TestSecHubJobInfoForUserListPage; +import com.mercedesbenz.sechub.integrationtest.api.TestUser; /** * Integration tests to check SIGTERM handling operations work @@ -42,19 +43,23 @@ public class SigTermSimulationJobScenario11IntTest { * * The PDS job will run in background without stop. When new SecHub server comes * up (we simulate by turning off termination flag in integration test mode...) - * the result from PDS are reused (if the PDS job is not already done SecHub - * will wait and reuse - so we have no race condition problems here) + * the result from PDS is reused (if the PDS job is not already done SecHub will + * wait and reuse - so we have no race condition problems here) */ public void simulate_SecHub_SIGTERM_handling_leads_to_restart_of_SecHub_job_and_reuse_of_pds_job() { + TestUser user = USER_1; + boolean resetCalled = false; try { /* @formatter:off */ /* check preconditions */ - assertThat(TestAPI.isSecHubTerminating()).describedAs("Ensure the server is not already terminating").isFalse(); + assertThat(isSecHubTerminating()). + describedAs("Ensure the server is not already terminating"). + isFalse(); - /* prepare */ - UUID sechubJobUUD = as(USER_1).triggerAsyncPDSCodeScanWithDifferentDataSections(project); + /* prepare 1 */ + UUID sechubJobUUD = as(user).triggerAsyncPDSCodeScanWithWantedTrafficLightResult(project, TrafficLight.YELLOW); waitForJobRunning(project, sechubJobUUD); UUID pdsJobUUID = waitForFirstPDSJobOfSecHubJobAndReturnPDSJobUUID(sechubJobUUD); waitForPDSJobInState(PDSJobStatusState.RUNNING, 2000,100,pdsJobUUID,true); @@ -67,29 +72,48 @@ public void simulate_SecHub_SIGTERM_handling_leads_to_restart_of_SecHub_job_and_ assertPDSJobStatus(pdsJobUUID).isInState(PDSJobStatusState.RUNNING); // pds job is still running // fetch last user job - must be the one we have created here... - TestSecHubJobInfoForUserListPage jobInfo = as(USER_1). + TestSecHubJobInfoForUserListPage jobInfo = as(user). fetchUserJobInfoListOneEntryOrNull(project); - assertUserJobInfo(jobInfo). + + assertUserJobInfo(jobInfo). hasJobInfoFor(sechubJobUUD). withEndedTimeStampNotNull(). withExecutionResult("NONE"); // no result - /* execute 2 - we simulate here a new server start - means without termination flag set -> job processing will start again and processes old job */ + /* prepare 2 - we simulate here a new server start - means without termination + * flag set -> job processing will start again and processes old job + * like a new server would do + */ resetSecHubTerminationService(); + resetCalled=true; + + /* test 2 - check suspended report will resume and be done automatically */ + waitForJobDone(project, sechubJobUUD, 15, true); - /* test 2 */ - waitForJobDone(project, sechubJobUUD, 5, true); + /* test 3 - check report is as expected */ + String report = as(user).getJobReport(project, sechubJobUUD); + assertReportUnordered(report). + hasTrafficLight(TrafficLight.YELLOW). + finding().description("i am a medium error").isContained(); + + /* test 4 - check only ONE PDS job was used */ List relatedPSjobUUIDs = fetchAllPDSJobUUIDsForSecHubJob(sechubJobUUD); assertThat(relatedPSjobUUIDs). describedAs("Check that ONE PDS job has been reused by SecHub job which was suspended and now restarted"). hasSize(1). contains(pdsJobUUID); + /* @formatter:on */ } finally { - // we always reset the termination service to avoid cross site effects on other - // tests or on restart of this test - TestAPI.resetSecHubTerminationService(); + if (!resetCalled) { + // IMPORTANT: + // ----------- + // we MUST ensure termination service reset is done to avoid cross site effects + // on other + // tests when reset was not called in former try block! + resetSecHubTerminationService(); + } } } } \ No newline at end of file diff --git a/sechub-integrationtest/src/test/java/com/mercedesbenz/sechub/integrationtest/scenario4/JobUsecasesEventTraceScenario4IntTest.java b/sechub-integrationtest/src/test/java/com/mercedesbenz/sechub/integrationtest/scenario4/JobUsecasesEventTraceScenario4IntTest.java index ae9ae5db8d..7464f89c65 100644 --- a/sechub-integrationtest/src/test/java/com/mercedesbenz/sechub/integrationtest/scenario4/JobUsecasesEventTraceScenario4IntTest.java +++ b/sechub-integrationtest/src/test/java/com/mercedesbenz/sechub/integrationtest/scenario4/JobUsecasesEventTraceScenario4IntTest.java @@ -120,7 +120,7 @@ public void UC_ADMIN_RESTARTS_JOB_HARD__simulate_jvm_crash_but_product_results_i to("com.mercedesbenz.sechub.domain.scan.ScanService"). /* 7 */ syncEvent(MessageID.REQUEST_SCHEDULER_JOB_STATUS). - from("com.mercedesbenz.sechub.domain.scan.ScanProgressMonitor"). + from("com.mercedesbenz.sechub.domain.scan.ScanProgressStateFetcher"). to("com.mercedesbenz.sechub.domain.schedule.job.SchedulerJobStatusRequestHandler"). /* 8 */ asyncEvent(MessageID.JOB_DONE). @@ -182,7 +182,7 @@ public void UC_ADMIN_RESTARTS_JOB_HARD__simulate_jvm_crash_no_product_results_in to("com.mercedesbenz.sechub.domain.scan.ScanService"). /* 6 */ syncEvent(MessageID.REQUEST_SCHEDULER_JOB_STATUS). - from("com.mercedesbenz.sechub.domain.scan.ScanProgressMonitor"). + from("com.mercedesbenz.sechub.domain.scan.ScanProgressStateFetcher"). to("com.mercedesbenz.sechub.domain.schedule.job.SchedulerJobStatusRequestHandler"). /* 7 */ asyncEvent(MessageID.JOB_DONE). @@ -279,7 +279,7 @@ public void UC_ADMIN_RESTARTS_JOB__simulate_jvm_crash_but_product_results_in_db( to("com.mercedesbenz.sechub.domain.scan.ScanService"). /* 5 */ syncEvent(MessageID.REQUEST_SCHEDULER_JOB_STATUS). - from("com.mercedesbenz.sechub.domain.scan.ScanProgressMonitor"). + from("com.mercedesbenz.sechub.domain.scan.ScanProgressStateFetcher"). to("com.mercedesbenz.sechub.domain.schedule.job.SchedulerJobStatusRequestHandler"). /* 6 */ asyncEvent(MessageID.JOB_DONE). @@ -338,7 +338,7 @@ public void UC_ADMIN_RESTARTS_JOB__simulate_jvm_crash_no_product_results_in_db_u to("com.mercedesbenz.sechub.domain.scan.ScanService"). /* 5 */ syncEvent(MessageID.REQUEST_SCHEDULER_JOB_STATUS). - from("com.mercedesbenz.sechub.domain.scan.ScanProgressMonitor"). + from("com.mercedesbenz.sechub.domain.scan.ScanProgressStateFetcher"). to("com.mercedesbenz.sechub.domain.schedule.job.SchedulerJobStatusRequestHandler"). /* 6 */ asyncEvent(MessageID.JOB_DONE). diff --git a/sechub-notification/src/main/java/com/mercedesbenz/sechub/domain/notification/superadmin/InformAdminsThatNewSchedulerInstanceHasBeenStartedNotificationService.java b/sechub-notification/src/main/java/com/mercedesbenz/sechub/domain/notification/superadmin/InformAdminsThatNewSchedulerInstanceHasBeenStartedNotificationService.java index 8e40e082bc..e6a2a66693 100644 --- a/sechub-notification/src/main/java/com/mercedesbenz/sechub/domain/notification/superadmin/InformAdminsThatNewSchedulerInstanceHasBeenStartedNotificationService.java +++ b/sechub-notification/src/main/java/com/mercedesbenz/sechub/domain/notification/superadmin/InformAdminsThatNewSchedulerInstanceHasBeenStartedNotificationService.java @@ -11,6 +11,7 @@ import com.mercedesbenz.sechub.domain.notification.NotificationConfiguration; import com.mercedesbenz.sechub.domain.notification.email.EmailService; import com.mercedesbenz.sechub.domain.notification.email.MailMessageFactory; +import com.mercedesbenz.sechub.sharedkernel.DocumentationScopeConstants; import com.mercedesbenz.sechub.sharedkernel.MustBeDocumented; import com.mercedesbenz.sechub.sharedkernel.Step; import com.mercedesbenz.sechub.sharedkernel.messaging.ClusterMemberMessage; @@ -22,7 +23,7 @@ public class InformAdminsThatNewSchedulerInstanceHasBeenStartedNotificationServi private static final Logger LOG = LoggerFactory.getLogger(InformAdminsThatNewSchedulerInstanceHasBeenStartedNotificationService.class); @Value("${sechub.notification.scheduler.startup.enabled:true}") - @MustBeDocumented(scope = "administration", value = "When enabled, administrators will be informed by notification " + @MustBeDocumented(scope = DocumentationScopeConstants.SCOPE_ADMINISTRATION, value = "When enabled, administrators will be informed by notification " + "when new scheduler instances are started. " + "Those notifications will also contain information about potential zombie jobs.\n\n" + " When disabled, incoming events will be ignored and no notification sent.") boolean notificationEnabled; diff --git a/sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoProductResultTransformer.java b/sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoProductResultTransformer.java index 4c20782c43..a456bb9d05 100644 --- a/sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoProductResultTransformer.java +++ b/sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoProductResultTransformer.java @@ -48,6 +48,7 @@ import com.mercedesbenz.sechub.sereco.metadata.SerecoWebEvidence; import com.mercedesbenz.sechub.sereco.metadata.SerecoWebRequest; import com.mercedesbenz.sechub.sereco.metadata.SerecoWebResponse; +import com.mercedesbenz.sechub.sharedkernel.DocumentationScopeConstants; import com.mercedesbenz.sechub.sharedkernel.MustBeDocumented; import com.mercedesbenz.sechub.sharedkernel.ProductIdentifier; @@ -58,7 +59,7 @@ public class SerecoProductResultTransformer implements ReportProductResultTransf SerecoFalsePositiveMarker falsePositiveMarker; @Value("${sechub.feature.showProductResultLink:false}") - @MustBeDocumented(scope = "administration", value = "Administrators can turn on this mode to allow product links in json and HTML output") + @MustBeDocumented(scope = DocumentationScopeConstants.SCOPE_ADMINISTRATION, value = "Administrators can turn on this mode to allow product links in json and HTML output") boolean showProductLineResultLink; private static final Logger LOG = LoggerFactory.getLogger(SerecoProductResultTransformer.class); diff --git a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/IntegrationTestSchedulerRestController.java b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/IntegrationTestSchedulerRestController.java index 096296300c..9e96565e19 100644 --- a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/IntegrationTestSchedulerRestController.java +++ b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/IntegrationTestSchedulerRestController.java @@ -18,7 +18,7 @@ import com.mercedesbenz.sechub.domain.schedule.encryption.ScheduleCipherPoolCleanupService; import com.mercedesbenz.sechub.domain.schedule.job.ScheduleSecHubJob; import com.mercedesbenz.sechub.domain.schedule.job.SecHubJobRepository; -import com.mercedesbenz.sechub.domain.schedule.strategy.SchedulerStrategyFactory; +import com.mercedesbenz.sechub.domain.schedule.strategy.SchedulerStrategyProvider; import com.mercedesbenz.sechub.sharedkernel.APIConstants; import com.mercedesbenz.sechub.sharedkernel.Profiles; @@ -40,7 +40,7 @@ public class IntegrationTestSchedulerRestController { private IntegrationTestSchedulerService integrationTestSchedulerService; @Autowired - private SchedulerStrategyFactory schedulerStrategyFactory; + private SchedulerStrategyProvider schedulerStrategyProvider; @Autowired private SchedulerConfigService scheduleConfigService; @@ -94,7 +94,7 @@ public void revertJobAsStillNotApproved(@PathVariable("sechubJobUUID") UUID sech @RequestMapping(path = APIConstants.API_ANONYMOUS + "integrationtest/scheduler/strategy/{strategyId}", method = RequestMethod.PUT, produces = { MediaType.APPLICATION_JSON_VALUE }) public void setSchedulerStrategy(@PathVariable("strategyId") String strategyId) { - schedulerStrategyFactory.setStrategyIdentifier(strategyId); + schedulerStrategyProvider.setStrategyIdentifier(strategyId); } @RequestMapping(path = APIConstants.API_ANONYMOUS diff --git a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/ScheduleJobMarkerService.java b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/ScheduleJobMarkerService.java index 2a5221b96f..b9b060c77f 100644 --- a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/ScheduleJobMarkerService.java +++ b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/ScheduleJobMarkerService.java @@ -21,8 +21,7 @@ import com.mercedesbenz.sechub.domain.schedule.job.ScheduleSecHubJob; import com.mercedesbenz.sechub.domain.schedule.job.ScheduleSecHubJobMessagesSupport; import com.mercedesbenz.sechub.domain.schedule.job.SecHubJobRepository; -import com.mercedesbenz.sechub.domain.schedule.strategy.SchedulerStrategy; -import com.mercedesbenz.sechub.domain.schedule.strategy.SchedulerStrategyFactory; +import com.mercedesbenz.sechub.domain.schedule.strategy.SchedulerNextJobResolver; /** * This service is only responsible to mark next {@link ScheduleSecHubJob} to @@ -41,9 +40,7 @@ public class ScheduleJobMarkerService { SecHubJobRepository jobRepository; @Autowired - SchedulerStrategyFactory schedulerStrategyFactory; - - private SchedulerStrategy schedulerStrategy; + SchedulerNextJobResolver nextJobResolver; private ScheduleSecHubJobMessagesSupport jobMessageSupport = new ScheduleSecHubJobMessagesSupport(); @@ -54,27 +51,29 @@ public class ScheduleJobMarkerService { @Transactional public ScheduleSecHubJob markNextJobToExecuteByThisInstance() { - schedulerStrategy = schedulerStrategyFactory.build(); - if (LOG.isTraceEnabled()) { LOG.trace("Trigger execution of next job started"); } - UUID nextJobId = schedulerStrategy.nextJobId(); + UUID nextJobId = nextJobResolver.resolveNextJob(); if (nextJobId == null) { return null; } - Optional secHubJobOptional = jobRepository.getJob(nextJobId); + Optional secHubJobOptional = jobRepository.getJobWhenExecutable(nextJobId); if (!secHubJobOptional.isPresent()) { - if (LOG.isTraceEnabled()) { - LOG.trace("No job found."); - } + LOG.warn("Did not found job for next job UUID:{}", nextJobId); return null; } ScheduleSecHubJob secHubJob = secHubJobOptional.get(); - secHubJob.setExecutionState(ExecutionState.STARTED); - secHubJob.setStarted(LocalDateTime.now()); + ExecutionState state = secHubJob.getExecutionState(); + + if (ExecutionState.SUSPENDED.equals(state)) { + secHubJob.setExecutionState(ExecutionState.RESUMING); + } else { + secHubJob.setExecutionState(ExecutionState.STARTED); + secHubJob.setStarted(LocalDateTime.now()); + } return jobRepository.save(secHubJob); } diff --git a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/ScheduleResumeJobService.java b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/ScheduleResumeJobService.java new file mode 100644 index 0000000000..ed7fbbb7a9 --- /dev/null +++ b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/ScheduleResumeJobService.java @@ -0,0 +1,49 @@ +package com.mercedesbenz.sechub.domain.schedule; + +import java.util.UUID; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.mercedesbenz.sechub.domain.schedule.job.ScheduleSecHubJob; +import com.mercedesbenz.sechub.sharedkernel.SecHubEnvironment; +import com.mercedesbenz.sechub.sharedkernel.Step; +import com.mercedesbenz.sechub.sharedkernel.messaging.DomainMessage; +import com.mercedesbenz.sechub.sharedkernel.messaging.DomainMessageFactory; +import com.mercedesbenz.sechub.sharedkernel.messaging.DomainMessageService; +import com.mercedesbenz.sechub.sharedkernel.messaging.IsSendingAsyncMessage; +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.UseCaseSystemHandlesSIGTERM; + +@Service +public class ScheduleResumeJobService { + + @Autowired + DomainMessageService eventBusService; + + @Autowired + SecHubEnvironment sechubEnvironment; + + @UseCaseSystemHandlesSIGTERM(@Step(number = 8, name = "Resume", description = "Triggers restart of job")) + public void resume(ScheduleSecHubJob next) { + triggerJobRestartRequest(next.getUUID()); + } + + @IsSendingAsyncMessage(MessageID.REQUEST_JOB_RESTART) + private void triggerJobRestartRequest(UUID jobUUID) { + + JobMessage message = new JobMessage(); + message.setJobUUID(jobUUID); + + // we do NOT use REQUEST_JOB_RESTART_HARD ! Resaon: hard would destroy all + // former data! + DomainMessage restartJobRequest = DomainMessageFactory.createEmptyRequest(MessageID.REQUEST_JOB_RESTART); + restartJobRequest.set(MessageDataKeys.JOB_RESTART_DATA, message); + restartJobRequest.set(MessageDataKeys.ENVIRONMENT_BASE_URL, sechubEnvironment.getServerBaseUrl()); + + eventBusService.sendAsynchron(restartJobRequest); + } + +} diff --git a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/SchedulerJobBatchTriggerService.java b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/SchedulerJobBatchTriggerService.java index 5a21bce250..e9f33192e0 100644 --- a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/SchedulerJobBatchTriggerService.java +++ b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/SchedulerJobBatchTriggerService.java @@ -14,14 +14,17 @@ import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; +import com.mercedesbenz.sechub.commons.model.job.ExecutionState; import com.mercedesbenz.sechub.domain.schedule.config.SchedulerConfigService; import com.mercedesbenz.sechub.domain.schedule.job.ScheduleSecHubJob; +import com.mercedesbenz.sechub.sharedkernel.DocumentationScopeConstants; import com.mercedesbenz.sechub.sharedkernel.MustBeDocumented; import com.mercedesbenz.sechub.sharedkernel.Step; import com.mercedesbenz.sechub.sharedkernel.cluster.ClusterEnvironmentService; import com.mercedesbenz.sechub.sharedkernel.logging.AlertLogService; import com.mercedesbenz.sechub.sharedkernel.monitoring.SystemMonitorService; import com.mercedesbenz.sechub.sharedkernel.usecases.job.UseCaseSchedulerStartsJob; +import com.mercedesbenz.sechub.sharedkernel.usecases.other.UseCaseSystemHandlesSIGTERM; import jakarta.annotation.PostConstruct; @@ -86,6 +89,9 @@ public class SchedulerJobBatchTriggerService { @Autowired SchedulerTerminationService schedulerTerminationService; + @Autowired + ScheduleResumeJobService resumeJobService; + @PostConstruct protected void postConstruct() { // show info about delay values in log (once) @@ -95,10 +101,11 @@ protected void postConstruct() { // default 10 seconds delay and 5 seconds initial @MustBeDocumented(value = "Job scheduling is triggered by a cron job operation - default is 10 seconds to delay after last execution. " + "For initial delay " + DEFAULT_INITIAL_DELAY_MILLIS - + " milliseconds are defined. It can be configured differently. This is useful when you need to startup a cluster. Simply change the initial delay values in to allow the cluster to startup.", scope = "schedule") + + " milliseconds are defined. It can be configured differently. This is useful when you need to startup a cluster. Simply change the initial delay values in to allow the cluster to startup.", scope = DocumentationScopeConstants.SCOPE_SCHEDULE) @Scheduled(initialDelayString = "${sechub.config.trigger.nextjob.initialdelay:" + DEFAULT_INITIAL_DELAY_MILLIS + "}", fixedDelayString = "${sechub.config.trigger.nextjob.delay:" + DEFAULT_FIXED_DELAY_MILLIS + "}") @UseCaseSchedulerStartsJob(@Step(number = 1, name = "Scheduling", description = "Fetches next schedule job from queue and trigger execution.")) + @UseCaseSystemHandlesSIGTERM(@Step(number = 7, name = "Scheduling", description = "When a suspended job exists which shall be executed again, it will be marked as SUSPENDED and a restart will be triggered")) public void triggerExecutionOfNextJob() { if (LOG.isTraceEnabled()) { /* NOSONAR */LOG.trace("Trigger execution of next job started. Environment: {}", environmentService.getEnvironment()); @@ -136,6 +143,12 @@ public void triggerExecutionOfNextJob() { if (next == null) { return; } + if (ExecutionState.RESUMING.equals(next.getExecutionState())) { + LOG.info("Resuming job: {}", next.getUUID()); + resumeJobService.resume(next); + return; + } + try { launcherService.executeJob(next); } catch (Exception e) { diff --git a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleCipherPoolCleanupService.java b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleCipherPoolCleanupService.java index f1e935b0c8..268a5968a4 100644 --- a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleCipherPoolCleanupService.java +++ b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleCipherPoolCleanupService.java @@ -40,7 +40,7 @@ public class ScheduleCipherPoolCleanupService { @UseCaseEncryptionCleanup(@Step(number = 1, name = "Schedule cipher pool data cleanup", description = DESCRIPTION)) @UseCaseScheduleAutoCleanExecution(@Step(number = 3, name = "Schedule cipher pool data cleanup", description = DESCRIPTION)) public void cleanupCipherPoolDataIfNecessaryAndPossible() { - LOG.debug("Encryption pool cleanup check"); + LOG.trace("Encryption pool cleanup check"); /* check clean up possible */ if (outdatedEncryptionPoolSupport.isOutdatedEncryptionPoolPossibleInCluster()) { @@ -74,11 +74,11 @@ public void cleanupCipherPoolDataIfNecessaryAndPossible() { } private void startEncryptionCleanup(List allPoolData, ScheduleCipherPoolData latestPoolDataFromDatabase) { - LOG.debug("Encryption pool cleanup start"); + LOG.trace("Encryption pool cleanup start"); List poolDataToRemove = calculatePoolDataToRemove(allPoolData, latestPoolDataFromDatabase); if (poolDataToRemove.isEmpty()) { - LOG.debug("Found no pool data to remove"); + LOG.trace("Found no pool data to remove"); return; } diff --git a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/job/SecHubJobRepositoryCustom.java b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/job/SecHubJobRepositoryCustom.java index 2b769ec44d..1d6b5c97a6 100644 --- a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/job/SecHubJobRepositoryCustom.java +++ b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/job/SecHubJobRepositoryCustom.java @@ -12,7 +12,14 @@ public interface SecHubJobRepositoryCustom { - Optional getJob(UUID id); + /** + * Returns job when in executable - means execution state is either + * READY_TO_START or SUSPENDED. + * + * @param sechubJobUUID job uuid to search for + * @return job or null + */ + Optional getJobWhenExecutable(UUID sechubJobUUID); Optional nextJobIdToExecuteFirstInFirstOut(Set acceptedEncryptiondPoolIds); @@ -20,6 +27,8 @@ public interface SecHubJobRepositoryCustom { Optional nextJobIdToExecuteForProjectAndModuleGroupNotYetExecuted(Set acceptedEncryptiondPoolIds); + Optional nextJobIdToExecuteSuspended(Set supportedPoolIds, long minumSuspendDurationInMilliseconds); + /** * Fetches next jobs which have been canceled or have ended but have an * encryption pool entry which is lower (means older) than the given one. The diff --git a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/job/SecHubJobRepositoryImpl.java b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/job/SecHubJobRepositoryImpl.java index 7af121d6a5..673db8e58f 100644 --- a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/job/SecHubJobRepositoryImpl.java +++ b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/job/SecHubJobRepositoryImpl.java @@ -3,6 +3,8 @@ import static com.mercedesbenz.sechub.domain.schedule.job.ScheduleSecHubJob.*; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; import java.util.List; import java.util.Optional; import java.util.Set; @@ -20,9 +22,11 @@ public class SecHubJobRepositoryImpl implements SecHubJobRepositoryCustom { private static final String PARAM_UUID = "p_uuid"; private static final String PARAM_EXECUTION_STATE = "p_exec_state"; + private static final String PARAM_EXECUTION_STATES = "p_exec_states"; private static final String PARAM_EXECUTION_STATE_SUB = "p_sub_exec_state"; private static final String PARAM_ENCRYPTION_POOL_ID = "p_encrypt_pool_id"; private static final String PARAM_ENCRYPTION_POOL_IDS = "p_encrypt_pool_ids"; + private static final String PARAM_MINIMUM_JOB_ENDED = "p_min_job_ended"; /* @formatter:off */ static final String JPQL_STRING_SELECT_BY_EXECUTION_STATE = @@ -33,7 +37,7 @@ public class SecHubJobRepositoryImpl implements SecHubJobRepositoryCustom { static final String JPQL_STRING_SELECT_BY_JOB_ID = "select j from " + CLASS_NAME + " j" + - " where j." + PROPERTY_EXECUTION_STATE + " = :" + PARAM_EXECUTION_STATE + + " where j." + PROPERTY_EXECUTION_STATE + " in :" + PARAM_EXECUTION_STATES + " and j." + PROPERTY_UUID + " = :" + PARAM_UUID; static final String SUB_JPQL_STRING_SELECT_PROJECTS_WITH_RUNNING_JOBS = @@ -76,6 +80,12 @@ public class SecHubJobRepositoryImpl implements SecHubJobRepositoryCustom { static final String JPQL_STRING_FETCH_ALL_USED_ENCRYPTION_POOL_IDS_IN_JOBS = "select DISTINCT j."+PROPERTY_ENCRYPTION_POOL_ID+" from " + CLASS_NAME + " j"; + static final String JPQL_STRING_SELECT_LATEST_SUSPENDED_JOB_OLDER_THAN_GIVEN_TIME = + "select j from " + CLASS_NAME + " j" + + " where j." + PROPERTY_EXECUTION_STATE + " = " + ExecutionState.SUSPENDED + + " and j."+ PROPERTY_ENCRYPTION_POOL_ID +" in (:"+PARAM_ENCRYPTION_POOL_IDS+")"+ + " and j." + PROPERTY_ENDED + " <:"+PARAM_MINIMUM_JOB_ENDED + + " order by " + PROPERTY_CREATED; /* @formatter:on */ @@ -85,9 +95,9 @@ public class SecHubJobRepositoryImpl implements SecHubJobRepositoryCustom { private EntityManager em; @Override - public Optional getJob(UUID id) { + public Optional getJobWhenExecutable(UUID id) { Query query = em.createQuery(JPQL_STRING_SELECT_BY_JOB_ID); - query.setParameter(PARAM_EXECUTION_STATE, ExecutionState.READY_TO_START); + query.setParameter(PARAM_EXECUTION_STATES, Set.of(ExecutionState.READY_TO_START, ExecutionState.SUSPENDED)); query.setParameter(PARAM_UUID, id); query.setMaxResults(1); query.setLockMode(LockModeType.OPTIMISTIC_FORCE_INCREMENT); @@ -165,4 +175,17 @@ public List collectAllUsedEncryptionPoolIdsInsideJobs() { return query.getResultList(); } + @Override + public Optional nextJobIdToExecuteSuspended(Set supportedPoolIds, long minumSuspendDurationInMilliseconds) { + LocalDateTime minimumJobEndedDateTime = LocalDateTime.now().minus(minumSuspendDurationInMilliseconds, ChronoUnit.MILLIS); + + Query query = em.createQuery(JPQL_STRING_SELECT_LATEST_SUSPENDED_JOB_OLDER_THAN_GIVEN_TIME); + query.setParameter(PARAM_ENCRYPTION_POOL_IDS, supportedPoolIds); + query.setParameter(PARAM_MINIMUM_JOB_ENDED, minimumJobEndedDateTime); + query.setMaxResults(1); + query.setLockMode(LockModeType.OPTIMISTIC_FORCE_INCREMENT); + + return getUUIDFromJob(typedQuerySupport.getSingleResultAsOptional(query)); + } + } diff --git a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/strategy/FirstComeFirstServeSchedulerStrategy.java b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/strategy/FirstComeFirstServeSchedulerStrategy.java index a56b4d4bfb..b7eaf0afb2 100644 --- a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/strategy/FirstComeFirstServeSchedulerStrategy.java +++ b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/strategy/FirstComeFirstServeSchedulerStrategy.java @@ -8,7 +8,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import com.mercedesbenz.sechub.domain.schedule.encryption.ScheduleEncryptionService; import com.mercedesbenz.sechub.domain.schedule.job.SecHubJobRepository; @Component @@ -17,24 +16,14 @@ public class FirstComeFirstServeSchedulerStrategy implements SchedulerStrategy { @Autowired public SecHubJobRepository jobRepository; - @Autowired - ScheduleEncryptionService encryptionService; - @Override - public SchedulerStrategyId getSchedulerId() { + public SchedulerStrategyId getSchedulerStrategyId() { return SchedulerStrategyId.FIRST_COME_FIRST_SERVE; } @Override - public UUID nextJobId() { - Set supportedPoolIds = encryptionService.getCurrentEncryptionPoolIds(); - - Optional nextJob = jobRepository.nextJobIdToExecuteFirstInFirstOut(supportedPoolIds); - if (!nextJob.isPresent()) { - return null; - } - - return nextJob.get(); + public Optional nextJobId(Set supportedEncryptionPoolIds) { + return jobRepository.nextJobIdToExecuteFirstInFirstOut(supportedEncryptionPoolIds); } } diff --git a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/strategy/OnlyOneScanPerProjectAndModuleGroupAtSameTimeStrategy.java b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/strategy/OnlyOneScanPerProjectAndModuleGroupAtSameTimeStrategy.java index 67125fdee1..f432bf8ab6 100644 --- a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/strategy/OnlyOneScanPerProjectAndModuleGroupAtSameTimeStrategy.java +++ b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/strategy/OnlyOneScanPerProjectAndModuleGroupAtSameTimeStrategy.java @@ -8,7 +8,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import com.mercedesbenz.sechub.domain.schedule.encryption.ScheduleEncryptionService; import com.mercedesbenz.sechub.domain.schedule.job.SecHubJobRepository; /** @@ -54,24 +53,14 @@ public class OnlyOneScanPerProjectAndModuleGroupAtSameTimeStrategy implements Sc @Autowired SecHubJobRepository jobRepository; - @Autowired - ScheduleEncryptionService encryptionService; - @Override - public SchedulerStrategyId getSchedulerId() { + public SchedulerStrategyId getSchedulerStrategyId() { return SchedulerStrategyId.ONE_SCAN_PER_PROJECT_AND_MODULE_GROUP; } @Override - public UUID nextJobId() { - Set supportedPoolIds = encryptionService.getCurrentEncryptionPoolIds(); - - Optional nextJob = jobRepository.nextJobIdToExecuteForProjectAndModuleGroupNotYetExecuted(supportedPoolIds); - if (!nextJob.isPresent()) { - return null; - } - - return nextJob.get(); + public Optional nextJobId(Set supportedEncryptionPoolIds) { + return jobRepository.nextJobIdToExecuteForProjectAndModuleGroupNotYetExecuted(supportedEncryptionPoolIds); } } diff --git a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/strategy/OnlyOneScanPerProjectAtSameTimeStrategy.java b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/strategy/OnlyOneScanPerProjectAtSameTimeStrategy.java index cb76a706f7..c438c80cba 100644 --- a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/strategy/OnlyOneScanPerProjectAtSameTimeStrategy.java +++ b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/strategy/OnlyOneScanPerProjectAtSameTimeStrategy.java @@ -8,7 +8,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import com.mercedesbenz.sechub.domain.schedule.encryption.ScheduleEncryptionService; import com.mercedesbenz.sechub.domain.schedule.job.SecHubJobRepository; @Component @@ -17,24 +16,14 @@ public class OnlyOneScanPerProjectAtSameTimeStrategy implements SchedulerStrateg @Autowired SecHubJobRepository jobRepository; - @Autowired - ScheduleEncryptionService encryptionService; - @Override - public SchedulerStrategyId getSchedulerId() { + public SchedulerStrategyId getSchedulerStrategyId() { return SchedulerStrategyId.ONE_SCAN_PER_PROJECT; } @Override - public UUID nextJobId() { - Set supportedPoolIds = encryptionService.getCurrentEncryptionPoolIds(); - - Optional nextJob = jobRepository.nextJobIdToExecuteForProjectNotYetExecuted(supportedPoolIds); - if (!nextJob.isPresent()) { - return null; - } - - return nextJob.get(); + public Optional nextJobId(Set supportedEncryptionPoolIds) { + return jobRepository.nextJobIdToExecuteForProjectNotYetExecuted(supportedEncryptionPoolIds); } } diff --git a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/strategy/SchedulerNextJobResolver.java b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/strategy/SchedulerNextJobResolver.java new file mode 100644 index 0000000000..527b62c248 --- /dev/null +++ b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/strategy/SchedulerNextJobResolver.java @@ -0,0 +1,79 @@ +package com.mercedesbenz.sechub.domain.schedule.strategy; + +import java.util.Optional; +import java.util.Set; +import java.util.UUID; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import com.mercedesbenz.sechub.domain.schedule.encryption.ScheduleEncryptionService; +import com.mercedesbenz.sechub.domain.schedule.job.SecHubJobRepository; +import com.mercedesbenz.sechub.sharedkernel.DocumentationScopeConstants; +import com.mercedesbenz.sechub.sharedkernel.MustBeDocumented; + +@Component +public class SchedulerNextJobResolver { + + private static final long DEFAULT_MIN_SUSPED_DURATION = 1000; + + @Autowired + ScheduleEncryptionService encryptionService; + + @Autowired + public SecHubJobRepository jobRepository; + + @Autowired + SchedulerStrategyProvider schedulerStrategyProvider; + + @Value("${sechub.scheduler.nextjob.suspend.miniumumduration.milliseconds:" + DEFAULT_MIN_SUSPED_DURATION + "}") + @MustBeDocumented(scope = DocumentationScopeConstants.SCOPE_SCHEDULE, value = """ + The scheduler automatically restarts the next suspended jobs, regardless of the defined schedule strategy. + This is done to get suspended jobs of another shut down instance back up and running as quickly as possible. + + To avoid suspended jobs being restarted too quickly, you can use this value to set the minimum time that must pass + before the next suspended job can be restarted. The value is defined in milliseconds. + + The (previous) end date of the suspended job is used. For example, this value is important for + K8s redeployment, because the servers that have not yet been updated should not immediately continue with the + suspended jobs - they will also be shut down soon and would suspend the restarted jobs again... + """) + long minimumSuspendDurationInMilliseconds; + + /** + * Resolves next job by given strategy. But before strategy is used, suspended + * jobs are fetched - if they are not too young (to avoid race conditions when + * we we have an ongoing deployment). + * + * @param strategy strategy to use when there are no suspended jobs + * @return uuid of job or null if there is no job to execute + */ + public UUID resolveNextJob() { + + SchedulerStrategy schedulerStrategy = schedulerStrategyProvider.build(); + + Set supportedPoolIds = encryptionService.getCurrentEncryptionPoolIds(); + + if (supportedPoolIds == null || supportedPoolIds.isEmpty()) { + return null; + } + /* + * we always fetch the next possible suspended job - no matter which kind of + * strategy + */ + Optional nextSuspendedJob = jobRepository.nextJobIdToExecuteSuspended(supportedPoolIds, minimumSuspendDurationInMilliseconds); + if (nextSuspendedJob.isPresent()) { + return nextSuspendedJob.get(); + } + + /* No suspended jobs available - use strategy to find next one */ + Optional nextJob = schedulerStrategy.nextJobId(supportedPoolIds); + if (!nextJob.isPresent()) { + return null; + } + + return nextJob.get(); + } + +} diff --git a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/strategy/SchedulerStrategy.java b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/strategy/SchedulerStrategy.java index 43ca789803..e2f524cb9c 100644 --- a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/strategy/SchedulerStrategy.java +++ b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/strategy/SchedulerStrategy.java @@ -1,11 +1,13 @@ // SPDX-License-Identifier: MIT package com.mercedesbenz.sechub.domain.schedule.strategy; +import java.util.Optional; +import java.util.Set; import java.util.UUID; public interface SchedulerStrategy { - public SchedulerStrategyId getSchedulerId(); + public SchedulerStrategyId getSchedulerStrategyId(); - public UUID nextJobId(); + public Optional nextJobId(Set supportedEncryptionPoolIds); } diff --git a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/strategy/SchedulerStrategyFactory.java b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/strategy/SchedulerStrategyProvider.java similarity index 97% rename from sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/strategy/SchedulerStrategyFactory.java rename to sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/strategy/SchedulerStrategyProvider.java index 28928314a0..bd461e5a82 100644 --- a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/strategy/SchedulerStrategyFactory.java +++ b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/strategy/SchedulerStrategyProvider.java @@ -10,9 +10,9 @@ import com.mercedesbenz.sechub.sharedkernel.MustBeDocumented; @Component -public class SchedulerStrategyFactory { +public class SchedulerStrategyProvider { - private static final Logger LOG = LoggerFactory.getLogger(SchedulerStrategyFactory.class); + private static final Logger LOG = LoggerFactory.getLogger(SchedulerStrategyProvider.class); @Autowired FirstComeFirstServeSchedulerStrategy firstComeFirstServeStrategy; diff --git a/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/job/JobCreator.java b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/job/JobCreator.java index e36d04f339..17635683dd 100644 --- a/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/job/JobCreator.java +++ b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/job/JobCreator.java @@ -86,6 +86,11 @@ public JobCreator created(LocalDateTime dateTime) { return this; } + public JobCreator ended(LocalDateTime dateTime) { + job.ended = dateTime; + return this; + } + public JobCreator project(String projectId) { job.projectId = projectId; return this; diff --git a/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/job/SecHubJobRepositoryDBTest.java b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/job/SecHubJobRepositoryDBTest.java index 3f33f5b16f..ed09a4021f 100644 --- a/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/job/SecHubJobRepositoryDBTest.java +++ b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/job/SecHubJobRepositoryDBTest.java @@ -27,6 +27,7 @@ import org.junit.jupiter.params.provider.ArgumentsSource; import org.junit.jupiter.params.provider.EmptySource; import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.EnumSource.Mode; import org.junit.jupiter.params.provider.NullSource; import org.junit.jupiter.params.provider.ValueSource; import org.springframework.beans.factory.annotation.Autowired; @@ -628,6 +629,128 @@ void delete_job_with_data_deletes_data() { assertNull(result); } + /* -------------------------------------------------------------------------- */ + /* ------------------ next job to execute suspended --------------- */ + /* -------------------------------------------------------------------------- */ + + @ParameterizedTest + @EnumSource(mode = Mode.EXCLUDE, value = ExecutionState.class, names = "SUSPENDED") + void custom_query_nextJobIdToExecuteSuspended_1000_ms_duration_no_suspended_exists_but_only_one_job_in_other_state(ExecutionState state) { + + /* prepare */ + jobCreator.being(state).ended(LocalDateTime.now().minusSeconds(2)).create(); + + /* execute */ + Optional uuid = jobRepository.nextJobIdToExecuteSuspended(FIRST_ENCRYPTION_POOL_ENTRY_ONLY, 1000L); + + /* test */ + assertFalse(uuid.isPresent()); + } + + @Test + void custom_query_nextJobIdToExecuteSuspended_2000_ms_duration_one_suspended_exist_but_only_1_second_so_nothing_returned() { + /* prepare */ + jobCreator.being(ExecutionState.SUSPENDED).ended(LocalDateTime.now().minusSeconds(1)).create(); + + /* execute */ + Optional uuid = jobRepository.nextJobIdToExecuteSuspended(FIRST_ENCRYPTION_POOL_ENTRY_ONLY, 2000L); + + /* test */ + assertFalse(uuid.isPresent()); + } + + @Test + void custom_query_nextJobIdToExecuteSuspended_1000_ms_duration_one_suspended_exist_older_1_second_returned() { + /* prepare */ + ScheduleSecHubJob newJob = jobCreator.being(ExecutionState.SUSPENDED).ended(LocalDateTime.now().minusSeconds(2)).create(); + + /* execute */ + Optional uuid = jobRepository.nextJobIdToExecuteSuspended(FIRST_ENCRYPTION_POOL_ENTRY_ONLY, 1000L); + + /* test */ + assertTrue(uuid.isPresent()); + assertEquals(newJob.getUUID(), uuid.get()); + } + + @Test + void custom_query_nextJobIdToExecuteSuspended_1000_ms_duration_three_suspended_exist_both_older_1_second_older_returned() { + /* prepare */ + jobCreator.created(LocalDateTime.now().minusSeconds(5)).being(ExecutionState.SUSPENDED).ended(LocalDateTime.now().minusSeconds(4)).create(); + ScheduleSecHubJob newJob2 = jobCreator.created(LocalDateTime.now().minusSeconds(14)).being(ExecutionState.SUSPENDED) + .ended(LocalDateTime.now().minusSeconds(3)).create(); + jobCreator.created(LocalDateTime.now().minusSeconds(4)).being(ExecutionState.SUSPENDED).ended(LocalDateTime.now().minusSeconds(2)).create(); + + /* execute */ + Optional uuid = jobRepository.nextJobIdToExecuteSuspended(FIRST_ENCRYPTION_POOL_ENTRY_ONLY, 1000L); + + /* test */ + assertTrue(uuid.isPresent()); + assertEquals(newJob2.getUUID(), uuid.get()); + } + + @Test + void custom_query_nextJobIdToExecuteSuspended_3000_ms_duration_three_suspended_exist_but_olders_only_2_second_returns_last_created_because_latest_suspended() { + /* prepare */ + jobCreator.created(LocalDateTime.now().minusSeconds(15)).being(ExecutionState.SUSPENDED).ended(LocalDateTime.now().minusSeconds(2)).create(); + ScheduleSecHubJob newJob2 = jobCreator.created(LocalDateTime.now().minusSeconds(14)).being(ExecutionState.SUSPENDED) + .ended(LocalDateTime.now().minusSeconds(3)).create(); + jobCreator.created(LocalDateTime.now().minusSeconds(16)).being(ExecutionState.SUSPENDED).ended(LocalDateTime.now().minusSeconds(2)).create(); + + /* execute */ + Optional uuid = jobRepository.nextJobIdToExecuteSuspended(FIRST_ENCRYPTION_POOL_ENTRY_ONLY, 3000L); + + /* test */ + assertTrue(uuid.isPresent()); + assertEquals(newJob2.getUUID(), uuid.get()); + } + + @Test + void custom_query_nextJobIdToExecuteSuspended_5000_ms_duration_three_suspended_exist_both_younger_5_seconds_none_returned() { + /* prepare */ + jobCreator.created(LocalDateTime.now().minusSeconds(5)).being(ExecutionState.SUSPENDED).ended(LocalDateTime.now().minusSeconds(4)).create(); + jobCreator.created(LocalDateTime.now().minusSeconds(14)).being(ExecutionState.SUSPENDED).ended(LocalDateTime.now().minusSeconds(3)).create(); + jobCreator.created(LocalDateTime.now().minusSeconds(4)).being(ExecutionState.SUSPENDED).ended(LocalDateTime.now().minusSeconds(2)).create(); + + /* execute */ + Optional uuid = jobRepository.nextJobIdToExecuteSuspended(FIRST_ENCRYPTION_POOL_ENTRY_ONLY, 5000L); + + /* test */ + assertFalse(uuid.isPresent()); + } + + /* -------------------------------------------------------------------------- */ + /* ------------------------ */ + /* -------------------------------------------------------------------------- */ + + @ParameterizedTest + @EnumSource(value = ExecutionState.class, names = { "READY_TO_START", "SUSPENDED" }, mode = Mode.EXCLUDE) + void custom_query_getJob_returns_not_created_job_because_not_in_accepted_state(ExecutionState state) { + /* prepare */ + ScheduleSecHubJob created = jobCreator.created(LocalDateTime.now().minusSeconds(5)).being(state).ended(LocalDateTime.now().minusSeconds(4)).create(); + + /* execute */ + Optional result = jobRepository.getJobWhenExecutable(created.getUUID()); + + /* test */ + assertFalse(result.isPresent()); + } + + @ParameterizedTest + @EnumSource(value = ExecutionState.class, names = { "READY_TO_START", "SUSPENDED" }, mode = Mode.INCLUDE) + void custom_query_getJob_returns_created_job_because_in_accepted_state(ExecutionState state) { + /* prepare */ + ScheduleSecHubJob created = jobCreator.created(LocalDateTime.now().minusSeconds(5)).being(state).ended(LocalDateTime.now().minusSeconds(4)).create(); + + /* execute */ + Optional result = jobRepository.getJobWhenExecutable(created.getUUID()); + + /* test */ + assertTrue(result.isPresent()); + assertEquals(created, result.get()); + } + + /* -------------------------------------------------------------------------- */ + @Test void custom_query_nextJobIdToExecuteForProjectAndModuleGroupNotYetExecuted() { /* prepare */ diff --git a/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/strategy/FirstComeFirstServeSchedulerStrategyTest.java b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/strategy/FirstComeFirstServeSchedulerStrategyTest.java index 6fa219c6ef..52916eca21 100644 --- a/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/strategy/FirstComeFirstServeSchedulerStrategyTest.java +++ b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/strategy/FirstComeFirstServeSchedulerStrategyTest.java @@ -12,7 +12,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import com.mercedesbenz.sechub.domain.schedule.encryption.ScheduleEncryptionService; import com.mercedesbenz.sechub.domain.schedule.job.SecHubJobRepository; class FirstComeFirstServeSchedulerStrategyTest { @@ -20,33 +19,29 @@ class FirstComeFirstServeSchedulerStrategyTest { private FirstComeFirstServeSchedulerStrategy strategyToTest; private SecHubJobRepository jobRepository; private UUID jobUUID; - private ScheduleEncryptionService encryptionService; @BeforeEach void beforeEach() { jobUUID = UUID.randomUUID(); jobRepository = mock(SecHubJobRepository.class); - encryptionService = mock(ScheduleEncryptionService.class); - strategyToTest = new FirstComeFirstServeSchedulerStrategy(); strategyToTest.jobRepository = jobRepository; - strategyToTest.encryptionService = encryptionService; } @Test void nextJobId_calls_expected_query_method() { /* prepare */ Set currentEncryptionPoolIds = Collections.emptySet(); - when(encryptionService.getCurrentEncryptionPoolIds()).thenReturn(currentEncryptionPoolIds); when(jobRepository.nextJobIdToExecuteFirstInFirstOut(currentEncryptionPoolIds)).thenReturn(Optional.of(jobUUID)); /* execute */ - UUID result = strategyToTest.nextJobId(); + Optional result = strategyToTest.nextJobId(currentEncryptionPoolIds); /* test */ - assertEquals(jobUUID, result); verify(jobRepository).nextJobIdToExecuteFirstInFirstOut(currentEncryptionPoolIds); + assertTrue(result.isPresent()); + assertEquals(jobUUID, result.get()); } } diff --git a/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/strategy/OnlyOneScanPerProjectAndModuleGroupAtSameTimeStrategyTest.java b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/strategy/OnlyOneScanPerProjectAndModuleGroupAtSameTimeStrategyTest.java index 681ce41f86..7416659115 100644 --- a/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/strategy/OnlyOneScanPerProjectAndModuleGroupAtSameTimeStrategyTest.java +++ b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/strategy/OnlyOneScanPerProjectAndModuleGroupAtSameTimeStrategyTest.java @@ -12,7 +12,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import com.mercedesbenz.sechub.domain.schedule.encryption.ScheduleEncryptionService; import com.mercedesbenz.sechub.domain.schedule.job.SecHubJobRepository; class OnlyOneScanPerProjectAndModuleGroupAtSameTimeStrategyTest { @@ -20,32 +19,29 @@ class OnlyOneScanPerProjectAndModuleGroupAtSameTimeStrategyTest { private OnlyOneScanPerProjectAndModuleGroupAtSameTimeStrategy strategyToTest; private SecHubJobRepository jobRepository; private UUID jobUUID; - private ScheduleEncryptionService encryptionService; @BeforeEach void beforeEach() { jobUUID = UUID.randomUUID(); jobRepository = mock(SecHubJobRepository.class); - encryptionService = mock(ScheduleEncryptionService.class); - strategyToTest = new OnlyOneScanPerProjectAndModuleGroupAtSameTimeStrategy(); strategyToTest.jobRepository = jobRepository; - strategyToTest.encryptionService = encryptionService; } @Test void nextJobId_calls_expected_query_method() { /* prepare */ - Set supportedPoolIds = Collections.emptySet(); - when(jobRepository.nextJobIdToExecuteForProjectAndModuleGroupNotYetExecuted(supportedPoolIds)).thenReturn(Optional.of(jobUUID)); + Set currentEncryptionPoolIds = Collections.emptySet(); + when(jobRepository.nextJobIdToExecuteForProjectAndModuleGroupNotYetExecuted(currentEncryptionPoolIds)).thenReturn(Optional.of(jobUUID)); /* execute */ - UUID result = strategyToTest.nextJobId(); + Optional result = strategyToTest.nextJobId(currentEncryptionPoolIds); /* test */ - assertEquals(jobUUID, result); - verify(jobRepository).nextJobIdToExecuteForProjectAndModuleGroupNotYetExecuted(supportedPoolIds); + verify(jobRepository).nextJobIdToExecuteForProjectAndModuleGroupNotYetExecuted(currentEncryptionPoolIds); + assertTrue(result.isPresent()); + assertEquals(jobUUID, result.get()); } } diff --git a/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/strategy/OnlyOneScanPerProjectAtSameTimeStrategyTest.java b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/strategy/OnlyOneScanPerProjectAtSameTimeStrategyTest.java index 5228496bb6..dfaa6333e2 100644 --- a/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/strategy/OnlyOneScanPerProjectAtSameTimeStrategyTest.java +++ b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/strategy/OnlyOneScanPerProjectAtSameTimeStrategyTest.java @@ -12,7 +12,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import com.mercedesbenz.sechub.domain.schedule.encryption.ScheduleEncryptionService; import com.mercedesbenz.sechub.domain.schedule.job.SecHubJobRepository; class OnlyOneScanPerProjectAtSameTimeStrategyTest { @@ -20,32 +19,29 @@ class OnlyOneScanPerProjectAtSameTimeStrategyTest { private OnlyOneScanPerProjectAtSameTimeStrategy strategyToTest; private SecHubJobRepository jobRepository; private UUID jobUUID; - private ScheduleEncryptionService encryptionService; @BeforeEach void beforeEach() { jobUUID = UUID.randomUUID(); jobRepository = mock(SecHubJobRepository.class); - encryptionService = mock(ScheduleEncryptionService.class); - strategyToTest = new OnlyOneScanPerProjectAtSameTimeStrategy(); strategyToTest.jobRepository = jobRepository; - strategyToTest.encryptionService = encryptionService; } @Test void nextJobId_calls_expected_query_method() { /* prepare */ - Set set = Collections.emptySet(); - when(jobRepository.nextJobIdToExecuteForProjectNotYetExecuted(set)).thenReturn(Optional.of(jobUUID)); + Set currentEncryptionPoolIds = Collections.emptySet(); + when(jobRepository.nextJobIdToExecuteForProjectNotYetExecuted(currentEncryptionPoolIds)).thenReturn(Optional.of(jobUUID)); /* execute */ - UUID result = strategyToTest.nextJobId(); + Optional result = strategyToTest.nextJobId(currentEncryptionPoolIds); /* test */ - assertEquals(jobUUID, result); - verify(jobRepository).nextJobIdToExecuteForProjectNotYetExecuted(set); + verify(jobRepository).nextJobIdToExecuteForProjectNotYetExecuted(currentEncryptionPoolIds); + assertTrue(result.isPresent()); + assertEquals(jobUUID, result.get()); } } diff --git a/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/strategy/SchedulerNextJobResolverTest.java b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/strategy/SchedulerNextJobResolverTest.java new file mode 100644 index 0000000000..6e3447098a --- /dev/null +++ b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/strategy/SchedulerNextJobResolverTest.java @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.schedule.strategy; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +import java.util.Optional; +import java.util.Set; +import java.util.UUID; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.mercedesbenz.sechub.domain.schedule.encryption.ScheduleEncryptionService; +import com.mercedesbenz.sechub.domain.schedule.job.SecHubJobRepository; + +class SchedulerNextJobResolverTest { + + private SchedulerNextJobResolver resolverToTest; + private SecHubJobRepository jobRepository; + private UUID jobUUID; + private ScheduleEncryptionService encryptionService; + private SchedulerStrategyProvider schedulerStrategyProvider; + private SchedulerStrategy strategy; + + @BeforeEach + void beforeEach() { + jobUUID = UUID.randomUUID(); + jobRepository = mock(SecHubJobRepository.class); + schedulerStrategyProvider = mock(SchedulerStrategyProvider.class); + strategy = mock(SchedulerStrategy.class); + + encryptionService = mock(ScheduleEncryptionService.class); + + resolverToTest = new SchedulerNextJobResolver(); + resolverToTest.jobRepository = jobRepository; + resolverToTest.encryptionService = encryptionService; + resolverToTest.schedulerStrategyProvider = schedulerStrategyProvider; + + when(schedulerStrategyProvider.build()).thenReturn(strategy); + } + + @Test + void resolveNextJob_uses_encryption_service_when_no_encryption_pool_ids_available_no_interaction_and_always_null() { + /* prepare */ + Set currentEncryptionPoolIds = Set.of(); // empty... + when(encryptionService.getCurrentEncryptionPoolIds()).thenReturn(currentEncryptionPoolIds); + when(jobRepository.nextJobIdToExecuteSuspended(currentEncryptionPoolIds, resolverToTest.minimumSuspendDurationInMilliseconds)) + .thenReturn(Optional.of(jobUUID)); + + /* execute */ + UUID result = resolverToTest.resolveNextJob(); + + /* test */ + verify(jobRepository, never()).nextJobIdToExecuteSuspended(anySet(), anyLong()); + verify(strategy, never()).nextJobId(anySet()); + assertEquals(null, result); + } + + @Test + void resolveNextJob_uses_encryption_service_and_fetches_suspended_job() { + /* prepare */ + Set currentEncryptionPoolIds = Set.of(1L); + when(encryptionService.getCurrentEncryptionPoolIds()).thenReturn(currentEncryptionPoolIds); + when(jobRepository.nextJobIdToExecuteSuspended(currentEncryptionPoolIds, resolverToTest.minimumSuspendDurationInMilliseconds)) + .thenReturn(Optional.of(jobUUID)); + + /* execute */ + UUID result = resolverToTest.resolveNextJob(); + + /* test */ + verify(jobRepository).nextJobIdToExecuteSuspended(currentEncryptionPoolIds, resolverToTest.minimumSuspendDurationInMilliseconds); + verify(strategy, never()).nextJobId(currentEncryptionPoolIds); + assertEquals(jobUUID, result); + } + + @Test + void resolveNextJob_uses_encryption_service_no_suspended_jobs_found_then_job_from_strategy_is_used() { + /* prepare */ + Set currentEncryptionPoolIds = Set.of(1L); + when(encryptionService.getCurrentEncryptionPoolIds()).thenReturn(currentEncryptionPoolIds); + when(jobRepository.nextJobIdToExecuteSuspended(currentEncryptionPoolIds, resolverToTest.minimumSuspendDurationInMilliseconds)) + .thenReturn(Optional.empty()); + when(strategy.nextJobId(currentEncryptionPoolIds)).thenReturn(Optional.of(jobUUID)); + + /* execute */ + UUID result = resolverToTest.resolveNextJob(); + + /* test */ + verify(jobRepository).nextJobIdToExecuteSuspended(currentEncryptionPoolIds, resolverToTest.minimumSuspendDurationInMilliseconds); + verify(strategy).nextJobId(currentEncryptionPoolIds); + assertEquals(jobUUID, result); + } + + @Test + void resolveNextJob_uses_encryption_service_and_fetches_no_suspended_jobs_when_found_job_from_strategy_is_used_but_also_not_found() { + /* prepare */ + Set currentEncryptionPoolIds = Set.of(1L); + when(encryptionService.getCurrentEncryptionPoolIds()).thenReturn(currentEncryptionPoolIds); + when(jobRepository.nextJobIdToExecuteSuspended(currentEncryptionPoolIds, resolverToTest.minimumSuspendDurationInMilliseconds)) + .thenReturn(Optional.empty()); + when(strategy.nextJobId(currentEncryptionPoolIds)).thenReturn(Optional.empty()); + + /* execute */ + UUID result = resolverToTest.resolveNextJob(); + + /* test */ + verify(jobRepository).nextJobIdToExecuteSuspended(currentEncryptionPoolIds, resolverToTest.minimumSuspendDurationInMilliseconds); + verify(strategy).nextJobId(currentEncryptionPoolIds); + assertEquals(null, result); + } + +} diff --git a/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/strategy/SchedulerStrategyFactoryTest.java b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/strategy/SchedulerStrategyProviderTest.java similarity index 96% rename from sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/strategy/SchedulerStrategyFactoryTest.java rename to sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/strategy/SchedulerStrategyProviderTest.java index 5abb965096..a91766f956 100644 --- a/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/strategy/SchedulerStrategyFactoryTest.java +++ b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/strategy/SchedulerStrategyProviderTest.java @@ -7,13 +7,13 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -class SchedulerStrategyFactoryTest { +class SchedulerStrategyProviderTest { private static final String FIRST_COME_FIRST_SERVE = "first-come-first-serve"; private static final String ONLY_ONE_SCAN_PER_PROJECT = "only-one-scan-per-project-at-a-time"; private static final String ONLY_ONE_SCAN_PER_PROJECT_AND_MODULE_GROUP = "only-one-scan-per-project-and-module-group"; - private SchedulerStrategyFactory factoryToTest; + private SchedulerStrategyProvider factoryToTest; private FirstComeFirstServeSchedulerStrategy firstComeFirstServeStrategy; private OnlyOneScanPerProjectAtSameTimeStrategy onlyOneScanPerProjectStrategy; private OnlyOneScanPerProjectAndModuleGroupAtSameTimeStrategy onlyOneScanPerProjectAndModuleGroupStrategy; @@ -25,7 +25,7 @@ void beforeEach() { onlyOneScanPerProjectStrategy = mock(OnlyOneScanPerProjectAtSameTimeStrategy.class); onlyOneScanPerProjectAndModuleGroupStrategy = mock(OnlyOneScanPerProjectAndModuleGroupAtSameTimeStrategy.class); - factoryToTest = new SchedulerStrategyFactory(); + factoryToTest = new SchedulerStrategyProvider(); factoryToTest.firstComeFirstServeStrategy = firstComeFirstServeStrategy; factoryToTest.onlyOneScanPerProjectStrategy = onlyOneScanPerProjectStrategy; diff --git a/sechub-server/src/main/java/com/mercedesbenz/sechub/server/SecHubServerFlywayFactory.java b/sechub-server/src/main/java/com/mercedesbenz/sechub/server/SecHubServerFlywayFactory.java index b3f1ba2be8..669dd29279 100644 --- a/sechub-server/src/main/java/com/mercedesbenz/sechub/server/SecHubServerFlywayFactory.java +++ b/sechub-server/src/main/java/com/mercedesbenz/sechub/server/SecHubServerFlywayFactory.java @@ -14,6 +14,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; +import com.mercedesbenz.sechub.sharedkernel.DocumentationScopeConstants; import com.mercedesbenz.sechub.sharedkernel.MustBeDocumented; import com.mercedesbenz.sechub.sharedkernel.Profiles; @@ -24,7 +25,7 @@ public class SecHubServerFlywayFactory { private static final Logger LOG = LoggerFactory.getLogger(SecHubServerFlywayFactory.class); @Value("${sechub.migration.flyway.autorepair:true}") - @MustBeDocumented(value = "When enabled, flyway migration problems will be automatically repaired", scope = "migration") + @MustBeDocumented(value = "When enabled, flyway migration problems will be automatically repaired", scope = DocumentationScopeConstants.SCOPE_MIGRATION) boolean repairAutomatically; @Bean diff --git a/sechub-server/src/main/java/com/mercedesbenz/sechub/server/SecHubSystemPropertyInjector.java b/sechub-server/src/main/java/com/mercedesbenz/sechub/server/SecHubSystemPropertyInjector.java index 9bf1c8ffa1..67ecf11e62 100644 --- a/sechub-server/src/main/java/com/mercedesbenz/sechub/server/SecHubSystemPropertyInjector.java +++ b/sechub-server/src/main/java/com/mercedesbenz/sechub/server/SecHubSystemPropertyInjector.java @@ -4,6 +4,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; +import com.mercedesbenz.sechub.sharedkernel.DocumentationScopeConstants; import com.mercedesbenz.sechub.sharedkernel.MustBeDocumented; import jakarta.annotation.PostConstruct; @@ -19,7 +20,7 @@ @Component public class SecHubSystemPropertyInjector { - @MustBeDocumented(value = "Define diffie hellman key length, see https://github.com/mercedes-benz/sechub/issues/689 for details", scope = "security") + @MustBeDocumented(value = "Define diffie hellman key length, see https://github.com/mercedes-benz/sechub/issues/689 for details", scope = DocumentationScopeConstants.SCOPE_SECURITY) @Value("${sechub.security.diffiehellman.length}") private String diffieHellmanLength; diff --git a/sechub-server/src/test/java/com/mercedesbenz/sechub/domain/schedule/ScheduleJobMarkerServiceTest.java b/sechub-server/src/test/java/com/mercedesbenz/sechub/domain/schedule/ScheduleJobMarkerServiceTest.java index f43c0c9165..153ff04608 100644 --- a/sechub-server/src/test/java/com/mercedesbenz/sechub/domain/schedule/ScheduleJobMarkerServiceTest.java +++ b/sechub-server/src/test/java/com/mercedesbenz/sechub/domain/schedule/ScheduleJobMarkerServiceTest.java @@ -14,14 +14,11 @@ import com.mercedesbenz.sechub.commons.model.job.ExecutionState; import com.mercedesbenz.sechub.domain.schedule.job.ScheduleSecHubJob; import com.mercedesbenz.sechub.domain.schedule.job.SecHubJobRepository; -import com.mercedesbenz.sechub.domain.schedule.strategy.FirstComeFirstServeSchedulerStrategy; -import com.mercedesbenz.sechub.domain.schedule.strategy.SchedulerStrategyFactory; +import com.mercedesbenz.sechub.domain.schedule.strategy.SchedulerNextJobResolver; public class ScheduleJobMarkerServiceTest { private SecHubJobRepository jobRepository; - private SchedulerStrategyFactory factory; - private FirstComeFirstServeSchedulerStrategy strategy; private ScheduleSecHubJob secHubJob; @@ -29,44 +26,44 @@ public class ScheduleJobMarkerServiceTest { private ScheduleJobMarkerService serviceToTest; + private SchedulerNextJobResolver nextJobResolver; + @Before public void before() throws Exception { serviceToTest = new ScheduleJobMarkerService(); - factory = mock(SchedulerStrategyFactory.class); - strategy = mock(FirstComeFirstServeSchedulerStrategy.class); jobRepository = mock(SecHubJobRepository.class); - + nextJobResolver = mock(SchedulerNextJobResolver.class); uuid = UUID.randomUUID(); serviceToTest.jobRepository = jobRepository; - serviceToTest.schedulerStrategyFactory = factory; - strategy.jobRepository = jobRepository; + serviceToTest.nextJobResolver = nextJobResolver; secHubJob = mock(ScheduleSecHubJob.class); - when(factory.build()).thenReturn(strategy); - when(strategy.nextJobId()).thenReturn(uuid); - when(jobRepository.getJob(uuid)).thenReturn(Optional.of(secHubJob)); + when(jobRepository.getJobWhenExecutable(uuid)).thenReturn(Optional.of(secHubJob)); } @Test - public void markNextJobExecutedByThisPOD__calls_jobrepository_getjob_executed() throws Exception { + public void markNextJobExecutedByThisPOD__calls_nextJobResolver() throws Exception { /* execute */ serviceToTest.markNextJobToExecuteByThisInstance(); /* test */ - verify(jobRepository).getJob(uuid); + verify(nextJobResolver).resolveNextJob(); } @Test - public void markNextJobExecutedByThisPOD__updates_execution_state_to_started() throws Exception { + public void markNextJobExecutedByThisPOD__next_job_found_updates_execution_state_to_started() throws Exception { /* prepare */ + when(nextJobResolver.resolveNextJob()).thenReturn(uuid); when(jobRepository.save(secHubJob)).thenReturn(secHubJob); /* execute */ ScheduleSecHubJob result = serviceToTest.markNextJobToExecuteByThisInstance(); /* test */ + verify(nextJobResolver).resolveNextJob(); + verify(jobRepository).save(secHubJob); verify(secHubJob).setStarted(any()); verify(secHubJob).setExecutionState(eq(ExecutionState.STARTED)); @@ -74,4 +71,18 @@ public void markNextJobExecutedByThisPOD__updates_execution_state_to_started() t assertEquals(secHubJob, result); } + @Test + public void markNextJobExecutedByThisPOD__next_job_not_found() throws Exception { + /* prepare */ + when(nextJobResolver.resolveNextJob()).thenReturn(null); + + /* execute */ + ScheduleSecHubJob result = serviceToTest.markNextJobToExecuteByThisInstance(); + + /* test */ + verify(nextJobResolver).resolveNextJob(); + verifyNoInteractions(jobRepository); + assertEquals(null,result); + } + } diff --git a/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/DocumentationScopeConstants.java b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/DocumentationScopeConstants.java new file mode 100644 index 0000000000..20c0965c07 --- /dev/null +++ b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/DocumentationScopeConstants.java @@ -0,0 +1,21 @@ +package com.mercedesbenz.sechub.sharedkernel; + +public class DocumentationScopeConstants { + + /** + * If this scope is used, it shall be replaced in generated output by lower + * cased class name of class where annotation is used + */ + public static final String SCOPE_USE_DEFINED_CLASSNAME_LOWERCASED = "definingClassNameToLowercase"; + + public static final String SCOPE_SCHEDULE = "schedule"; + + public static final String SCOPE_ADMINISTRATION = "administration"; + + public static final String SCOPE_MIGRATION = "migration"; + + public static final String SCOPE_SECURITY = "security"; + + public static final String SCOPE_STORAGE = "storage"; + +} diff --git a/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/MustBeDocumented.java b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/MustBeDocumented.java index f8caaedf92..ec3905ee91 100644 --- a/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/MustBeDocumented.java +++ b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/MustBeDocumented.java @@ -27,12 +27,6 @@ @Retention(RetentionPolicy.RUNTIME) public @interface MustBeDocumented { - /** - * If this scope is used, it shall be replaced in generated output by lower - * cased class name of class where annotation is used - */ - String SCOPE_USE_DEFINED_CLASSNAME_LOWERCASED = "definingClassNameToLowercase"; - /** * A description what the documented part is used for * @@ -41,13 +35,15 @@ String value() default ""; /** - * The scope name for the documentation - when not set - * {@link #SCOPE_USE_DEFINED_CLASSNAME_LOWERCASED} is used as scope name. Thus - * information can be used for generating documentation and separate groups etc. + * The scope name for the documentation - default fallback value is + * {@link DocumentationScopeConstants#SCOPE_USE_DEFINED_CLASSNAME_LOWERCASED} + * (will result in a generated scope name depending on annotated class package). + * The scope information is used for generating documentation and separate + * groups etc. * * @return scope */ - String scope() default SCOPE_USE_DEFINED_CLASSNAME_LOWERCASED; + String scope() default DocumentationScopeConstants.SCOPE_USE_DEFINED_CLASSNAME_LOWERCASED; /** * When true the information of this annotation must be handled diff --git a/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/storage/S3PropertiesSetup.java b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/storage/S3PropertiesSetup.java index 123fcb6fcd..612b543f8d 100644 --- a/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/storage/S3PropertiesSetup.java +++ b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/storage/S3PropertiesSetup.java @@ -4,6 +4,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; +import com.mercedesbenz.sechub.sharedkernel.DocumentationScopeConstants; import com.mercedesbenz.sechub.sharedkernel.MustBeDocumented; import com.mercedesbenz.sechub.storage.core.S3Setup; @@ -11,61 +12,61 @@ public class S3PropertiesSetup implements S3Setup { private static final String UNDEFINED = "undefined"; - @MustBeDocumented(value = "Defines the access key for used S3 bucket", scope = "storage", secret = true) + @MustBeDocumented(value = "Defines the access key for used S3 bucket", scope = DocumentationScopeConstants.SCOPE_STORAGE, secret = true) @Value("${sechub.storage.s3.accesskey:" + UNDEFINED + "}") // we use undefined here. Will be used in isValid private String accessKey; - @MustBeDocumented(value = "Defines the secret key for used S3 bucket", scope = "storage", secret = true) + @MustBeDocumented(value = "Defines the secret key for used S3 bucket", scope = DocumentationScopeConstants.SCOPE_STORAGE, secret = true) @Value("${sechub.storage.s3.secretkey:" + UNDEFINED + "}") // we use undefined here. Will be used in isValid private String secretKey; - @MustBeDocumented(value = "Defines the S3 bucket name", scope = "storage") + @MustBeDocumented(value = "Defines the S3 bucket name", scope = DocumentationScopeConstants.SCOPE_STORAGE) @Value("${sechub.storage.s3.bucketname:" + UNDEFINED + "}") // we use undefined here. Will be used in isValid private String bucketName; - @MustBeDocumented(value = "Defines the S3 endpoint - e.g. https://play.min.io", scope = "storage") + @MustBeDocumented(value = "Defines the S3 endpoint - e.g. https://play.min.io", scope = DocumentationScopeConstants.SCOPE_STORAGE) @Value("${sechub.storage.s3.endpoint:" + UNDEFINED + "}") // we use undefined here. Will be used in isValid private String endpoint; /* timeout */ - @MustBeDocumented(value = "S3 client timeout (in milliseconds) for creating new connections.", scope = "storage") + @MustBeDocumented(value = "S3 client timeout (in milliseconds) for creating new connections.", scope = DocumentationScopeConstants.SCOPE_STORAGE) @Value("${sechub.storage.s3.timeout.connection.milliseconds:" + S3Setup.DEFAULT_CONNECTION_TIMEOUT + "}") private int connectionTimeoutInMilliseconds; - @MustBeDocumented(value = "S3 client timeout (in milliseconds) for reading from a connected socket.", scope = "storage") + @MustBeDocumented(value = "S3 client timeout (in milliseconds) for reading from a connected socket.", scope = DocumentationScopeConstants.SCOPE_STORAGE) @Value("${sechub.storage.s3.timeout.socket.milliseconds:" + S3Setup.DEFAULT_SOCKET_TIMEOUT + "}") private int socketTimeoutInMilliseconds; - @MustBeDocumented(value = "S3 client timeout (in milliseconds) for a request. 0 means it is disabled.", scope = "storage") + @MustBeDocumented(value = "S3 client timeout (in milliseconds) for a request. 0 means it is disabled.", scope = DocumentationScopeConstants.SCOPE_STORAGE) @Value("${sechub.storage.s3.timeout.request.milliseconds:" + S3Setup.DEFAULT_REQUEST_TIMEOUT + "}") private int requestTimeoutInMilliseconds; - @MustBeDocumented(value = "S3 client timeout (in milliseconds) for execution. 0 means it is disabled.", scope = "storage") + @MustBeDocumented(value = "S3 client timeout (in milliseconds) for execution. 0 means it is disabled.", scope = DocumentationScopeConstants.SCOPE_STORAGE) @Value("${sechub.storage.s3.timeout.execution.milliseconds:" + S3Setup.DEFAULT_CLIENT_EXECUTION_TIMEOUT + "}") private int clientExecutionTimeoutInMilliseconds; /* connections */ - @MustBeDocumented(value = "S3 client max connection pool size.", scope = "storage") + @MustBeDocumented(value = "S3 client max connection pool size.", scope = DocumentationScopeConstants.SCOPE_STORAGE) @Value("${sechub.storage.s3.connection.max.poolsize:" + S3Setup.DEFAULT_MAX_CONNECTIONS + "}") private int maximumAllowedConnections; - @MustBeDocumented(value = "S3 client expiration time (in milliseconds) for a connection in the connection pool. -1 means deactivated", scope = "storage") + @MustBeDocumented(value = "S3 client expiration time (in milliseconds) for a connection in the connection pool. -1 means deactivated", scope = DocumentationScopeConstants.SCOPE_STORAGE) @Value("${sechub.storage.s3.connection.ttl.milliseconds:" + S3Setup.DEFAULT_CONNECTION_TTL + "}") private long connectionTTLInMilliseconds; - @MustBeDocumented(value = "S3 client maximum idle time (in milliseconds) for a connection in the connection pool.", scope = "storage") + @MustBeDocumented(value = "S3 client maximum idle time (in milliseconds) for a connection in the connection pool.", scope = DocumentationScopeConstants.SCOPE_STORAGE) @Value("${sechub.storage.s3.connection.idle.max.milliseconds:" + S3Setup.DEFAULT_CONNECTION_MAX_IDLE_MILLIS + "}") private long connectionMaxIdleInMilliseconds; - @MustBeDocumented(value = "S3 client time (in milliseconds) a connection can be idle in the connection pool before it must be validated that it's still open.", scope = "storage") + @MustBeDocumented(value = "S3 client time (in milliseconds) a connection can be idle in the connection pool before it must be validated that it's still open.", scope = DocumentationScopeConstants.SCOPE_STORAGE) @Value("${sechub.storage.s3.connection.idle.validate.milliseconds:" + S3Setup.DEFAULT_VALIDATE_AFTER_INACTIVITY_MILLIS + "}") private int validateAfterInactivityInMilliseconds; /* signer */ - @MustBeDocumented(value = "Can be used to override the default name of the signature algorithm used to sign requests.", scope = "storage") + @MustBeDocumented(value = "Can be used to override the default name of the signature algorithm used to sign requests.", scope = DocumentationScopeConstants.SCOPE_STORAGE) @Value("${sechub.storage.s3.signer.override:" + S3Setup.DEFAULT_SIGNER_OVERRIDE + "}") private String signerOverride; diff --git a/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/storage/SharedVolumePropertiesSetup.java b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/storage/SharedVolumePropertiesSetup.java index 7a6ec702fc..f84d250f03 100644 --- a/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/storage/SharedVolumePropertiesSetup.java +++ b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/storage/SharedVolumePropertiesSetup.java @@ -4,6 +4,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; +import com.mercedesbenz.sechub.sharedkernel.DocumentationScopeConstants; import com.mercedesbenz.sechub.sharedkernel.MustBeDocumented; import com.mercedesbenz.sechub.storage.sharevolume.spring.AbstractSharedVolumePropertiesSetup; @@ -14,7 +15,7 @@ public class SharedVolumePropertiesSetup extends AbstractSharedVolumePropertiesS * Folder location for storing files. When using "temp" a temporary folder on * server side will be used */ - @MustBeDocumented(value = "Defines the root path for shared volume uploads - e.g. for sourcecode.zip etc. When using keyword *temp* as path, this will create a temporary directory (for testing).", scope = "storage") + @MustBeDocumented(value = "Defines the root path for shared volume uploads - e.g. for sourcecode.zip etc. When using keyword *temp* as path, this will create a temporary directory (for testing).", scope = DocumentationScopeConstants.SCOPE_STORAGE) @Value("${sechub.storage.sharedvolume.upload.dir:" + UNDEFINED + "}") // we use undefined here. Will be used in #isValid() private String configuredUploadDir = UNDEFINED;;