feat(接口自动化): 修改资源池执行方式,由主动推送数据改为被动拉取。

This commit is contained in:
fit2-zhao 2021-06-11 18:05:34 +08:00 committed by fit2-zhao
parent 5bbee94e00
commit b75e52122a
18 changed files with 617 additions and 142 deletions

View File

@ -0,0 +1,39 @@
package io.metersphere.api.controller;
import io.metersphere.api.service.ApiJmeterFileService;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.UUID;
@RestController
@RequestMapping("/api/jmeter")
public class ApiJmeterFileController {
@Resource
private ApiJmeterFileService apiJmeterFileService;
@GetMapping("download")
public ResponseEntity<byte[]> downloadJmeterFiles(@RequestParam("testId") String testId, @RequestParam("reportId") String reportId, @RequestParam("runMode") String runMode, @RequestParam("testPlanScenarioId") String testPlanScenarioId) {
byte[] bytes = apiJmeterFileService.downloadJmeterFiles(runMode,testId, reportId, testPlanScenarioId);
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType("application/octet-stream"))
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + testId + ".zip\"")
.body(bytes);
}
@GetMapping("download/jar")
public ResponseEntity<byte[]> downloadJmeterFiles() {
byte[] bytes = apiJmeterFileService.downloadJmeterJar();
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType("application/octet-stream"))
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + UUID.randomUUID().toString() + ".zip\"")
.body(bytes);
}
}

View File

@ -0,0 +1,25 @@
package io.metersphere.api.dto;
import lombok.Getter;
import lombok.Setter;
import org.apache.jorphan.collections.HashTree;
@Getter
@Setter
public class RunModeDataDTO {
// 执行HashTree
private HashTree hashTree;
// 测试场景/测试用例
private String testId;
// 报告id
private String reportId;
public RunModeDataDTO(String testId,String reportId) {
this.testId = testId;
this.reportId = reportId;
}
public RunModeDataDTO(HashTree hashTree,String reportId) {
this.hashTree = hashTree;
this.reportId = reportId;
}
}

View File

@ -8,6 +8,7 @@ import lombok.Setter;
@Setter
public class RunRequest {
private String testId;
private String url;
private String userId;
private boolean isDebug;
private String runMode;

View File

@ -0,0 +1,18 @@
package io.metersphere.api.dto.definition;
import io.metersphere.api.dto.automation.RunModeConfig;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Setter
@Getter
public class BatchRunDefinitionRequest {
private String id;
private List<String> planIds;
private RunModeConfig config;
}

View File

@ -40,8 +40,6 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl
public final static String TEST_ID = "ms.test.id";
public final static String TEST_REPORT_ID = "ms.test.report.name";
private final static String THREAD_SPLIT = " ";
private final static String ID_SPLIT = "-";
@ -76,8 +74,6 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl
private String testId;
private String debugReportId;
// 只有合并报告是这个有值
private String setReportId;
//获得控制台内容
private PrintStream oldPrintStream = System.out;
@ -156,8 +152,7 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl
TestResult testResult = new TestResult();
testResult.setTestId(testId);
testResult.setTotal(queue.size());
testResult.setSetReportId(this.setReportId);
testResult.setConsole(getConsole());
testResult.setConsole(getConsole());
// 一个脚本里可能包含多个场景(ThreadGroup)所以要区分开key: 场景Id
final Map<String, ScenarioResult> scenarios = new LinkedHashMap<>();
@ -506,7 +501,6 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl
private void setParam(BackendListenerContext context) {
this.testId = context.getParameter(TEST_ID);
this.setReportId = context.getParameter(TEST_REPORT_ID);
this.runMode = context.getParameter("runMode");
this.debugReportId = context.getParameter("debugReportId");
if (StringUtils.isBlank(this.runMode)) {

View File

@ -3,13 +3,11 @@ package io.metersphere.api.jmeter;
import com.alibaba.fastjson.JSON;
import io.metersphere.api.dto.RunRequest;
import io.metersphere.api.dto.automation.RunModeConfig;
import io.metersphere.api.dto.definition.request.MsTestPlan;
import io.metersphere.api.dto.scenario.request.BodyFile;
import io.metersphere.api.service.ApiScenarioReportService;
import io.metersphere.base.domain.JarConfig;
import io.metersphere.base.domain.TestResource;
import io.metersphere.commons.constants.ApiRunMode;
import io.metersphere.commons.constants.RunModeConstants;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.*;
import io.metersphere.config.JmeterProperties;
@ -31,13 +29,8 @@ import org.apache.jorphan.collections.HashTree;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import javax.annotation.PostConstruct;
@ -47,7 +40,10 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.*;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
@Service
public class JMeterService {
@ -125,10 +121,6 @@ public class JMeterService {
BackendListener backendListener = new BackendListener();
backendListener.setName(testId);
Arguments arguments = new Arguments();
if (config != null && config.getMode().equals(RunModeConstants.SERIAL.toString()) && config.getReportType().equals(RunModeConstants.SET_REPORT.toString())) {
arguments.addArgument(APIBackendListenerClient.TEST_REPORT_ID, config.getReportId());
}
arguments.addArgument(APIBackendListenerClient.TEST_ID, testId);
if (StringUtils.isNotBlank(runMode)) {
arguments.addArgument("runMode", runMode);
@ -141,6 +133,16 @@ public class JMeterService {
testPlan.add(testPlan.getArray()[0], backendListener);
}
public void addBackendListener(String testId, HashTree testPlan) {
BackendListener backendListener = new BackendListener();
backendListener.setName(testId);
Arguments arguments = new Arguments();
arguments.addArgument(APIBackendListenerClient.TEST_ID, testId);
backendListener.setArguments(arguments);
backendListener.setClassname(APIBackendListenerClient.class.getCanonicalName());
testPlan.add(testPlan.getArray()[0], backendListener);
}
public void runDefinition(String testId, HashTree testPlan, String debugReportId, String runMode) {
try {
init();
@ -198,7 +200,7 @@ public class JMeterService {
}
}
private byte[] fileToByte(File tradeFile) {
public byte[] fileToByte(File tradeFile) {
byte[] buffer = null;
try (FileInputStream fis = new FileInputStream(tradeFile);
ByteArrayOutputStream bos = new ByteArrayOutputStream();) {
@ -213,7 +215,7 @@ public class JMeterService {
return buffer;
}
private List<Object> getZipJar() {
public List<Object> getZipJar() {
List<Object> jarFiles = new LinkedList<>();
// jar
JarConfigService jarConfigService = CommonBeanFactory.getBean(JarConfigService.class);
@ -249,7 +251,7 @@ public class JMeterService {
return jarFiles;
}
private List<Object> getJar() {
public List<Object> getJar() {
List<Object> jarFiles = new LinkedList<>();
// jar
JarConfigService jarConfigService = CommonBeanFactory.getBean(JarConfigService.class);
@ -280,7 +282,7 @@ public class JMeterService {
return jarFiles;
}
private List<Object> getMultipartFiles(HashTree hashTree) {
public List<Object> getMultipartFiles(HashTree hashTree) {
List<Object> multipartFiles = new LinkedList<>();
// 获取附件
List<BodyFile> files = new LinkedList<>();
@ -306,12 +308,7 @@ public class JMeterService {
return multipartFiles;
}
public void runTest(String testId, HashTree hashTree, String runMode, boolean isDebug, RunModeConfig config) {
// 获取JMX使用到的附件
List<Object> multipartFiles = getMultipartFiles(hashTree);
// 获取JAR
List<Object> jarFiles = getJar();
public void runTest(String testId, String reportId, String runMode, String testPlanScenarioId, RunModeConfig config) {
// 获取可以执行的资源池
String resourcePoolId = config.getResourcePoolId();
TestResource testResource = resourcePoolCalculation.getPool(resourcePoolId);
@ -327,54 +324,26 @@ public class JMeterService {
if (baseInfo != null) {
metersphereUrl = baseInfo.getUrl();
}
// 检查地址是否正确
String jmeterPingUrl = metersphereUrl + "/jmeter/ping";
// docker 不能从 localhost 中下载文件
if (StringUtils.contains(metersphereUrl, "http://localhost")
|| !UrlTestUtils.testUrlWithTimeOut(jmeterPingUrl, 1000)) {
MSException.throwException(Translator.get("run_load_test_file_init_error"));
}
String uri = String.format(BASE_URL + "/jmeter/api/run", nodeIp, port);
try {
File file = new File(FileUtils.BODY_FILE_DIR + "/tmp");
if (!file.exists()) {
file.createNewFile();
}
RunRequest runRequest = new RunRequest();
runRequest.setTestId(testId);
runRequest.setDebug(isDebug);
metersphereUrl += "/api/jmeter/download?testId=" + testId + "&reportId=" + reportId + "&testPlanScenarioId" + "&runMode=" + runMode;
if (StringUtils.isNotEmpty(testPlanScenarioId)) {
metersphereUrl += "=" + testPlanScenarioId;
}
runRequest.setUrl(metersphereUrl);
runRequest.setRunMode(runMode);
runRequest.setConfig(config);
runRequest.setUserId(Objects.requireNonNull(SessionUtils.getUser()).getId());
runRequest.setJmx(new MsTestPlan().getJmx(hashTree));
MultiValueMap<String, Object> postParameters = new LinkedMultiValueMap<>();
if (CollectionUtils.isEmpty(multipartFiles)) {
multipartFiles.add(new FileSystemResource(file));
}
if (CollectionUtils.isEmpty(jarFiles)) {
jarFiles.add(new FileSystemResource(file));
}
postParameters.put("files", multipartFiles);
postParameters.put("jarFiles", jarFiles);
postParameters.add("request", JSON.toJSONString(runRequest));
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN));
HttpEntity<MultiValueMap<String, Object>> request = new HttpEntity<>(postParameters, headers);
ResponseEntity<String> result = restTemplate.postForEntity(uri, request, String.class);
String uri = String.format(BASE_URL + "/jmeter/api/start", nodeIp, port);
ResponseEntity<String> result = restTemplate.postForEntity(uri, runRequest, String.class);
if (result == null || !StringUtils.equals("SUCCESS", result.getBody())) {
// 清理零时报告
ApiScenarioReportService apiScenarioReportService = CommonBeanFactory.getBean(ApiScenarioReportService.class);
apiScenarioReportService.delete(testId);
apiScenarioReportService.delete(reportId);
MSException.throwException("执行失败:" + result);
}
} catch (Exception e) {
e.printStackTrace();
MSException.throwException("Please check node-controller status.");
MSException.throwException(e.getMessage());
}
}
}

View File

@ -16,8 +16,6 @@ public class TestResult {
private String testId;
private String setReportId;
private int scenarioTotal;
private int scenarioSuccess;

View File

@ -776,7 +776,7 @@ public class ApiAutomationService {
}
}
private HashTree generateHashTree(ApiScenarioWithBLOBs item, String reportId, Map<String, String> planEnvMap) {
public HashTree generateHashTree(ApiScenarioWithBLOBs item, String reportId, Map<String, String> planEnvMap) {
HashTree jmeterHashTree = new HashTree();
MsTestPlan testPlan = new MsTestPlan();
testPlan.setHashTree(new LinkedList<>());
@ -903,7 +903,7 @@ public class ApiAutomationService {
request.setTriggerMode(ReportTriggerMode.MANUAL.name());
}
String reportId = request.getId();
Map<APIScenarioReportResult, HashTree> map = new LinkedHashMap<>();
Map<APIScenarioReportResult, RunModeDataDTO> map = new LinkedHashMap<>();
List<String> scenarioIds = new ArrayList<>();
StringBuilder scenarioNames = new StringBuilder();
// 按照场景执行
@ -928,19 +928,23 @@ public class ApiAutomationService {
if (request.isTestPlanScheduleJob()) {
String savedScenarioId = testPlanScenarioId + ":" + request.getTestPlanReportId();
report = createScenarioReport(reportId, savedScenarioId, item.getName(), request.getTriggerMode(),
request.getExecuteType(), item.getProjectId(), request.getReportUserID(),request.getConfig());
request.getExecuteType(), item.getProjectId(), request.getReportUserID(), request.getConfig());
} else {
report = createScenarioReport(reportId, testPlanScenarioId, item.getName(), request.getTriggerMode(),
request.getExecuteType(), item.getProjectId(), request.getReportUserID(),request.getConfig());
request.getExecuteType(), item.getProjectId(), request.getReportUserID(), request.getConfig());
}
} else {
report = createScenarioReport(reportId, ExecuteType.Marge.name().equals(request.getExecuteType()) ? serialReportId : item.getId(), item.getName(), request.getTriggerMode(),
request.getExecuteType(), item.getProjectId(), request.getReportUserID(),request.getConfig());
request.getExecuteType(), item.getProjectId(), request.getReportUserID(), request.getConfig());
}
try {
// 生成报告和HashTree
HashTree hashTree = generateHashTree(item, reportId, planEnvMap);
map.put(report, hashTree);
if (request.getConfig() != null && StringUtils.isNotBlank(request.getConfig().getResourcePoolId())) {
map.put(report, new RunModeDataDTO(item.getId(), report.getId()));
} else {
// 生成报告和HashTree
HashTree hashTree = generateHashTree(item, reportId, planEnvMap);
map.put(report, new RunModeDataDTO(hashTree, report.getId()));
}
scenarioIds.add(item.getId());
scenarioNames.append(item.getName()).append(",");
// 重置报告ID
@ -953,7 +957,7 @@ public class ApiAutomationService {
if (request.getConfig() != null && StringUtils.equals(request.getConfig().getReportType(), RunModeConstants.SET_REPORT.toString()) && StringUtils.isNotEmpty(request.getConfig().getReportName())) {
request.getConfig().setReportId(UUID.randomUUID().toString());
APIScenarioReportResult report = createScenarioReport(request.getConfig().getReportId(), JSON.toJSONString(scenarioIds), scenarioNames.deleteCharAt(scenarioNames.toString().length() - 1).toString(), ReportTriggerMode.MANUAL.name(),
ExecuteType.Saved.name(), request.getProjectId(), request.getReportUserID(),request.getConfig());
ExecuteType.Saved.name(), request.getProjectId(), request.getReportUserID(), request.getConfig());
report.setName(request.getConfig().getReportName());
report.setId(serialReportId);
apiScenarioReportMapper.insert(report);
@ -974,7 +978,7 @@ public class ApiAutomationService {
return request.getId();
}
private void run(Map<APIScenarioReportResult, HashTree> map, RunScenarioRequest request, String serialReportId) {
private void run(Map<APIScenarioReportResult, RunModeDataDTO> map, RunScenarioRequest request, String serialReportId) {
// 开始选择执行模式
ExecutorService executorService = Executors.newFixedThreadPool(map.size());
if (request.getConfig() != null && request.getConfig().getMode().equals(RunModeConstants.SERIAL.toString())) {
@ -987,7 +991,7 @@ public class ApiAutomationService {
apiScenarioReportMapper.insert(key);
reportIds.add(key.getId());
try {
Future<ApiScenarioReport> future = executorService.submit(new SerialScenarioExecTask(jMeterService, apiScenarioReportMapper, key.getId(), map.get(key), request));
Future<ApiScenarioReport> future = executorService.submit(new SerialScenarioExecTask(jMeterService, apiScenarioReportMapper, map.get(key), request));
ApiScenarioReport report = future.get();
// 如果开启失败结束执行则判断返回结果状态
if (request.getConfig().isOnSampleError()) {
@ -1015,7 +1019,7 @@ public class ApiAutomationService {
for (APIScenarioReportResult report : map.keySet()) {
//存储报告
batchMapper.insert(report);
executorService.submit(new ParallelScenarioExecTask(jMeterService, report.getId(), map.get(report), request));
executorService.submit(new ParallelScenarioExecTask(jMeterService, map.get(report), request));
}
sqlSession.flushStatements();
}
@ -1095,14 +1099,14 @@ public class ApiAutomationService {
if (request.isTestPlanScheduleJob()) {
String savedScenarioId = testPlanScenarioId + ":" + request.getTestPlanReportId();
report = createScenarioReport(group.getName(), savedScenarioId, item.getName(), request.getTriggerMode(),
request.getExecuteType(), item.getProjectId(), request.getReportUserID(),request.getConfig());
request.getExecuteType(), item.getProjectId(), request.getReportUserID(), request.getConfig());
} else {
report = createScenarioReport(group.getName(), testPlanScenarioId, item.getName(), request.getTriggerMode() == null ? ReportTriggerMode.MANUAL.name() : request.getTriggerMode(),
request.getExecuteType(), item.getProjectId(), request.getReportUserID(),request.getConfig());
request.getExecuteType(), item.getProjectId(), request.getReportUserID(), request.getConfig());
}
} else {
report = createScenarioReport(group.getName(), item.getId(), item.getName(), request.getTriggerMode() == null ? ReportTriggerMode.MANUAL.name() : request.getTriggerMode(),
request.getExecuteType(), item.getProjectId(), request.getReportUserID(),request.getConfig());
request.getExecuteType(), item.getProjectId(), request.getReportUserID(), request.getConfig());
}
batchMapper.insert(report);
reportIds.add(group.getName());
@ -1223,12 +1227,7 @@ public class ApiAutomationService {
List<String> reportIds = new LinkedList<>();
try {
HashTree hashTree = generateHashTree(apiScenarios, request, reportIds);
if (request.getConfig() != null && StringUtils.isNotBlank(request.getConfig().getResourcePoolId())) {
jMeterService.runTest(JSON.toJSONString(reportIds), hashTree, runMode, false, request.getConfig());
} else {
jMeterService.runSerial(JSON.toJSONString(reportIds), hashTree, request.getReportId(), runMode, request.getConfig());
}
jMeterService.runSerial(JSON.toJSONString(reportIds), hashTree, request.getReportId(), runMode, request.getConfig());
} catch (Exception e) {
LogUtil.error(e.getMessage());
MSException.throwException(e.getMessage());
@ -1322,7 +1321,7 @@ public class ApiAutomationService {
}
APIScenarioReportResult report = createScenarioReport(request.getId(), request.getScenarioId(), request.getScenarioName(), ReportTriggerMode.MANUAL.name(), request.getExecuteType(), request.getProjectId(),
SessionUtils.getUserId(),request.getConfig());
SessionUtils.getUserId(), request.getConfig());
apiScenarioReportMapper.insert(report);
uploadBodyFiles(request.getBodyFileRequestIds(), bodyFiles);

View File

@ -589,7 +589,7 @@ public class ApiDefinitionService {
// 调用执行方法
if (request.getConfig() != null && StringUtils.isNotBlank(request.getConfig().getResourcePoolId())) {
jMeterService.runTest(request.getId(), hashTree, runMode, request.getReportId() != null, request.getConfig());
jMeterService.runTest(request.getId(), request.getId(), runMode, null, request.getConfig());
} else {
jMeterService.runDefinition(request.getId(), hashTree, request.getReportId(), runMode);
}
@ -637,6 +637,7 @@ public class ApiDefinitionService {
ApiDefinitionExecResult result = extApiDefinitionExecResultMapper.selectMaxResultByResourceId(testId);
return buildAPIReportResult(result);
}
public APIReportResult getReportById(String testId) {
ApiDefinitionExecResult result = apiDefinitionExecResultMapper.selectByPrimaryKey(testId);
return buildAPIReportResult(result);

View File

@ -0,0 +1,158 @@
package io.metersphere.api.service;
import com.alibaba.fastjson.JSON;
import io.metersphere.api.dto.definition.request.MsTestPlan;
import io.metersphere.api.dto.scenario.request.BodyFile;
import io.metersphere.api.jmeter.JMeterService;
import io.metersphere.base.domain.ApiScenarioWithBLOBs;
import io.metersphere.base.domain.JarConfig;
import io.metersphere.base.domain.TestPlanApiScenario;
import io.metersphere.base.mapper.ApiScenarioMapper;
import io.metersphere.base.mapper.TestPlanApiScenarioMapper;
import io.metersphere.commons.constants.ApiRunMode;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.service.JarConfigService;
import io.metersphere.track.service.TestPlanApiCaseService;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.jorphan.collections.HashTree;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
@Service
public class ApiJmeterFileService {
@Resource
private ApiAutomationService apiAutomationService;
@Resource
private TestPlanApiCaseService testPlanApiCaseService;
@Resource
private TestPlanApiScenarioMapper testPlanApiScenarioMapper;
@Resource
private ApiScenarioMapper apiScenarioMapper;
@Resource
private JMeterService jMeterService;
public byte[] downloadJmeterFiles(String runMode, String testId, String reportId, String testPlanScenarioId) {
Map<String, String> planEnvMap = new HashMap<>();
if (StringUtils.isNotEmpty(testPlanScenarioId)) {
// 获取场景用例单独的执行环境
TestPlanApiScenario planApiScenario = testPlanApiScenarioMapper.selectByPrimaryKey(testPlanScenarioId);
String environment = planApiScenario.getEnvironment();
if (StringUtils.isNotBlank(environment)) {
planEnvMap = JSON.parseObject(environment, Map.class);
}
}
HashTree hashTree = null;
if (ApiRunMode.DEFINITION.name().equals(runMode) || ApiRunMode.API_PLAN.name().equals(runMode)) {
hashTree = testPlanApiCaseService.generateHashTree(testId);
} else {
ApiScenarioWithBLOBs item = apiScenarioMapper.selectByPrimaryKey(testId);
if (item == null) {
MSException.throwException("未找到执行场景。");
}
hashTree = apiAutomationService.generateHashTree(item, reportId, planEnvMap);
}
//jMeterService.addBackendListener(reportId, hashTree);
return zipFilesToByteArray(testId, hashTree);
}
public byte[] downloadJmeterJar() {
Map<String, byte[]> files = new HashMap<>();
// 获取JAR
Map<String, byte[]> jarFiles = this.getJar();
if (!com.alibaba.excel.util.CollectionUtils.isEmpty(jarFiles)) {
for (String k : jarFiles.keySet()) {
byte[] v = jarFiles.get(k);
files.put(k, v);
}
}
return listBytesToZip(files);
}
private Map<String, byte[]> getJar() {
Map<String, byte[]> jarFiles = new LinkedHashMap<>();
// jar
JarConfigService jarConfigService = CommonBeanFactory.getBean(JarConfigService.class);
List<JarConfig> jars = jarConfigService.list();
jars.forEach(jarConfig -> {
try {
String path = jarConfig.getPath();
File file = new File(path);
if (file.isDirectory() && !path.endsWith("/")) {
file = new File(path + "/");
}
byte[] fileByte = jMeterService.fileToByte(file);
if (fileByte != null) {
jarFiles.put(file.getName(), fileByte);
}
} catch (Exception e) {
LogUtil.error(e.getMessage(), e);
}
});
return jarFiles;
}
private Map<String, byte[]> getMultipartFiles(HashTree hashTree) {
Map<String, byte[]> multipartFiles = new LinkedHashMap<>();
// 获取附件
List<BodyFile> files = new LinkedList<>();
jMeterService.getFiles(hashTree, files);
if (CollectionUtils.isNotEmpty(files)) {
for (BodyFile bodyFile : files) {
File file = new File(bodyFile.getName());
if (file != null && !file.exists()) {
byte[] fileByte = jMeterService.fileToByte(file);
if (fileByte != null) {
multipartFiles.put(file.getName(), fileByte);
}
}
}
}
return multipartFiles;
}
private byte[] zipFilesToByteArray(String testId, HashTree hashTree) {
String fileName = testId + ".jmx";
String jmx = new MsTestPlan().getJmx(hashTree);
Map<String, byte[]> files = new HashMap<>();
// 每个测试生成一个文件夹
files.put(fileName, jmx.getBytes(StandardCharsets.UTF_8));
// 获取JMX使用到的附件
Map<String, byte[]> multipartFiles = this.getMultipartFiles(hashTree);
if (!com.alibaba.excel.util.CollectionUtils.isEmpty(multipartFiles)) {
for (String k : multipartFiles.keySet()) {
byte[] v = multipartFiles.get(k);
files.put(k, v);
}
}
return listBytesToZip(files);
}
private byte[] listBytesToZip(Map<String, byte[]> mapReport) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ZipOutputStream zos = new ZipOutputStream(baos);
for (Map.Entry<String, byte[]> report : mapReport.entrySet()) {
ZipEntry entry = new ZipEntry(report.getKey());
entry.setSize(report.getValue().length);
zos.putNextEntry(entry);
zos.write(report.getValue());
}
zos.closeEntry();
zos.close();
return baos.toByteArray();
} catch (Exception e) {
return null;
}
}
}

View File

@ -358,33 +358,6 @@ public class ApiScenarioReportService {
}
}
}
private void margeReport(TestResult result, StringBuilder scenarioIds, StringBuilder scenarioNames, String runMode, String projectId, String userId, List<String> reportIds) {
// 合并生成一份报告
if (StringUtils.isNotEmpty(result.getSetReportId())) {
// 清理其他报告保留一份合并后的报告
this.deleteByIds(reportIds);
ApiScenarioReport report = apiScenarioReportMapper.selectByPrimaryKey(result.getSetReportId());
report.setStatus(result.getError() > 0 ? "Error" : "Success");
if (StringUtils.isNotEmpty(userId)) {
report.setUserId(userId);
} else {
report.setUserId(SessionUtils.getUserId());
}
report.setExecuteType(ExecuteType.Saved.name());
report.setProjectId(projectId);
report.setScenarioName(scenarioNames.toString().substring(0, scenarioNames.toString().length() - 1));
report.setScenarioId(scenarioIds.toString());
apiScenarioReportMapper.updateByPrimaryKey(report);
ApiScenarioReportDetail detail = new ApiScenarioReportDetail();
detail.setContent(JSON.toJSONString(result).getBytes(StandardCharsets.UTF_8));
detail.setReportId(report.getId());
detail.setProjectId(report.getProjectId());
apiScenarioReportDetailMapper.insert(detail);
}
}
public void margeReport(String reportId, List<String> reportIds) {
// 合并生成一份报告
if (CollectionUtils.isNotEmpty(reportIds)) {

View File

@ -3,35 +3,33 @@
*/
package io.metersphere.api.service.task;
import io.metersphere.api.dto.RunModeDataDTO;
import io.metersphere.api.dto.automation.RunScenarioRequest;
import io.metersphere.api.jmeter.JMeterService;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.LogUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.jorphan.collections.HashTree;
import java.util.concurrent.Callable;
public class ParallelScenarioExecTask<T> implements Callable<T> {
private RunScenarioRequest request;
private JMeterService jMeterService;
private HashTree hashTree;
private String id;
private RunModeDataDTO runModeDataDTO;
public ParallelScenarioExecTask(JMeterService jMeterService, String id, HashTree hashTree, RunScenarioRequest request) {
public ParallelScenarioExecTask(JMeterService jMeterService, RunModeDataDTO runModeDataDTO, RunScenarioRequest request) {
this.jMeterService = jMeterService;
this.request = request;
this.hashTree = hashTree;
this.id = id;
this.runModeDataDTO = runModeDataDTO;
}
@Override
public T call() {
try {
if (request.getConfig() != null && StringUtils.isNotBlank(request.getConfig().getResourcePoolId())) {
jMeterService.runTest(id, hashTree, request.getRunMode(), false, request.getConfig());
jMeterService.runTest(runModeDataDTO.getTestId(), runModeDataDTO.getReportId(), request.getRunMode(), request.getPlanScenarioId(), request.getConfig());
} else {
jMeterService.runSerial(id, hashTree, request.getReportId(), request.getRunMode(), request.getConfig());
jMeterService.runSerial(runModeDataDTO.getReportId(), runModeDataDTO.getHashTree(), request.getReportId(), request.getRunMode(), request.getConfig());
}
return null;
} catch (Exception ex) {

View File

@ -3,6 +3,7 @@
*/
package io.metersphere.api.service.task;
import io.metersphere.api.dto.RunModeDataDTO;
import io.metersphere.api.dto.automation.RunScenarioRequest;
import io.metersphere.api.jmeter.JMeterService;
import io.metersphere.base.domain.ApiScenarioReport;
@ -11,7 +12,6 @@ import io.metersphere.commons.constants.APITestStatus;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.LogUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.jorphan.collections.HashTree;
import java.util.concurrent.Callable;
@ -19,32 +19,30 @@ public class SerialScenarioExecTask<T> implements Callable<T> {
private RunScenarioRequest request;
private JMeterService jMeterService;
private ApiScenarioReportMapper apiScenarioReportMapper;
private HashTree hashTree;
private RunModeDataDTO runModeDataDTO;
ApiScenarioReport report = null;
private String id;
public SerialScenarioExecTask(JMeterService jMeterService, ApiScenarioReportMapper apiScenarioReportMapper, String id, HashTree hashTree, RunScenarioRequest request) {
public SerialScenarioExecTask(JMeterService jMeterService, ApiScenarioReportMapper apiScenarioReportMapper, RunModeDataDTO runModeDataDTO, RunScenarioRequest request) {
this.jMeterService = jMeterService;
this.apiScenarioReportMapper = apiScenarioReportMapper;
this.request = request;
this.hashTree = hashTree;
this.id = id;
this.runModeDataDTO = runModeDataDTO;
}
@Override
public T call() {
try {
if (request.getConfig() != null && StringUtils.isNotBlank(request.getConfig().getResourcePoolId())) {
jMeterService.runTest(id, hashTree, request.getRunMode(), false, request.getConfig());
jMeterService.runTest(runModeDataDTO.getTestId(), runModeDataDTO.getReportId(), request.getRunMode(), request.getPlanScenarioId(), request.getConfig());
} else {
jMeterService.runSerial(id, hashTree, request.getReportId(), request.getRunMode(), request.getConfig());
jMeterService.runSerial(runModeDataDTO.getReportId(), runModeDataDTO.getHashTree(), request.getReportId(), request.getRunMode(), request.getConfig());
}
// 轮询查看报告状态最多200次防止死循环
int index = 1;
while (index < 200) {
Thread.sleep(3000);
index++;
report = apiScenarioReportMapper.selectByPrimaryKey(id);
report = apiScenarioReportMapper.selectByPrimaryKey(runModeDataDTO.getReportId());
if (report != null && !report.getStatus().equals(APITestStatus.Running.name())) {
break;
}

View File

@ -34,6 +34,8 @@ public class ShiroUtils {
filterChainDefinitionMap.put("/sso/signin", "anon");
filterChainDefinitionMap.put("/sso/callback", "anon");
filterChainDefinitionMap.put("/license/valid", "anon");
filterChainDefinitionMap.put("/api/jmeter/download", "anon");
filterChainDefinitionMap.put("/api/jmeter/download/jar", "anon");
// for swagger
filterChainDefinitionMap.put("/swagger-ui.html", "anon");

View File

@ -4,18 +4,16 @@ import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import io.metersphere.api.dto.definition.ApiTestCaseDTO;
import io.metersphere.api.dto.definition.ApiTestCaseRequest;
import io.metersphere.api.dto.definition.BatchRunDefinitionRequest;
import io.metersphere.api.dto.definition.TestPlanApiCaseDTO;
import io.metersphere.commons.constants.OperLogConstants;
import io.metersphere.commons.constants.PermissionConstants;
import io.metersphere.commons.constants.RoleConstants;
import io.metersphere.commons.utils.PageUtils;
import io.metersphere.commons.utils.Pager;
import io.metersphere.log.annotation.MsAuditLog;
import io.metersphere.track.request.testcase.TestPlanApiCaseBatchRequest;
import io.metersphere.track.service.TestPlanApiCaseService;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
@ -66,4 +64,8 @@ public class TestPlanApiCaseController {
testPlanApiCaseService.batchUpdateEnv(request);
}
@PostMapping(value = "/run")
public String run(@RequestPart("request") BatchRunDefinitionRequest request) {
return testPlanApiCaseService.run(request);
}
}

View File

@ -1,24 +1,46 @@
package io.metersphere.track.service;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import io.metersphere.api.dto.RunModeDataDTO;
import io.metersphere.api.dto.definition.ApiTestCaseDTO;
import io.metersphere.api.dto.definition.ApiTestCaseRequest;
import io.metersphere.api.dto.definition.BatchRunDefinitionRequest;
import io.metersphere.api.dto.definition.TestPlanApiCaseDTO;
import io.metersphere.api.dto.definition.request.MsTestElement;
import io.metersphere.api.dto.definition.request.MsTestPlan;
import io.metersphere.api.dto.definition.request.MsThreadGroup;
import io.metersphere.api.dto.definition.request.ParameterConfig;
import io.metersphere.api.dto.definition.request.sampler.MsDubboSampler;
import io.metersphere.api.dto.definition.request.sampler.MsHTTPSamplerProxy;
import io.metersphere.api.dto.definition.request.sampler.MsJDBCSampler;
import io.metersphere.api.dto.definition.request.sampler.MsTCPSampler;
import io.metersphere.api.jmeter.JMeterService;
import io.metersphere.api.service.ApiDefinitionExecResultService;
import io.metersphere.api.service.ApiTestCaseService;
import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.ApiDefinitionExecResultMapper;
import io.metersphere.base.mapper.ApiTestCaseMapper;
import io.metersphere.base.mapper.TestPlanApiCaseMapper;
import io.metersphere.base.mapper.TestPlanMapper;
import io.metersphere.base.mapper.ext.ExtTestPlanApiCaseMapper;
import io.metersphere.commons.utils.PageUtils;
import io.metersphere.commons.utils.Pager;
import io.metersphere.commons.utils.ServiceUtils;
import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.commons.constants.ApiRunMode;
import io.metersphere.commons.constants.RunModeConstants;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.*;
import io.metersphere.dto.BaseSystemConfigDTO;
import io.metersphere.log.vo.OperatingLogDetails;
import io.metersphere.service.SystemParameterService;
import io.metersphere.track.request.testcase.TestPlanApiCaseBatchRequest;
import io.metersphere.track.service.task.ParallelApiExecTask;
import io.metersphere.track.service.task.SerialApiExecTask;
import org.apache.commons.lang3.StringUtils;
import org.apache.jorphan.collections.HashTree;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -26,6 +48,9 @@ import org.springframework.util.CollectionUtils;
import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.stream.Collectors;
@Service
@ -45,6 +70,12 @@ public class TestPlanApiCaseService {
private TestPlanMapper testPlanMapper;
@Resource
ApiTestCaseMapper apiTestCaseMapper;
@Resource
private SystemParameterService systemParameterService;
@Resource
private JMeterService jMeterService;
@Resource
private ApiDefinitionExecResultMapper mapper;
public TestPlanApiCase getInfo(String caseId, String testPlanId) {
TestPlanApiCaseExample example = new TestPlanApiCaseExample();
@ -219,4 +250,169 @@ public class TestPlanApiCaseService {
}
return null;
}
private MsTestElement parse(String api, String planId) {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
try {
JSONObject element = JSON.parseObject(api);
LinkedList<MsTestElement> list = new LinkedList<>();
if (element != null && StringUtils.isNotEmpty(element.getString("hashTree"))) {
LinkedList<MsTestElement> elements = mapper.readValue(element.getString("hashTree"),
new TypeReference<LinkedList<MsTestElement>>() {
});
list.addAll(elements);
}
TestPlanApiCase apiCase = testPlanApiCaseMapper.selectByPrimaryKey(planId);
Map<String, String> envMap = null;
if (apiCase != null) {
envMap = JSON.parseObject(apiCase.getEnvironmentId(), Map.class);
}
if (element.getString("type").equals("HTTPSamplerProxy")) {
MsHTTPSamplerProxy httpSamplerProxy = JSON.parseObject(api, MsHTTPSamplerProxy.class);
httpSamplerProxy.setHashTree(list);
if (envMap != null && envMap.containsKey(httpSamplerProxy.getProjectId())) {
httpSamplerProxy.setUseEnvironment(envMap.get(httpSamplerProxy.getProjectId()));
}
return httpSamplerProxy;
}
if (element.getString("type").equals("TCPSampler")) {
MsTCPSampler msTCPSampler = JSON.parseObject(api, MsTCPSampler.class);
if (envMap != null && envMap.containsKey(msTCPSampler.getProjectId())) {
msTCPSampler.setUseEnvironment(envMap.get(msTCPSampler.getProjectId()));
}
msTCPSampler.setHashTree(list);
return msTCPSampler;
}
if (element.getString("type").equals("DubboSampler")) {
MsDubboSampler dubboSampler = JSON.parseObject(api, MsDubboSampler.class);
if (envMap != null && envMap.containsKey(dubboSampler.getProjectId())) {
dubboSampler.setUseEnvironment(envMap.get(dubboSampler.getProjectId()));
}
dubboSampler.setHashTree(list);
return dubboSampler;
}
if (element.getString("type").equals("JDBCSampler")) {
MsJDBCSampler jDBCSampler = JSON.parseObject(api, MsJDBCSampler.class);
if (envMap != null && envMap.containsKey(jDBCSampler.getProjectId())) {
jDBCSampler.setUseEnvironment(envMap.get(jDBCSampler.getProjectId()));
}
jDBCSampler.setHashTree(list);
return jDBCSampler;
}
} catch (Exception e) {
e.printStackTrace();
LogUtil.error(e.getMessage());
}
return null;
}
public HashTree generateHashTree(String testId) {
TestPlanApiCase apiCase = testPlanApiCaseMapper.selectByPrimaryKey(testId);
if (apiCase != null) {
ApiTestCaseWithBLOBs caseWithBLOBs = apiTestCaseMapper.selectByPrimaryKey(apiCase.getApiCaseId());
HashTree jmeterHashTree = new HashTree();
MsTestPlan testPlan = new MsTestPlan();
testPlan.setHashTree(new LinkedList<>());
if (caseWithBLOBs != null) {
try {
MsThreadGroup group = new MsThreadGroup();
group.setLabel(caseWithBLOBs.getName());
group.setName(testId);
MsTestElement testElement = parse(caseWithBLOBs.getRequest(), testId);
group.setHashTree(new LinkedList<>());
group.getHashTree().add(testElement);
testPlan.getHashTree().add(group);
} catch (Exception ex) {
MSException.throwException(ex.getMessage());
}
}
testPlan.toHashTree(jmeterHashTree, testPlan.getHashTree(), new ParameterConfig());
return jmeterHashTree;
}
return null;
}
public String modeRun(BatchRunDefinitionRequest request) {
List<String> ids = request.getPlanIds();
TestPlanApiCaseExample example = new TestPlanApiCaseExample();
example.createCriteria().andIdIn(ids);
List<TestPlanApiCase> planApiCases = testPlanApiCaseMapper.selectByExample(example);
// 开始选择执行模式
ExecutorService executorService = Executors.newFixedThreadPool(planApiCases.size());
if (request.getConfig() != null && request.getConfig().getMode().equals(RunModeConstants.SERIAL.toString())) {
// 开始串行执行
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for (TestPlanApiCase key : planApiCases) {
try {
RunModeDataDTO modeDataDTO = null;
if (StringUtils.isNotBlank(request.getConfig().getResourcePoolId())) {
modeDataDTO = new RunModeDataDTO(key.getId(), UUID.randomUUID().toString());
} else {
// 生成报告和HashTree
HashTree hashTree = generateHashTree(key.getId());
modeDataDTO = new RunModeDataDTO(hashTree, UUID.randomUUID().toString());
}
Future<ApiDefinitionExecResult> future = executorService.submit(new SerialApiExecTask(jMeterService, mapper, modeDataDTO, request.getConfig(), ApiRunMode.API_PLAN.name()));
ApiDefinitionExecResult report = future.get();
// 如果开启失败结束执行则判断返回结果状态
if (request.getConfig().isOnSampleError()) {
if (report == null || !report.getStatus().equals("Success")) {
break;
}
}
} catch (Exception e) {
LogUtil.error("执行终止:" + e.getMessage());
break;
}
}
}
});
thread.start();
} else {
// 开始并发执行
for (TestPlanApiCase key : planApiCases) {
RunModeDataDTO modeDataDTO = null;
if (StringUtils.isNotBlank(request.getConfig().getResourcePoolId())) {
modeDataDTO = new RunModeDataDTO(key.getId(), UUID.randomUUID().toString());
} else {
// 生成报告和HashTree
HashTree hashTree = generateHashTree(key.getId());
modeDataDTO = new RunModeDataDTO(hashTree, UUID.randomUUID().toString());
}
executorService.submit(new ParallelApiExecTask(jMeterService, mapper, modeDataDTO, request.getConfig(), ApiRunMode.API_PLAN.name()));
}
}
return request.getId();
}
/**
* 测试执行
*
* @param request
* @return
*/
public String run(BatchRunDefinitionRequest request) {
if (request.getConfig() != null) {
if (request.getConfig().getMode().equals(RunModeConstants.PARALLEL.toString())) {
// 校验并发数量
int count = 50;
BaseSystemConfigDTO dto = systemParameterService.getBaseInfo();
if (StringUtils.isNotEmpty(dto.getConcurrency())) {
count = Integer.parseInt(dto.getConcurrency());
}
if (request.getPlanIds().size() > count) {
MSException.throwException("并发数量过大,请重新选择!");
}
return this.modeRun(request);
} else {
return this.modeRun(request);
}
}
return request.getId();
}
}

View File

@ -0,0 +1,46 @@
/**
*
*/
package io.metersphere.track.service.task;
import io.metersphere.api.dto.RunModeDataDTO;
import io.metersphere.api.dto.automation.RunModeConfig;
import io.metersphere.api.jmeter.JMeterService;
import io.metersphere.base.mapper.ApiDefinitionExecResultMapper;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.LogUtil;
import org.apache.commons.lang3.StringUtils;
import java.util.concurrent.Callable;
public class ParallelApiExecTask<T> implements Callable<T> {
private RunModeConfig config;
private JMeterService jMeterService;
private RunModeDataDTO runModeDataDTO;
private String runMode;
private ApiDefinitionExecResultMapper mapper;
public ParallelApiExecTask(JMeterService jMeterService, ApiDefinitionExecResultMapper mapper, RunModeDataDTO runModeDataDTO, RunModeConfig config, String runMode) {
this.jMeterService = jMeterService;
this.config = config;
this.runModeDataDTO = runModeDataDTO;
this.runMode = runMode;
this.mapper = mapper;
}
@Override
public T call() {
try {
if (config != null && StringUtils.isNotBlank(config.getResourcePoolId())) {
jMeterService.runTest(runModeDataDTO.getTestId(), runModeDataDTO.getReportId(), runMode, null, config);
} else {
jMeterService.runDefinition(runModeDataDTO.getReportId(), runModeDataDTO.getHashTree(), null, runMode);
}
return null;
} catch (Exception ex) {
LogUtil.error(ex.getMessage());
MSException.throwException(ex.getMessage());
return null;
}
}
}

View File

@ -0,0 +1,58 @@
/**
*
*/
package io.metersphere.track.service.task;
import io.metersphere.api.dto.RunModeDataDTO;
import io.metersphere.api.dto.automation.RunModeConfig;
import io.metersphere.api.jmeter.JMeterService;
import io.metersphere.base.domain.ApiDefinitionExecResult;
import io.metersphere.base.mapper.ApiDefinitionExecResultMapper;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.LogUtil;
import org.apache.commons.lang3.StringUtils;
import java.util.concurrent.Callable;
public class SerialApiExecTask<T> implements Callable<T> {
private RunModeConfig config;
private JMeterService jMeterService;
private RunModeDataDTO runModeDataDTO;
private String runMode;
private ApiDefinitionExecResultMapper mapper;
public SerialApiExecTask(JMeterService jMeterService, ApiDefinitionExecResultMapper mapper, RunModeDataDTO runModeDataDTO, RunModeConfig config, String runMode) {
this.jMeterService = jMeterService;
this.config = config;
this.runModeDataDTO = runModeDataDTO;
this.runMode = runMode;
this.mapper = mapper;
}
@Override
public T call() {
try {
if (config != null && StringUtils.isNotBlank(config.getResourcePoolId())) {
jMeterService.runTest(runModeDataDTO.getTestId(), runModeDataDTO.getReportId(), runMode, null, config);
} else {
jMeterService.runDefinition(runModeDataDTO.getReportId(), runModeDataDTO.getHashTree(), null, runMode);
}
// 轮询查看报告状态最多200次防止死循环
ApiDefinitionExecResult report = null;
int index = 1;
while (index < 200) {
Thread.sleep(3000);
index++;
report = mapper.selectByPrimaryKey(runModeDataDTO.getReportId());
if (report != null) {
break;
}
}
return (T) report;
} catch (Exception ex) {
LogUtil.error(ex.getMessage());
MSException.throwException(ex.getMessage());
return null;
}
}
}