diff --git a/.github/workflows/deploy.to.dev.jinil.yml b/.github/workflows/deploy.to.dev.jinil.yml deleted file mode 100644 index 814b1fe8..00000000 --- a/.github/workflows/deploy.to.dev.jinil.yml +++ /dev/null @@ -1,129 +0,0 @@ -name: Build & Deploy to DEV-Jinil - -env: - # 🖊️ EDIT your repository secrets to log into your OpenShift cluster and set up the context. - # See https://github.com/redhat-actions/oc-login#readme for how to retrieve these values. - # To get a permanent token, refer to https://github.com/redhat-actions/oc-login/wiki/Using-a-Service-Account-for-GitHub-Actions - OPENSHIFT_SERVER: ${{ secrets.OPENSHIFT_SERVER }} - OPENSHIFT_TOKEN: ${{ secrets.OPENSHIFT_TOKEN }} - OPENSHIFT_NAMESPACE: ${{ secrets.GRAD_NAMESPACE }}-dev - COMMON_NAMESPACE: ${{ secrets.COMMON_NAMESPACE }} - NAMESPACE: ${{ secrets.GRAD_NAMESPACE }} - BUSINESS_NAMESPACE: ${{ secrets.GRAD_BUSINESS_NAMESPACE }} - - # 🖊️ EDIT to change the image registry settings. - # Registries such as GHCR, Quay.io, and Docker Hub are supported. - IMAGE_REGISTRY: ghcr.io/${{ github.repository_owner }} - IMAGE_REGISTRY_USER: ${{ github.actor }} - IMAGE_REGISTRY_PASSWORD: ${{ github.token }} - - SPRING_BOOT_IMAGE_NAME: educ-grad-batch-graduation-api-dc - DOCKER_ARTIFACTORY_REPO: artifacts.developer.gov.bc.ca/docker-remote - - REPO_NAME: "educ-grad-batch-graduation-api" - APP_DOMAIN: ${{ secrets.APP_DOMAIN }} - BRANCH: "main" - TAG: "latest" - MIN_CPU: "50m" - MAX_CPU: "300m" - MIN_MEM: "1024Mi" - MAX_MEM: "4Gi" - MIN_REPLICAS: "3" - MAX_REPLICAS: "3" - -on: - # https://docs.github.com/en/actions/reference/events-that-trigger-workflows - workflow_dispatch: - -jobs: - openshift-ci-cd: - name: Build and deploy to OpenShift DEV - # ubuntu-20.04 can also be used. - runs-on: ubuntu-20.04 - environment: dev - - #outputs: - #ROUTE: ${{ steps.deploy-and-expose.outputs.route }} - #SELECTOR: ${{ steps.deploy-and-expose.outputs.selector }} - - steps: - - name: Check out repository - uses: actions/checkout@v3 - with: - ref: develop/jinil - - - name: Determine image tags - if: env.TAG == '' - run: | - echo "TAG=latest ${GITHUB_SHA::12}" | tee -a $GITHUB_ENV - - - name: Login to Docker Hub - uses: docker/login-action@v2 - with: - registry: ${{ env.DOCKER_ARTIFACTORY_REPO }} - username: ${{ secrets.DOCKER_ARTIFACTORY_USERNAME }} - password: ${{ secrets.DOCKER_ARTIFACTORY_ACCESS_TOKEN }} - - # https://github.com/redhat-actions/buildah-build#readme - - name: Build from Dockerfile - id: build-image - uses: redhat-actions/buildah-build@v2 - with: - image: ${{ env.REPO_NAME }} - tags: ${{ env.TAG }} - - # If you don't have a Dockerfile/Containerfile, refer to https://github.com/redhat-actions/buildah-build#scratch-build-inputs - # Or, perform a source-to-image build using https://github.com/redhat-actions/s2i-build - # Otherwise, point this to your Dockerfile/Containerfile relative to the repository root. - dockerfiles: | - ./Dockerfile - - # https://github.com/redhat-actions/push-to-registry#readme - - name: Push to registry - id: push-image - uses: redhat-actions/push-to-registry@v2 - with: - image: ${{ steps.build-image.outputs.image }} - tags: ${{ steps.build-image.outputs.tags }} - registry: ${{ env.IMAGE_REGISTRY }} - username: ${{ env.IMAGE_REGISTRY_USER }} - password: ${{ env.IMAGE_REGISTRY_PASSWORD }} - - # The path the image was pushed to is now stored in ${{ steps.push-image.outputs.registry-path }} - - name: Install oc - uses: redhat-actions/openshift-tools-installer@v1 - with: - oc: 4 - - # https://github.com/redhat-actions/oc-login#readme - - name: Deploy - run: | - set -eux - # Login to OpenShift and select project - oc login --token=${{ env.OPENSHIFT_TOKEN }} --server=${{ env.OPENSHIFT_SERVER }} - oc project ${{ env.OPENSHIFT_NAMESPACE }} - # Cancel any rollouts in progress - oc rollout cancel dc/${{ env.SPRING_BOOT_IMAGE_NAME }} 2> /dev/null \ - || true && echo "No rollout in progress" - # tag image stream - oc -n ${{ env.OPENSHIFT_NAMESPACE }} tag ${{ steps.push-image.outputs.registry-path }} ${{ env.REPO_NAME }}:${{ env.TAG }} - - # Process and apply deployment template - oc process -f tools/openshift/api.dc.yaml -p IS_NAMESPACE=${{ env.OPENSHIFT_NAMESPACE }} -p REPO_NAME=${{ env.REPO_NAME }} -p TAG_NAME=${{ env.TAG }} -p HOST_ROUTE=${{ env.REPO_NAME }}-${{ env.OPENSHIFT_NAMESPACE }}.${{ env.APP_DOMAIN }} -p MIN_REPLICAS=${{ env.MIN_REPLICAS }} -p MAX_REPLICAS=${{ env.MAX_REPLICAS }} -p MIN_CPU=${{ env.MIN_CPU }} -p MAX_CPU=${{ env.MAX_CPU }} -p MIN_MEM=${{ env.MIN_MEM }} -p MAX_MEM=${{ env.MAX_MEM }} \ - | oc apply -f - - - # UPDATE Configmaps - curl -s https://raw.githubusercontent.com/bcgov/${{ env.REPO_NAME }}/${{ env.BRANCH }}/tools/update-configmap.sh | bash /dev/stdin dev ${{ env.REPO_NAME }} ${{ env.NAMESPACE }} ${{ env.COMMON_NAMESPACE }} ${{ secrets.SPLUNK_TOKEN }} - - # Start rollout (if necessary) and follow it - oc rollout latest dc/${{ env.SPRING_BOOT_IMAGE_NAME }} 2> /dev/null \ - || true && echo "Rollout in progress" - oc logs -f dc/${{ env.SPRING_BOOT_IMAGE_NAME }} - # Get status, returns 0 if rollout is successful - oc rollout status dc/${{ env.SPRING_BOOT_IMAGE_NAME }} - - # now hit it with a zap scan - - name: ZAP Scan - uses: zaproxy/action-api-scan@v0.1.0 - with: - target: 'https://${{ env.REPO_NAME }}-${{ env.OPENSHIFT_NAMESPACE }}-dev.apps.silver.devops.gov.bc.ca/api/v1/api-docs' diff --git a/Dockerfile b/Dockerfile index 745092a2..861508e1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,7 +14,7 @@ ARG DEPENDENCY=/workspace/app/target/dependency COPY --from=build ${DEPENDENCY}/BOOT-INF/lib /app/lib COPY --from=build ${DEPENDENCY}/META-INF /app/META-INF COPY --from=build ${DEPENDENCY}/BOOT-INF/classes /app -ENTRYPOINT ["java","-Duser.name=EDUC_GRAD_BATCH_GRADUATION_API","-Xms4000m","-Xmx4000m","-noverify","-XX:TieredStopAtLevel=1",\ +ENTRYPOINT ["java","-Duser.name=EDUC_GRAD_BATCH_GRADUATION_API","-Xms3000m","-Xmx3800m","-noverify","-XX:TieredStopAtLevel=1",\ "-XX:+UseParallelGC","-XX:MinHeapFreeRatio=20","-XX:MaxHeapFreeRatio=40","-XX:GCTimeRatio=4",\ "-XX:AdaptiveSizePolicyWeight=90","-XX:MaxMetaspaceSize=500m","-XX:ParallelGCThreads=1",\ "-Djava.util.concurrent.ForkJoinPool.common.parallelism=1","-XX:CICompilerCount=2",\ diff --git a/api/pom.xml b/api/pom.xml index d5cf5ee4..95d49225 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -6,7 +6,7 @@ ca.bc.gov.educ educ-grad-batch-graduation-api - 1.8.50 + 1.8.53 educ-grad-batch-graduation-api Ministry of Education GRAD BATCH GRADUATION API diff --git a/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/config/AsyncConfig.java b/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/config/AsyncConfig.java index 084cce6a..4c66469d 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/config/AsyncConfig.java +++ b/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/config/AsyncConfig.java @@ -1,24 +1,18 @@ package ca.bc.gov.educ.api.batchgraduation.config; -import ca.bc.gov.educ.api.batchgraduation.util.EducGradBatchGraduationApiConstants; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; +import org.springframework.core.task.SimpleAsyncTaskExecutor; import org.springframework.core.task.TaskExecutor; import org.springframework.scheduling.annotation.EnableAsync; -import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; @Configuration @EnableAsync @Profile("!test") public class AsyncConfig { @Bean(name = "asyncExecutor") - public TaskExecutor asyncExecutor(EducGradBatchGraduationApiConstants constants) { - ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); - executor.setCorePoolSize(constants.getNumberOfPartitions()); - executor.setMaxPoolSize(constants.getNumberOfPartitions()); - executor.setThreadNamePrefix("async-"); - executor.initialize(); - return executor; + public TaskExecutor asyncExecutor() { + return new SimpleAsyncTaskExecutor("async-"); } } diff --git a/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/config/BatchConfig.java b/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/config/BatchConfig.java index c3c53202..51afdca1 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/config/BatchConfig.java +++ b/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/config/BatchConfig.java @@ -29,7 +29,7 @@ public class BatchConfig { public JobLauncher asyncJobLauncher(JobRepository jobRepository) throws Exception { TaskExecutorJobLauncher jobLauncher = new TaskExecutorJobLauncher(); jobLauncher.setJobRepository(jobRepository); - jobLauncher.setTaskExecutor(new SimpleAsyncTaskExecutor()); + jobLauncher.setTaskExecutor(new SimpleAsyncTaskExecutor("asyncTask-")); jobLauncher.afterPropertiesSet(); return jobLauncher; } diff --git a/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/config/BatchJobConfig.java b/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/config/BatchJobConfig.java index 0946217c..2c5cce72 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/config/BatchJobConfig.java +++ b/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/config/BatchJobConfig.java @@ -23,8 +23,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.task.SimpleAsyncTaskExecutor; import org.springframework.core.task.TaskExecutor; -import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionException; @@ -33,7 +33,7 @@ import java.util.UUID; @Configuration -@EnableBatchProcessing +@EnableBatchProcessing(isolationLevelForCreate = "ISOLATION_READ_COMMITTED") public class BatchJobConfig { // Partitioning for Regular Grad Run updates @@ -87,7 +87,7 @@ public Step masterStepRegGrad(JobRepository jobRepository, EducGradBatchGraduati .partitioner(graduationJobStep(jobRepository, skipListener).getName(), partitionerRegGrad()) .step(graduationJobStep(jobRepository, skipListener)) .gridSize(constants.getNumberOfPartitions()) - .taskExecutor(taskExecutor(constants)) + .taskExecutor(taskExecutor()) .build(); } @@ -97,7 +97,7 @@ public Step masterStepRegGradError(JobRepository jobRepository, EducGradBatchGra .partitioner(graduationJobErrorStep(jobRepository, skipListener).getName(), partitionerRegGradRetry()) .step(graduationJobErrorStep(jobRepository, skipListener)) .gridSize(constants.getNumberOfPartitions()) - .taskExecutor(taskExecutor(constants)) + .taskExecutor(taskExecutor()) .build(); } @@ -107,7 +107,7 @@ public Step masterStepRegGradErrorRetry(JobRepository jobRepository, EducGradBat .partitioner(graduationJobErrorRetryStep(jobRepository, skipListener).getName(), partitionerRegGradRetry()) .step(graduationJobErrorRetryStep(jobRepository, skipListener)) .gridSize(constants.getNumberOfPartitions()) - .taskExecutor(taskExecutor(constants)) + .taskExecutor(taskExecutor()) .build(); } @@ -230,7 +230,7 @@ public Step masterStepTvrRun(JobRepository jobRepository, EducGradBatchGraduatio .partitioner(tvrJobStep(jobRepository,skipListener).getName(), partitionerTvrRun()) .step(tvrJobStep(jobRepository,skipListener)) .gridSize(constants.getNumberOfPartitions()) - .taskExecutor(taskExecutor(constants)) + .taskExecutor(taskExecutor()) .build(); } @@ -240,7 +240,7 @@ public Step masterStepTvrRunError(JobRepository jobRepository, EducGradBatchGrad .partitioner(tvrJobErrorStep(jobRepository,skipListener).getName(), partitionerTvrRunRetry()) .step(tvrJobErrorStep(jobRepository,skipListener)) .gridSize(constants.getNumberOfPartitions()) - .taskExecutor(taskExecutor(constants)) + .taskExecutor(taskExecutor()) .build(); } @@ -250,7 +250,7 @@ public Step masterStepTvrRunErrorRetry(JobRepository jobRepository, EducGradBatc .partitioner(tvrJobErrorRetryStep(jobRepository,skipListener).getName(), partitionerTvrRunRetry()) .step(tvrJobErrorRetryStep(jobRepository,skipListener)) .gridSize(constants.getNumberOfPartitions()) - .taskExecutor(taskExecutor(constants)) + .taskExecutor(taskExecutor()) .build(); } @@ -362,7 +362,7 @@ public Step masterStepSpcRegGrad(JobRepository jobRepository, EducGradBatchGradu .partitioner(slaveSpcRegGradStep(jobRepository,skipListener).getName(), partitionerSpcRegGrad()) .step(slaveSpcRegGradStep(jobRepository,skipListener)) .gridSize(constants.getNumberOfPartitions()) - .taskExecutor(taskExecutor(constants)) + .taskExecutor(taskExecutor()) .build(); } @@ -372,7 +372,7 @@ public Step masterStepSpcRegGradError(JobRepository jobRepository, EducGradBatch .partitioner(slaveSpcRegGradErrorStep(jobRepository,skipListener).getName(), partitionerSpcRegGradRetry()) .step(slaveSpcRegGradErrorStep(jobRepository,skipListener)) .gridSize(constants.getNumberOfPartitions()) - .taskExecutor(taskExecutor(constants)) + .taskExecutor(taskExecutor()) .build(); } @@ -382,7 +382,7 @@ public Step masterStepSpcRegGradErrorRetry(JobRepository jobRepository, EducGrad .partitioner(slaveSpcRegGradErrorRetryStep(jobRepository,skipListener).getName(), partitionerSpcRegGradRetry()) .step(slaveSpcRegGradErrorRetryStep(jobRepository,skipListener)) .gridSize(constants.getNumberOfPartitions()) - .taskExecutor(taskExecutor(constants)) + .taskExecutor(taskExecutor()) .build(); } @@ -491,7 +491,7 @@ public Step masterStepSpcTvrRun(JobRepository jobRepository, EducGradBatchGradua .partitioner(slaveStepSpcTvrRun(jobRepository,skipListener).getName(), partitionerSpcRegGrad()) .step(slaveStepSpcTvrRun(jobRepository,skipListener)) .gridSize(constants.getNumberOfPartitions()) - .taskExecutor(taskExecutor(constants)) + .taskExecutor(taskExecutor()) .build(); } @@ -501,7 +501,7 @@ public Step masterStepSpcTvrRunError(JobRepository jobRepository, EducGradBatchG .partitioner(slaveStepSpcTvrRunError(jobRepository,skipListener).getName(), partitionerSpcRegGradRetry()) .step(slaveStepSpcTvrRunError(jobRepository,skipListener)) .gridSize(constants.getNumberOfPartitions()) - .taskExecutor(taskExecutor(constants)) + .taskExecutor(taskExecutor()) .build(); } @@ -511,7 +511,7 @@ public Step masterStepSpcTvrRunErrorRetry(JobRepository jobRepository, EducGradB .partitioner(slaveStepSpcTvrRunErrorRetry(jobRepository,skipListener).getName(), partitionerSpcRegGradRetry()) .step(slaveStepSpcTvrRunErrorRetry(jobRepository,skipListener)) .gridSize(constants.getNumberOfPartitions()) - .taskExecutor(taskExecutor(constants)) + .taskExecutor(taskExecutor()) .build(); } @@ -669,7 +669,7 @@ public Step masterStepDisRun(JobRepository jobRepository, EducGradBatchGraduatio .partitioner(slaveStepDisRun(jobRepository).getName(), partitionerDisRun()) .step(slaveStepDisRun(jobRepository)) .gridSize(constants.getNumberOfPartitions()) - .taskExecutor(taskExecutor(constants)) + .taskExecutor(taskExecutor()) .build(); } @@ -701,7 +701,7 @@ public Step masterStepDisRunYearly(JobRepository jobRepository, EducGradBatchGra .partitioner(slaveStepDisRun(jobRepository).getName(), partitionerDisRunYearly()) .step(slaveStepDisRunYearly(jobRepository)) .gridSize(constants.getNumberOfPartitions()) - .taskExecutor(taskExecutor(constants)) + .taskExecutor(taskExecutor()) .build(); } @@ -727,7 +727,7 @@ public Step masterStepDisRunYearlyNonGrad(JobRepository jobRepository, EducGradB .partitioner(slaveStepDisRunYearlyNonGradByMincode(jobRepository).getName(), partitionerDisRunYearlyNonGrad()) .step(slaveStepDisRunYearlyNonGradByMincode(jobRepository)) .gridSize(constants.getNumberOfPartitions()) - .taskExecutor(taskExecutor(constants)) + .taskExecutor(taskExecutor()) .build(); } @@ -753,7 +753,7 @@ public Step masterStepDisRunSupplemental(JobRepository jobRepository, EducGradBa .partitioner(slaveStepDisRun(jobRepository).getName(), partitionerDisRunSupplemental()) .step(slaveStepDisRun(jobRepository)) .gridSize(constants.getNumberOfPartitions()) - .taskExecutor(taskExecutor(constants)) + .taskExecutor(taskExecutor()) .build(); } @@ -785,7 +785,7 @@ public Step masterStepUserReqDisRun(JobRepository jobRepository, EducGradBatchGr .partitioner(slaveStepDisRun(jobRepository).getName(), partitionerDisRunUserReq()) .step(slaveStepDisRun(jobRepository)) .gridSize(constants.getNumberOfPartitions()) - .taskExecutor(taskExecutor(constants)) + .taskExecutor(taskExecutor()) .build(); } @@ -847,7 +847,7 @@ public Step masterStepBlankUserReqDisRun(JobRepository jobRepository, EducGradBa .partitioner(slaveStepBlankDisRun(jobRepository).getName(), partitionerDisRunBlankUserReq()) .step(slaveStepBlankDisRun(jobRepository)) .gridSize(constants.getNumberOfPartitions()) - .taskExecutor(taskExecutor(constants)) + .taskExecutor(taskExecutor()) .build(); } @@ -910,7 +910,7 @@ public Step masterStepPsiUserReqDisRun(JobRepository jobRepository, EducGradBatc .partitioner(slaveStepPsiDisRun(jobRepository).getName(), partitionerDisRunPsiUserReq()) .step(slaveStepPsiDisRun(jobRepository)) .gridSize(constants.getNumberOfPartitions()) - .taskExecutor(taskExecutor(constants)) + .taskExecutor(taskExecutor()) .build(); } @@ -958,7 +958,7 @@ public Step masterStepCertRegen(JobRepository jobRepository, EducGradBatchGradua .partitioner(certRegenJobStep(jobRepository, skipListener).getName(), partitionerCertRegen()) .step(certRegenJobStep(jobRepository, skipListener)) .gridSize(constants.getNumberOfPartitions()) - .taskExecutor(taskExecutor(constants)) + .taskExecutor(taskExecutor()) .build(); } @@ -1022,7 +1022,7 @@ public Step masterStepEdwSnapshotSchool(JobRepository jobRepository, EducGradBat .partitioner(edwSnapshotSchoolJobStep(jobRepository, skipListener).getName(), partitionerEDWSnapshotSchool()) .step(edwSnapshotSchoolJobStep(jobRepository, skipListener)) .gridSize(constants.getNumberOfPartitions()) - .taskExecutor(taskExecutor(constants)) + .taskExecutor(taskExecutor()) .build(); } @@ -1072,7 +1072,7 @@ public Step masterStepEdwSnapshot(JobRepository jobRepository, EducGradBatchGrad .partitioner(edwSnapshotJobStep(jobRepository, skipListener).getName(), partitionerEDWSnapshot()) .step(edwSnapshotJobStep(jobRepository, skipListener)) .gridSize(constants.getNumberOfPartitions()) - .taskExecutor(taskExecutor(constants)) + .taskExecutor(taskExecutor()) .build(); } @@ -1155,12 +1155,7 @@ public JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor() { } @Bean - public TaskExecutor taskExecutor(EducGradBatchGraduationApiConstants constants) { - ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); - executor.setCorePoolSize(constants.getNumberOfPartitions()); - executor.setMaxPoolSize(constants.getNumberOfPartitions()); - executor.setThreadNamePrefix("partition-"); - executor.initialize(); - return executor; + public TaskExecutor taskExecutor() { + return new SimpleAsyncTaskExecutor("partition-"); } } \ No newline at end of file diff --git a/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/config/BatchJobLauncher.java b/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/config/BatchJobLauncher.java index db5ad3fc..c2789099 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/config/BatchJobLauncher.java +++ b/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/config/BatchJobLauncher.java @@ -2,6 +2,7 @@ import ca.bc.gov.educ.api.batchgraduation.entity.BatchProcessingEntity; import ca.bc.gov.educ.api.batchgraduation.service.GradDashboardService; +import net.javacrumbs.shedlock.core.LockAssert; import net.javacrumbs.shedlock.spring.annotation.SchedulerLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -46,6 +47,7 @@ public class BatchJobLauncher { private Job userScheduledBatchJobRefresher; @Autowired + @Qualifier("asyncJobLauncher") private JobLauncher jobLauncher; @Autowired @@ -66,9 +68,10 @@ public class BatchJobLauncher { private static final String ERROR_MSG = "Error {}"; @Scheduled(cron = "${batch.regalg.cron}") - @SchedulerLock(name = "GraduationBatchJob", lockAtLeastFor = "20s", lockAtMostFor = "1200m") + @SchedulerLock(name = "GraduationBatchJob", lockAtLeastFor = "${batch.system.scheduled.routines.lockAtLeastFor}", lockAtMostFor = "${batch.system.scheduled.routines.lockAtMostFor}") public void runRegularGradAlgorithm() { LOGGER.info(BATCH_STARTED); + LockAssert.assertLocked(); JobParametersBuilder builder = new JobParametersBuilder(); builder.addLong(TIME, System.currentTimeMillis()).toJobParameters(); builder.addString(JOB_TRIGGER, BATCH_TRIGGER); @@ -86,9 +89,10 @@ public void runRegularGradAlgorithm() { } @Scheduled(cron = "${batch.tvrrun.cron}") - @SchedulerLock(name = "tvrBatchJob", lockAtLeastFor = "20s", lockAtMostFor = "1200m") + @SchedulerLock(name = "tvrBatchJob", lockAtLeastFor = "${batch.system.scheduled.routines.lockAtLeastFor}", lockAtMostFor = "${batch.system.scheduled.routines.lockAtMostFor}") public void runTranscriptVerificationReportProcess() { LOGGER.info(BATCH_STARTED); + LockAssert.assertLocked(); JobParametersBuilder builder = new JobParametersBuilder(); builder.addLong(TIME, System.currentTimeMillis()).toJobParameters(); builder.addString(JOB_TRIGGER, BATCH_TRIGGER); @@ -106,9 +110,10 @@ public void runTranscriptVerificationReportProcess() { } @Scheduled(cron = "${batch.distrun.cron}") - @SchedulerLock(name = "DistributionBatchJob", lockAtLeastFor = "10s", lockAtMostFor = "120m") + @SchedulerLock(name = "DistributionBatchJob", lockAtLeastFor = "PT10S", lockAtMostFor = "PT120M") public void runMonthlyDistributionProcess() { LOGGER.info(BATCH_STARTED); + LockAssert.assertLocked(); JobParametersBuilder builder = new JobParametersBuilder(); builder.addLong(TIME, System.currentTimeMillis()).toJobParameters(); builder.addString(JOB_TRIGGER, BATCH_TRIGGER); @@ -126,9 +131,10 @@ public void runMonthlyDistributionProcess() { } @Scheduled(fixedDelayString = "PT30M") - @SchedulerLock(name = "userScheduledBatchJobRefresher", lockAtLeastFor = "10s", lockAtMostFor = "5m") + @SchedulerLock(name = "userScheduledBatchJobRefresher", lockAtLeastFor = "PT10S", lockAtMostFor = "PT5M") public void refreshUserScheduledQueue() { LOGGER.info(BATCH_STARTED); + LockAssert.assertLocked(); JobParametersBuilder builder = new JobParametersBuilder(); builder.addLong(TIME, System.currentTimeMillis()).toJobParameters(); try { diff --git a/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/config/TaskSchedulerConfig.java b/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/config/TaskSchedulerConfig.java index 2c4d823e..fe5f2158 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/config/TaskSchedulerConfig.java +++ b/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/config/TaskSchedulerConfig.java @@ -36,7 +36,8 @@ public LockableTaskScheduler getScheduler() { LockManager lockManager = new DefaultLockManager(lockProvider, lockConfigurationExtractor); ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); - scheduler.setThreadNamePrefix("UserScheduledTaskScheduler"); + scheduler.setPoolSize(30); + scheduler.setThreadNamePrefix("UserScheduledTask-"); scheduler.initialize(); return new LockableTaskScheduler(scheduler, lockManager); } diff --git a/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/controller/JobLauncherController.java b/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/controller/JobLauncherController.java index 6b1f195f..1b72e427 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/controller/JobLauncherController.java +++ b/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/controller/JobLauncherController.java @@ -466,6 +466,14 @@ public ResponseEntity launchMonthlyDistributionRunJob() @Operation(summary = "Run Distribution Runs", description = "Run Distribution Runs", tags = { "Distribution" }) @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"),@ApiResponse(responseCode = "500", description = "Internal Server Error")}) public ResponseEntity launchDistributionRunJob() { + return launchDistributionRunJob(null); + } + + @PostMapping(EducGradBatchGraduationApiConstants.EXECUTE_DIS_RUN_BATCH_JOB) + @PreAuthorize(PermissionsConstants.RUN_GRAD_ALGORITHM) + @Operation(summary = "Run Distribution Runs", description = "Run Distribution Runs", tags = { "Distribution" }) + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"),@ApiResponse(responseCode = "500", description = "Internal Server Error")}) + public ResponseEntity launchDistributionRunJob(@RequestBody StudentSearchRequest request) { logger.debug("launchDistributionRunJob"); JobParametersBuilder builder = new JobParametersBuilder(); builder.addLong(TIME, System.currentTimeMillis()).toJobParameters(); @@ -473,6 +481,9 @@ public ResponseEntity launchDistributionRunJob() { builder.addString(JOB_TRIGGER, MANUAL); builder.addString(JOB_TYPE, DISTRUN); try { + if(request != null) { + builder.addString(SEARCH_REQUEST, jsonTransformer.marshall(request)); + } JobExecution jobExecution = asyncJobLauncher.run(jobRegistry.getJob("DistributionBatchJob"), builder.toJobParameters()); ExecutionContext jobContext = jobExecution.getExecutionContext(); DistributionSummaryDTO summaryDTO = (DistributionSummaryDTO)jobContext.get(DISDTO); @@ -483,7 +494,7 @@ public ResponseEntity launchDistributionRunJob() { summaryDTO.setBatchId(jobExecution.getId()); return ResponseEntity.ok(summaryDTO); } catch (JobExecutionAlreadyRunningException | JobRestartException | JobInstanceAlreadyCompleteException - | JobParametersInvalidException | NoSuchJobException e) { + | JobParametersInvalidException | NoSuchJobException e) { DistributionSummaryDTO summaryDTO = new DistributionSummaryDTO(); summaryDTO.setException(e.getLocalizedMessage()); return ResponseEntity.status(500).body(summaryDTO); diff --git a/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/entity/BatchJobExecutionEntity.java b/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/entity/BatchJobExecutionEntity.java index c8d02118..ea51c85a 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/entity/BatchJobExecutionEntity.java +++ b/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/entity/BatchJobExecutionEntity.java @@ -44,8 +44,9 @@ public class BatchJobExecutionEntity { @Column(name = "EXIT_MESSAGE", length = 2500) private String exitMessage; - @Column(name = "LAST_UPDATED") - private Instant lastUpdated; + @Column(name = "LAST_UPDATED", nullable = true) + @Temporal(TemporalType.TIMESTAMP) + private LocalDateTime lastUpdated; @Column(name = "JOB_CONFIGURATION_LOCATION", length = 2500) private String jobConfigurationLocation; diff --git a/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/entity/BatchStepExecutionEntity.java b/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/entity/BatchStepExecutionEntity.java index eefb4f1e..6b01c9e8 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/entity/BatchStepExecutionEntity.java +++ b/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/entity/BatchStepExecutionEntity.java @@ -1,12 +1,9 @@ package ca.bc.gov.educ.api.batchgraduation.entity; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.Id; -import jakarta.persistence.Table; +import jakarta.persistence.*; import lombok.Data; -import java.time.Instant; +import java.time.LocalDateTime; @Data @Entity @@ -27,10 +24,12 @@ public class BatchStepExecutionEntity { private String stepName; @Column(name = "START_TIME") - private Instant startTime = Instant.now(); + @Temporal(TemporalType.TIMESTAMP) + private LocalDateTime startTime = LocalDateTime.now(); @Column(name = "END_TIME") - private Instant endTime; + @Temporal(TemporalType.TIMESTAMP) + private LocalDateTime endTime; @Column(name = "STATUS", length = 10) private String status; @@ -66,9 +65,11 @@ public class BatchStepExecutionEntity { private String exitMessage; @Column(name = "LAST_UPDATED") - private Instant lastUpdated = Instant.now(); + @Temporal(TemporalType.TIMESTAMP) + private LocalDateTime lastUpdated = LocalDateTime.now(); @Column(name = "CREATE_TIME") - private Instant createTime = Instant.now(); + @Temporal(TemporalType.TIMESTAMP) + private LocalDateTime createTime = LocalDateTime.now(); } \ No newline at end of file diff --git a/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/listener/UserReqBlankDistributionRunCompletionNotificationListener.java b/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/listener/UserReqBlankDistributionRunCompletionNotificationListener.java index 8360a520..7dd9a14c 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/listener/UserReqBlankDistributionRunCompletionNotificationListener.java +++ b/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/listener/UserReqBlankDistributionRunCompletionNotificationListener.java @@ -77,14 +77,16 @@ public void afterJob(JobExecution jobExecution) { BlankDistributionSummaryDTO finalSummaryDTO = summaryDTO; summaryDTO.getCredentialCountMap().forEach((key, value) -> LOGGER.info(" {} count : {}", key, finalSummaryDTO.getCredentialCountMap().get(key))); + StudentSearchRequest studentSearchRequestObject = (StudentSearchRequest)jsonTransformer.unmarshall(studentSearchRequest, StudentSearchRequest.class); + ResponseObj obj = restUtils.getTokenResponseObject(); LOGGER.info("Starting Report Process --------------------------------------------------------------------------"); - processGlobalList(credentialType,summaryDTO.getGlobalList(),jobExecutionId,summaryDTO.getMapDist(),obj.getAccess_token(),localDownLoad,properName); + processGlobalList(studentSearchRequestObject, credentialType,summaryDTO.getGlobalList(),jobExecutionId,summaryDTO.getMapDist(),obj.getAccess_token(),localDownLoad,properName); LOGGER.info(LOG_SEPARATION); } } - private void processGlobalList(String credentialType, List cList, Long batchId, Map mapDist, String accessToken,String localDownload,String properName) { + private void processGlobalList(StudentSearchRequest studentSearchRequest , String credentialType, List cList, Long batchId, Map mapDist, String accessToken,String localDownload,String properName) { List uniqueSchoolList = cList.stream().map(BlankCredentialDistribution::getSchoolOfRecord).distinct().collect(Collectors.toList()); uniqueSchoolList.forEach(usl->{ List yed4List = new ArrayList<>(); @@ -109,7 +111,7 @@ private void processGlobalList(String credentialType, List schoolOfRecords; private List credentialTypeCode; + private String user; + private Address address; private int quantity; String localDownload; } diff --git a/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/model/StudentSearchRequest.java b/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/model/StudentSearchRequest.java index 9f502e42..8bff9ac9 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/model/StudentSearchRequest.java +++ b/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/model/StudentSearchRequest.java @@ -26,6 +26,7 @@ public class StudentSearchRequest implements Serializable { private List studentIDs; private String user; + private Address address; @JsonFormat(pattern = "yyyy-MM-dd") Date gradDateFrom; diff --git a/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/reader/BasePartitioner.java b/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/reader/BasePartitioner.java index bba796f7..8c8fd18b 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/reader/BasePartitioner.java +++ b/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/reader/BasePartitioner.java @@ -226,16 +226,19 @@ void filterByStudentSearchRequest(List eligibleSt useFilterSchoolDistricts.add(school.getMincode()); } } - eligibleStudentSchoolDistricts.removeIf(scr->!useFilterSchoolDistricts.contains(scr.getSchoolOfRecord())); + eligibleStudentSchoolDistricts.removeIf(scr->StringUtils.isNotBlank(scr.getSchoolOfRecord()) && !useFilterSchoolDistricts.contains(scr.getSchoolOfRecord())); } if(searchRequest != null && searchRequest.getDistricts() != null && !searchRequest.getDistricts().isEmpty()) { - eligibleStudentSchoolDistricts.removeIf(scr->!searchRequest.getDistricts().contains(StringUtils.substring(scr.getSchoolOfRecord(), 0, 3))); + eligibleStudentSchoolDistricts.removeIf(scr->StringUtils.isNotBlank(scr.getSchoolOfRecord()) && !searchRequest.getDistricts().contains(StringUtils.substring(scr.getSchoolOfRecord(), 0, 3))); } if(searchRequest != null && searchRequest.getSchoolOfRecords() != null && !searchRequest.getSchoolOfRecords().isEmpty()) { - eligibleStudentSchoolDistricts.removeIf(scr->!searchRequest.getSchoolOfRecords().contains(scr.getSchoolOfRecord())); + eligibleStudentSchoolDistricts.removeIf(scr->StringUtils.isNotBlank(scr.getSchoolOfRecord()) && !searchRequest.getSchoolOfRecords().contains(scr.getSchoolOfRecord())); + } + if(searchRequest != null && searchRequest.getStudentIDs() != null && !searchRequest.getStudentIDs().isEmpty()) { + eligibleStudentSchoolDistricts.removeIf(scr->scr.getStudentID() != null && !searchRequest.getStudentIDs().contains(scr.getStudentID())); } if(searchRequest != null && searchRequest.getPens() != null && !searchRequest.getPens().isEmpty()) { - eligibleStudentSchoolDistricts.removeIf(scr->!searchRequest.getPens().contains(scr.getPen())); + eligibleStudentSchoolDistricts.removeIf(scr->StringUtils.isNotBlank(scr.getPen()) && !searchRequest.getPens().contains(scr.getPen())); } } diff --git a/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/reader/DistributionRunPartitioner.java b/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/reader/DistributionRunPartitioner.java index 95b565c3..8580c37c 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/reader/DistributionRunPartitioner.java +++ b/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/reader/DistributionRunPartitioner.java @@ -1,7 +1,6 @@ package ca.bc.gov.educ.api.batchgraduation.reader; import ca.bc.gov.educ.api.batchgraduation.model.DistributionDataParallelDTO; -import ca.bc.gov.educ.api.batchgraduation.model.ResponseObj; import ca.bc.gov.educ.api.batchgraduation.model.StudentCredentialDistribution; import ca.bc.gov.educ.api.batchgraduation.service.ParallelDataFetch; import org.slf4j.Logger; @@ -12,14 +11,17 @@ import org.springframework.beans.factory.annotation.Value; import reactor.core.publisher.Mono; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; /** * Monthly Distribution Partitioner */ public class DistributionRunPartitioner extends BasePartitioner { - private static final Logger LOGGER = LoggerFactory.getLogger(DistributionRunPartitioner.class); + private static final Logger logger = LoggerFactory.getLogger(DistributionRunPartitioner.class); @Value("#{stepExecution.jobExecution}") JobExecution context; @@ -29,23 +31,18 @@ public class DistributionRunPartitioner extends BasePartitioner { @Override public Map partition(int gridSize) { - ResponseObj res = restUtils.getTokenResponseObject(); - String accessToken = null; - if (res != null) { - accessToken = res.getAccess_token(); - } // Clean up existing reports before running new one - LOGGER.debug("Delete School Reports for Monthly Distribution"); + logger.debug("Delete School Reports for Monthly Distribution"); long startTime = System.currentTimeMillis(); restUtils.deleteSchoolReportRecord("", "ADDRESS_LABEL_SCHL", restUtils.getAccessToken()); long endTime = System.currentTimeMillis(); long diff = (endTime - startTime)/1000; - LOGGER.debug("Old School Reports deleted in {} sec", diff); + logger.debug("Old School Reports deleted in {} sec", diff); startTime = System.currentTimeMillis(); - LOGGER.debug("Retrieve students for Monthly Distribution"); - Mono parallelDTOMono = parallelDataFetch.fetchDistributionRequiredData(accessToken); + logger.debug("Retrieve students for Monthly Distribution"); + Mono parallelDTOMono = parallelDataFetch.fetchDistributionRequiredData(restUtils.getAccessToken()); DistributionDataParallelDTO parallelDTO = parallelDTOMono.block(); List credentialList = new ArrayList<>(); if(parallelDTO != null) { @@ -54,20 +51,14 @@ public Map partition(int gridSize) { } endTime = System.currentTimeMillis(); diff = (endTime - startTime)/1000; - LOGGER.debug("Total {} eligible StudentCredentialDistributions found in {} sec", credentialList.size(), diff); + logger.debug("Total {} eligible StudentCredentialDistributions found in {} sec", credentialList.size(), diff); + filterByStudentSearchRequest(credentialList); if(!credentialList.isEmpty()) { - LOGGER.debug("Total size of credential list: {}", credentialList.size()); - // Filter deceased students out - List deceasedIDs = restUtils.getDeceasedStudentIDs(credentialList.stream().map(StudentCredentialDistribution::getStudentID).distinct().toList(), restUtils.getAccessToken()); - if (!deceasedIDs.isEmpty()) { - LOGGER.debug("Deceased students: {}", deceasedIDs.size()); - credentialList.removeIf(cr -> deceasedIDs.contains(cr.getStudentID())); - LOGGER.debug("Revised size of credential list: {}", credentialList.size()); - } + filterOutDeceasedStudents(credentialList); updateBatchJobHistory(createBatchJobHistory(), (long) credentialList.size()); return getStringExecutionContextMap(gridSize, credentialList, null); } - LOGGER.info("No Credentials Found for Processing"); + logger.info("No Credentials Found for Processing"); return new HashMap<>(); } diff --git a/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/reader/DistributionRunPartitionerBlankUserReq.java b/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/reader/DistributionRunPartitionerBlankUserReq.java index c97ac3e1..9c254db5 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/reader/DistributionRunPartitionerBlankUserReq.java +++ b/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/reader/DistributionRunPartitionerBlankUserReq.java @@ -75,6 +75,7 @@ private List getRecordsForBlankUserReqDisRun(BlankC bcd.setQuantity(req.getQuantity()); bcd.setSchoolOfRecord(sch); bcd.setCredentialTypeCode(ctc); + bcd.setAddress(req.getAddress()); blankList.add(bcd); } }); diff --git a/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/reader/DistributionRunSupplementalPartitioner.java b/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/reader/DistributionRunSupplementalPartitioner.java index 1c54319b..7d500b41 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/reader/DistributionRunSupplementalPartitioner.java +++ b/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/reader/DistributionRunSupplementalPartitioner.java @@ -11,10 +11,7 @@ import org.springframework.beans.factory.annotation.Value; import reactor.core.publisher.Mono; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; public class DistributionRunSupplementalPartitioner extends BasePartitioner { @@ -31,28 +28,22 @@ public Map partition(int gridSize) { logger.debug("Delete School Reports for Supplemental Distribution"); long startTime = System.currentTimeMillis(); - String accessToken = restUtils.getAccessToken(); // Clean up existing reports before running new one - restUtils.deleteSchoolReportRecord("", "ADDRESS_LABEL_SCHL", accessToken); - restUtils.deleteSchoolReportRecord("", "DISTREP_SC", accessToken); + restUtils.deleteSchoolReportRecord("", "ADDRESS_LABEL_SCHL", restUtils.getAccessToken()); + restUtils.deleteSchoolReportRecord("", "DISTREP_SC", restUtils.getAccessToken()); long endTime = System.currentTimeMillis(); long diff = (endTime - startTime)/1000; logger.debug("Old School Reports deleted in {} sec", diff); startTime = System.currentTimeMillis(); logger.debug("Retrieve students for Supplemental Distribution"); - Mono parallelDTOMono = parallelDataFetch.fetchDistributionRequiredDataYearly(accessToken); - DistributionDataParallelDTO parallelDTO = parallelDTOMono.block(); - List eligibleStudentSchoolDistricts = new ArrayList<>(); - if(parallelDTO != null) { - eligibleStudentSchoolDistricts.addAll(parallelDTO.transcriptList()); - eligibleStudentSchoolDistricts.addAll(parallelDTO.certificateList()); - } + List eligibleStudentSchoolDistricts = parallelDataFetch.fetchStudentCredentialsDistributionDataYearly(); endTime = System.currentTimeMillis(); diff = (endTime - startTime)/1000; logger.debug("Total {} eligible StudentCredentialDistributions found in {} sec", eligibleStudentSchoolDistricts.size(), diff); filterByStudentSearchRequest(eligibleStudentSchoolDistricts); if(!eligibleStudentSchoolDistricts.isEmpty()) { + filterOutDeceasedStudents(eligibleStudentSchoolDistricts); updateBatchJobHistory(createBatchJobHistory(), (long) eligibleStudentSchoolDistricts.size()); return getStringExecutionContextMap(gridSize, eligibleStudentSchoolDistricts, null); } diff --git a/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/repository/BatchStepExecutionRepository.java b/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/repository/BatchStepExecutionRepository.java new file mode 100644 index 00000000..0d4adcfa --- /dev/null +++ b/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/repository/BatchStepExecutionRepository.java @@ -0,0 +1,14 @@ +package ca.bc.gov.educ.api.batchgraduation.repository; + +import ca.bc.gov.educ.api.batchgraduation.entity.BatchStepExecutionEntity; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface BatchStepExecutionRepository extends JpaRepository { + + List findByJobExecutionIdOrderByEndTimeDesc(Long jobExecutionId); + +} diff --git a/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/service/GradDashboardService.java b/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/service/GradDashboardService.java index 126f1fea..30f55394 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/service/GradDashboardService.java +++ b/api/src/main/java/ca/bc/gov/educ/api/batchgraduation/service/GradDashboardService.java @@ -2,20 +2,17 @@ import ca.bc.gov.educ.api.batchgraduation.entity.*; import ca.bc.gov.educ.api.batchgraduation.model.*; -import ca.bc.gov.educ.api.batchgraduation.repository.BatchGradAlgorithmJobHistoryRepository; -import ca.bc.gov.educ.api.batchgraduation.repository.BatchGradAlgorithmStudentRepository; -import ca.bc.gov.educ.api.batchgraduation.repository.BatchJobExecutionRepository; -import ca.bc.gov.educ.api.batchgraduation.repository.BatchProcessingRepository; +import ca.bc.gov.educ.api.batchgraduation.repository.*; import ca.bc.gov.educ.api.batchgraduation.rest.RestUtils; import ca.bc.gov.educ.api.batchgraduation.transformer.BatchGradAlgorithmJobHistoryTransformer; import ca.bc.gov.educ.api.batchgraduation.transformer.BatchProcessingTransformer; -import org.apache.commons.lang3.time.DateUtils; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.time.Duration; import java.time.LocalDateTime; import java.util.*; import java.util.stream.Collectors; @@ -23,10 +20,13 @@ @Service public class GradDashboardService extends GradService { + private static final String JOB_STATUS_STARTED = BatchStatusEnum.STARTED.name(); + private final BatchGradAlgorithmJobHistoryRepository batchGradAlgorithmJobHistoryRepository; private final BatchGradAlgorithmStudentRepository batchGradAlgorithmStudentRepository; private final BatchGradAlgorithmJobHistoryTransformer batchGradAlgorithmJobHistoryTransformer; private final BatchJobExecutionRepository batchJobExecutionRepository; + private final BatchStepExecutionRepository batchStepExecutionRepository; private final BatchProcessingTransformer batchProcessingTransformer; private final BatchProcessingRepository batchProcessingRepository; private final RestUtils restUtils; @@ -34,13 +34,16 @@ public class GradDashboardService extends GradService { public GradDashboardService(BatchGradAlgorithmJobHistoryRepository batchGradAlgorithmJobHistoryRepository, BatchGradAlgorithmJobHistoryTransformer batchGradAlgorithmJobHistoryTransformer, RestUtils restUtils, - BatchJobExecutionRepository batchJobExecutionRepository,BatchProcessingRepository batchProcessingRepository,BatchProcessingTransformer batchProcessingTransformer, + BatchJobExecutionRepository batchJobExecutionRepository, + BatchStepExecutionRepository batchStepExecutionRepository, + BatchProcessingRepository batchProcessingRepository,BatchProcessingTransformer batchProcessingTransformer, BatchGradAlgorithmStudentRepository batchGradAlgorithmStudentRepository) { this.batchGradAlgorithmJobHistoryRepository = batchGradAlgorithmJobHistoryRepository; this.batchGradAlgorithmJobHistoryTransformer = batchGradAlgorithmJobHistoryTransformer; this.batchProcessingTransformer = batchProcessingTransformer; this.batchGradAlgorithmStudentRepository = batchGradAlgorithmStudentRepository; this.batchJobExecutionRepository = batchJobExecutionRepository; + this.batchStepExecutionRepository = batchStepExecutionRepository; this.batchProcessingRepository = batchProcessingRepository; this.restUtils = restUtils; } @@ -50,7 +53,7 @@ public GradDashboard getDashboardInfo() { start(); GradDashboard gradDash = new GradDashboard(); List infoDetailsList= batchGradAlgorithmJobHistoryTransformer.transformToDTO(batchGradAlgorithmJobHistoryRepository.findAll()); - infoDetailsList = infoDetailsList.stream().map(this::handleDeadJob).collect(Collectors.toList()); + infoDetailsList = infoDetailsList.stream().map(this::handleDeadJob).map(this::handleFrozenJob).collect(Collectors.toList()); infoDetailsList.sort(Comparator.comparing(BatchGradAlgorithmJobHistory::getStartTime).reversed()); if(!infoDetailsList.isEmpty()) { BatchGradAlgorithmJobHistory info = infoDetailsList.get(0); @@ -144,30 +147,55 @@ public Optional findBatchProcessing(String jobType) { return batchProcessingRepository.findByJobType(jobType); } + /** + * If any batch jobs have "STARTED" status more than 3 days(72 hours), then treat it as FAILED job + */ @Transactional public BatchGradAlgorithmJobHistory handleDeadJob(BatchGradAlgorithmJobHistory batchJobHistory) { - if ("STARTED".equalsIgnoreCase(batchJobHistory.getStatus()) - && batchJobHistory.getEndTime() == null) { - Integer jobExecutionId = batchJobHistory.getJobExecutionId(); + if (JOB_STATUS_STARTED.equalsIgnoreCase(batchJobHistory.getStatus()) && batchJobHistory.getEndTime() == null) { + LocalDateTime now = LocalDateTime.now(); + Duration duration = Duration.between(batchJobHistory.getStartTime(), now); + long hours = duration.getSeconds() / 3600; + if (hours > 72) { + updateBatchJobStatus(batchJobHistory, BatchStatusEnum.FAILED); + } + } - Date now = new Date(System.currentTimeMillis()); - LocalDateTime deadline = ca.bc.gov.educ.api.batchgraduation.util.DateUtils.toLocalDateTime(DateUtils.addDays(now, -3)); + return batchJobHistory; + } - if (batchJobHistory.getStartTime().isBefore(deadline)) { - Optional optional = batchJobExecutionRepository.findById(jobExecutionId.longValue()); - if (optional.isPresent()) { - BatchJobExecutionEntity batchJobExecution = optional.get(); - if ("UNKNOWN".equalsIgnoreCase(batchJobExecution.getExitCode()) - || BatchStatusEnum.FAILED.toString().equalsIgnoreCase(batchJobExecution.getExitCode()) ) { - BatchGradAlgorithmJobHistoryEntity entity = batchGradAlgorithmJobHistoryTransformer.transformToEntity(batchJobHistory); - entity.setStatus(BatchStatusEnum.FAILED.toString()); - batchGradAlgorithmJobHistoryRepository.save(entity); - batchJobHistory.setStatus(BatchStatusEnum.FAILED.toString()); - } - } + @Transactional + public BatchGradAlgorithmJobHistory handleFrozenJob(BatchGradAlgorithmJobHistory batchJobHistory) { + if (JOB_STATUS_STARTED.equalsIgnoreCase(batchJobHistory.getStatus()) && batchJobHistory.getEndTime() == null) { + Integer jobExecutionId = batchJobHistory.getJobExecutionId(); + if (isFrozen(jobExecutionId.longValue())) { + updateBatchJobStatus(batchJobHistory, BatchStatusEnum.FAILED); } } - return batchJobHistory; } + + /** + * If last updated time is frozen more than 5 hours for all of "STARTED" steps, then treat it as FAILED job + */ + private boolean isFrozen(Long jobExecutionId) { + List steps = batchStepExecutionRepository.findByJobExecutionIdOrderByEndTimeDesc(jobExecutionId); + LocalDateTime now = LocalDateTime.now(); + boolean frozenStepFound = false; + for (BatchStepExecutionEntity step : steps) { + if (step.getStepName().contains("partition") && JOB_STATUS_STARTED.equalsIgnoreCase(step.getStatus())) { + Duration duration = Duration.between(step.getLastUpdated(), now); + long hours = duration.getSeconds() / 3600; + frozenStepFound = hours > 5; + } + } + return frozenStepFound; + } + + private void updateBatchJobStatus(BatchGradAlgorithmJobHistory batchJobHistory, BatchStatusEnum batchStatus) { + BatchGradAlgorithmJobHistoryEntity entity = batchGradAlgorithmJobHistoryTransformer.transformToEntity(batchJobHistory); + entity.setStatus(batchStatus.toString()); + batchGradAlgorithmJobHistoryRepository.save(entity); + batchJobHistory.setStatus(BatchStatusEnum.FAILED.toString()); + } } diff --git a/api/src/main/resources/application.yaml b/api/src/main/resources/application.yaml index 7acc1f1b..4014ad4e 100644 --- a/api/src/main/resources/application.yaml +++ b/api/src/main/resources/application.yaml @@ -153,6 +153,11 @@ batch: jobs: lockAtLeastFor: ${CRON_USER_SCHEDULED_JOBS_LOCK_AT_LEAST_FOR} lockAtMostFor: ${CRON_USER_SCHEDULED_JOBS_LOCK_AT_MOST_FOR} + system: + scheduled: + routines: + lockAtLeastFor: ${CRON_SYSTEM_SCHEDULED_ROUTINES_LOCK_AT_LEAST_FOR} + lockAtMostFor: ${CRON_SYSTEM_SCHEDULED_ROUTINES_LOCK_AT_MOST_FOR} #Endpoints endpoint: diff --git a/api/src/test/java/ca/bc/gov/educ/api/batchgraduation/service/GradDashboardServiceTest.java b/api/src/test/java/ca/bc/gov/educ/api/batchgraduation/service/GradDashboardServiceTest.java index 980b0ea4..8be796a2 100644 --- a/api/src/test/java/ca/bc/gov/educ/api/batchgraduation/service/GradDashboardServiceTest.java +++ b/api/src/test/java/ca/bc/gov/educ/api/batchgraduation/service/GradDashboardServiceTest.java @@ -1,14 +1,8 @@ package ca.bc.gov.educ.api.batchgraduation.service; -import ca.bc.gov.educ.api.batchgraduation.entity.BatchGradAlgorithmJobHistoryEntity; -import ca.bc.gov.educ.api.batchgraduation.entity.BatchGradAlgorithmStudentEntity; -import ca.bc.gov.educ.api.batchgraduation.entity.BatchJobExecutionEntity; -import ca.bc.gov.educ.api.batchgraduation.entity.BatchProcessingEntity; +import ca.bc.gov.educ.api.batchgraduation.entity.*; import ca.bc.gov.educ.api.batchgraduation.model.*; -import ca.bc.gov.educ.api.batchgraduation.repository.BatchGradAlgorithmJobHistoryRepository; -import ca.bc.gov.educ.api.batchgraduation.repository.BatchGradAlgorithmStudentRepository; -import ca.bc.gov.educ.api.batchgraduation.repository.BatchJobExecutionRepository; -import ca.bc.gov.educ.api.batchgraduation.repository.BatchProcessingRepository; +import ca.bc.gov.educ.api.batchgraduation.repository.*; import ca.bc.gov.educ.api.batchgraduation.rest.RestUtils; import org.apache.commons.lang3.time.DateUtils; import org.junit.Test; @@ -48,6 +42,9 @@ public class GradDashboardServiceTest { @MockBean BatchJobExecutionRepository batchJobExecutionRepository; + @MockBean + BatchStepExecutionRepository batchStepExecutionRepository; + @MockBean BatchProcessingRepository batchProcessingRepository; @@ -86,7 +83,7 @@ public void testGetDashboardInfo_whenStartedDate_isOlderThan3Days_thenUpdateStat hist.setExpectedStudentsProcessed(20L); hist.setJobExecutionId(121L); Date today = new Date(System.currentTimeMillis()); - Date startedDateTime = DateUtils.addDays(today, -3); + Date startedDateTime = DateUtils.addDays(today, -4); hist.setStartTime(ca.bc.gov.educ.api.batchgraduation.util.DateUtils.toLocalDateTime(startedDateTime)); hist.setStatus("STARTED"); list.add(hist); @@ -109,6 +106,48 @@ public void testGetDashboardInfo_whenStartedDate_isOlderThan3Days_thenUpdateStat } + @Test + public void testGetDashboardInfo_whenLastUpdatedDate_isOlderThan5hours_thenUpdateStatusAsFailed() { + + List list = new ArrayList<>(); + BatchGradAlgorithmJobHistoryEntity hist = new BatchGradAlgorithmJobHistoryEntity(); + hist.setId(new UUID(1,1)); + hist.setExpectedStudentsProcessed(20L); + hist.setJobExecutionId(121L); + Date today = new Date(System.currentTimeMillis()); + Date startedDateTime = DateUtils.addDays(today, -1); + hist.setStartTime(ca.bc.gov.educ.api.batchgraduation.util.DateUtils.toLocalDateTime(startedDateTime)); + hist.setStatus("STARTED"); + list.add(hist); + + BatchJobExecutionEntity batchJobExecution = new BatchJobExecutionEntity(); + batchJobExecution.setJobExecutionId(hist.getJobExecutionId()); + batchJobExecution.setId(Long.valueOf("123")); + batchJobExecution.setStatus("STARTED"); + batchJobExecution.setStartTime(hist.getStartTime()); + + BatchStepExecutionEntity step = new BatchStepExecutionEntity(); + step.setStepName("test-partition12"); + step.setJobExecutionId(hist.getJobExecutionId()); + step.setId(Long.valueOf("123")); + step.setStatus("STARTED"); + step.setStartTime(ca.bc.gov.educ.api.batchgraduation.util.DateUtils.toLocalDateTime(startedDateTime)); + Date lastUpdatedDateTime = DateUtils.addHours(today, -6); + step.setLastUpdated(ca.bc.gov.educ.api.batchgraduation.util.DateUtils.toLocalDateTime(lastUpdatedDateTime)); + batchJobExecution.setStartTime(hist.getStartTime()); + + when(batchGradAlgorithmJobHistoryRepository.findAll()).thenReturn(list); + when(batchJobExecutionRepository.findById(hist.getJobExecutionId())).thenReturn(Optional.of(batchJobExecution)); + when(batchStepExecutionRepository.findByJobExecutionIdOrderByEndTimeDesc(hist.getJobExecutionId())).thenReturn(List.of(step)); + + GradDashboard dash = gradDashboardService.getDashboardInfo(); + assertThat(dash).isNotNull(); + assertThat(dash.getTotalBatchRuns()).isEqualTo(1); + assertThat(dash.getBatchInfoList()).isNotEmpty(); + assertThat(dash.getBatchInfoList().get(0).getStatus()).isEqualTo("FAILED"); + + } + @Test public void testgetProcessingList() { diff --git a/api/src/test/resources/application.yaml b/api/src/test/resources/application.yaml index 0fb49899..67671136 100644 --- a/api/src/test/resources/application.yaml +++ b/api/src/test/resources/application.yaml @@ -82,6 +82,11 @@ batch: jobs: lockAtLeastFor: 10 lockAtMostFor: 180 + system: + scheduled: + routines: + lockAtLeastFor: PT1M + lockAtMostFor: PT600M #Endpoints endpoint: