From b3da50c96063cef75b8e9ea2a42c8d509ad75e8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Vorburger=20=E2=9B=91=EF=B8=8F?= Date: Sun, 24 May 2020 21:54:37 +0200 Subject: [PATCH] intro. SchedulerJobsHelper.executeAndAwaitJob() [FINERACT-922] (#817) --- .../integrationtests/SchedulerJobsTest.java | 34 +--- .../common/SchedulerJobHelper.java | 181 +++++++++++++----- 2 files changed, 136 insertions(+), 79 deletions(-) diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/SchedulerJobsTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/SchedulerJobsTest.java index f256047e5..5739a86dc 100644 --- a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/SchedulerJobsTest.java +++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/SchedulerJobsTest.java @@ -20,8 +20,6 @@ package org.apache.fineract.integrationtests; import static org.awaitility.Awaitility.await; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; import io.restassured.builder.RequestSpecBuilder; import io.restassured.http.ContentType; @@ -35,7 +33,6 @@ import org.apache.fineract.integrationtests.common.SchedulerJobHelper; import org.apache.fineract.integrationtests.common.Utils; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; public class SchedulerJobsTest { @@ -129,34 +126,9 @@ public class SchedulerJobsTest { } @Test - @Ignore // TODO FINERACT-852 & FINERACT-922 - public void testSchedulerJobs() throws InterruptedException { - // For each retrieved scheduled job (by ID)... - for (Integer jobId : schedulerJobHelper.getAllSchedulerJobIds()) { - // Retrieving Scheduler Job by ID - Map schedulerJob = schedulerJobHelper.getSchedulerJobById(jobId); - - // Executing Scheduler Job - SchedulerJobHelper.runSchedulerJob(requestSpec, jobId.toString()); - - // Retrieving Scheduler Job by ID - schedulerJob = schedulerJobHelper.getSchedulerJobById(jobId); - assertNotNull(schedulerJob); - - // Waiting for Job to complete - while ((Boolean) schedulerJob.get("currentlyRunning") == true) { - Thread.sleep(500); - schedulerJob = schedulerJobHelper.getSchedulerJobById(jobId); - assertNotNull(schedulerJob); - } - @SuppressWarnings({ "unchecked", "rawtypes" }) - List jobHistoryData = schedulerJobHelper.getSchedulerJobHistory(jobId); - - // Verifying the Status of the Recently executed Scheduler Job - assertFalse("Job History is empty :( Was it too slow? Failures in background job?", - jobHistoryData.isEmpty()); - assertEquals("Verifying Last Scheduler Job Status", "success", - jobHistoryData.get(jobHistoryData.size() - 1).get("status")); + public void testTriggeringManualExecutionOfAllSchedulerJobs() { + for (String jobName : schedulerJobHelper.getAllSchedulerJobNames()) { + schedulerJobHelper.executeAndAwaitJob(jobName); } } } \ No newline at end of file diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/SchedulerJobHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/SchedulerJobHelper.java index 8382ca7fd..21dafcb9d 100644 --- a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/SchedulerJobHelper.java +++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/SchedulerJobHelper.java @@ -18,23 +18,32 @@ */ package org.apache.fineract.integrationtests.common; +import static java.time.Instant.now; +import static org.awaitility.Awaitility.await; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; import com.google.gson.Gson; import io.restassured.builder.ResponseSpecBuilder; import io.restassured.specification.RequestSpecification; import io.restassured.specification.ResponseSpecification; -import java.util.ArrayList; +import java.time.Duration; +import java.time.Instant; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.function.ToIntFunction; +import java.util.concurrent.Callable; +import java.util.function.Function; import java.util.stream.Collectors; -import org.junit.Assert; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -@SuppressWarnings({ "rawtypes", "unchecked" }) public class SchedulerJobHelper { private final static Logger LOG = LoggerFactory.getLogger(SchedulerJobHelper.class); private final RequestSpecification requestSpec; @@ -53,16 +62,24 @@ public class SchedulerJobHelper { this.response202Spec = responseSpec; } - private List getAllSchedulerJobs() { + private List> getAllSchedulerJobs() { final String GET_ALL_SCHEDULER_JOBS_URL = "/fineract-provider/api/v1/jobs?" + Utils.TENANT_IDENTIFIER; LOG.info("------------------------ RETRIEVING ALL SCHEDULER JOBS -------------------------"); - final ArrayList response = Utils.performServerGet(requestSpec, response200Spec, GET_ALL_SCHEDULER_JOBS_URL, ""); + List> response = Utils.performServerGet(requestSpec, response200Spec, GET_ALL_SCHEDULER_JOBS_URL, ""); + assertNotNull(response); return response; } + private List getAllSchedulerJobDetails(Function, T> mapper) { + return getAllSchedulerJobs().stream().map(mapper).collect(Collectors.toList()); + } + public List getAllSchedulerJobIds() { - ToIntFunction mapper = map -> (Integer) map.get("jobId"); - return getAllSchedulerJobs().stream().mapToInt(mapper).boxed().collect(Collectors.toList()); + return getAllSchedulerJobDetails(map -> (Integer) map.get("jobId")); + } + + public List getAllSchedulerJobNames() { + return getAllSchedulerJobDetails(map -> (String) map.get("displayName")); } public Map getSchedulerJobById(int jobId) { @@ -103,14 +120,15 @@ public class SchedulerJobHelper { return new Gson().toJson(map); } - public List getSchedulerJobHistory(int jobId) { + @SuppressWarnings("unchecked") + private List> getSchedulerJobHistory(int jobId) { final String GET_SCHEDULER_STATUS_URL = "/fineract-provider/api/v1/jobs/" + jobId + "/runhistory?" + Utils.TENANT_IDENTIFIER; LOG.info("------------------------ RETRIEVING SCHEDULER JOB HISTORY -------------------------"); - final Map response = Utils.performServerGet(requestSpec, response200Spec, GET_SCHEDULER_STATUS_URL, ""); - return (ArrayList) response.get("pageItems"); + final Map response = Utils.performServerGet(requestSpec, response200Spec, GET_SCHEDULER_STATUS_URL, ""); + return (List>) response.get("pageItems"); } - public static void runSchedulerJob(final RequestSpecification requestSpec, final String jobId) { + private void runSchedulerJob(int jobId) { final ResponseSpecification responseSpec = new ResponseSpecBuilder().expectStatusCode(202).build(); final String RUN_SCHEDULER_JOB_URL = "/fineract-provider/api/v1/jobs/" + jobId + "?command=executeJob&" + Utils.TENANT_IDENTIFIER; LOG.info("------------------------ RUN SCHEDULER JOB -------------------------"); @@ -124,45 +142,112 @@ public class SchedulerJobHelper { return runSchedulerJob; } - public void executeJob(String jobName) throws InterruptedException { - List allSchedulerJobsData = getAllSchedulerJobs(); - Assert.assertNotNull(allSchedulerJobsData); - + private int getSchedulerJobIdByName(String jobName) { + List> allSchedulerJobsData = getAllSchedulerJobs(); for (Integer jobIndex = 0; jobIndex < allSchedulerJobsData.size(); jobIndex++) { if (allSchedulerJobsData.get(jobIndex).get("displayName").equals(jobName)) { - Integer jobId = (Integer) allSchedulerJobsData.get(jobIndex).get("jobId"); - - // Executing Scheduler Job - runSchedulerJob(this.requestSpec, jobId.toString()); - - // Retrieving Scheduler Job by ID - Map schedulerJob = getSchedulerJobById(jobId); - Assert.assertNotNull(schedulerJob); - - // Waiting for Job to complete - while ((Boolean) schedulerJob.get("currentlyRunning") == true) { - Thread.sleep(15000); - schedulerJob = getSchedulerJobById(jobId); - Assert.assertNotNull(schedulerJob); - LOG.info("Job is Still Running"); - } - - List jobHistoryData = getSchedulerJobHistory(jobId); - - Assert.assertFalse("Job History is empty :( Was it too slow? Failures in background job?", jobHistoryData.isEmpty()); - - // print error associated with recent job failure (if any) - LOG.info("Job run error message (printed only if the job fails: {}" - , jobHistoryData.get(jobHistoryData.size() - 1).get("jobRunErrorMessage")); - LOG.info("Job failure error log (printed only if the job fails: {}" - , jobHistoryData.get(jobHistoryData.size() - 1).get("jobRunErrorLog")); - - // Verifying the Status of the Recently executed Scheduler Job - Assert.assertEquals("Verifying Last Scheduler Job Status", "success", - jobHistoryData.get(jobHistoryData.size() - 1).get("status")); - - break; + return (Integer) allSchedulerJobsData.get(jobIndex).get("jobId"); } } + throw new IllegalArgumentException("No such named Job (see org.apache.fineract.infrastructure.jobs.service.JobName enum):" + jobName); + } + + // FINERACT-922 TODO Gradually replace use of this method with new executeAndAwaitJob() below, if it proves to be more stable than this one + public void executeJob(String jobName) throws InterruptedException { + // Stop the Scheduler while we manually trigger execution of job, to avoid side effects and simplify debugging when readings logs + updateSchedulerStatus(false); + + int jobId = getSchedulerJobIdByName(jobName); + + // Executing Scheduler Job + runSchedulerJob(jobId); + + // Retrieving Scheduler Job by ID + Map schedulerJob = getSchedulerJobById(jobId); + + // Waiting for Job to complete + while ((Boolean) schedulerJob.get("currentlyRunning") == true) { + Thread.sleep(15000); + schedulerJob = getSchedulerJobById(jobId); + assertNotNull(schedulerJob); + LOG.info("Job is Still Running"); + } + + List> jobHistoryData = getSchedulerJobHistory(jobId); + + // print error associated with recent job failure (if any) + LOG.error("Last Job run error message (only relevent if the job fails: {}", + jobHistoryData.get(jobHistoryData.size() - 1).get("jobRunErrorMessage")); + LOG.error("Lsat Job failure error log (only relevent if the job fails: {}", + jobHistoryData.get(jobHistoryData.size() - 1).get("jobRunErrorLog")); + + assertFalse("Job History is empty :( Was it too slow? Failures in background job?", jobHistoryData.isEmpty()); + + // Verifying the Status of the Recently executed Scheduler Job + assertEquals("Verifying Last Scheduler Job Status", "success", + jobHistoryData.get(jobHistoryData.size() - 1).get("status")); + } + + /** + * Launches a Job and awaits its completion. + * @param jobName displayName (see {@link org.apache.fineract.infrastructure.jobs.service.JobName}) of Scheduler Job + * + * @author Michael Vorburger.ch + */ + public void executeAndAwaitJob(String jobName) { + Duration TIMEOUT = Duration.ofSeconds(30); + Duration PAUSE = Duration.ofMillis(500); + DateTimeFormatter df = DateTimeFormatter.ISO_INSTANT; // FINERACT-926 + Instant beforeExecuteTime = now().truncatedTo(ChronoUnit.SECONDS); + + // Stop the Scheduler while we manually trigger execution of job, to avoid side effects and simplify debugging when readings logs + updateSchedulerStatus(false); + + // Executing Scheduler Job + int jobId = getSchedulerJobIdByName(jobName); + runSchedulerJob(jobId); + + // Await JobDetailData.lastRunHistory [JobDetailHistoryData] jobRunStartTime >= beforeExecuteTime (or timeout) + await().atMost(TIMEOUT).pollInterval(PAUSE).until(jobLastRunHistorySupplier(jobId), lastRunHistory -> { + String jobRunStartText = lastRunHistory.get("jobRunStartTime"); + if (jobRunStartText == null) { + return false; + } + Instant jobRunStartTime = df.parse(jobRunStartText, Instant::from); + return jobRunStartTime.equals(beforeExecuteTime) || jobRunStartTime.isAfter(beforeExecuteTime); + }); + + // Await JobDetailData.lastRunHistory [JobDetailHistoryData] jobRunEndTime to be both set and >= jobRunStartTime (or timeout) + Map finalLastRunHistory = await().atMost(TIMEOUT).pollInterval(PAUSE).until(jobLastRunHistorySupplier(jobId), lastRunHistory -> { + String jobRunEndText = lastRunHistory.get("jobRunEndTime"); + if (jobRunEndText == null) { + return false; + } + Instant jobRunEndTime = df.parse(jobRunEndText, Instant::from); + Instant jobRunStartTime = df.parse(lastRunHistory.get("jobRunStartTime"), Instant::from); + return jobRunEndTime.equals(jobRunStartTime) || jobRunEndTime.isAfter(jobRunStartTime); + }); + + // Verify triggerType + assertThat(finalLastRunHistory.get("triggerType"), is("application")); + + // Verify status & propagate jobRunErrorMessage and/or jobRunErrorLog (if any) + String status = finalLastRunHistory.get("status"); + if (!status.equals("success")) { + fail("Job status is not success: " + finalLastRunHistory.toString()); + } + + // PS: Checking getSchedulerJobHistory() [/runhistory] is pointless, because the lastRunHistory JobDetailHistoryData is already part of JobDetailData anyway. + } + + @SuppressWarnings("unchecked") + private Callable> jobLastRunHistorySupplier(int jobId) { + return () -> { + Map job = getSchedulerJobById(jobId); + if (job == null) { + return null; + } + return (Map) job.get("lastRunHistory"); + }; } }