feat(接口测试): 接口测试支持禁用本地执行

--story=1009842 --user=赵勇 接口测试支持禁用本地执行 https://www.tapd.cn/55049933/s/1291450
This commit is contained in:
fit2-zhao 2022-11-08 15:59:00 +08:00 committed by fit2-zhao
parent 3a1992cfb0
commit e765433d40
40 changed files with 610 additions and 318 deletions

View File

@ -0,0 +1,11 @@
package io.metersphere.api.dto;
import io.metersphere.request.BodyFile;
import lombok.Data;
import java.util.List;
@Data
public class BodyFileRequest {
private String reportId;
private List<BodyFile> bodyFiles;
}

View File

@ -9,6 +9,7 @@ import io.metersphere.api.dto.definition.BatchRunDefinitionRequest;
import io.metersphere.api.dto.scenario.DatabaseConfig;
import io.metersphere.api.dto.scenario.environment.EnvironmentConfig;
import io.metersphere.api.exec.queue.DBTestQueue;
import io.metersphere.api.jmeter.JMeterService;
import io.metersphere.service.definition.ApiCaseResultService;
import io.metersphere.service.ApiExecutionQueueService;
import io.metersphere.service.scenario.ApiScenarioReportStructureService;
@ -66,7 +67,8 @@ public class ApiCaseExecuteService {
private ApiDefinitionExecResultMapper apiDefinitionExecResultMapper;
@Resource
private TestPlanApiCaseMapper testPlanApiCaseMapper;
@Resource
private JMeterService jMeterService;
/**
* 测试计划case执行
@ -275,6 +277,7 @@ public class ApiCaseExecuteService {
if (request.getConfig() == null) {
request.setConfig(new RunModeConfigDTO());
}
jMeterService.verifyPool(request.getProjectId(), request.getConfig());
if (StringUtils.equals(EnvironmentType.GROUP.toString(), request.getConfig().getEnvironmentType()) && StringUtils.isNotEmpty(request.getConfig().getEnvironmentGroupId())) {
request.getConfig().setEnvMap(environmentGroupProjectService.getEnvMap(request.getConfig().getEnvironmentGroupId()));

View File

@ -23,12 +23,16 @@ import io.metersphere.base.mapper.ext.ExtApiTestCaseMapper;
import io.metersphere.base.mapper.plan.TestPlanApiCaseMapper;
import io.metersphere.commons.constants.ApiRunMode;
import io.metersphere.commons.constants.ElementConstants;
import io.metersphere.commons.constants.ExtendedParameter;
import io.metersphere.commons.enums.ApiReportStatus;
import io.metersphere.commons.utils.*;
import io.metersphere.dto.BaseSystemConfigDTO;
import io.metersphere.dto.JmeterRunRequestDTO;
import io.metersphere.dto.MsExecResponseDTO;
import io.metersphere.dto.RunModeConfigDTO;
import io.metersphere.environment.service.BaseEnvironmentService;
import io.metersphere.plugin.core.MsTestElement;
import io.metersphere.service.SystemParameterService;
import io.metersphere.service.definition.TcpApiParamService;
import io.metersphere.utils.LoggerUtil;
import org.apache.commons.collections.CollectionUtils;
@ -65,6 +69,8 @@ public class ApiExecuteService {
private ObjectMapper mapper;
@Resource
private TestPlanApiCaseMapper testPlanApiCaseMapper;
@Resource
private SystemParameterService systemParameterService;
public MsExecResponseDTO jenkinsRun(RunCaseRequest request) {
ApiTestCaseWithBLOBs caseWithBLOBs = null;
@ -80,6 +86,8 @@ public class ApiExecuteService {
if (caseWithBLOBs == null) {
return null;
}
jMeterService.verifyPool(caseWithBLOBs.getProjectId(), new RunModeConfigDTO());
if (StringUtils.isBlank(request.getEnvironmentId())) {
request.setEnvironmentId(extApiTestCaseMapper.getApiCaseEnvironment(request.getCaseId()));
}
@ -232,10 +240,17 @@ public class ApiExecuteService {
runRequest.setDebug(request.isDebug());
runRequest.setRunMode(runMode);
runRequest.setExtendedParameters(new HashMap<String, Object>() {{
this.put("SYN_RES", request.isSyncResult());
this.put(ExtendedParameter.SYNC_STATUS, request.isSyncResult());
this.put("userId", SessionUtils.getUser().getId());
this.put("userName", SessionUtils.getUser().getName());
}});
// 开始执行
if (StringUtils.isNotEmpty(request.getConfig().getResourcePoolId())) {
runRequest.setPool(GenerateHashTreeUtil.isResourcePool(request.getConfig().getResourcePoolId()));
runRequest.setPoolId(request.getConfig().getResourcePoolId());
BaseSystemConfigDTO baseInfo = systemParameterService.getBaseInfo();
runRequest.setPlatformUrl(GenerateHashTreeUtil.getPlatformUrl(baseInfo, runRequest, null));
}
return runRequest;
}

View File

@ -2,7 +2,9 @@ package io.metersphere.api.exec.engine;
import io.fabric8.kubernetes.api.model.Pod;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.metersphere.api.dto.definition.request.MsTestPlan;
import io.metersphere.base.domain.TestResource;
import io.metersphere.commons.constants.ExtendedParameter;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.JSON;
import io.metersphere.commons.utils.LogUtil;
@ -55,13 +57,20 @@ public class KubernetesTestEngine extends AbstractEngine {
.append(StringUtils.LF).append("Pod信息")
.append(JSON.toJSONString(pod.getMetadata())).append("");
LoggerUtil.info(logMsg);
String path = "api/start";
if (runRequest.getHashTree() != null) {
path = "debug";
runRequest.getExtendedParameters().put(ExtendedParameter.JMX, new MsTestPlan().getJmx(runRequest.getHashTree()));
runRequest.setHashTree(null);
LoggerUtil.info("进入DEBUG执行模式", runRequest.getReportId());
}
// 拼接CURL执行命令
StringBuffer command = new StringBuffer("curl -H \"Accept: application/json\" -H \"Content-type: application/json\" -X POST -d").append(StringUtils.SPACE);
command.append("'").append(JSON.toJSONString(runRequest)).append("'"); // 请求参数
command.append(StringUtils.SPACE).append("--connect-timeout 30"); // 设置连接超时时间为30S
command.append(StringUtils.SPACE).append("--max-time 120"); // 设置请求超时时间为120S
command.append(StringUtils.SPACE).append("--retry 3"); // 设置重试次数3次
command.append(StringUtils.SPACE).append("http://127.0.0.1:8082/jmeter/api/start");
command.append(StringUtils.SPACE).append("http://127.0.0.1:8082/jmeter/").append(path);
KubernetesApiExec.newExecWatch(client, clientCredential.getNamespace(), pod.getMetadata().getName(), command.toString());
} catch (Exception e) {
LoggerUtil.error("当前报告:【" + runRequest.getReportId() + "】资源:【" + runRequest.getTestId() + "】CURL失败", e);

View File

@ -13,7 +13,9 @@ import io.metersphere.api.exec.api.ApiCaseExecuteService;
import io.metersphere.api.exec.queue.DBTestQueue;
import io.metersphere.api.jmeter.JMeterService;
import io.metersphere.api.jmeter.NewDriverManager;
import io.metersphere.dto.BaseSystemConfigDTO;
import io.metersphere.service.ApiExecutionQueueService;
import io.metersphere.service.SystemParameterService;
import io.metersphere.service.scenario.ApiScenarioReportService;
import io.metersphere.service.scenario.ApiScenarioReportStructureService;
import io.metersphere.service.definition.TcpApiParamService;
@ -99,7 +101,9 @@ public class ApiScenarioExecuteService {
@Resource
protected TestPlanApiScenarioMapper testPlanApiScenarioMapper;
@Resource
ExtTestPlanScenarioCaseMapper extTestPlanScenarioCaseMapper;
private ExtTestPlanScenarioCaseMapper extTestPlanScenarioCaseMapper;
@Resource
private SystemParameterService systemParameterService;
public List<MsExecResponseDTO> run(RunScenarioRequest request) {
if (LoggerUtil.getLogger().isDebugEnabled()) {
@ -417,6 +421,12 @@ public class ApiScenarioExecuteService {
JmeterRunRequestDTO runRequest = new JmeterRunRequestDTO(request.getId(), request.getId(), runMode, hashTree);
LoggerUtil.info(new MsTestPlan().getJmx(hashTree));
runRequest.setDebug(true);
if (request.getConfig() != null && StringUtils.isNotEmpty(request.getConfig().getResourcePoolId())) {
runRequest.setPool(GenerateHashTreeUtil.isResourcePool(request.getConfig().getResourcePoolId()));
runRequest.setPoolId(request.getConfig().getResourcePoolId());
BaseSystemConfigDTO baseInfo = systemParameterService.getBaseInfo();
runRequest.setPlatformUrl(GenerateHashTreeUtil.getPlatformUrl(baseInfo, runRequest, null));
}
jMeterService.run(runRequest);
return request.getId();
}

View File

@ -1,6 +1,7 @@
package io.metersphere.api.jmeter;
import io.metersphere.api.dto.definition.request.MsTestPlan;
import io.metersphere.api.exec.engine.EngineFactory;
import io.metersphere.api.exec.queue.ExecThreadPoolExecutor;
import io.metersphere.api.jmeter.utils.ServerConfig;
@ -8,16 +9,19 @@ import io.metersphere.api.jmeter.utils.SmoothWeighted;
import io.metersphere.base.domain.TestResource;
import io.metersphere.commons.config.KafkaConfig;
import io.metersphere.commons.constants.ApiRunMode;
import io.metersphere.commons.constants.ExtendedParameter;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.*;
import io.metersphere.config.JmeterProperties;
import io.metersphere.constants.BackendListenerConstants;
import io.metersphere.constants.RunModeConstants;
import io.metersphere.dto.JmeterRunRequestDTO;
import io.metersphere.dto.NodeDTO;
import io.metersphere.dto.*;
import io.metersphere.engine.Engine;
import io.metersphere.jmeter.JMeterBase;
import io.metersphere.jmeter.LocalRunner;
import io.metersphere.service.BaseProjectApplicationService;
import io.metersphere.service.RemakeReportService;
import io.metersphere.service.SystemParameterService;
import io.metersphere.utils.LoggerUtil;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
@ -42,12 +46,18 @@ import java.util.List;
@Service
public class JMeterService {
public static final String BASE_URL = "http://%s:%d";
public static final String POOL = "POOL";
@Resource
private JmeterProperties jmeterProperties;
@Resource
private RestTemplate restTemplate;
@Resource
private RedisTemplate<String, Object> redisTemplate;
@Resource
private SystemParameterService systemParameterService;
@Resource
private BaseProjectApplicationService projectApplicationService;
@PostConstruct
private void init() {
@ -112,8 +122,8 @@ public class JMeterService {
}
if (MapUtils.isNotEmpty(request.getExtendedParameters())
&& request.getExtendedParameters().containsKey("SYN_RES")
&& (Boolean) request.getExtendedParameters().get("SYN_RES")) {
&& request.getExtendedParameters().containsKey(ExtendedParameter.SYNC_STATUS)
&& (Boolean) request.getExtendedParameters().get(ExtendedParameter.SYNC_STATUS)) {
LoggerUtil.debug("为请求 [ " + request.getReportId() + " ] 添加Debug Listener");
addDebugListener(request.getReportId(), request.getHashTree());
}
@ -144,11 +154,52 @@ public class JMeterService {
apiScenarioReportService.testEnded(request, e.getMessage());
LoggerUtil.error("调用K8S执行请求[ " + request.getTestId() + " ]失败:", request.getReportId(), e);
}
} else if ((MapUtils.isNotEmpty(request.getExtendedParameters())
&& request.getExtendedParameters().containsKey(ExtendedParameter.SYNC_STATUS)
&& (Boolean) request.getExtendedParameters().get(ExtendedParameter.SYNC_STATUS))
|| request.isDebug()) {
this.nodeDebug(request);
} else {
this.send(request);
}
}
private synchronized void nodeDebug(JmeterRunRequestDTO request) {
try {
if (request.isDebug() && !StringUtils.equalsAny(request.getRunMode(), ApiRunMode.DEFINITION.name())) {
request.getExtendedParameters().put(ExtendedParameter.SAVE_RESULT, true);
} else if (!request.isDebug()) {
request.getExtendedParameters().put(ExtendedParameter.SAVE_RESULT, true);
}
List<TestResource> resources = GenerateHashTreeUtil.setPoolResource(request.getPoolId());
String uri = null;
int index = (int) (Math.random() * resources.size());
String configuration = resources.get(index).getConfiguration();
if (StringUtils.isNotEmpty(configuration)) {
NodeDTO node = com.alibaba.fastjson.JSON.parseObject(configuration, NodeDTO.class);
uri = String.format(BASE_URL + "/jmeter/debug", node.getIp(), node.getPort());
}
if (StringUtils.isEmpty(uri)) {
LoggerUtil.info("未获取到资源池,请检查配置【系统设置-系统-测试资源池】", request.getReportId());
MSException.throwException("调用资源池执行失败,请检查资源池是否配置正常");
}
request.getExtendedParameters().put(ExtendedParameter.JMX, new MsTestPlan().getJmx(request.getHashTree()));
request.setHashTree(null);
LoggerUtil.info("开始发送请求【 " + request.getTestId() + " 】到 " + uri + " 节点执行", request.getReportId());
ResponseEntity<String> result = restTemplate.postForEntity(uri, request, String.class);
if (result == null || !StringUtils.equals("SUCCESS", result.getBody())) {
LoggerUtil.error("发送请求[ " + request.getTestId() + " ] 到" + uri + " 节点执行失败", request.getReportId());
LoggerUtil.info(result);
MSException.throwException("调用资源池执行失败,请检查资源池是否配置正常");
}
} catch (Exception e) {
RemakeReportService remakeReportService = CommonBeanFactory.getBean(RemakeReportService.class);
remakeReportService.remake(request);
LoggerUtil.error("发送请求[ " + request.getTestId() + " ] 执行失败,进行数据回滚:", request.getReportId(), e);
MSException.throwException("调用资源池执行失败,请检查资源池是否配置正常");
}
}
private synchronized void send(JmeterRunRequestDTO request) {
try {
if (redisTemplate.opsForValue().get(SmoothWeighted.EXEC_INDEX + request.getPoolId()) != null) {
@ -221,4 +272,19 @@ public class JMeterService {
return false;
}
}
public void verifyPool(String projectId, RunModeConfigDTO runConfig) {
// 检查是否禁用了本地执行
if (runConfig != null && StringUtils.isEmpty(runConfig.getResourcePoolId())) {
BaseSystemConfigDTO configDTO = systemParameterService.getBaseInfo();
if (StringUtils.equals(configDTO.getRunMode(), POOL)) {
ProjectConfig config = projectApplicationService.getProjectConfig(projectId);
if (config == null || !config.getPoolEnable() || StringUtils.isEmpty(config.getResourcePoolId())) {
MSException.throwException("请在【项目设置-应用管理-接口测试】中选择资源池");
}
runConfig = runConfig == null ? new RunModeConfigDTO() : runConfig;
runConfig.setResourcePoolId(config.getResourcePoolId());
}
}
}
}

View File

@ -1,11 +1,15 @@
package io.metersphere.api.jmeter;
import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.metersphere.api.dto.MsgDTO;
import io.metersphere.commons.constants.KafkaTopicConstants;
import io.metersphere.commons.utils.NamedThreadFactory;
import io.metersphere.commons.utils.WebSocketUtil;
import io.metersphere.service.ApiExecutionQueueService;
import io.metersphere.service.TestResultService;
import io.metersphere.utils.LoggerUtil;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.springframework.context.annotation.Configuration;
@ -21,6 +25,7 @@ import java.util.concurrent.TimeUnit;
@Configuration
public class MsKafkaListener {
public static final String CONSUME_ID = "ms-api-exec-consume";
public static final String DEBUG_CONSUME_ID = "ms-api-debug-consume";
@Resource
private ApiExecutionQueueService apiExecutionQueueService;
@Resource
@ -64,6 +69,19 @@ public class MsKafkaListener {
}
}
@KafkaListener(id = DEBUG_CONSUME_ID, topics = KafkaTopicConstants.DEBUG_TOPICS, groupId = "${spring.kafka.consumer.debug.group-id}")
public void debugConsume(ConsumerRecord<?, String> record) {
try {
LoggerUtil.info("接收到执行结果:", record.key());
if (ObjectUtils.isNotEmpty(record.value()) && WebSocketUtil.has(record.key().toString())) {
MsgDTO dto = JSON.parseObject(record.value(), MsgDTO.class);
WebSocketUtil.sendMessageSingle(dto);
}
} catch (Exception e) {
LoggerUtil.error("KAFKA消费失败", e);
}
}
public void outKafkaPoolLogger() {
StringBuffer buffer = new StringBuffer()
.append(StringUtils.LF)

View File

@ -22,6 +22,7 @@ import java.util.Map;
@Configuration
public class KafkaConfig {
public static final String DEBUG_TOPICS_KEY = "MS-API-DEBUG-KEY";
@Resource
private KafkaProperties kafkaProperties;
@ -38,11 +39,18 @@ public class KafkaConfig {
.build();
}
@Bean
public NewTopic debugTopic() {
return TopicBuilder.name(KafkaTopicConstants.DEBUG_TOPICS)
.build();
}
public static Map<String, Object> getKafka() {
KafkaProperties kafkaProperties = CommonBeanFactory.getBean(KafkaProperties.class);
Map<String, Object> producerProps = new HashMap<>();
producerProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, kafkaProperties.getBootstrapServers());
producerProps.put(ProducerConfig.MAX_REQUEST_SIZE_CONFIG, kafkaProperties.getMaxRequestSize());
producerProps.put(DEBUG_TOPICS_KEY, KafkaTopicConstants.DEBUG_TOPICS);
return producerProps;
}

View File

@ -0,0 +1,7 @@
package io.metersphere.commons.constants;
public class ExtendedParameter {
public static final String JMX = "JMX";
public static final String SYNC_STATUS = "SYN_RES";
public static final String SAVE_RESULT = "SAVE_RESULT";
}

View File

@ -40,6 +40,10 @@ public class WebSocketUtil {
});
}
public static boolean has(String key) {
return StringUtils.isNotEmpty(key) && ONLINE_USER_SESSIONS.containsKey(key);
}
//当前的Session 移除
public static void onClose(String reportId) {
try {

View File

@ -1,5 +1,6 @@
package io.metersphere.controller;
import io.metersphere.api.dto.BodyFileRequest;
import io.metersphere.api.jmeter.JMeterThreadUtils;
import io.metersphere.service.ApiJMeterFileService;
import org.springframework.http.HttpHeaders;
@ -58,4 +59,13 @@ public class ApiJMeterFileController {
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + UUID.randomUUID().toString() + ".zip\"")
.body(bytes);
}
@PostMapping("download/files")
public ResponseEntity<byte[]> downloadJmeterFiles(@RequestBody BodyFileRequest request) {
byte[] bytes = apiJmeterFileService.zipFilesToByteArray(request);
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType("application/octet-stream"))
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + request.getReportId() + ".zip\"")
.body(bytes);
}
}

View File

@ -1,5 +1,6 @@
package io.metersphere.service;
import io.metersphere.api.dto.BodyFileRequest;
import io.metersphere.api.dto.EnvironmentType;
import io.metersphere.api.dto.definition.request.MsTestPlan;
import io.metersphere.api.exec.api.ApiCaseSerialService;
@ -308,4 +309,21 @@ public class ApiJMeterFileService {
return null;
}
}
public byte[] zipFilesToByteArray(BodyFileRequest request) {
Map<String, byte[]> files = new LinkedHashMap<>();
if (CollectionUtils.isNotEmpty(request.getBodyFiles())) {
for (BodyFile bodyFile : request.getBodyFiles()) {
File file = new File(bodyFile.getName());
if (file != null && file.exists()) {
byte[] fileByte = FileUtils.fileToByte(file);
if (fileByte != null) {
files.put(file.getAbsolutePath(), fileByte);
}
}
}
}
return listBytesToZip(files);
}
}

View File

@ -22,6 +22,7 @@ import io.metersphere.api.dto.mock.config.MockConfigImportDTO;
import io.metersphere.api.dto.swaggerurl.SwaggerTaskResult;
import io.metersphere.api.dto.swaggerurl.SwaggerUrlRequest;
import io.metersphere.api.exec.api.ApiExecuteService;
import io.metersphere.api.jmeter.JMeterService;
import io.metersphere.api.parse.ApiImportParser;
import io.metersphere.api.parse.api.ApiDefinitionImport;
import io.metersphere.api.parse.api.ApiDefinitionImportParserFactory;
@ -166,7 +167,8 @@ public class ApiDefinitionService {
private ApiCustomFieldService customFieldApiService;
@Resource
private ApiScenarioMapper apiScenarioMapper;
@Resource
private JMeterService jMeterService;
private final ThreadLocal<Long> currentApiOrder = new ThreadLocal<>();
private final ThreadLocal<Long> currentApiCaseOrder = new ThreadLocal<>();
@ -2838,6 +2840,11 @@ public class ApiDefinitionService {
* @return
*/
public MsExecResponseDTO run(RunDefinitionRequest request, List<MultipartFile> bodyFiles) {
if(request.getConfig() == null ) {
request.setConfig(new RunModeConfigDTO());
}
// 验证是否本地执行
jMeterService.verifyPool(request.getProjectId(), request.getConfig());
if (!request.isDebug()) {
String testId = request.getTestElement() != null && CollectionUtils.isNotEmpty(request.getTestElement().getHashTree()) && CollectionUtils.isNotEmpty(request.getTestElement().getHashTree().get(0).getHashTree()) ? request.getTestElement().getHashTree().get(0).getHashTree().get(0).getName() : request.getId();
String reportName = this.getReportNameByTestId(testId);

View File

@ -24,6 +24,7 @@ import io.metersphere.base.mapper.plan.TestPlanApiCaseMapper;
import io.metersphere.base.mapper.plan.ext.ExtTestPlanApiCaseMapper;
import io.metersphere.commons.constants.ApiRunMode;
import io.metersphere.commons.constants.CommonConstants;
import io.metersphere.commons.constants.ExtendedParameter;
import io.metersphere.commons.constants.TriggerMode;
import io.metersphere.commons.enums.ApiReportStatus;
import io.metersphere.commons.exception.MSException;
@ -773,7 +774,7 @@ public class TestPlanApiCaseService {
request.setBloBs(apiCase);
request.setReportId(reportId);
Map<String, Object> extendedParameters = new HashMap<>();
extendedParameters.put("SYN_RES", true);
extendedParameters.put(ExtendedParameter.SYNC_STATUS, true);
apiExecuteService.exec(request, extendedParameters);
}
}

View File

@ -12,6 +12,7 @@ import io.metersphere.api.dto.definition.request.unknown.MsJmeterElement;
import io.metersphere.api.dto.export.ScenarioToPerformanceInfoDTO;
import io.metersphere.api.exec.scenario.ApiScenarioEnvService;
import io.metersphere.api.exec.scenario.ApiScenarioExecuteService;
import io.metersphere.api.jmeter.JMeterService;
import io.metersphere.api.jmeter.NewDriverManager;
import io.metersphere.api.parse.ApiImportParser;
import io.metersphere.api.parse.scenario.ApiScenarioImportUtil;
@ -35,6 +36,7 @@ import io.metersphere.commons.utils.mock.MockApiUtils;
import io.metersphere.dto.BaseCase;
import io.metersphere.dto.MsExecResponseDTO;
import io.metersphere.dto.ProjectConfig;
import io.metersphere.dto.RunModeConfigDTO;
import io.metersphere.environment.service.BaseEnvGroupProjectService;
import io.metersphere.i18n.Translator;
import io.metersphere.log.utils.ReflexObjectUtil;
@ -148,6 +150,8 @@ public class ApiScenarioService {
private ExtTestPlanApiCaseMapper extTestPlanApiCaseMapper;
@Resource
private ExtTestPlanScenarioCaseMapper extTestPlanScenarioCaseMapper;
@Resource
private JMeterService jMeterService;
private ThreadLocal<Long> currentScenarioOrder = new ThreadLocal<>();
@ -915,6 +919,8 @@ public class ApiScenarioService {
* @return
*/
public String debugRun(RunDefinitionRequest request, List<MultipartFile> bodyFiles, List<MultipartFile> scenarioFiles) {
request.setConfig(new RunModeConfigDTO());
jMeterService.verifyPool(request.getProjectId(), request.getConfig());
return apiScenarioExecuteService.debug(request, bodyFiles, scenarioFiles);
}

View File

@ -4,43 +4,44 @@
:title="$t('load_test.runtime_config')"
width="550px"
@close="close"
:visible.sync="runModeVisible"
>
:visible.sync="runModeVisible">
<div v-loading="loading">
<div style="margin-bottom: 10px;">
<span class="ms-mode-span"> {{ $t("commons.environment") }} </span>
<env-popover :project-ids="projectIds"
<env-popover
:project-ids="projectIds"
:placement="'bottom-start'"
:project-list="projectList"
:project-env-map="projectEnvListMap"
:environment-type.sync="runConfig.environmentType"
:group-id="runConfig.environmentGroupId"
:has-option-group="true"
:show-env-group="isScenario"
@setEnvGroup="setEnvGroup"
@setProjectEnvMap="setProjectEnvMap"
@showPopover="showPopover"
ref="envPopover" class="env-popover"/>
</div>
<div>
<span class="ms-mode-span">{{ $t("run_mode.title") }}</span>
<span>{{ $t("run_mode.title") }}</span>
<el-radio-group v-model="runConfig.mode" @change="changeMode">
<el-radio label="serial">{{ $t("run_mode.serial") }}</el-radio>
<el-radio label="parallel">{{ $t("run_mode.parallel") }}</el-radio>
</el-radio-group>
</div>
<!-- 资源池 -->
<div class="ms-mode-div">
<el-row>
<el-col :span="6">
<span class="ms-mode-span">{{ $t("run_mode.other_config") }}</span>
</el-col>
<el-col :span="18">
<div>
<span>
<el-radio-group v-model="runConfig.reportType">
<el-radio label="iddReport">{{ $t("run_mode.idd_report") }}</el-radio>
<el-radio label="setReport">{{ $t("run_mode.set_report") }}</el-radio>
</el-radio-group>
</div>
<div style="padding-top: 10px">
<el-checkbox v-model="runConfig.runWithinResourcePool" style="padding-right: 10px;">
</span>
<div style="padding:10px 90px">
<el-checkbox v-model="runConfig.runWithinResourcePool"
style="padding-right: 10px;" :disabled="runMode === 'POOL'">
{{ $t('run_mode.run_with_resource_pool') }}
</el-checkbox>
<el-select :disabled="!runConfig.runWithinResourcePool" v-model="runConfig.resourcePoolId" size="mini">
@ -53,13 +54,11 @@
</el-option>
</el-select>
</div>
</el-col>
</el-row>
</div>
<!--- 失败停止 -->
<div style="margin-top: 10px" v-if="runConfig.mode === 'serial'">
<el-checkbox v-model="runConfig.onSampleError" style="margin-left: 127px">
<div style="padding:0px 90px" v-if="runConfig.mode === 'serial'">
<el-checkbox v-model="runConfig.onSampleError">
{{ $t("api_test.fail_to_stop") }}
</el-checkbox>
</div>
@ -72,26 +71,32 @@
size="small"
style="width: 300px"/>
</div>
</div>
<template v-slot:footer>
<ms-dialog-footer @cancel="close" @confirm="handleRunBatch"/>
</template>
</el-dialog>
</template>
<script>
import {apiScenarioEnv} from "@/api/scenario";
import MsDialogFooter from "metersphere-frontend/src/components/MsDialogFooter";
import {ENV_TYPE} from "metersphere-frontend/src/utils/constants";
import {strMapToObj} from "metersphere-frontend/src/utils";
import EnvPopover from "@/business/automation/scenario/EnvPopover";
import {getOwnerProjects} from "@/api/project";
import {getOwnerProjects, getProjectConfig} from "@/api/project";
import {getTestResourcePools} from "@/api/test-resource-pool";
import {getCurrentProjectID} from "metersphere-frontend/src/utils/token";
import {getSystemBaseSetting} from "metersphere-frontend/src/api/system";
export default {
name: "RunMode",
components: {MsDialogFooter, EnvPopover},
data() {
return {
runMode: "",
loading: false,
runModeVisible: false,
testType: null,
resourcePools: [],
@ -111,8 +116,13 @@ export default {
projectIds: new Set(),
};
},
props: ['request'],
props: {
request: Object,
isScenario: {
type: Boolean,
default: true
}
},
watch: {
'runConfig.runWithinResourcePool'() {
if (!this.runConfig.runWithinResourcePool) {
@ -131,11 +141,10 @@ export default {
this.runModeVisible = true;
this.getResourcePools();
this.getWsProjects();
this.query();
this.runConfig.environmentType = ENV_TYPE.JSON;
},
changeMode() {
this.runConfig.runWithinResourcePool = false;
this.runConfig.resourcePoolId = null;
this.runConfig.reportType = "iddReport";
this.runConfig.reportName = "";
},
@ -173,6 +182,29 @@ export default {
this.resourcePools = response.data;
});
},
query() {
this.loading = true;
this.result = getSystemBaseSetting().then(response => {
if (!response.data.runMode) {
response.data.runMode = 'LOCAL'
}
this.runMode = response.data.runMode;
if (this.runMode === 'POOL') {
this.runConfig.runWithinResourcePool = true;
this.getProjectApplication();
} else {
this.loading = false;
}
})
},
getProjectApplication() {
getProjectConfig(getCurrentProjectID(), "").then(res => {
if (res.data && res.data.poolEnable && res.data.resourcePoolId) {
this.runConfig.resourcePoolId = res.data.resourcePoolId;
}
this.loading = false;
});
},
setEnvGroup(id) {
this.runConfig.environmentGroupId = id;
},
@ -180,6 +212,18 @@ export default {
this.runConfig.envMap = strMapToObj(projectEnvMap);
},
showPopover() {
if (this.isScenario) {
this.showScenarioPopover();
} else {
this.showApiPopover();
}
},
showApiPopover() {
this.projectIds.clear();
this.projectIds.add(getCurrentProjectID());
this.$refs.envPopover.openEnvSelect();
},
showScenarioPopover() {
this.projectIds.clear();
apiScenarioEnv(this.request).then(res => {
let data = res.data;
@ -198,6 +242,9 @@ export default {
<style scoped>
.ms-mode-span {
margin-right: 10px;
display: -moz-inline-box;
display: inline-block;
width: 90px;
}
.ms-mode-div {

View File

@ -3,7 +3,7 @@
:append-to-body='true'
@close="close">
<template>
<div>
<div v-loading="loading">
<el-tabs v-model="activeName">
<el-tab-pane :label="$t('schedule.task_config')" name="first">
<div class="el-step__icon is-text" style="margin-right: 10px;">
@ -60,7 +60,7 @@
</div>
<div class="ms-mode-div">
<span class="ms-mode-span">{{ $t("run_mode.other_config") }}</span>
<el-checkbox v-model="runConfig.runWithinResourcePool">
<el-checkbox v-model="runConfig.runWithinResourcePool" :disabled="runMode === 'POOL'">
{{ $t('run_mode.run_with_resource_pool') }}
</el-checkbox>
<el-select style="margin-left: 10px" :disabled="!runConfig.runWithinResourcePool"
@ -106,8 +106,9 @@ import MsScheduleNotification from "./ScheduleNotification";
import ScheduleSwitch from "@/business/automation/schedule/ScheduleSwitch";
import {ENV_TYPE} from "metersphere-frontend/src/utils/constants";
import EnvPopover from "@/business/automation/scenario/EnvPopover";
import {getMaintainer, getOwnerProjects} from "@/api/project";
import {getMaintainer, getOwnerProjects, getProjectConfig} from "@/api/project";
import {getTestResourcePools} from "@/api/test-resource-pool";
import {getSystemBaseSetting} from "metersphere-frontend/src/api/system";
function defaultCustomValidate() {
return {pass: true};
@ -162,6 +163,8 @@ export default {
}
};
return {
runMode: "",
loading: false,
scheduleReceiverOptions: [],
operation: true,
dialogVisible: false,
@ -198,6 +201,29 @@ export default {
}
},
methods: {
query() {
this.loading = true;
this.result = getSystemBaseSetting().then(response => {
if (!response.data.runMode) {
response.data.runMode = 'LOCAL'
}
this.runMode = response.data.runMode;
if (this.runMode === 'POOL') {
this.runConfig.runWithinResourcePool = true;
this.getProjectApplication();
} else {
this.loading = false;
}
})
},
getProjectApplication() {
getProjectConfig(getCurrentProjectID(), "").then(res => {
if (res.data && res.data.poolEnable && res.data.resourcePoolId) {
this.runConfig.resourcePoolId = res.data.resourcePoolId;
}
this.loading = false;
});
},
currentUser: () => {
return getCurrentUser();
},
@ -208,8 +234,6 @@ export default {
return true;
},
changeMode() {
this.runConfig.runWithinResourcePool = false;
this.runConfig.resourcePoolId = null;
this.runConfig.reportType = "iddReport";
this.runConfig.reportName = "";
},
@ -293,6 +317,7 @@ export default {
this.activeName = 'first';
this.getResourcePools();
this.getWsProjects();
this.query();
this.runConfig.environmentType = ENV_TYPE.JSON;
},
findSchedule() {

View File

@ -1,213 +0,0 @@
<template>
<el-dialog
destroy-on-close
:title="$t('load_test.runtime_config')"
width="550px"
@close="close"
:visible.sync="runModeVisible"
>
<div style="margin-bottom: 10px;">
<span class="ms-mode-span">{{ $t("commons.environment") }}</span>
<env-popover :project-ids="projectIds"
:placement="'bottom-start'"
:project-list="projectList"
:project-env-map="projectEnvListMap"
:environment-type.sync="runConfig.environmentType"
:group-id="runConfig.environmentGroupId"
@setEnvGroup="setEnvGroup"
@setProjectEnvMap="setProjectEnvMap"
@showPopover="showPopover"
:show-env-group="false"
ref="envPopover" class="env-popover"/>
</div>
<div>
<span class="ms-mode-span">{{ $t("run_mode.title") }}</span>
<el-radio-group v-model="runConfig.mode" @change="changeMode">
<el-radio label="serial">{{ $t("run_mode.serial") }}</el-radio>
<el-radio label="parallel">{{ $t("run_mode.parallel") }}</el-radio>
</el-radio-group>
</div>
<div class="ms-mode-div">
<el-row>
<el-col :span="6">
<span class="ms-mode-span">{{ $t("run_mode.other_config") }}</span>
</el-col>
<el-col :span="18">
<div>
<el-radio-group v-model="runConfig.reportType">
<el-radio label="iddReport">{{ $t("run_mode.idd_report") }}</el-radio>
<el-radio label="setReport">{{ $t("run_mode.set_report") }}</el-radio>
</el-radio-group>
</div>
<div style="padding-top: 10px">
<el-checkbox v-model="runConfig.runWithinResourcePool" style="padding-right: 10px;">
{{ $t('run_mode.run_with_resource_pool') }}
</el-checkbox>
<el-select :disabled="!runConfig.runWithinResourcePool" v-model="runConfig.resourcePoolId" size="mini">
<el-option
v-for="item in resourcePools"
:key="item.id"
:label="item.name"
:disabled="!item.api"
:value="item.id">
</el-option>
</el-select>
</div>
</el-col>
</el-row>
</div>
<!--- 失败停止 -->
<div style="margin-top: 10px" v-if="runConfig.mode === 'serial'">
<el-checkbox v-model="runConfig.onSampleError" style="margin-left: 127px">
{{ $t("api_test.fail_to_stop") }}
</el-checkbox>
</div>
<div class="ms-mode-div" v-if="runConfig.reportType === 'setReport'">
<span class="ms-mode-span-label">{{ $t("run_mode.report_name") }}</span>
<el-input
v-model="runConfig.reportName"
:placeholder="$t('commons.input_content')"
size="small"
style="width: 300px"/>
</div>
<template v-slot:footer>
<ms-dialog-footer @cancel="close" @confirm="handleRunBatch"/>
</template>
</el-dialog>
</template>
<script>
import MsDialogFooter from "metersphere-frontend/src/components/MsDialogFooter";
import EnvPopover from "@/business/automation/scenario/EnvPopover";
import {strMapToObj} from "metersphere-frontend/src/utils";
import {ENV_TYPE} from "metersphere-frontend/src/utils/constants";
import {parseEnvironment} from "@/business/environment/model/EnvironmentModel";
import {getOwnerProjects} from "@/api/project";
import {getEnvironmentByProjectId} from "metersphere-frontend/src/api/environment";
import {getTestResourcePools} from "@/api/test-resource-pool";
export default {
name: "MsApiCaseRunModeWithEnv",
components: {EnvPopover, MsDialogFooter},
data() {
return {
runModeVisible: false,
resourcePools: [],
runConfig: {
reportName: "",
mode: "serial",
reportType: "iddReport",
onSampleError: false,
runWithinResourcePool: false,
resourcePoolId: null,
envMap: new Map(),
environmentGroupId: "",
environmentType: ENV_TYPE.JSON
},
projectEnvListMap: {},
projectList: [],
projectIds: new Set(),
};
},
props: ['projectId'],
methods: {
open() {
this.runModeVisible = true;
this.getResourcePools();
this.getWsProjects();
},
changeMode() {
this.runConfig.onSampleError = false;
this.runConfig.runWithinResourcePool = false;
this.runConfig.resourcePoolId = null;
this.runConfig.reportName = "";
},
close() {
this.runConfig = {
mode: "serial",
reportType: "iddReport",
onSampleError: false,
reportName: "",
runWithinResourcePool: false,
resourcePoolId: null,
envMap: new Map(),
environmentGroupId: "",
environmentType: ENV_TYPE.JSON
};
this.runModeVisible = false;
this.$emit('close');
},
handleRunBatch() {
if ((this.runConfig.mode === 'serial' || this.runConfig.mode === 'parallel') && this.runConfig.reportType === 'setReport' && this.runConfig.reportName.trim() === "") {
this.$warning(this.$t('commons.input_name'));
return;
}
if (this.runConfig.runWithinResourcePool && this.runConfig.resourcePoolId == null) {
this.$warning(this.$t('workspace.env_group.please_select_run_within_resource_pool'));
return;
}
this.$emit("handleRunBatch", this.runConfig);
this.close();
},
getResourcePools() {
this.result = getTestResourcePools().then(response => {
this.resourcePools = response.data;
});
},
setProjectEnvMap(projectEnvMap) {
this.runConfig.envMap = strMapToObj(projectEnvMap);
},
setEnvGroup(id) {
this.runConfig.environmentGroupId = id;
},
getWsProjects() {
getOwnerProjects().then(res => {
this.projectList = res.data;
})
},
getEnvironments() {
return new Promise((resolve) => {
if (this.projectId) {
getEnvironmentByProjectId(this.projectId).then(response => {
this.environments = response.data;
this.environments.forEach(environment => {
parseEnvironment(environment);
});
resolve();
});
}
})
},
showPopover() {
this.projectIds.clear();
this.projectIds.add(this.projectId);
this.$refs.envPopover.openEnvSelect();
},
},
};
</script>
<style scoped>
.ms-mode-span {
margin-right: 10px;
}
.ms-mode-div {
margin-top: 20px;
}
.ms-mode-span {
margin-right: 10px;
margin-left: 10px;
}
.ms-mode-span-label:before {
content: '*';
color: #F56C6C;
margin-right: 4px;
margin-left: 10px;
}
</style>

View File

@ -247,6 +247,7 @@
<ms-task-center ref="taskCenter" :show-menu="false"/>
<ms-api-case-run-mode-with-env
:is-scenario="false"
:project-id="projectId"
@handleRunBatch="runBatch"
@close="initTable"
@ -304,7 +305,7 @@ import MsContainer from "metersphere-frontend/src/components/MsContainer";
import MsBottomContainer from "../BottomContainer";
import ShowMoreBtn from "@/business/commons/ShowMoreBtn";
import MsBatchEdit from "../basis/BatchEdit";
import MsApiCaseRunModeWithEnv from "./ApiCaseRunModeWithEnv";
import MsApiCaseRunModeWithEnv from "@/business/automation/scenario/common/RunMode";
import {getUUID, operationConfirm} from "metersphere-frontend/src/utils";
import {API_METHOD_COLOUR, CASE_PRIORITY, DUBBO_METHOD, REQ_METHOD, SQL_METHOD, TCP_METHOD} from "../../model/JsonData";

View File

@ -0,0 +1,11 @@
import {get} from "../plugins/request";
export function getSystemBaseSetting() {
return get('/system/base/info');
}
export function getTestResourcePools() {
let url = '/testresourcepool/list/quota/valid';
return get(url);
}

View File

@ -1,7 +1,10 @@
package io.metersphere.utils;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.type.CollectionType;
import java.io.IOException;
@ -13,6 +16,11 @@ public class JsonUtils {
static {
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// 自动检测所有类的全部属性
objectMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
// 如果一个对象中没有任何的属性那么在序列化的时候就会报错
objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
}
public static String toJSONString(Object value) {

View File

@ -7,6 +7,7 @@ public interface KafkaTopicConstants {
String CLEAN_UP_REPORT_SCHEDULE = "CLEAN_UP_REPORT_SCHEDULE";
// 原接口执行结果TOPIC
String API_REPORT_TOPIC = "ms-api-exec-topic";
String DEBUG_TOPICS = "MS-API-DEBUG-TOPIC";
// 测试计划接收执行结果TOPIC
String TEST_PLAN_REPORT_TOPIC = "TEST_PLAN_REPORT_TOPIC";
// 保存当前站点时检查MOCK环境

View File

@ -115,7 +115,8 @@ public interface ParamConstants {
CONCURRENCY("base.concurrency"),
GRID_CONCURRENCY("base.grid.concurrency"),
PROMETHEUS_HOST("base.prometheus.host"),
SELENIUM_DOCKER_URL("base.selenium.docker.url");
SELENIUM_DOCKER_URL("base.selenium.docker.url"),
RUN_MODE("base.run.mode");
private String value;

View File

@ -98,5 +98,13 @@ public enum ProjectApplicationType {
/**
* 我的工作台-开启待更新规则
*/
OPEN_UPDATE_RULE
OPEN_UPDATE_RULE,
/**
* 开启接口测试默认资源池
*/
POOL_ENABLE,
/**
* 资源池ID
*/
RESOURCE_POOL_ID,
}

View File

@ -33,4 +33,6 @@ public class ProjectConfig {
private Boolean openUpdateTime = false;
private String openUpdateRuleTime;
private Boolean openUpdateRule;
private String resourcePoolId;
private Boolean poolEnable = false;
}

View File

@ -236,6 +236,9 @@ public class SystemParameterService {
if (StringUtils.equals(param.getParamKey(), ParamConstants.BASE.SELENIUM_DOCKER_URL.getValue())) {
baseSystemConfigDTO.setSeleniumDockerUrl(param.getParamValue());
}
if (StringUtils.equals(param.getParamKey(), ParamConstants.BASE.RUN_MODE.getValue())) {
baseSystemConfigDTO.setRunMode(param.getParamValue());
}
}
}
return baseSystemConfigDTO;

View File

@ -22,6 +22,7 @@ spring.datasource.quartz.hikari.connection-test-query=SELECT 1
#kafka
spring.kafka.bootstrap-servers=${kafka.bootstrap-servers}
spring.kafka.consumer.group-id=metersphere_group_id
spring.kafka.consumer.debug.group-id=metersphere_group_id_${random.uuid}
# mybatis
mybatis.configuration.cache-enabled=true
mybatis.configuration.lazy-loading-enabled=false

View File

@ -94,6 +94,29 @@
:unit-options="applyUnitOptions"
@chooseChange="switchChange('API_SHARE_REPORT_TIME', config.apiShareReportTime, config.shareReport)"
:title="$t('report.report_sharing_link')"/>
<!-- 接口测试资源池 -->
<app-manage-item :title="$t('pj.api_run_pool_title')" :prepend-span="8" :middle-span="12"
:append-span="4" v-if="isPool">
<template #middle>
<el-select v-model="config.resourcePoolId"
size="mini"
@change="runModeChange(true, ['RESOURCE_POOL_ID', config.resourcePoolId])">
<el-option
v-for="item in resourcePools"
:key="item.id"
:label="item.name"
:disabled="!item.api"
:value="item.id">
</el-option>
</el-select>
</template>
<template #append>
<el-switch v-model="config.poolEnable"
@change="runModeChange($event, ['RESOURCE_POOL_ID', config.resourcePoolId])"></el-switch>
</template>
</app-manage-item>
</el-row>
</el-col>
<el-col :span="8" class="commons-view-setting">
@ -301,6 +324,7 @@ import TimingItem from "./TimingItem";
import {genTcpMockPort} from "../../../api/project";
import {batchModifyAppSetting, getProjectAppSetting} from "../../../api/app-setting";
import {PROJECT_APP_SETTING} from "../../../common/js/constants";
import {getSystemBaseSetting, getTestResourcePools} from "metersphere-frontend/src/api/system";
export default {
name: "appManage",
@ -313,6 +337,8 @@ export default {
data() {
return {
activeName: 'test_track',
resourcePools: [],
isPool: false,
form: {
cleanTrackReport: false,
cleanTrackReportExpr: "",
@ -381,6 +407,8 @@ export default {
},
created() {
this.init();
this.getBase();
this.getResourcePools();
this.isXpack = !!hasLicense();
},
computed: {
@ -389,6 +417,24 @@ export default {
},
},
methods: {
getBase() {
this.result = getSystemBaseSetting().then(response => {
this.isPool = response.data.runMode === 'POOL';
})
},
getResourcePools() {
this.result = getTestResourcePools().then(response => {
this.resourcePools = response.data;
});
},
runModeChange(value, other) {
if (value && !this.config.resourcePoolId) {
this.$warning(this.$t('workspace.env_group.please_select_run_within_resource_pool'));
this.config.poolEnable = false;
} else {
this.switchChange("POOL_ENABLE", value, other);
}
},
tcpMockSwitchChange(value, other) {
if (value && this.config.mockTcpPort === 0) {
genTcpMockPort(this.projectId).then(res => {

View File

@ -6,6 +6,7 @@ const message = {
pj: {
environment_import_repeat_tip: "(Environment configuration with the same name filtered {0})",
check_third_project_success: "inspection passed",
api_run_pool_title: 'Interface execution resource pool',
},
file_manage: {
my_file: 'My File',

View File

@ -6,6 +6,7 @@ const message = {
pj: {
environment_import_repeat_tip: "(已过滤同名称的环境配置 {0})",
check_third_project_success: "检查通过",
api_run_pool_title: '接口执行资源池',
},
file_manage: {
my_file: '我的文件',

View File

@ -6,6 +6,7 @@ const message = {
pj: {
environment_import_repeat_tip: "(已過濾同名稱的環境配置 {0})",
check_third_project_success: "檢查通過",
api_run_pool_title: '接口執行資源池',
},
file_manage: {
my_file: '我的文件',

View File

@ -19,6 +19,9 @@
<el-input v-model="formInline.seleniumDockerUrl" :placeholder="$t('system_config.selenium_docker.url_tip')"/>
<i>({{ $t('commons.examples') }}:http://localhost:4444)</i>
</el-form-item>
<el-form-item :label="$t('system.api_default_run')" prop="seleniumDockerUrl">
<el-switch active-value="LOCAL" inactive-value="POOL" v-model="formInline.runMode" @change="modeChange"/>
</el-form-item>
</el-col>
</el-row>
</el-form>
@ -40,7 +43,7 @@ export default {
name: "BaseSetting",
data() {
return {
formInline: {},
formInline: {runMode: true},
input: '',
visible: true,
showEdit: true,
@ -75,12 +78,29 @@ export default {
methods: {
query() {
this.loading = getSystemBaseSetting().then(res => {
if(!res.data.runMode) {
res.data.runMode = 'LOCAL'
}
this.formInline = res.data;
this.$nextTick(() => {
this.$refs.formInline.clearValidate();
})
});
},
modeChange(v){
if(v === 'POOL'){
this.formInline.runMode = 'LOCAL';
this.$alert(this.$t('system.api_default_run_message'), '', {
confirmButtonText: this.$t('commons.confirm'),
cancelButtonText: this.$t('commons.cancel'),
callback: (action) => {
if (action === 'confirm') {
this.formInline.runMode = v;
}
}
});
}
},
edit() {
this.showEdit = false;
this.showSave = true;
@ -101,6 +121,7 @@ export default {
{paramKey: "base.concurrency", paramValue: this.formInline.concurrency, type: "text", sort: 2},
{paramKey: "base.prometheus.host", paramValue: this.formInline.prometheusHost, type: "text", sort: 1},
{paramKey: "base.selenium.docker.url", paramValue: this.formInline.seleniumDockerUrl, type: "text", sort: 1},
{paramKey: "base.run.mode", paramValue: this.formInline.runMode, type: "text", sort: 5}
];
this.loading = saveSystemBaseSetting(param).then(res => {
if (res.success) {

View File

@ -9,7 +9,9 @@ const message = {
system: {
environment_import_repeat_tip: "(Environment configuration with the same name filtered {0})",
search_by_environment_name: "search by environment name",
check_third_project_success: "inspection passed"
check_third_project_success: "inspection passed",
api_default_run_message: 'In order not to affect the normal execution of the interface, please configure the resource pool for interface execution in [Project Settings - Application Management - Interface Test]',
api_default_run: 'The interface is executed locally by default',
},
display: {
title: 'Theme',

View File

@ -9,7 +9,9 @@ const message = {
system: {
environment_import_repeat_tip: "(已过滤同名称的环境配置 {0})",
search_by_environment_name: "根据环境的名称搜索",
check_third_project_success: "检查通过"
check_third_project_success: "检查通过",
api_default_run_message: '为了不影响接口正常执行,请在【 项目设置-应用管理-接口测试 】中配置接口执行的资源池',
api_default_run: '接口默认本地执行',
},
display: {
title: '显示设置',

View File

@ -9,7 +9,9 @@ const message = {
system: {
environment_import_repeat_tip: "(已過濾同名稱的環境配置 {0})",
search_by_environment_name: "根據環境的名稱搜索",
check_third_project_success: "檢查通過"
check_third_project_success: "檢查通過",
api_default_run_message: '為了不影響接口正常執行,請在【 項目設置-應用管理-接口測試 】中配置接口執行的資源池',
api_default_run: '接口默認本地執行',
},
display: {
title: '顯示設置',

View File

@ -147,6 +147,12 @@ public class TestPlanService {
private KafkaTemplate<String, String> kafkaTemplate;
@Resource
private ObjectMapper objectMapper;
@Resource
private SystemParameterService systemParameterService;
@Resource
private BaseProjectApplicationService projectApplicationService;
public static final String POOL = "POOL";
public synchronized TestPlan addTestPlan(AddTestPlanRequest testPlan) {
if (getTestPlanByName(testPlan.getName()).size() > 0) {
MSException.throwException(Translator.get("plan_name_already_exists"));
@ -847,7 +853,7 @@ public class TestPlanService {
if (planReportId == null) {
planReportId = UUID.randomUUID().toString();
}
this.verifyPool(projectId, runModeConfig);
//创建测试报告然后返回的ID重新赋值为resourceID作为后续的参数
TestPlanScheduleReportInfoDTO reportInfoDTO = this.genTestPlanReport(planReportId, testPlanId, userId, triggerMode, runModeConfig);
//测试计划准备执行取消测试计划的实际结束时间
@ -888,6 +894,20 @@ public class TestPlanService {
return planReportId;
}
public void verifyPool(String projectId, RunModeConfigDTO runConfig) {
// 检查是否禁用了本地执行
if (runConfig != null && StringUtils.isEmpty(runConfig.getResourcePoolId())) {
BaseSystemConfigDTO configDTO = systemParameterService.getBaseInfo();
if (StringUtils.equals(configDTO.getRunMode(), POOL)) {
ProjectConfig config = projectApplicationService.getProjectConfig(projectId);
if (config == null || !config.getPoolEnable() || StringUtils.isEmpty(config.getResourcePoolId())) {
MSException.throwException("请在【项目设置-应用管理-接口测试】中选择资源池");
}
runConfig = runConfig == null ? new RunModeConfigDTO() : runConfig;
runConfig.setResourcePoolId(config.getResourcePoolId());
}
}
}
/**
* 将测试计划运行时的triggerMode转化为性能测试中辨别更明确的值
*

View File

@ -6,7 +6,7 @@
@close="close"
:visible.sync="runModeVisible"
>
<div class="env-container">
<div class="env-container" v-loading="loading">
<div class="run-env-row wrap">
<div class="title">{{ $t("commons.environment") }}</div>
<div class="content">
@ -68,6 +68,7 @@
<el-checkbox
v-model="runConfig.runWithinResourcePool"
style="padding-right: 10px"
:disabled="runMode === 'POOL'"
>
{{ $t("run_mode.run_with_resource_pool") }}
</el-checkbox>
@ -94,6 +95,7 @@
<el-checkbox
v-model="runConfig.runWithinResourcePool"
style="padding-right: 10px"
:disabled="runMode === 'POOL'"
>
{{ $t("run_mode.run_with_resource_pool") }}
</el-checkbox>
@ -191,17 +193,21 @@ import {hasLicense} from "metersphere-frontend/src/utils/permission";
import {strMapToObj} from "metersphere-frontend/src/utils";
import {ENV_TYPE} from "metersphere-frontend/src/utils/constants";
import {getOwnerProjects} from "@/business/utils/sdk-utils";
import {getCurrentProjectID, getOwnerProjects} from "@/business/utils/sdk-utils";
import {getQuotaValidResourcePools} from "@/api/remote/resource-pool";
import EnvGroupPopover from "@/business/plan/env/EnvGroupPopover";
import {getApiCaseEnv} from "@/api/remote/plan/test-plan-api-case";
import {getApiScenarioEnv, getPlanCaseEnv} from "@/api/remote/plan/test-plan";
import {getSystemBaseSetting} from "metersphere-frontend/src/api/system";
import {getProjectConfig} from "@/api/project";
export default {
name: "MsPlanRunModeWithEnv",
components: {EnvGroupPopover, MsDialogFooter},
data() {
return {
loading: false,
runMode: "",
btnStyle: {
width: "260px",
},
@ -275,11 +281,33 @@ export default {
this.testType = testType;
this.getResourcePools();
this.getWsProjects();
this.query();
},
query() {
this.loading = true;
this.result = getSystemBaseSetting().then(response => {
if (!response.data.runMode) {
response.data.runMode = 'LOCAL'
}
this.runMode = response.data.runMode;
if (this.runMode === 'POOL') {
this.runConfig.runWithinResourcePool = true;
this.getProjectApplication();
} else {
this.loading = false;
}
})
},
getProjectApplication() {
getProjectConfig(getCurrentProjectID(), "").then(res => {
if (res.data && res.data.poolEnable && res.data.resourcePoolId) {
this.runConfig.resourcePoolId = res.data.resourcePoolId;
}
this.loading = false;
});
},
changeMode() {
this.runConfig.onSampleError = false;
this.runConfig.runWithinResourcePool = false;
this.runConfig.resourcePoolId = null;
},
close() {
this.runConfig = {

View File

@ -64,6 +64,7 @@
v-model="runConfig.runWithinResourcePool"
style="padding-right: 10px"
class="radio-change"
:disabled="runMode === 'POOL'"
>
{{ $t("run_mode.run_with_resource_pool") }}
</el-checkbox><br/>
@ -91,6 +92,7 @@
v-model="runConfig.runWithinResourcePool"
style="padding-right: 10px"
class="radio-change"
:disabled="runMode === 'POOL'"
>
{{ $t("run_mode.run_with_resource_pool") }}
</el-checkbox><br/>
@ -192,7 +194,7 @@ import {hasLicense} from "metersphere-frontend/src/utils/permission";
import {strMapToObj} from "metersphere-frontend/src/utils";
import MsTag from "metersphere-frontend/src/components/MsTag";
import {ENV_TYPE} from "metersphere-frontend/src/utils/constants";
import {getOwnerProjects} from "@/business/utils/sdk-utils";
import {getCurrentProjectID, getOwnerProjects} from "@/business/utils/sdk-utils";
import {getQuotaValidResourcePools} from "@/api/remote/resource-pool";
import EnvGroupPopover from "@/business/plan/env/EnvGroupPopover";
import {getApiCaseEnv} from "@/api/remote/plan/test-plan-api-case";
@ -200,6 +202,8 @@ import {getApiScenarioEnv, getPlanCaseEnv} from "@/api/remote/plan/test-plan";
import EnvGroupWithOption from "../env/EnvGroupWithOption";
import EnvironmentGroup from "@/business/plan/env/EnvironmentGroupList";
import EnvSelectPopover from "@/business/plan/env/EnvSelectPopover";
import {getSystemBaseSetting} from "metersphere-frontend/src/api/system";
import {getProjectConfig} from "@/api/project";
export default {
name: "MsTestPlanRunModeWithEnv",
@ -211,6 +215,7 @@ export default {
},
data() {
return {
runMode: "",
btnStyle: {
width: "260px",
},
@ -287,11 +292,33 @@ export default {
this.getResourcePools();
this.getWsProjects();
this.showPopover();
this.query();
},
query() {
this.loading = true;
this.result = getSystemBaseSetting().then(response => {
if (!response.data.runMode) {
response.data.runMode = 'LOCAL'
}
this.runMode = response.data.runMode;
if (this.runMode === 'POOL') {
this.runConfig.runWithinResourcePool = true;
this.getProjectApplication();
} else {
this.loading = false;
}
})
},
getProjectApplication() {
getProjectConfig(getCurrentProjectID(), "").then(res => {
if (res.data && res.data.poolEnable && res.data.resourcePoolId) {
this.runConfig.resourcePoolId = res.data.resourcePoolId;
}
this.loading = false;
});
},
changeMode() {
this.runConfig.onSampleError = false;
this.runConfig.runWithinResourcePool = false;
this.runConfig.resourcePoolId = null;
},
close() {
this.runConfig = {

View File

@ -76,7 +76,7 @@
</el-col>
<el-col :span="18">
<div v-if="testType === 'API'">
<el-checkbox v-model="runConfig.runWithinResourcePool" style="padding-right: 10px;">
<el-checkbox v-model="runConfig.runWithinResourcePool" style="padding-right: 10px;" :disabled="runMode === 'POOL'">
{{ $t('run_mode.run_with_resource_pool') }}
</el-checkbox>
<el-select :disabled="!runConfig.runWithinResourcePool" v-model="runConfig.resourcePoolId"
@ -99,7 +99,7 @@
</el-col>
<el-col :span="18">
<div v-if="testType === 'API'">
<el-checkbox v-model="runConfig.runWithinResourcePool" style="padding-right: 10px;">
<el-checkbox v-model="runConfig.runWithinResourcePool" style="padding-right: 10px;" :disabled="runMode === 'POOL'">
{{ $t('run_mode.run_with_resource_pool') }}
</el-checkbox>
<el-select :disabled="!runConfig.runWithinResourcePool" v-model="runConfig.resourcePoolId"
@ -203,6 +203,8 @@ import {
import {saveNotice} from "@/api/notice";
import {getProjectMember} from "@/api/user";
import {getQuotaValidResourcePools} from "@/api/remote/resource-pool";
import {getProjectConfig} from "@/api/project";
import {getSystemBaseSetting} from "metersphere-frontend/src/api/system";
function defaultCustomValidate() {
return {pass: true};
@ -266,6 +268,7 @@ export default {
}
};
return {
runMode: "",
isHasLicense: hasLicense(),
result: {},
scheduleReceiverOptions: [],
@ -314,6 +317,29 @@ export default {
};
},
methods: {
query() {
this.loading = true;
this.result = getSystemBaseSetting().then(response => {
if (!response.data.runMode) {
response.data.runMode = 'LOCAL'
}
this.runMode = response.data.runMode;
if (this.runMode === 'POOL') {
this.runConfig.runWithinResourcePool = true;
this.getProjectApplication();
} else {
this.loading = false;
}
})
},
getProjectApplication() {
getProjectConfig(getCurrentProjectID(), "").then(res => {
if (res.data && res.data.poolEnable && res.data.resourcePoolId) {
this.runConfig.resourcePoolId = res.data.resourcePoolId;
}
this.loading = false;
});
},
currentUser: () => {
return getCurrentUser();
},

View File

@ -68,6 +68,7 @@
<el-checkbox
v-model="runConfig.runWithinResourcePool"
style="padding-right: 10px"
:disabled="runMode === 'POOL'"
>
{{ $t("run_mode.run_with_resource_pool") }}
</el-checkbox>
@ -94,6 +95,7 @@
<el-checkbox
v-model="runConfig.runWithinResourcePool"
style="padding-right: 10px"
:disabled="runMode === 'POOL'"
>
{{ $t("run_mode.run_with_resource_pool") }}
</el-checkbox>
@ -186,16 +188,19 @@
</template>
<script>
import {getOwnerProjects, hasLicense} from "@/business/utils/sdk-utils";
import {getCurrentProjectID, getOwnerProjects, hasLicense} from "@/business/utils/sdk-utils";
import {BODY_TYPE as ENV_TYPE} from "@/business/plan/env/ApiTestModel";
import MsDialogFooter from "metersphere-frontend/src/components/MsDialogFooter";
import EnvPopover from "@/business/plan/env/EnvPopover";
import {getProjectConfig} from "@/api/project";
import {getSystemBaseSetting} from "metersphere-frontend/src/api/system";
export default {
name: "MsPlanRunModeWithEnv",
components: {EnvPopover, MsDialogFooter},
data() {
return {
runMode: "",
btnStyle: {
width: "260px",
},
@ -269,11 +274,33 @@ export default {
this.testType = testType;
this.getResourcePools();
this.getWsProjects();
this.query();
},
query() {
this.loading = true;
this.result = getSystemBaseSetting().then(response => {
if (!response.data.runMode) {
response.data.runMode = 'LOCAL'
}
this.runMode = response.data.runMode;
if (this.runMode === 'POOL') {
this.runConfig.runWithinResourcePool = true;
this.getProjectApplication();
} else {
this.loading = false;
}
})
},
getProjectApplication() {
getProjectConfig(getCurrentProjectID(), "").then(res => {
if (res.data && res.data.poolEnable && res.data.resourcePoolId) {
this.runConfig.resourcePoolId = res.data.resourcePoolId;
}
this.loading = false;
});
},
changeMode() {
this.runConfig.onSampleError = false;
this.runConfig.runWithinResourcePool = false;
this.runConfig.resourcePoolId = null;
},
close() {
this.runConfig = {