fix(测试计划执行): 增加quartz配置并合并1.14版本发现的针对多节点测试计划执行时的代码更改

增加quartz配置并合并1.14版本发现的针对多节点测试计划执行时的代码更改
This commit is contained in:
song-tianyang 2021-12-22 13:52:53 +08:00 committed by 刘瑞斌
parent c769f16a4b
commit 36961210f2
7 changed files with 166 additions and 50 deletions

View File

@ -9,6 +9,7 @@ import io.metersphere.base.mapper.ApiScenarioReportMapper;
import io.metersphere.commons.constants.TestPlanApiExecuteStatus; import io.metersphere.commons.constants.TestPlanApiExecuteStatus;
import io.metersphere.commons.constants.TestPlanResourceType; import io.metersphere.commons.constants.TestPlanResourceType;
import io.metersphere.commons.utils.CommonBeanFactory; import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.track.dto.TestPlanReportExecuteCheckResultDTO;
import io.metersphere.utils.LoggerUtil; import io.metersphere.utils.LoggerUtil;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
@ -20,6 +21,7 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/** /**
* @author song.tianyang * @author song.tianyang
@ -30,17 +32,16 @@ import java.util.Map;
public class TestPlanExecuteInfo { public class TestPlanExecuteInfo {
private String reportId; private String reportId;
private String creator; private String creator;
private Map<String, String> apiCaseExecInfo = new HashMap<>(); private Map<String, String> apiCaseExecInfo = new ConcurrentHashMap<>();
private Map<String, String> apiScenarioCaseExecInfo = new HashMap<>(); private Map<String, String> apiScenarioCaseExecInfo = new ConcurrentHashMap<>();
private Map<String, String> loadCaseExecInfo = new HashMap<>(); private Map<String, String> loadCaseExecInfo = new ConcurrentHashMap<>();
private Map<String, String> apiCaseExecuteThreadMap = new HashMap<>(); private Map<String, String> apiCaseExecuteThreadMap = new ConcurrentHashMap<>();
private Map<String, String> apiScenarioThreadMap = new HashMap<>(); private Map<String, String> apiScenarioThreadMap = new ConcurrentHashMap<>();
private Map<String, String> loadCaseReportIdMap = new HashMap<>(); private Map<String, String> loadCaseReportIdMap = new ConcurrentHashMap<>();
private Map<String, String> apiCaseReportMap = new HashMap<>();
private Map<String, String> apiScenarioReportMap = new HashMap<>();
private Map<String, String> apiCaseReportMap = new ConcurrentHashMap<>();
private Map<String, String> apiScenarioReportMap = new ConcurrentHashMap<>();
private boolean reportDataInDataBase; private boolean reportDataInDataBase;
int lastUnFinishedNumCount = 0; int lastUnFinishedNumCount = 0;
@ -83,7 +84,8 @@ public class TestPlanExecuteInfo {
} }
} }
public synchronized int countUnFinishedNum() { public synchronized TestPlanReportExecuteCheckResultDTO countUnFinishedNum() {
TestPlanReportExecuteCheckResultDTO executeCheck = new TestPlanReportExecuteCheckResultDTO();
int unFinishedCount = 0; int unFinishedCount = 0;
this.isApiCaseAllExecuted = true; this.isApiCaseAllExecuted = true;
@ -129,8 +131,22 @@ public class TestPlanExecuteInfo {
LoggerUtil.info("执行的报告还在队列中,重置超时时间"); LoggerUtil.info("执行的报告还在队列中,重置超时时间");
lastUnFinishedNumCount = unFinishedCount; lastUnFinishedNumCount = unFinishedCount;
lastFinishedNumCountTime = System.currentTimeMillis(); lastFinishedNumCountTime = System.currentTimeMillis();
executeCheck.setFinishedCaseChanged(true);
} else if (unFinishedCount == 0) {
executeCheck.setFinishedCaseChanged(true);
} else {
executeCheck.setFinishedCaseChanged(false);
} }
return unFinishedCount;
executeCheck.setTimeOut(false);
if (unFinishedCount > 0) {
//20分钟没有案例执行结果更新则定位超时
long nowTime = System.currentTimeMillis();
if (nowTime - lastFinishedNumCountTime > 1200000) {
executeCheck.setTimeOut(true);
}
}
return executeCheck;
} }
public Map<String, Map<String, String>> getExecutedResult() { public Map<String, Map<String, String>> getExecutedResult() {
@ -228,7 +244,7 @@ public class TestPlanExecuteInfo {
this.countUnFinishedNum(); this.countUnFinishedNum();
} }
public void updateReport(Map<String, String> apiCaseExecResultInfo, Map<String, String> apiScenarioCaseExecResultInfo) { public synchronized void updateReport(Map<String, String> apiCaseExecResultInfo, Map<String, String> apiScenarioCaseExecResultInfo) {
if (MapUtils.isNotEmpty(apiCaseExecResultInfo)) { if (MapUtils.isNotEmpty(apiCaseExecResultInfo)) {
this.apiCaseReportMap.putAll(apiCaseExecResultInfo); this.apiCaseReportMap.putAll(apiCaseExecResultInfo);
} }
@ -236,6 +252,35 @@ public class TestPlanExecuteInfo {
if (MapUtils.isNotEmpty(apiScenarioCaseExecResultInfo)) { if (MapUtils.isNotEmpty(apiScenarioCaseExecResultInfo)) {
this.apiScenarioReportMap.putAll(apiScenarioCaseExecResultInfo); this.apiScenarioReportMap.putAll(apiScenarioCaseExecResultInfo);
} }
}
public Map<String, String> getRunningApiCaseReportMap() {
//key: reportId, value: testPlanApiCaseId
Map<String, String> returnMap = new HashMap<>();
for (Map.Entry<String, String> entry : apiCaseExecInfo.entrySet()) {
String planCaseId = entry.getKey();
String status = entry.getValue();
if (StringUtils.equalsIgnoreCase(status, TestPlanApiExecuteStatus.RUNNING.name())) {
if (apiCaseExecuteThreadMap.containsKey(planCaseId)) {
returnMap.put(apiCaseExecuteThreadMap.get(planCaseId), planCaseId);
}
}
}
return returnMap;
}
public Map<String, String> getRunningScenarioReportMap() {
//key: reportId, value: testPlanApiScenarioId
Map<String, String> returnMap = new HashMap<>();
for (Map.Entry<String, String> entry : apiScenarioCaseExecInfo.entrySet()) {
String planScenarioId = entry.getKey();
String status = entry.getValue();
if (StringUtils.equalsIgnoreCase(status, TestPlanApiExecuteStatus.RUNNING.name())) {
if (apiScenarioThreadMap.containsKey(planScenarioId)) {
returnMap.put(apiScenarioThreadMap.get(planScenarioId), planScenarioId);
}
}
}
return returnMap;
} }
} }

View File

@ -1,6 +1,7 @@
package io.metersphere.api.cache; package io.metersphere.api.cache;
import io.metersphere.commons.constants.TestPlanApiExecuteStatus; import io.metersphere.commons.constants.TestPlanApiExecuteStatus;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -50,7 +51,11 @@ public class TestPlanReportExecuteCatch {
} }
public synchronized static boolean containsReport(String reportId) { public synchronized static boolean containsReport(String reportId) {
return testPlanReportMap != null && testPlanReportMap.containsKey(reportId); if(StringUtils.isEmpty(reportId)){
return false;
}else {
return testPlanReportMap != null && testPlanReportMap.containsKey(reportId);
}
} }
public synchronized static void updateApiTestPlanExecuteInfo(String reportId, public synchronized static void updateApiTestPlanExecuteInfo(String reportId,

View File

@ -3,6 +3,7 @@ package io.metersphere.job.sechedule;
import io.metersphere.commons.constants.ReportTriggerMode; import io.metersphere.commons.constants.ReportTriggerMode;
import io.metersphere.commons.constants.ScheduleGroup; import io.metersphere.commons.constants.ScheduleGroup;
import io.metersphere.commons.utils.CommonBeanFactory; import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.track.service.TestPlanService; import io.metersphere.track.service.TestPlanService;
import org.quartz.*; import org.quartz.*;
@ -16,25 +17,10 @@ import org.quartz.*;
public class TestPlanTestJob extends MsScheduleJob { public class TestPlanTestJob extends MsScheduleJob {
private String projectID; private String projectID;
// private PerformanceTestService performanceTestService;
// private TestPlanScenarioCaseService testPlanScenarioCaseService;
// private TestPlanApiCaseService testPlanApiCaseService;
// private ApiTestCaseService apiTestCaseService;
// private TestPlanReportService testPlanReportService;
// private TestPlanLoadCaseService testPlanLoadCaseService;
private TestPlanService testPlanService; private TestPlanService testPlanService;
public TestPlanTestJob() { public TestPlanTestJob() {
// this.performanceTestService = CommonBeanFactory.getBean(PerformanceTestService.class);
// this.testPlanScenarioCaseService = CommonBeanFactory.getBean(TestPlanScenarioCaseService.class);
// this.testPlanApiCaseService = CommonBeanFactory.getBean(TestPlanApiCaseService.class);
// this.apiTestCaseService = CommonBeanFactory.getBean(ApiTestCaseService.class);
// this.testPlanReportService = CommonBeanFactory.getBean(TestPlanReportService.class);
// this.testPlanLoadCaseService = CommonBeanFactory.getBean(TestPlanLoadCaseService.class);
this.testPlanService = CommonBeanFactory.getBean(TestPlanService.class); this.testPlanService = CommonBeanFactory.getBean(TestPlanService.class);
} }
/** /**
@ -63,7 +49,17 @@ public class TestPlanTestJob extends MsScheduleJob {
JobDataMap jobDataMap = context.getJobDetail().getJobDataMap(); JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
String config = jobDataMap.getString("config"); String config = jobDataMap.getString("config");
testPlanService.run(this.resourceId, this.projectID, this.userId, ReportTriggerMode.SCHEDULE.name(),config); String runResourceId = this.resourceId;
String runProjectId = this.projectID;
String runUserId = this.userId;
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
LogUtil.info("Start test_plan_scehdule. test_plan_id:" + runResourceId);
testPlanService.run(runResourceId, runProjectId, runUserId, ReportTriggerMode.SCHEDULE.name(),config);
}
});
thread.start();
} }
public static JobKey getJobKey(String testId) { public static JobKey getJobKey(String testId) {

View File

@ -68,6 +68,14 @@ public class AppStartListener implements ApplicationListener<ApplicationReadyEve
@Value("${jmeter.home}") @Value("${jmeter.home}")
private String jmeterHome; private String jmeterHome;
@Value("${quartz.properties.org.quartz.jobStore.acquireTriggersWithinLock}")
private String acquireTriggersWithinLock;
@Value("${quartz.enabled}")
private boolean quartzEnable;
@Value("${quartz.scheduler-name}")
private String quartzScheduleName;
@Value("${quartz.thread-count}")
private int quartzThreadCount;
@Override @Override
public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
@ -99,7 +107,12 @@ public class AppStartListener implements ApplicationListener<ApplicationReadyEve
} catch (InterruptedException e) { } catch (InterruptedException e) {
e.printStackTrace(); e.printStackTrace();
} }
LogUtil.info("开始启动定时任务。 相关设置:" +
"quartz.acquireTriggersWithinLock :" + acquireTriggersWithinLock + "\r\n" +
"quartz.enabled :" + quartzEnable + "\r\n" +
"quartz.scheduler-name :" + quartzScheduleName + "\r\n" +
"quartz.thread-count :" + quartzThreadCount + "\r\n"
);
scheduleService.startEnableSchedules(); scheduleService.startEnableSchedules();
} }

View File

@ -0,0 +1,11 @@
package io.metersphere.track.dto;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class TestPlanReportExecuteCheckResultDTO {
private boolean isTimeOut;
private boolean isFinishedCaseChanged;
}

View File

@ -81,7 +81,9 @@ public class TestPlanReportService {
@Resource @Resource
ExtTestPlanApiCaseMapper extTestPlanApiCaseMapper; ExtTestPlanApiCaseMapper extTestPlanApiCaseMapper;
@Resource @Resource
ApiTestCaseMapper apiTestCaseMapper; ExtApiDefinitionExecResultMapper extApiDefinitionExecResultMapper;
@Resource
ExtApiScenarioReportMapper extApiScenarioReportMapper;
@Resource @Resource
LoadTestReportMapper loadTestReportMapper; LoadTestReportMapper loadTestReportMapper;
@Resource @Resource
@ -551,8 +553,6 @@ public class TestPlanReportService {
boolean scenarioIsOk = executeInfo.isScenarioAllExecuted(); boolean scenarioIsOk = executeInfo.isScenarioAllExecuted();
boolean performanceIsOk = executeInfo.isLoadCaseAllExecuted(); boolean performanceIsOk = executeInfo.isLoadCaseAllExecuted();
testPlanLog.info("ReportId[" + testPlanReport.getId() + "] count over. Testplan Execute Result: Api is over ->" + apiCaseIsOk + "; scenario is over ->" + scenarioIsOk + "; performance is over ->" + performanceIsOk);
if (apiCaseIsOk) { if (apiCaseIsOk) {
testPlanReport.setIsApiCaseExecuting(false); testPlanReport.setIsApiCaseExecuting(false);
} }
@ -1075,12 +1075,16 @@ public class TestPlanReportService {
} }
public void countReport(String planReportId) { public void countReport(String planReportId) {
boolean isTimeOut = this.checkTestPlanReportIsTimeOut(planReportId); TestPlanReportExecuteCheckResultDTO checkResult = this.checkTestPlanReportIsTimeOut(planReportId);
if (isTimeOut) { testPlanLog.info("Check PlanReport:" + planReportId + "; result: "+ JSON.toJSONString(checkResult));
if (checkResult.isTimeOut()) {
//判断是否超时超时时强行停止任务 //判断是否超时超时时强行停止任务
TestPlanReportExecuteCatch.finishAllTask(planReportId); TestPlanReportExecuteCatch.finishAllTask(planReportId);
checkResult.setFinishedCaseChanged(true);
}
if(checkResult.isFinishedCaseChanged()){
this.updateExecuteApis(planReportId);
} }
this.updateExecuteApis(planReportId);
} }
public TestPlanSimpleReportDTO getReport(String reportId) { public TestPlanSimpleReportDTO getReport(String reportId) {
@ -1216,19 +1220,61 @@ public class TestPlanReportService {
testPlanReportContentMapper.updateByExampleSelective(bloBs,example); testPlanReportContentMapper.updateByExampleSelective(bloBs,example);
} }
private boolean checkTestPlanReportIsTimeOut(String planReportId) { private TestPlanReportExecuteCheckResultDTO checkTestPlanReportIsTimeOut(String planReportId) {
//同步数据库更新状态信息
try {
this.syncReportStatus(planReportId);
} catch (Exception e) {
LogUtil.info("联动数据库同步执行状态失败! " + e.getMessage());
LogUtil.error(e);
}
TestPlanExecuteInfo executeInfo = TestPlanReportExecuteCatch.getTestPlanExecuteInfo(planReportId); TestPlanExecuteInfo executeInfo = TestPlanReportExecuteCatch.getTestPlanExecuteInfo(planReportId);
int unFinishNum = executeInfo.countUnFinishedNum(); TestPlanReportExecuteCheckResultDTO checkResult = executeInfo.countUnFinishedNum();
if (unFinishNum > 0) { return checkResult;
//20分钟没有案例执行结果更新则定位超时 }
long lastCountTime = executeInfo.getLastFinishedNumCountTime();
long nowTime = System.currentTimeMillis(); private void syncReportStatus(String planReportId) {
testPlanLog.info("ReportId: ["+planReportId+"]; timeCount:"+ (nowTime - lastCountTime)); if (TestPlanReportExecuteCatch.containsReport(planReportId)) {
if (nowTime - lastCountTime > 1200000) { TestPlanExecuteInfo executeInfo = TestPlanReportExecuteCatch.getTestPlanExecuteInfo(planReportId);
return true; if (executeInfo != null) {
//同步接口案例结果
Map<String, String> updateCaseStatusMap = new HashMap<>();
Map<String, String> apiCaseReportMap = executeInfo.getRunningApiCaseReportMap();
if (MapUtils.isNotEmpty(apiCaseReportMap)) {
List<ApiDefinitionExecResult> execList = extApiDefinitionExecResultMapper.selectStatusByIdList(apiCaseReportMap.keySet());
for (ApiDefinitionExecResult report : execList) {
String reportId = report.getId();
String status = report.getStatus();
if (!StringUtils.equalsAnyIgnoreCase(status, "Running", "Waiting")) {
String planCaseId = apiCaseReportMap.get(reportId);
if (StringUtils.isNotEmpty(planCaseId)) {
updateCaseStatusMap.put(planCaseId, status);
}
}
}
}
//同步场景结果
Map<String, String> updateScenarioStatusMap = new HashMap<>();
Map<String, String> scenarioReportMap = executeInfo.getRunningScenarioReportMap();
if (MapUtils.isNotEmpty(scenarioReportMap)) {
List<ApiScenarioReport> reportList = extApiScenarioReportMapper.selectStatusByIds(scenarioReportMap.keySet());
for (ApiScenarioReport report : reportList) {
String reportId = report.getId();
String status = report.getStatus();
if (!StringUtils.equalsAnyIgnoreCase(status, "Running", "Waiting")) {
String planScenarioId = scenarioReportMap.get(reportId);
if (StringUtils.isNotEmpty(planScenarioId)) {
updateScenarioStatusMap.put(planScenarioId, status);
}
}
}
}
testPlanLog.info("ReportID:"+planReportId+" 本次数据库同步,案例ID"+JSON.toJSONString(apiCaseReportMap.keySet())+";场景ID"+JSON.toJSONString(scenarioReportMap.keySet())+"; 同步结果,案例:"+JSON.toJSONString(updateCaseStatusMap)+";场景:"+JSON.toJSONString(updateScenarioStatusMap));
TestPlanReportExecuteCatch.updateApiTestPlanExecuteInfo(planReportId, updateCaseStatusMap, updateScenarioStatusMap, null);
}else {
testPlanLog.info("同步数据库查询执行信息失败! 报告ID在缓存中未找到"+planReportId);
} }
} }
return false;
} }
private void finishTestPlanReport(String planReportId) { private void finishTestPlanReport(String planReportId) {
@ -1239,5 +1285,4 @@ public class TestPlanReportService {
} }
TestPlanReportExecuteCatch.remove(planReportId); TestPlanReportExecuteCatch.remove(planReportId);
} }
} }

View File

@ -12,7 +12,7 @@ server.ssl.key-alias=localhost
# Hikari # Hikari
spring.datasource.type=com.zaxxer.hikari.HikariDataSource spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.hikari.maximum-pool-size=100 spring.datasource.hikari.maximum-pool-size=200
spring.datasource.hikari.auto-commit=true spring.datasource.hikari.auto-commit=true
spring.datasource.hikari.idle-timeout=10000 spring.datasource.hikari.idle-timeout=10000
spring.datasource.hikari.pool-name=DatebookHikariCP spring.datasource.hikari.pool-name=DatebookHikariCP
@ -23,7 +23,7 @@ spring.datasource.hikari.connection-test-query=SELECT 1
spring.datasource.quartz.url=${spring.datasource.url} spring.datasource.quartz.url=${spring.datasource.url}
spring.datasource.quartz.username=${spring.datasource.username} spring.datasource.quartz.username=${spring.datasource.username}
spring.datasource.quartz.password=${spring.datasource.password} spring.datasource.quartz.password=${spring.datasource.password}
spring.datasource.quartz.hikari.maximum-pool-size=50 spring.datasource.quartz.hikari.maximum-pool-size=200
spring.datasource.quartz.hikari.auto-commit=true spring.datasource.quartz.hikari.auto-commit=true
spring.datasource.quartz.hikari.idle-timeout=10000 spring.datasource.quartz.hikari.idle-timeout=10000
spring.datasource.quartz.hikari.pool-name=DatebookHikariCP spring.datasource.quartz.hikari.pool-name=DatebookHikariCP
@ -92,7 +92,8 @@ jmeter.home=/opt/jmeter
# quartz # quartz
quartz.enabled=true quartz.enabled=true
quartz.scheduler-name=msServerJob quartz.scheduler-name=msServerJob
quartz.thread-count=30 quartz.thread-count=60
quartz.properties.org.quartz.jobStore.acquireTriggersWithinLock=true
# file upload # file upload
spring.servlet.multipart.max-file-size=500MB spring.servlet.multipart.max-file-size=500MB
spring.servlet.multipart.max-request-size=500MB spring.servlet.multipart.max-request-size=500MB