提交 6b8c059f 编写于 作者: C chenjianxing

merge

...@@ -152,25 +152,13 @@ ...@@ -152,25 +152,13 @@
<artifactId>jmeter-plugins-casutg</artifactId> <artifactId>jmeter-plugins-casutg</artifactId>
<version>2.9</version> <version>2.9</version>
</dependency> </dependency>
<!-- jmeter graph -->
<dependency>
<groupId>kg.apc</groupId>
<artifactId>jmeter-plugins-cmd</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>kg.apc</groupId>
<artifactId>jmeter-plugins-synthesis</artifactId>
<version>2.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/kg.apc/jmeter-plugins-standard -->
<dependency> <dependency>
<groupId>kg.apc</groupId> <groupId>com.opencsv</groupId>
<artifactId>jmeter-plugins-standard</artifactId> <artifactId>opencsv</artifactId>
<version>1.4.0</version> <version>5.1</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.httpcomponents</groupId> <groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId> <artifactId>httpclient</artifactId>
......
...@@ -6,6 +6,8 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; ...@@ -6,6 +6,8 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration; import org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.ServletComponentScan; import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication(exclude = {QuartzAutoConfiguration.class}) @SpringBootApplication(exclude = {QuartzAutoConfiguration.class})
@ServletComponentScan @ServletComponentScan
...@@ -14,4 +16,9 @@ public class Application { ...@@ -14,4 +16,9 @@ public class Application {
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(Application.class, args); SpringApplication.run(Application.class, args);
} }
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
} }
...@@ -9,8 +9,6 @@ public class FileMetadata implements Serializable { ...@@ -9,8 +9,6 @@ public class FileMetadata implements Serializable {
private String type; private String type;
private String engine;
private Long createTime; private Long createTime;
private Long updateTime; private Long updateTime;
...@@ -43,14 +41,6 @@ public class FileMetadata implements Serializable { ...@@ -43,14 +41,6 @@ public class FileMetadata implements Serializable {
this.type = type == null ? null : type.trim(); this.type = type == null ? null : type.trim();
} }
public String getEngine() {
return engine;
}
public void setEngine(String engine) {
this.engine = engine == null ? null : engine.trim();
}
public Long getCreateTime() { public Long getCreateTime() {
return createTime; return createTime;
} }
......
...@@ -314,76 +314,6 @@ public class FileMetadataExample { ...@@ -314,76 +314,6 @@ public class FileMetadataExample {
return (Criteria) this; return (Criteria) this;
} }
public Criteria andEngineIsNull() {
addCriterion("engine is null");
return (Criteria) this;
}
public Criteria andEngineIsNotNull() {
addCriterion("engine is not null");
return (Criteria) this;
}
public Criteria andEngineEqualTo(String value) {
addCriterion("engine =", value, "engine");
return (Criteria) this;
}
public Criteria andEngineNotEqualTo(String value) {
addCriterion("engine <>", value, "engine");
return (Criteria) this;
}
public Criteria andEngineGreaterThan(String value) {
addCriterion("engine >", value, "engine");
return (Criteria) this;
}
public Criteria andEngineGreaterThanOrEqualTo(String value) {
addCriterion("engine >=", value, "engine");
return (Criteria) this;
}
public Criteria andEngineLessThan(String value) {
addCriterion("engine <", value, "engine");
return (Criteria) this;
}
public Criteria andEngineLessThanOrEqualTo(String value) {
addCriterion("engine <=", value, "engine");
return (Criteria) this;
}
public Criteria andEngineLike(String value) {
addCriterion("engine like", value, "engine");
return (Criteria) this;
}
public Criteria andEngineNotLike(String value) {
addCriterion("engine not like", value, "engine");
return (Criteria) this;
}
public Criteria andEngineIn(List<String> values) {
addCriterion("engine in", values, "engine");
return (Criteria) this;
}
public Criteria andEngineNotIn(List<String> values) {
addCriterion("engine not in", values, "engine");
return (Criteria) this;
}
public Criteria andEngineBetween(String value1, String value2) {
addCriterion("engine between", value1, value2, "engine");
return (Criteria) this;
}
public Criteria andEngineNotBetween(String value1, String value2) {
addCriterion("engine not between", value1, value2, "engine");
return (Criteria) this;
}
public Criteria andCreateTimeIsNull() { public Criteria andCreateTimeIsNull() {
addCriterion("create_time is null"); addCriterion("create_time is null");
return (Criteria) this; return (Criteria) this;
......
...@@ -5,7 +5,6 @@ ...@@ -5,7 +5,6 @@
<id column="id" jdbcType="VARCHAR" property="id" /> <id column="id" jdbcType="VARCHAR" property="id" />
<result column="name" jdbcType="VARCHAR" property="name" /> <result column="name" jdbcType="VARCHAR" property="name" />
<result column="type" jdbcType="VARCHAR" property="type" /> <result column="type" jdbcType="VARCHAR" property="type" />
<result column="engine" jdbcType="VARCHAR" property="engine" />
<result column="create_time" jdbcType="BIGINT" property="createTime" /> <result column="create_time" jdbcType="BIGINT" property="createTime" />
<result column="update_time" jdbcType="BIGINT" property="updateTime" /> <result column="update_time" jdbcType="BIGINT" property="updateTime" />
<result column="size" jdbcType="BIGINT" property="size" /> <result column="size" jdbcType="BIGINT" property="size" />
...@@ -69,7 +68,7 @@ ...@@ -69,7 +68,7 @@
</where> </where>
</sql> </sql>
<sql id="Base_Column_List"> <sql id="Base_Column_List">
id, name, type, engine, create_time, update_time, size id, name, type, create_time, update_time, size
</sql> </sql>
<select id="selectByExample" parameterType="io.metersphere.base.domain.FileMetadataExample" resultMap="BaseResultMap"> <select id="selectByExample" parameterType="io.metersphere.base.domain.FileMetadataExample" resultMap="BaseResultMap">
select select
...@@ -103,11 +102,11 @@ ...@@ -103,11 +102,11 @@
</delete> </delete>
<insert id="insert" parameterType="io.metersphere.base.domain.FileMetadata"> <insert id="insert" parameterType="io.metersphere.base.domain.FileMetadata">
insert into file_metadata (id, name, type, insert into file_metadata (id, name, type,
engine, create_time, update_time, create_time, update_time, size
size) )
values (#{id,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR}, #{type,jdbcType=VARCHAR}, values (#{id,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR}, #{type,jdbcType=VARCHAR},
#{engine,jdbcType=VARCHAR}, #{createTime,jdbcType=BIGINT}, #{updateTime,jdbcType=BIGINT}, #{createTime,jdbcType=BIGINT}, #{updateTime,jdbcType=BIGINT}, #{size,jdbcType=BIGINT}
#{size,jdbcType=BIGINT}) )
</insert> </insert>
<insert id="insertSelective" parameterType="io.metersphere.base.domain.FileMetadata"> <insert id="insertSelective" parameterType="io.metersphere.base.domain.FileMetadata">
insert into file_metadata insert into file_metadata
...@@ -121,9 +120,6 @@ ...@@ -121,9 +120,6 @@
<if test="type != null"> <if test="type != null">
type, type,
</if> </if>
<if test="engine != null">
engine,
</if>
<if test="createTime != null"> <if test="createTime != null">
create_time, create_time,
</if> </if>
...@@ -144,9 +140,6 @@ ...@@ -144,9 +140,6 @@
<if test="type != null"> <if test="type != null">
#{type,jdbcType=VARCHAR}, #{type,jdbcType=VARCHAR},
</if> </if>
<if test="engine != null">
#{engine,jdbcType=VARCHAR},
</if>
<if test="createTime != null"> <if test="createTime != null">
#{createTime,jdbcType=BIGINT}, #{createTime,jdbcType=BIGINT},
</if> </if>
...@@ -176,9 +169,6 @@ ...@@ -176,9 +169,6 @@
<if test="record.type != null"> <if test="record.type != null">
type = #{record.type,jdbcType=VARCHAR}, type = #{record.type,jdbcType=VARCHAR},
</if> </if>
<if test="record.engine != null">
engine = #{record.engine,jdbcType=VARCHAR},
</if>
<if test="record.createTime != null"> <if test="record.createTime != null">
create_time = #{record.createTime,jdbcType=BIGINT}, create_time = #{record.createTime,jdbcType=BIGINT},
</if> </if>
...@@ -198,7 +188,6 @@ ...@@ -198,7 +188,6 @@
set id = #{record.id,jdbcType=VARCHAR}, set id = #{record.id,jdbcType=VARCHAR},
name = #{record.name,jdbcType=VARCHAR}, name = #{record.name,jdbcType=VARCHAR},
type = #{record.type,jdbcType=VARCHAR}, type = #{record.type,jdbcType=VARCHAR},
engine = #{record.engine,jdbcType=VARCHAR},
create_time = #{record.createTime,jdbcType=BIGINT}, create_time = #{record.createTime,jdbcType=BIGINT},
update_time = #{record.updateTime,jdbcType=BIGINT}, update_time = #{record.updateTime,jdbcType=BIGINT},
size = #{record.size,jdbcType=BIGINT} size = #{record.size,jdbcType=BIGINT}
...@@ -215,9 +204,6 @@ ...@@ -215,9 +204,6 @@
<if test="type != null"> <if test="type != null">
type = #{type,jdbcType=VARCHAR}, type = #{type,jdbcType=VARCHAR},
</if> </if>
<if test="engine != null">
engine = #{engine,jdbcType=VARCHAR},
</if>
<if test="createTime != null"> <if test="createTime != null">
create_time = #{createTime,jdbcType=BIGINT}, create_time = #{createTime,jdbcType=BIGINT},
</if> </if>
...@@ -234,7 +220,6 @@ ...@@ -234,7 +220,6 @@
update file_metadata update file_metadata
set name = #{name,jdbcType=VARCHAR}, set name = #{name,jdbcType=VARCHAR},
type = #{type,jdbcType=VARCHAR}, type = #{type,jdbcType=VARCHAR},
engine = #{engine,jdbcType=VARCHAR},
create_time = #{createTime,jdbcType=BIGINT}, create_time = #{createTime,jdbcType=BIGINT},
update_time = #{updateTime,jdbcType=BIGINT}, update_time = #{updateTime,jdbcType=BIGINT},
size = #{size,jdbcType=BIGINT} size = #{size,jdbcType=BIGINT}
......
package io.metersphere.commons.constants;
public enum EngineType {
DOCKER, KUBERNETES
}
package io.metersphere.commons.constants; package io.metersphere.commons.constants;
public enum ResourceTypeEnum { public enum ResourcePoolTypeEnum {
/** /**
* k8s 资源池 * k8s 资源池
*/ */
......
...@@ -39,4 +39,13 @@ public class TestResourcePoolController { ...@@ -39,4 +39,13 @@ public class TestResourcePoolController {
Page<Object> page = PageHelper.startPage(goPage, pageSize, true); Page<Object> page = PageHelper.startPage(goPage, pageSize, true);
return PageUtils.setPageInfo(page, testResourcePoolService.listResourcePools(request)); return PageUtils.setPageInfo(page, testResourcePoolService.listResourcePools(request));
} }
@GetMapping("list/all")
public List<TestResourcePool> listResourcePools() {
PageHelper.startPage(1, 10000, true);
QueryResourcePoolRequest request = new QueryResourcePoolRequest();
return testResourcePoolService.listResourcePools(request);
}
} }
package io.metersphere.dto;
public class KubernetesDTO {
private String masterUrl;
private String token;
private Integer maxConcurrency;
private Boolean validate;
public String getMasterUrl() {
return masterUrl;
}
public void setMasterUrl(String masterUrl) {
this.masterUrl = masterUrl;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public Integer getMaxConcurrency() {
return maxConcurrency;
}
public void setMaxConcurrency(Integer maxConcurrency) {
this.maxConcurrency = maxConcurrency;
}
public Boolean getValidate() {
return validate;
}
public void setValidate(Boolean validate) {
this.validate = validate;
}
}
package io.metersphere.dto;
public class NodeDTO {
private String ip;
private Integer port;
private Integer maxConcurrency;
private Boolean validate;
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public Integer getPort() {
return port;
}
public void setPort(Integer port) {
this.port = port;
}
public Integer getMaxConcurrency() {
return maxConcurrency;
}
public void setMaxConcurrency(Integer maxConcurrency) {
this.maxConcurrency = maxConcurrency;
}
public Boolean getValidate() {
return validate;
}
public void setValidate(Boolean validate) {
this.validate = validate;
}
}
...@@ -7,7 +7,6 @@ public class EngineContext { ...@@ -7,7 +7,6 @@ public class EngineContext {
private String testId; private String testId;
private String testName; private String testName;
private String namespace; private String namespace;
private String engineType;
private String fileType; private String fileType;
private String content; private String content;
private Map<String, Object> properties = new HashMap<>(); private Map<String, Object> properties = new HashMap<>();
...@@ -37,14 +36,6 @@ public class EngineContext { ...@@ -37,14 +36,6 @@ public class EngineContext {
this.namespace = namespace; this.namespace = namespace;
} }
public String getEngineType() {
return engineType;
}
public void setEngineType(String engineType) {
this.engineType = engineType;
}
public void addProperty(String key, Object value) { public void addProperty(String key, Object value) {
this.properties.put(key, value); this.properties.put(key, value);
} }
......
...@@ -5,7 +5,8 @@ import com.alibaba.fastjson.JSONObject; ...@@ -5,7 +5,8 @@ import com.alibaba.fastjson.JSONObject;
import io.metersphere.base.domain.FileContent; import io.metersphere.base.domain.FileContent;
import io.metersphere.base.domain.FileMetadata; import io.metersphere.base.domain.FileMetadata;
import io.metersphere.base.domain.LoadTestWithBLOBs; import io.metersphere.base.domain.LoadTestWithBLOBs;
import io.metersphere.commons.constants.EngineType; import io.metersphere.base.domain.TestResourcePool;
import io.metersphere.commons.constants.ResourcePoolTypeEnum;
import io.metersphere.commons.exception.MSException; import io.metersphere.commons.exception.MSException;
import io.metersphere.engine.docker.DockerTestEngine; import io.metersphere.engine.docker.DockerTestEngine;
import io.metersphere.engine.kubernetes.KubernetesTestEngine; import io.metersphere.engine.kubernetes.KubernetesTestEngine;
...@@ -13,6 +14,7 @@ import io.metersphere.i18n.Translator; ...@@ -13,6 +14,7 @@ import io.metersphere.i18n.Translator;
import io.metersphere.parse.EngineSourceParser; import io.metersphere.parse.EngineSourceParser;
import io.metersphere.parse.EngineSourceParserFactory; import io.metersphere.parse.EngineSourceParserFactory;
import io.metersphere.service.FileService; import io.metersphere.service.FileService;
import io.metersphere.service.TestResourcePoolService;
import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
...@@ -26,14 +28,35 @@ import java.util.Map; ...@@ -26,14 +28,35 @@ import java.util.Map;
@Service @Service
public class EngineFactory { public class EngineFactory {
private static FileService fileService; private static FileService fileService;
private static TestResourcePoolService testResourcePoolService;
public static Engine createEngine(String engineType) { public static Engine createEngine(LoadTestWithBLOBs loadTest) {
final EngineType type = EngineType.valueOf(engineType); String resourcePoolId = null;
if (!StringUtils.isEmpty(loadTest.getLoadConfiguration())) {
final JSONArray jsonArray = JSONObject.parseArray(loadTest.getLoadConfiguration());
for (int i = 0; i < jsonArray.size(); i++) {
final JSONObject jsonObject = jsonArray.getJSONObject(i);
if (StringUtils.equals(jsonObject.getString("key"), "resourcePoolId")) {
resourcePoolId = jsonObject.getString("value");
break;
}
}
}
if (StringUtils.isBlank(resourcePoolId)) {
MSException.throwException("Resource Pool ID is empty.");
}
TestResourcePool resourcePool = testResourcePoolService.getResourcePool(resourcePoolId);
if (resourcePool == null) {
MSException.throwException("Resource Pool is empty.");
}
final ResourcePoolTypeEnum type = ResourcePoolTypeEnum.valueOf(resourcePool.getType());
switch (type) { switch (type) {
case DOCKER: case NODE:
return new DockerTestEngine(); return new DockerTestEngine();
case KUBERNETES: case K8S:
return new KubernetesTestEngine(); return new KubernetesTestEngine();
} }
return null; return null;
...@@ -48,7 +71,6 @@ public class EngineFactory { ...@@ -48,7 +71,6 @@ public class EngineFactory {
engineContext.setTestId(loadTest.getId()); engineContext.setTestId(loadTest.getId());
engineContext.setTestName(loadTest.getName()); engineContext.setTestName(loadTest.getName());
engineContext.setNamespace(loadTest.getProjectId()); engineContext.setNamespace(loadTest.getProjectId());
engineContext.setEngineType(fileMetadata.getEngine());
engineContext.setFileType(fileMetadata.getType()); engineContext.setFileType(fileMetadata.getType());
if (!StringUtils.isEmpty(loadTest.getLoadConfiguration())) { if (!StringUtils.isEmpty(loadTest.getLoadConfiguration())) {
...@@ -86,4 +108,9 @@ public class EngineFactory { ...@@ -86,4 +108,9 @@ public class EngineFactory {
private void setFileService(FileService fileService) { private void setFileService(FileService fileService) {
EngineFactory.fileService = fileService; EngineFactory.fileService = fileService;
} }
@Resource
public void setTestResourcePoolService(TestResourcePoolService testResourcePoolService) {
EngineFactory.testResourcePoolService = testResourcePoolService;
}
} }
package io.metersphere.engine.docker; package io.metersphere.engine.docker;
import io.metersphere.commons.exception.MSException; import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.controller.request.TestRequest; import io.metersphere.controller.request.TestRequest;
import io.metersphere.engine.Engine; import io.metersphere.engine.Engine;
import io.metersphere.engine.EngineContext; import io.metersphere.engine.EngineContext;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
public class DockerTestEngine implements Engine { public class DockerTestEngine implements Engine {
private EngineContext context; private EngineContext context;
RestTemplate restTemplate;
@Override @Override
public boolean init(EngineContext context) { public boolean init(EngineContext context) {
this.restTemplate = CommonBeanFactory.getBean(RestTemplate.class);
// todo 初始化操作 // todo 初始化操作
this.context = context; this.context = context;
return true; return true;
...@@ -22,8 +25,8 @@ public class DockerTestEngine implements Engine { ...@@ -22,8 +25,8 @@ public class DockerTestEngine implements Engine {
@Override @Override
public void start() { public void start() {
RestTemplate restTemplate = new RestTemplate(); // todo 运行测试
// RestTemplate restTemplate = new RestTemplate();
String testId = context.getTestId(); String testId = context.getTestId();
String content = context.getContent(); String content = context.getContent();
...@@ -34,6 +37,7 @@ public class DockerTestEngine implements Engine { ...@@ -34,6 +37,7 @@ public class DockerTestEngine implements Engine {
testRequest.setTestId(testId); testRequest.setTestId(testId);
testRequest.setFileString(content); testRequest.setFileString(content);
// todo 判断测试状态
String taskStatusUri = "http://localhost:8082/jmeter/task/status/" + testId; String taskStatusUri = "http://localhost:8082/jmeter/task/status/" + testId;
List containerList = restTemplate.getForObject(taskStatusUri, List.class); List containerList = restTemplate.getForObject(taskStatusUri, List.class);
for (int i = 0; i < containerList.size(); i++) { for (int i = 0; i < containerList.size(); i++) {
...@@ -48,12 +52,13 @@ public class DockerTestEngine implements Engine { ...@@ -48,12 +52,13 @@ public class DockerTestEngine implements Engine {
@Override @Override
public void stop() { public void stop() {
RestTemplate restTemplate = new RestTemplate(); // TODO 停止运行测试
// RestTemplate restTemplate = new RestTemplate();
String testId = context.getTestId(); String testId = context.getTestId();
String uri = "http://localhost:8082/jmeter/container/stop" + testId; String uri = "http://localhost:8082/jmeter/container/stop/" + testId;
restTemplate.getForObject(uri, String.class); restTemplate.postForObject(uri, "", String.class);
} }
} }
...@@ -4,6 +4,7 @@ import com.alibaba.fastjson.JSON; ...@@ -4,6 +4,7 @@ import com.alibaba.fastjson.JSON;
import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.api.model.ObjectMeta;
import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.KubernetesClient;
import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.commons.utils.LogUtil; import io.metersphere.commons.utils.LogUtil;
import io.metersphere.engine.Engine; import io.metersphere.engine.Engine;
import io.metersphere.engine.EngineContext; import io.metersphere.engine.EngineContext;
...@@ -11,17 +12,20 @@ import io.metersphere.engine.kubernetes.crds.jmeter.Jmeter; ...@@ -11,17 +12,20 @@ import io.metersphere.engine.kubernetes.crds.jmeter.Jmeter;
import io.metersphere.engine.kubernetes.crds.jmeter.JmeterSpec; import io.metersphere.engine.kubernetes.crds.jmeter.JmeterSpec;
import io.metersphere.engine.kubernetes.provider.ClientCredential; import io.metersphere.engine.kubernetes.provider.ClientCredential;
import io.metersphere.engine.kubernetes.provider.KubernetesProvider; import io.metersphere.engine.kubernetes.provider.KubernetesProvider;
import io.metersphere.service.TestResourcePoolService;
import org.apache.commons.collections.MapUtils; import org.apache.commons.collections.MapUtils;
import java.util.HashMap; import java.util.HashMap;
public class KubernetesTestEngine implements Engine { public class KubernetesTestEngine implements Engine {
private EngineContext context; private EngineContext context;
private TestResourcePoolService testResourcePoolService;
@Override @Override
public boolean init(EngineContext context) { public boolean init(EngineContext context) {
// todo 初始化操作 // todo 初始化操作
this.context = context; this.context = context;
this.testResourcePoolService = CommonBeanFactory.getBean(TestResourcePoolService.class);
return true; return true;
} }
......
...@@ -233,9 +233,11 @@ public class JmeterDocumentParser implements DocumentParser { ...@@ -233,9 +233,11 @@ public class JmeterDocumentParser implements DocumentParser {
collectionProp.appendChild(createKafkaProp(document, "kafka.batch.size", kafkaProperties.getBatchSize())); collectionProp.appendChild(createKafkaProp(document, "kafka.batch.size", kafkaProperties.getBatchSize()));
collectionProp.appendChild(createKafkaProp(document, "kafka.client.id", kafkaProperties.getClientId())); collectionProp.appendChild(createKafkaProp(document, "kafka.client.id", kafkaProperties.getClientId()));
collectionProp.appendChild(createKafkaProp(document, "kafka.connections.max.idle.ms", kafkaProperties.getConnectionsMaxIdleMs())); collectionProp.appendChild(createKafkaProp(document, "kafka.connections.max.idle.ms", kafkaProperties.getConnectionsMaxIdleMs()));
// 添加关联关系 test.id test.name // 添加关联关系 test.id test.name test.startTime test.size
collectionProp.appendChild(createKafkaProp(document, "test.id", context.getTestId())); collectionProp.appendChild(createKafkaProp(document, "test.id", context.getTestId()));
collectionProp.appendChild(createKafkaProp(document, "test.name", context.getTestName())); collectionProp.appendChild(createKafkaProp(document, "test.name", context.getTestName()));
collectionProp.appendChild(createKafkaProp(document, "test.startTime", "" + System.currentTimeMillis()));
collectionProp.appendChild(createKafkaProp(document, "test.size", "1"));
elementProp.appendChild(collectionProp); elementProp.appendChild(collectionProp);
// set elementProp // set elementProp
......
package io.metersphere.report;
import com.alibaba.fastjson.JSONObject;
import com.opencsv.bean.CsvToBean;
import com.opencsv.bean.CsvToBeanBuilder;
import com.opencsv.bean.HeaderColumnNameMappingStrategy;
import io.metersphere.report.base.Metric;
import io.metersphere.report.base.RequestStatistics;
import java.io.Reader;
import java.io.StringReader;
import java.util.*;
import java.util.stream.Collectors;
public class JtlResolver {
private List<Metric> resolver(String jtlString) {
HeaderColumnNameMappingStrategy<Metric> ms = new HeaderColumnNameMappingStrategy<>();
ms.setType(Metric.class);
try (Reader reader = new StringReader(jtlString)) {
CsvToBean<Metric> cb = new CsvToBeanBuilder<Metric>(reader)
.withType(Metric.class)
.withSkipLines(0)
.withMappingStrategy(ms)
.withIgnoreLeadingWhiteSpace(true)
.build();
return cb.parse();
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
private List<RequestStatistics> getOneRpsResult(Map<String, List<Metric>> map){
List<RequestStatistics> requestStatisticsList = new ArrayList<>();
Iterator<Map.Entry<String, List<Metric>>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, List<Metric>> entry = iterator.next();
String label = entry.getKey();
List<Metric> list = entry.getValue();
List<String> timestampList = list.stream().map(Metric::getTimestamp).collect(Collectors.toList());
int index=0;
//总的响应时间
int sumElapsed=0;
Integer failSize = 0;
Integer totalBytes = 0;
List<Integer> elapsedList = new ArrayList<Integer>();
for (int i = 0; i < list.size(); i++) {
try {
Metric row = list.get(i);
//响应时间
String elapsed = row.getElapsed();
sumElapsed += Integer.valueOf(elapsed);
elapsedList.add(Integer.valueOf(elapsed));
//成功与否
String success = row.getSuccess();
if (!"true".equals(success)){
failSize++;
}
//字节
String bytes = row.getBytes();
totalBytes += Integer.valueOf(bytes);
index++;
}catch (Exception e){
System.out.println("exception i:"+i);
}
}
Collections.sort(elapsedList, new Comparator<Integer>() {
public int compare(Integer o1, Integer o2) {
return o1-o2;
}
});
Integer tp90 = elapsedList.size()*9/10;
Integer tp95 = elapsedList.size()*95/100;
Integer tp99 = elapsedList.size()*99/100;
Long l = Long.valueOf(timestampList.get(index-1)) - Long.valueOf(timestampList.get(0));
RequestStatistics requestStatistics = new RequestStatistics();
requestStatistics.setRequestLabel(label);
requestStatistics.setSamples(index+"");
requestStatistics.setAverage(sumElapsed/index+"");
/**
* TP90的计算
* 1,把一段时间内全部的请求的响应时间,从小到大排序,获得序列A
* 2,总的请求数量,乘以90%,获得90%对应的请求个数C
* 3,从序列A中找到第C个请求,它的响应时间,即为TP90的值
* 其余相似的指标还有TP95, TP99
*/
requestStatistics.setTp90(elapsedList.get(tp90)+"");
requestStatistics.setTp95(elapsedList.get(tp95)+"");
requestStatistics.setTp99(elapsedList.get(tp99)+"");
requestStatistics.setMin(elapsedList.get(0)+"");
requestStatistics.setMax(elapsedList.get(index-1)+"");
requestStatistics.setErrors(String.format("%.2f",failSize*100.0/index)+"%");
requestStatistics.setKbPerSec(String.format("%.2f",totalBytes*1.0/1024/(l*1.0/1000)));
requestStatisticsList.add(requestStatistics);
}
return requestStatisticsList;
}
public List<RequestStatistics> getRequestStatistics(String jtlString) {
List<Metric> totalLines = resolver(jtlString);
Map<String, List<Metric>> map = totalLines.stream().collect(Collectors.groupingBy(Metric::getLabel));
return getOneRpsResult(map);
}
}
\ No newline at end of file
package io.metersphere.report.base;
import com.opencsv.bean.CsvBindByName;
public class Metric {
// timestamp,elapsed,label,responseCode,responseMessage,threadName,dataType,success,failureMessage,bytes,sentBytes,grpThreads,allThreads,URL,Latency,IdleTime,Connect
@CsvBindByName(column = "timestamp") // 访问开始时间
private String timestamp;
@CsvBindByName(column = "elapsed") // 访问开始到结束的用时 - 响应时间
private String elapsed;
@CsvBindByName(column = "label") // 请求的标签
private String label;
@CsvBindByName(column = "responseCode") // 响应码
private String responseCode;
@CsvBindByName(column = "responseMessage") // 响应信息
private String responseMessage;
@CsvBindByName(column = "threadName") // 请求所属线程
private String threadName;
@CsvBindByName(column = "dataType") // 数据类型
private String dataType;
@CsvBindByName(column = "success") // 访问是否成功
private String success;
@CsvBindByName(column = "failureMessage") // 访问失败信息
private String failureMessage;
@CsvBindByName(column = "bytes") //
private String bytes;
@CsvBindByName(column = "sentBytes") //
private String sentBytes;
@CsvBindByName(column = "grpThreads") // 线程组
private String grpThreads;
@CsvBindByName(column = "allThreads") //
private String allThreads;
@CsvBindByName(column = "URL") //
private String url;
@CsvBindByName(column = "Latency") // 延时
private String latency;
@CsvBindByName(column = "IdleTime") // 闲置时间
private String idleTime;
@CsvBindByName(column = "Connect") //
private String connect;
public String getTimestamp() {
return timestamp;
}
public void setTimestamp(String timestamp) {
this.timestamp = timestamp;
}
public String getElapsed() {
return elapsed;
}
public void setElapsed(String elapsed) {
this.elapsed = elapsed;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public String getResponseCode() {
return responseCode;
}
public void setResponseCode(String responseCode) {
this.responseCode = responseCode;
}
public String getResponseMessage() {
return responseMessage;
}
public void setResponseMessage(String responseMessage) {
this.responseMessage = responseMessage;
}
public String getThreadName() {
return threadName;
}
public void setThreadName(String threadName) {
this.threadName = threadName;
}
public String getDataType() {
return dataType;
}
public void setDataType(String dataType) {
this.dataType = dataType;
}
public String getSuccess() {
return success;
}
public void setSuccess(String success) {
this.success = success;
}
public String getFailureMessage() {
return failureMessage;
}
public void setFailureMessage(String failureMessage) {
this.failureMessage = failureMessage;
}
public String getBytes() {
return bytes;
}
public void setBytes(String bytes) {
this.bytes = bytes;
}
public String getSentBytes() {
return sentBytes;
}
public void setSentBytes(String sentBytes) {
this.sentBytes = sentBytes;
}
public String getGrpThreads() {
return grpThreads;
}
public void setGrpThreads(String grpThreads) {
this.grpThreads = grpThreads;
}
public String getAllThreads() {
return allThreads;
}
public void setAllThreads(String allThreads) {
this.allThreads = allThreads;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getLatency() {
return latency;
}
public void setLatency(String latency) {
this.latency = latency;
}
public String getIdleTime() {
return idleTime;
}
public void setIdleTime(String idleTime) {
this.idleTime = idleTime;
}
public String getConnect() {
return connect;
}
public void setConnect(String connect) {
this.connect = connect;
}
}
package io.metersphere.report.base;
public class RequestStatistics {
/**请求标签*/
private String requestLabel;
/**压测请求数*/
private String samples;
/**平均响应时间*/
private String average;
/**平均点击率*/
private Double avgHits;
/**90% Line*/
private String tp90;
/**95% Line*/
private String tp95;
/**99% Line*/
private String tp99;
/**最小请求时间 Min Response Time /ms */
private String min;
/**最大请求时间 Max Response Time /ms */
private String max;
/**吞吐量 KB/sec*/
private String kbPerSec;
/**错误率 Error Percentage */
private String errors;
public String getRequestLabel() {
return requestLabel;
}
public void setRequestLabel(String requestLabel) {
this.requestLabel = requestLabel;
}
public String getSamples() {
return samples;
}
public void setSamples(String samples) {
this.samples = samples;
}
public String getAverage() {
return average;
}
public void setAverage(String average) {
this.average = average;
}
public Double getAvgHits() {
return avgHits;
}
public void setAvgHits(Double avgHits) {
this.avgHits = avgHits;
}
public String getTp90() {
return tp90;
}
public void setTp90(String tp90) {
this.tp90 = tp90;
}
public String getTp95() {
return tp95;
}
public void setTp95(String tp95) {
this.tp95 = tp95;
}
public String getTp99() {
return tp99;
}
public void setTp99(String tp99) {
this.tp99 = tp99;
}
public String getMin() {
return min;
}
public void setMin(String min) {
this.min = min;
}
public String getMax() {
return max;
}
public void setMax(String max) {
this.max = max;
}
public String getKbPerSec() {
return kbPerSec;
}
public void setKbPerSec(String kbPerSec) {
this.kbPerSec = kbPerSec;
}
public String getErrors() {
return errors;
}
public void setErrors(String errors) {
this.errors = errors;
}
}
...@@ -3,12 +3,9 @@ package io.metersphere.service; ...@@ -3,12 +3,9 @@ package io.metersphere.service;
import io.metersphere.base.domain.*; import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.*; import io.metersphere.base.mapper.*;
import io.metersphere.base.mapper.ext.ExtFunctionalTestMapper; import io.metersphere.base.mapper.ext.ExtFunctionalTestMapper;
import io.metersphere.commons.constants.EngineType;
import io.metersphere.commons.exception.MSException; import io.metersphere.commons.exception.MSException;
import io.metersphere.controller.request.testplan.*; import io.metersphere.controller.request.testplan.*;
import io.metersphere.dto.FunctionalTestDTO; import io.metersphere.dto.FunctionalTestDTO;
import io.metersphere.engine.Engine;
import io.metersphere.engine.EngineFactory;
import io.metersphere.i18n.Translator; import io.metersphere.i18n.Translator;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
...@@ -94,8 +91,6 @@ public class FuctionalTestService { ...@@ -94,8 +91,6 @@ public class FuctionalTestService {
fileMetadata.setCreateTime(System.currentTimeMillis()); fileMetadata.setCreateTime(System.currentTimeMillis());
fileMetadata.setUpdateTime(System.currentTimeMillis()); fileMetadata.setUpdateTime(System.currentTimeMillis());
fileMetadata.setType("jmx"); fileMetadata.setType("jmx");
// TODO engine 选择
fileMetadata.setEngine(EngineType.DOCKER.name());
fileMetadataMapper.insert(fileMetadata); fileMetadataMapper.insert(fileMetadata);
FileContent fileContent = new FileContent(); FileContent fileContent = new FileContent();
......
...@@ -6,7 +6,6 @@ import io.metersphere.base.mapper.FileMetadataMapper; ...@@ -6,7 +6,6 @@ import io.metersphere.base.mapper.FileMetadataMapper;
import io.metersphere.base.mapper.LoadTestFileMapper; import io.metersphere.base.mapper.LoadTestFileMapper;
import io.metersphere.base.mapper.LoadTestMapper; import io.metersphere.base.mapper.LoadTestMapper;
import io.metersphere.base.mapper.ext.ExtLoadTestMapper; import io.metersphere.base.mapper.ext.ExtLoadTestMapper;
import io.metersphere.commons.constants.EngineType;
import io.metersphere.commons.constants.FileType; import io.metersphere.commons.constants.FileType;
import io.metersphere.commons.constants.TestStatus; import io.metersphere.commons.constants.TestStatus;
import io.metersphere.commons.exception.MSException; import io.metersphere.commons.exception.MSException;
...@@ -45,6 +44,8 @@ public class LoadTestService { ...@@ -45,6 +44,8 @@ public class LoadTestService {
private LoadTestFileMapper loadTestFileMapper; private LoadTestFileMapper loadTestFileMapper;
@Resource @Resource
private FileService fileService; private FileService fileService;
@Resource
private TestResourcePoolService testResourcePoolService;
public List<LoadTestDTO> list(QueryTestPlanRequest request) { public List<LoadTestDTO> list(QueryTestPlanRequest request) {
return extLoadTestMapper.list(request); return extLoadTestMapper.list(request);
...@@ -102,8 +103,6 @@ public class LoadTestService { ...@@ -102,8 +103,6 @@ public class LoadTestService {
fileMetadata.setUpdateTime(System.currentTimeMillis()); fileMetadata.setUpdateTime(System.currentTimeMillis());
FileType fileType = getFileType(fileMetadata.getName()); FileType fileType = getFileType(fileMetadata.getName());
fileMetadata.setType(fileType.name()); fileMetadata.setType(fileType.name());
// TODO engine 选择
fileMetadata.setEngine(EngineType.DOCKER.name());
fileMetadataMapper.insert(fileMetadata); fileMetadataMapper.insert(fileMetadata);
FileContent fileContent = new FileContent(); FileContent fileContent = new FileContent();
...@@ -179,9 +178,8 @@ public class LoadTestService { ...@@ -179,9 +178,8 @@ public class LoadTestService {
List<FileMetadata> csvFiles = fileMetadataList.stream().filter(f -> StringUtils.equalsIgnoreCase(f.getType(), FileType.CSV.name())).collect(Collectors.toList()); List<FileMetadata> csvFiles = fileMetadataList.stream().filter(f -> StringUtils.equalsIgnoreCase(f.getType(), FileType.CSV.name())).collect(Collectors.toList());
LogUtil.info("Load test started " + loadTest.getName()); LogUtil.info("Load test started " + loadTest.getName());
// engine type (DOCKER|KUBERNETES) // engine type (NODE|K8S)
// todo set type final Engine engine = EngineFactory.createEngine(loadTest);
final Engine engine = EngineFactory.createEngine(fileMetadata.getEngine());
if (engine == null) { if (engine == null) {
MSException.throwException(String.format("Test cannot be run,test ID:%s,file type:%s", request.getId(), fileMetadata.getType())); MSException.throwException(String.format("Test cannot be run,test ID:%s,file type:%s", request.getId(), fileMetadata.getType()));
} }
......
...@@ -41,4 +41,8 @@ public class ReportService { ...@@ -41,4 +41,8 @@ public class ReportService {
public ReportDTO getReportTestAndProInfo(String reportId) { public ReportDTO getReportTestAndProInfo(String reportId) {
return extLoadTestReportMapper.getReportTestAndProInfo(reportId); return extLoadTestReportMapper.getReportTestAndProInfo(reportId);
} }
public LoadTestReport getReport(String id) {
return loadTestReportMapper.selectByPrimaryKey(id);
}
} }
package io.metersphere.service; package io.metersphere.service;
import com.alibaba.fastjson.JSON;
import io.metersphere.base.domain.TestResourcePool; import io.metersphere.base.domain.TestResourcePool;
import io.metersphere.base.domain.TestResourcePoolExample; import io.metersphere.base.domain.TestResourcePoolExample;
import io.metersphere.base.mapper.TestResourcePoolMapper; import io.metersphere.base.mapper.TestResourcePoolMapper;
import io.metersphere.commons.constants.ResourcePoolTypeEnum;
import io.metersphere.commons.utils.BeanUtils;
import io.metersphere.controller.request.resourcepool.QueryResourcePoolRequest; import io.metersphere.controller.request.resourcepool.QueryResourcePoolRequest;
import io.metersphere.dto.KubernetesDTO;
import io.metersphere.dto.NodeDTO;
import io.metersphere.engine.kubernetes.provider.ClientCredential;
import io.metersphere.engine.kubernetes.provider.KubernetesProvider;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.List; import java.util.List;
...@@ -19,6 +30,8 @@ import java.util.UUID; ...@@ -19,6 +30,8 @@ import java.util.UUID;
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public class TestResourcePoolService { public class TestResourcePoolService {
private final static String nodeControllerUrl = "%s:%s/status";
@Resource @Resource
private TestResourcePoolMapper testResourcePoolMapper; private TestResourcePoolMapper testResourcePoolMapper;
...@@ -27,6 +40,7 @@ public class TestResourcePoolService { ...@@ -27,6 +40,7 @@ public class TestResourcePoolService {
testResourcePool.setCreateTime(System.currentTimeMillis()); testResourcePool.setCreateTime(System.currentTimeMillis());
testResourcePool.setUpdateTime(System.currentTimeMillis()); testResourcePool.setUpdateTime(System.currentTimeMillis());
testResourcePool.setStatus("1"); testResourcePool.setStatus("1");
validateTestResourcePool(testResourcePool);
testResourcePoolMapper.insertSelective(testResourcePool); testResourcePoolMapper.insertSelective(testResourcePool);
return testResourcePool; return testResourcePool;
} }
...@@ -37,6 +51,7 @@ public class TestResourcePoolService { ...@@ -37,6 +51,7 @@ public class TestResourcePoolService {
public void updateTestResourcePool(TestResourcePool testResourcePool) { public void updateTestResourcePool(TestResourcePool testResourcePool) {
testResourcePool.setUpdateTime(System.currentTimeMillis()); testResourcePool.setUpdateTime(System.currentTimeMillis());
validateTestResourcePool(testResourcePool);
testResourcePoolMapper.updateByPrimaryKeySelective(testResourcePool); testResourcePoolMapper.updateByPrimaryKeySelective(testResourcePool);
} }
...@@ -47,4 +62,59 @@ public class TestResourcePoolService { ...@@ -47,4 +62,59 @@ public class TestResourcePoolService {
} }
return testResourcePoolMapper.selectByExample(example); return testResourcePoolMapper.selectByExample(example);
} }
private void validateTestResourcePool(TestResourcePool testResourcePool) {
if (StringUtils.equalsIgnoreCase(testResourcePool.getType(), ResourcePoolTypeEnum.K8S.name())) {
validateK8s(testResourcePool);
return;
}
validateNodes(testResourcePool);
}
private void validateNodes(TestResourcePool testResourcePool) {
List<NodeDTO> nodes = JSON.parseArray(testResourcePool.getInfo(), NodeDTO.class);
if (CollectionUtils.isEmpty(nodes)) {
throw new RuntimeException("没有节点信息");
}
for (NodeDTO node : nodes) {
boolean isValidate = validateNode(node);
if (!isValidate) {
testResourcePool.setStatus("0");
}
node.setValidate(isValidate);
}
testResourcePool.setInfo(JSON.toJSONString(nodes));
}
private boolean validateNode(NodeDTO dto) {
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> entity = restTemplate.getForEntity(String.format(nodeControllerUrl, dto.getIp(), dto.getPort()), String.class);
return entity.getStatusCode().value() == HttpStatus.SC_OK;
}
private void validateK8s(TestResourcePool testResourcePool) {
List<KubernetesDTO> dtos = JSON.parseArray(testResourcePool.getInfo(), KubernetesDTO.class);
if (CollectionUtils.isEmpty(dtos) || dtos.size() != 1) {
throw new RuntimeException("只能添加一个 K8s");
}
ClientCredential clientCredential = new ClientCredential();
BeanUtils.copyBean(clientCredential, dtos.get(0));
try {
KubernetesProvider provider = new KubernetesProvider(JSON.toJSONString(clientCredential));
provider.validateCredential();
dtos.get(0).setValidate(true);
} catch (Exception e) {
dtos.get(0).setValidate(false);
testResourcePool.setStatus("0");
}
testResourcePool.setInfo(JSON.toJSONString(dtos));
}
public TestResourcePool getResourcePool(String resourcePoolId) {
return testResourcePoolMapper.selectByPrimaryKey(resourcePoolId);
}
} }
...@@ -11,7 +11,6 @@ CREATE TABLE IF NOT EXISTS `file_metadata` ( ...@@ -11,7 +11,6 @@ CREATE TABLE IF NOT EXISTS `file_metadata` (
`id` varchar(64) NOT NULL COMMENT 'File ID', `id` varchar(64) NOT NULL COMMENT 'File ID',
`name` varchar(64) NOT NULL COMMENT 'File name', `name` varchar(64) NOT NULL COMMENT 'File name',
`type` varchar(64) DEFAULT NULL COMMENT 'File type', `type` varchar(64) DEFAULT NULL COMMENT 'File type',
`engine` varchar(64) DEFAULT 'DOCKER' COMMENT 'engine type',
`size` bigint(13) NOT NULL COMMENT 'File size', `size` bigint(13) NOT NULL COMMENT 'File size',
`create_time` bigint(13) NOT NULL COMMENT 'Create timestamp', `create_time` bigint(13) NOT NULL COMMENT 'Create timestamp',
`update_time` bigint(13) NOT NULL COMMENT 'Update timestamp', `update_time` bigint(13) NOT NULL COMMENT 'Update timestamp',
......
...@@ -45,10 +45,20 @@ ...@@ -45,10 +45,20 @@
</javaClientGenerator> </javaClientGenerator>
<!--要生成的数据库表 --> <!--要生成的数据库表 -->
<table tableName="test_plan"/>
<table tableName="test_case_node"/>
<table tableName="test_case"/>
<table tableName="test_plan_test_case"/>
<!-- <table tableName="user"/>-->
<!-- <table tableName="user_role"/>-->
<!-- <table tableName="workspace"/>-->
<!-- <table tableName="test_resource_pool"/>-->
<!-- <table tableName="test_resource"/>-->
<!-- <table tableName="system_parameter"/>-->
<!-- <table tableName="role"/>-->
<!-- <table tableName="project"/>-->
<!-- <table tableName="organization"/>-->
<!-- <table tableName="load_test_report"/>-->
<!-- <table tableName="load_test"/>-->
<!-- <table tableName="file_content"/>-->
<table tableName="file_metadata"/>
<!-- <table tableName="load_test_file"/>-->
</context> </context>
</generatorConfiguration> </generatorConfiguration>
\ No newline at end of file
package io.metersphere; package io.metersphere;
import io.metersphere.commons.constants.JmeterReportType; import com.opencsv.bean.CsvToBean;
import kg.apc.jmeter.PluginsCMDWorker; import com.opencsv.bean.CsvToBeanBuilder;
import org.apache.jmeter.util.JMeterUtils; import com.opencsv.bean.HeaderColumnNameMappingStrategy;
import com.opencsv.bean.MappingStrategy;
import org.junit.Test; import org.junit.Test;
import java.io.File;
import java.io.Reader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
public class GenerateGraphTest { public class GenerateGraphTest {
/*
AggregateReport = JMeter's native Aggregate Report, can be saved only as CSV
SynthesisReport = mix between JMeter's native Summary Report and Aggregate Report, can be saved only as CSV
ThreadsStateOverTime = Active Threads Over Time
BytesThroughputOverTime
HitsPerSecond
LatenciesOverTime
PerfMon = PerfMon Metrics Collector
DbMon = DbMon Metrics Collector, DataBase, get performance counters via sql
JMXMon = JMXMon Metrics Collector, Java Management Extensions counters
ResponseCodesPerSecond
ResponseTimesDistribution
ResponseTimesOverTime
ResponseTimesPercentiles
ThroughputVsThreads
TimesVsThreads = Response Times VS Threads
TransactionsPerSecond
PageDataExtractorOverTime
MergeResults = MergeResults Command Line Merge Tool to simplify the comparison of two or more load tests, need properties file (like merge-results.properties)
*/
@Test @Test
public void test1() { public void test1() {
JMeterUtils.setJMeterHome("/opt/fit2cloud/apache-jmeter-5.2.1"); File csvFile = new File("/Users/liuruibin/Desktop/0316.jtl");
JMeterUtils.loadJMeterProperties("/opt/fit2cloud/apache-jmeter-5.2.1/bin/jmeter.properties"); HeaderColumnNameMappingStrategy<Metric> ms = new HeaderColumnNameMappingStrategy<>();
PluginsCMDWorker worker = new PluginsCMDWorker(); ms.setType(Metric.class);
worker.setPluginType(JmeterReportType.AggregateReport.name()); List<Metric> metrics = beanBuilderExample(csvFile.toPath(), ms);
worker.addExportMode(2); metrics.forEach(c -> {
worker.setOutputCSVFile("/tmp/test0320.csv"); System.out.println(c.getTimestamp());
worker.setInputFile("/Users/liuruibin/Desktop/0316.jtl"); });
worker.doJob(); }
public static List<Metric> beanBuilderExample(Path path, MappingStrategy<Metric> ms) {
try (Reader reader = Files.newBufferedReader(path)) {
CsvToBean<Metric> cb = new CsvToBeanBuilder<Metric>(reader)
.withType(Metric.class)
.withSkipLines(0)
.withMappingStrategy(ms)
.withIgnoreLeadingWhiteSpace(true)
.build();
return cb.parse();
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
} }
} }
此差异已折叠。
package io.metersphere;
import com.opencsv.bean.CsvBindByName;
public class Metric {
// timestamp,elapsed,label,responseCode,responseMessage,threadName,dataType,success,failureMessage,bytes,sentBytes,grpThreads,allThreads,URL,Latency,IdleTime,Connect
@CsvBindByName(column = "timestamp")
private String timestamp;
@CsvBindByName(column = "elapsed")
private String elapsed;
@CsvBindByName(column = "label")
private String label;
@CsvBindByName(column = "responseCode")
private String responseCode;
@CsvBindByName(column = "responseMessage")
private String responseMessage;
@CsvBindByName(column = "threadName")
private String threadName;
@CsvBindByName(column = "dataType")
private String dataType;
@CsvBindByName(column = "success")
private String success;
@CsvBindByName(column = "failureMessage")
private String failureMessage;
@CsvBindByName(column = "bytes")
private String bytes;
@CsvBindByName(column = "sentBytes")
private String sentBytes;
@CsvBindByName(column = "grpThreads")
private String grpThreads;
@CsvBindByName(column = "allThreads")
private String allThreads;
@CsvBindByName(column = "URL")
private String url;
@CsvBindByName(column = "Latency")
private String latency;
@CsvBindByName(column = "IdleTime")
private String idleTime;
@CsvBindByName(column = "Connect")
private String connect;
public String getTimestamp() {
return timestamp;
}
public void setTimestamp(String timestamp) {
this.timestamp = timestamp;
}
public String getElapsed() {
return elapsed;
}
public void setElapsed(String elapsed) {
this.elapsed = elapsed;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public String getResponseCode() {
return responseCode;
}
public void setResponseCode(String responseCode) {
this.responseCode = responseCode;
}
public String getResponseMessage() {
return responseMessage;
}
public void setResponseMessage(String responseMessage) {
this.responseMessage = responseMessage;
}
public String getThreadName() {
return threadName;
}
public void setThreadName(String threadName) {
this.threadName = threadName;
}
public String getDataType() {
return dataType;
}
public void setDataType(String dataType) {
this.dataType = dataType;
}
public String getSuccess() {
return success;
}
public void setSuccess(String success) {
this.success = success;
}
public String getFailureMessage() {
return failureMessage;
}
public void setFailureMessage(String failureMessage) {
this.failureMessage = failureMessage;
}
public String getBytes() {
return bytes;
}
public void setBytes(String bytes) {
this.bytes = bytes;
}
public String getSentBytes() {
return sentBytes;
}
public void setSentBytes(String sentBytes) {
this.sentBytes = sentBytes;
}
public String getGrpThreads() {
return grpThreads;
}
public void setGrpThreads(String grpThreads) {
this.grpThreads = grpThreads;
}
public String getAllThreads() {
return allThreads;
}
public void setAllThreads(String allThreads) {
this.allThreads = allThreads;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getLatency() {
return latency;
}
public void setLatency(String latency) {
this.latency = latency;
}
public String getIdleTime() {
return idleTime;
}
public void setIdleTime(String idleTime) {
this.idleTime = idleTime;
}
public String getConnect() {
return connect;
}
public void setConnect(String connect) {
this.connect = connect;
}
}
...@@ -207,6 +207,10 @@ ...@@ -207,6 +207,10 @@
return false; return false;
} }
if (!this.$refs.pressureConfig.validConfig()) {
return false;
}
if (!this.$refs.advancedConfig.validConfig()) { if (!this.$refs.advancedConfig.validConfig()) {
return false; return false;
} }
......
...@@ -73,6 +73,21 @@ ...@@ -73,6 +73,21 @@
<div>{{$t('load_test.ramp_up_time_times')}}</div> <div>{{$t('load_test.ramp_up_time_times')}}</div>
</el-form-item> </el-form-item>
</el-form> </el-form>
<el-form :inline="true" class="input-bottom-border">
<el-form-item>
<div>{{$t('load_test.select_resource_pool')}}</div>
</el-form-item>
<el-form-item>
<el-select v-model="resourcePool" size="mini">
<el-option
v-for="item in resourcePools"
:key="item.id"
:label="item.name"
:value="item.id">
</el-option>
</el-select>
</el-form-item>
</el-form>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<chart class="chart-container" ref="chart1" :options="orgOptions" :autoresize="true"></chart> <chart class="chart-container" ref="chart1" :options="orgOptions" :autoresize="true"></chart>
...@@ -89,6 +104,7 @@ ...@@ -89,6 +104,7 @@
const STEPS = "Steps"; const STEPS = "Steps";
const DURATION = "duration"; const DURATION = "duration";
const RPS_LIMIT = "rpsLimit"; const RPS_LIMIT = "rpsLimit";
const RESOURCE_POOL = "resourcePoolId";
export default { export default {
name: "PerformancePressureConfig", name: "PerformancePressureConfig",
...@@ -101,6 +117,8 @@ ...@@ -101,6 +117,8 @@
step: 10, step: 10,
rpsLimit: 10, rpsLimit: 10,
orgOptions: {}, orgOptions: {},
resourcePool: null,
resourcePools: [],
} }
}, },
mounted() { mounted() {
...@@ -110,6 +128,8 @@ ...@@ -110,6 +128,8 @@
} else { } else {
this.calculateChart(); this.calculateChart();
} }
this.getResourcePools();
}, },
watch: { watch: {
'$route'(to, from) { '$route'(to, from) {
...@@ -125,11 +145,16 @@ ...@@ -125,11 +145,16 @@
} }
}, },
methods: { methods: {
getResourcePools() {
this.$get('/testresourcepool/list/all', response => {
this.resourcePools = response.data;
})
},
getLoadConfig(testId) { getLoadConfig(testId) {
if (testId) { if (testId) {
this.$get('/testplan/get-load-config/' + testId, (response) => { this.$get('/testplan/get-load-config/' + testId, (response) => {
if (response.data && response.data != "") { if (response.data) {
let data = JSON.parse(response.data); let data = JSON.parse(response.data);
data.forEach(d => { data.forEach(d => {
...@@ -149,6 +174,9 @@ ...@@ -149,6 +174,9 @@
case RPS_LIMIT: case RPS_LIMIT:
this.rpsLimit = d.value; this.rpsLimit = d.value;
break; break;
case RESOURCE_POOL:
this.resourcePool = d.value;
break;
default: default:
break; break;
} }
...@@ -252,6 +280,17 @@ ...@@ -252,6 +280,17 @@
} }
} }
}, },
validConfig() {
if (!this.resourcePool) {
this.$message({
message: this.$t('load_test.resource_pool_is_null'),
type: 'warning'
});
return false;
}
return true;
},
convertProperty() { convertProperty() {
/// todo:下面4个属性是jmeter ConcurrencyThreadGroup plugin的属性,这种硬编码不太好吧,在哪能转换这种属性? /// todo:下面4个属性是jmeter ConcurrencyThreadGroup plugin的属性,这种硬编码不太好吧,在哪能转换这种属性?
return [ return [
...@@ -259,7 +298,8 @@ ...@@ -259,7 +298,8 @@
{key: RAMP_UP, value: this.rampUpTime}, {key: RAMP_UP, value: this.rampUpTime},
{key: STEPS, value: this.step}, {key: STEPS, value: this.step},
{key: DURATION, value: this.duration}, {key: DURATION, value: this.duration},
{key: RPS_LIMIT, value: this.rpsLimit} {key: RPS_LIMIT, value: this.rpsLimit},
{key: RESOURCE_POOL, value: this.resourcePool},
]; ];
} }
} }
......
...@@ -46,7 +46,7 @@ ...@@ -46,7 +46,7 @@
</el-card> </el-card>
<el-dialog :title="title" :visible.sync="createVisible"> <el-dialog :title="title" :visible.sync="createVisible">
<el-form :model="form" :rules="rules" ref="form" label-position="left" label-width="100px" size="small"> <el-form :model="form" :rules="rules" ref="form" label-position="right" label-width="100px" size="small">
<el-form-item :label="$t('commons.name')"> <el-form-item :label="$t('commons.name')">
<el-input v-model="form.name" autocomplete="off"></el-input> <el-input v-model="form.name" autocomplete="off"></el-input>
</el-form-item> </el-form-item>
......
...@@ -51,7 +51,7 @@ ...@@ -51,7 +51,7 @@
</el-card> </el-card>
<el-dialog :title="$t('member.create')" :visible.sync="createVisible" width="30%" :destroy-on-close="true" @close="closeFunc"> <el-dialog :title="$t('member.create')" :visible.sync="createVisible" width="30%" :destroy-on-close="true" @close="closeFunc">
<el-form :model="form" ref="form" :rules="rules" label-position="left" label-width="100px" size="small"> <el-form :model="form" ref="form" :rules="rules" label-position="right" label-width="100px" size="small">
<el-form-item :label="$t('commons.member')" prop="userIds"> <el-form-item :label="$t('commons.member')" prop="userIds">
<el-select v-model="form.userIds" multiple :placeholder="$t('member.please_choose_member')" class="select-width"> <el-select v-model="form.userIds" multiple :placeholder="$t('member.please_choose_member')" class="select-width">
<el-option <el-option
...@@ -81,7 +81,7 @@ ...@@ -81,7 +81,7 @@
</el-dialog> </el-dialog>
<el-dialog :title="$t('member.modify')" :visible.sync="updateVisible" width="30%" :destroy-on-close="true" @close="closeFunc"> <el-dialog :title="$t('member.modify')" :visible.sync="updateVisible" width="30%" :destroy-on-close="true" @close="closeFunc">
<el-form :model="form" label-position="left" label-width="100px" size="small" ref="updateUserForm"> <el-form :model="form" label-position="right" label-width="100px" size="small" ref="updateUserForm">
<el-form-item label="ID" prop="id"> <el-form-item label="ID" prop="id">
<el-input v-model="form.id" autocomplete="off" :disabled="true"/> <el-input v-model="form.id" autocomplete="off" :disabled="true"/>
</el-form-item> </el-form-item>
......
...@@ -48,7 +48,7 @@ ...@@ -48,7 +48,7 @@
</el-card> </el-card>
<el-dialog :title="$t('workspace.create')" :visible.sync="createVisible" width="30%"> <el-dialog :title="$t('workspace.create')" :visible.sync="createVisible" width="30%">
<el-form :model="form" :rules="rules" ref="form" label-position="left" label-width="100px" size="small"> <el-form :model="form" :rules="rules" ref="form" label-position="right" label-width="100px" size="small">
<el-form-item :label="$t('commons.name')" prop="name"> <el-form-item :label="$t('commons.name')" prop="name">
<el-input v-model="form.name" autocomplete="off"/> <el-input v-model="form.name" autocomplete="off"/>
</el-form-item> </el-form-item>
...@@ -114,7 +114,7 @@ ...@@ -114,7 +114,7 @@
<!-- add workspace member dialog --> <!-- add workspace member dialog -->
<el-dialog :title="$t('member.create')" :visible.sync="addMemberVisible" width="30%" :destroy-on-close="true" @close="closeFunc"> <el-dialog :title="$t('member.create')" :visible.sync="addMemberVisible" width="30%" :destroy-on-close="true" @close="closeFunc">
<el-form :model="memberForm" ref="form" :rules="wsMemberRule" label-position="left" label-width="100px" size="small"> <el-form :model="memberForm" ref="form" :rules="wsMemberRule" label-position="right" label-width="100px" size="small">
<el-form-item :label="$t('commons.member')" prop="userIds"> <el-form-item :label="$t('commons.member')" prop="userIds">
<el-select v-model="memberForm.userIds" multiple :placeholder="$t('member.please_choose_member')" class="select-width"> <el-select v-model="memberForm.userIds" multiple :placeholder="$t('member.please_choose_member')" class="select-width">
<el-option <el-option
...@@ -145,7 +145,7 @@ ...@@ -145,7 +145,7 @@
<!-- update workspace member dialog --> <!-- update workspace member dialog -->
<el-dialog :title="$t('member.modify')" :visible.sync="updateMemberVisible" width="30%" :destroy-on-close="true" @close="closeFunc"> <el-dialog :title="$t('member.modify')" :visible.sync="updateMemberVisible" width="30%" :destroy-on-close="true" @close="closeFunc">
<el-form :model="memberForm" label-position="left" label-width="100px" size="small" ref="updateUserForm"> <el-form :model="memberForm" label-position="right" label-width="100px" size="small" ref="updateUserForm">
<el-form-item label="ID" prop="id"> <el-form-item label="ID" prop="id">
<el-input v-model="memberForm.id" autocomplete="off" :disabled="true"/> <el-input v-model="memberForm.id" autocomplete="off" :disabled="true"/>
</el-form-item> </el-form-item>
......
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
</el-table> </el-table>
<el-dialog :title="$t('member.modify_personal_info')" :visible.sync="updateVisible" width="30%" :destroy-on-close="true" @close="closeFunc"> <el-dialog :title="$t('member.modify_personal_info')" :visible.sync="updateVisible" width="30%" :destroy-on-close="true" @close="closeFunc">
<el-form :model="form" label-position="left" label-width="100px" size="small" :rules="rule" ref="updateUserForm"> <el-form :model="form" label-position="right" label-width="100px" size="small" :rules="rule" ref="updateUserForm">
<el-form-item label="ID" prop="id"> <el-form-item label="ID" prop="id">
<el-input v-model="form.id" autocomplete="off" :disabled="true"/> <el-input v-model="form.id" autocomplete="off" :disabled="true"/>
</el-form-item> </el-form-item>
......
...@@ -101,7 +101,7 @@ ...@@ -101,7 +101,7 @@
<!-- add organization form --> <!-- add organization form -->
<el-dialog :title="$t('organization.create')" :visible.sync="createVisible" width="30%" @closed="closeFunc" :destroy-on-close="true"> <el-dialog :title="$t('organization.create')" :visible.sync="createVisible" width="30%" @closed="closeFunc" :destroy-on-close="true">
<el-form :model="form" label-position="left" label-width="100px" size="small" :rules="rule" ref="createOrganization"> <el-form :model="form" label-position="right" label-width="100px" size="small" :rules="rule" ref="createOrganization">
<el-form-item :label="$t('commons.name')" prop="name"> <el-form-item :label="$t('commons.name')" prop="name">
<el-input v-model="form.name" autocomplete="off"/> <el-input v-model="form.name" autocomplete="off"/>
</el-form-item> </el-form-item>
...@@ -116,7 +116,7 @@ ...@@ -116,7 +116,7 @@
<!-- update organization form --> <!-- update organization form -->
<el-dialog :title="$t('organization.modify')" :visible.sync="updateVisible" width="30%" :destroy-on-close="true" @close="closeFunc"> <el-dialog :title="$t('organization.modify')" :visible.sync="updateVisible" width="30%" :destroy-on-close="true" @close="closeFunc">
<el-form :model="form" label-position="left" label-width="100px" size="small" :rules="rule" ref="updateOrganizationForm"> <el-form :model="form" label-position="right" label-width="100px" size="small" :rules="rule" ref="updateOrganizationForm">
<el-form-item :label="$t('commons.name')" prop="name"> <el-form-item :label="$t('commons.name')" prop="name">
<el-input v-model="form.name" autocomplete="off"/> <el-input v-model="form.name" autocomplete="off"/>
</el-form-item> </el-form-item>
...@@ -131,7 +131,7 @@ ...@@ -131,7 +131,7 @@
<!-- add organization member form --> <!-- add organization member form -->
<el-dialog :title="$t('member.create')" :visible.sync="addMemberVisible" width="30%" :destroy-on-close="true" @close="closeFunc"> <el-dialog :title="$t('member.create')" :visible.sync="addMemberVisible" width="30%" :destroy-on-close="true" @close="closeFunc">
<el-form :model="memberForm" ref="form" :rules="orgMemberRule" label-position="left" label-width="100px" size="small"> <el-form :model="memberForm" ref="form" :rules="orgMemberRule" label-position="right" label-width="100px" size="small">
<el-form-item :label="$t('commons.member')" prop="userIds"> <el-form-item :label="$t('commons.member')" prop="userIds">
<el-select v-model="memberForm.userIds" multiple :placeholder="$t('member.please_choose_member')" class="select-width"> <el-select v-model="memberForm.userIds" multiple :placeholder="$t('member.please_choose_member')" class="select-width">
<el-option <el-option
...@@ -162,7 +162,7 @@ ...@@ -162,7 +162,7 @@
<!-- update organization member form --> <!-- update organization member form -->
<el-dialog :title="$t('member.modify')" :visible.sync="updateMemberVisible" width="30%" :destroy-on-close="true" @close="closeFunc"> <el-dialog :title="$t('member.modify')" :visible.sync="updateMemberVisible" width="30%" :destroy-on-close="true" @close="closeFunc">
<el-form :model="memberForm" label-position="left" label-width="100px" size="small" ref="updateUserForm"> <el-form :model="memberForm" label-position="right" label-width="100px" size="small" ref="updateUserForm">
<el-form-item label="ID" prop="id"> <el-form-item label="ID" prop="id">
<el-input v-model="memberForm.id" autocomplete="off" :disabled="true"/> <el-input v-model="memberForm.id" autocomplete="off" :disabled="true"/>
</el-form-item> </el-form-item>
......
...@@ -53,7 +53,7 @@ ...@@ -53,7 +53,7 @@
<!-- add workspace dialog --> <!-- add workspace dialog -->
<el-dialog :title="$t('workspace.create')" :visible.sync="createVisible" width="30%"> <el-dialog :title="$t('workspace.create')" :visible.sync="createVisible" width="30%">
<el-form :model="form" :rules="rules" ref="form" label-position="left" label-width="100px" size="small"> <el-form :model="form" :rules="rules" ref="form" label-position="right" label-width="100px" size="small">
<el-form-item :label="$t('commons.name')" prop="name"> <el-form-item :label="$t('commons.name')" prop="name">
<el-input v-model="form.name" autocomplete="off"/> <el-input v-model="form.name" autocomplete="off"/>
</el-form-item> </el-form-item>
...@@ -78,7 +78,7 @@ ...@@ -78,7 +78,7 @@
<!-- update workspace dialog --> <!-- update workspace dialog -->
<el-dialog :title="$t('workspace.update')" :visible.sync="updateVisible" width="30%"> <el-dialog :title="$t('workspace.update')" :visible.sync="updateVisible" width="30%">
<el-form :model="form" :rules="rules" ref="updateForm" label-position="left" label-width="100px" size="small"> <el-form :model="form" :rules="rules" ref="updateForm" label-position="right" label-width="100px" size="small">
<el-form-item :label="$t('commons.name')" prop="name"> <el-form-item :label="$t('commons.name')" prop="name">
<el-input v-model="form.name" autocomplete="off"/> <el-input v-model="form.name" autocomplete="off"/>
</el-form-item> </el-form-item>
...@@ -154,7 +154,7 @@ ...@@ -154,7 +154,7 @@
<!-- add workspace member dialog --> <!-- add workspace member dialog -->
<el-dialog :title="$t('member.create')" :visible.sync="addMemberVisible" width="30%" :destroy-on-close="true" @close="closeFunc"> <el-dialog :title="$t('member.create')" :visible.sync="addMemberVisible" width="30%" :destroy-on-close="true" @close="closeFunc">
<el-form :model="memberForm" ref="form" :rules="wsMemberRule" label-position="left" label-width="100px" size="small"> <el-form :model="memberForm" ref="form" :rules="wsMemberRule" label-position="right" label-width="100px" size="small">
<el-form-item :label="$t('commons.member')" prop="userIds"> <el-form-item :label="$t('commons.member')" prop="userIds">
<el-select v-model="memberForm.userIds" multiple :placeholder="$t('member.please_choose_member')" class="select-width"> <el-select v-model="memberForm.userIds" multiple :placeholder="$t('member.please_choose_member')" class="select-width">
<el-option <el-option
...@@ -185,7 +185,7 @@ ...@@ -185,7 +185,7 @@
<!-- update workspace member dialog --> <!-- update workspace member dialog -->
<el-dialog :title="$t('member.modify')" :visible.sync="updateMemberVisible" width="30%" :destroy-on-close="true" @close="closeFunc"> <el-dialog :title="$t('member.modify')" :visible.sync="updateMemberVisible" width="30%" :destroy-on-close="true" @close="closeFunc">
<el-form :model="memberForm" label-position="left" label-width="100px" size="small" ref="updateUserForm"> <el-form :model="memberForm" label-position="right" label-width="100px" size="small" ref="updateUserForm">
<el-form-item label="ID" prop="id"> <el-form-item label="ID" prop="id">
<el-input v-model="memberForm.id" autocomplete="off" :disabled="true"/> <el-input v-model="memberForm.id" autocomplete="off" :disabled="true"/>
</el-form-item> </el-form-item>
......
...@@ -69,8 +69,9 @@ ...@@ -69,8 +69,9 @@
</div> </div>
</el-card> </el-card>
<el-dialog title="创建资源池" :visible.sync="createVisible" width="70%" @closed="closeFunc" :destroy-on-close="true"> <el-dialog v-loading="testLoading" title="创建资源池" :visible.sync="createVisible" width="70%" @closed="closeFunc"
<el-form :model="form" label-position="left" label-width="100px" size="small" :rules="rule" :destroy-on-close="true">
<el-form :model="form" label-position="right" label-width="100px" size="small" :rules="rule"
ref="createTestResourcePoolForm"> ref="createTestResourcePoolForm">
<el-form-item label="名称" prop="name"> <el-form-item label="名称" prop="name">
<el-input v-model="form.name" autocomplete="off"/> <el-input v-model="form.name" autocomplete="off"/>
...@@ -85,46 +86,41 @@ ...@@ -85,46 +86,41 @@
</el-select> </el-select>
</el-form-item> </el-form-item>
<div v-for="(item,index) in infoList " :key="index"> <div v-for="(item,index) in infoList " :key="index">
<div class="current-row" v-if="form.type === 'K8S'"> <div class="node-line" v-if="form.type === 'K8S'">
<div style="width: 35%;float: left"> <div class="k8s-master">
<label class="el-form-item__label">Master URL</label> <el-col :span="11">
<div class="el-form-item__content" style="margin-left: 100px">
<input v-model="item.masterUrl" autocomplete="off" class="el-input__inner form-input"/> </el-col>
</div> <el-form-item prop="masterUrl" label="Master URL">
<el-input v-model="item.masterUrl" autocomplete="off"/>
</el-form-item>
</div> </div>
<div style="width: 35%;float: left"> <div class="k8s-token">
<label class="el-form-item__label" style="padding-left: 20px">Token</label> <el-form-item prop="token" label="Token">
<div class="el-form-item__content" style="margin-left: 100px"> <el-input v-model="item.token" show-password autocomplete="off"/>
<input v-model="item.token" autocomplete="off" class="el-input__inner form-input"/> </el-form-item>
</div>
</div> </div>
<div style="width: 30%;float: left"> <div style="width: 30%;float: left">
<label class="el-form-item__label" style="padding-left: 20px">最大并发数</label> <el-form-item prop="maxConcurrency" label="最大并发数">
<div class="el-form-item__content" style="margin-left: 102px"> <el-input-number v-model="item.maxConcurrency" :min="1" :max="9999"></el-input-number>
<input v-model="item.maxConcurrency" autocomplete="off" type="number" </el-form-item>
class="el-input__inner form-input"/>
</div>
</div> </div>
</div> </div>
<div class="current-row" v-if="form.type === 'NODE'"> <div class="node-line" v-if="form.type === 'NODE'">
<div style="width: 42%;float: left"> <div style="width: 30%;float: left">
<label class="el-form-item__label">IP</label> <el-form-item prop="ip" label="IP">
<div class="el-form-item__content" style="margin-left: 100px"> <el-input v-model="item.ip" autocomplete="off"/>
<input v-model="item.ip" autocomplete="off" class="el-input__inner form-input"/> </el-form-item>
</div>
</div> </div>
<div style="width: 20%;float: left"> <div style="width: 30%;float: left">
<label class="el-form-item__label" style="padding-left: 20px">port</label> <el-form-item prop="port" label="Port">
<div class="el-form-item__content" style="margin-left: 100px"> <el-input-number v-model="item.port" :min="1" :max="9999"></el-input-number>
<input v-model="item.port" autocomplete="off" type="number" class="el-input__inner form-input"/> </el-form-item>
</div>
</div> </div>
<div style="width: 20%;float: left"> <div style="width: 30%;float: left">
<label class="el-form-item__label" style="padding-left: 20px">最大并发数</label> <el-form-item prop="maxConcurrency" label="最大并发数">
<div class="el-form-item__content" style="margin-left: 102px"> <el-input-number v-model="item.maxConcurrency" :min="1" :max="9999"></el-input-number>
<input v-model="item.maxConcurrency" autocomplete="off" type="number" </el-form-item>
class="el-input__inner form-input"/>
</div>
</div> </div>
<div class="op"> <div class="op">
<span class="box"> <span class="box">
...@@ -148,8 +144,9 @@ ...@@ -148,8 +144,9 @@
</span> </span>
</el-dialog> </el-dialog>
<el-dialog title="修改资源池" :visible.sync="updateVisible" width="70%" :destroy-on-close="true" @close="closeFunc"> <el-dialog v-loading="testLoading" title="修改资源池" :visible.sync="updateVisible" width="70%" :destroy-on-close="true"
<el-form :model="form" label-position="left" label-width="100px" size="small" :rules="rule" @close="closeFunc">
<el-form :model="form" label-position="right" label-width="100px" size="small" :rules="rule"
ref="updateTestResourcePoolForm"> ref="updateTestResourcePoolForm">
<el-form-item label="名称" prop="name"> <el-form-item label="名称" prop="name">
<el-input v-model="form.name" autocomplete="off"/> <el-input v-model="form.name" autocomplete="off"/>
...@@ -158,52 +155,44 @@ ...@@ -158,52 +155,44 @@
<el-input v-model="form.description" autocomplete="off"/> <el-input v-model="form.description" autocomplete="off"/>
</el-form-item> </el-form-item>
<el-form-item label="资源类型" prop="type"> <el-form-item label="资源类型" prop="type">
<el-select v-model="form.type" placeholder="选择资源类型"> <el-select v-model="form.type" placeholder="选择资源类型" @change="changeResourceType()">
<el-option key="K8S" value="K8S" label="Kubernetes">Kubernetes</el-option> <el-option key="K8S" value="K8S" label="Kubernetes">Kubernetes</el-option>
<el-option key="NODE" value="NODE" label="独立节点">独立节点</el-option> <el-option key="NODE" value="NODE" label="独立节点">独立节点</el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
<div v-for="(item,index) in infoList " :key="index"> <div v-for="(item,index) in infoList " :key="index">
<div class="current-row" v-if="form.type === 'K8S'"> <div class="node-line" v-if="form.type === 'K8S'">
<div style="width: 35%;float: left"> <div class="k8s-master">
<label class="el-form-item__label">Master URL</label> <el-form-item prop="masterUrl" label="Master URL">
<div class="el-form-item__content" style="margin-left: 100px"> <el-input v-model="item.masterUrl" autocomplete="off"/>
<input v-model="item.masterUrl" autocomplete="off" class="el-input__inner form-input"/> </el-form-item>
</div>
</div> </div>
<div style="width: 35%;float: left"> <div class="k8s-token">
<label class="el-form-item__label" style="padding-left: 20px">Token</label> <el-form-item prop="password" label="Token" style="padding-left: 20px">
<div class="el-form-item__content" style="margin-left: 100px"> <el-input v-model="item.token" show-password autocomplete="off"/>
<input v-model="item.token" autocomplete="off" class="el-input__inner form-input"/> </el-form-item>
</div>
</div> </div>
<div style="width: 30%;float: left"> <div style="width: 30%;float: left">
<label class="el-form-item__label" style="padding-left: 20px">最大并发数</label> <el-form-item prop="maxConcurrency" label="最大并发数" style="padding-left: 20px">
<div class="el-form-item__content" style="margin-left: 102px"> <el-input-number v-model="item.maxConcurrency" :min="1" :max="9999"></el-input-number>
<input v-model="item.maxConcurrency" autocomplete="off" type="number" </el-form-item>
class="el-input__inner form-input"/>
</div>
</div> </div>
</div> </div>
<div class="current-row" v-if="form.type === 'NODE'"> <div class="node-line" v-if="form.type === 'NODE'">
<div style="width: 42%;float: left"> <div style="width: 30%;float: left">
<label class="el-form-item__label">IP</label> <el-form-item prop="ip" label="IP">
<div class="el-form-item__content" style="margin-left: 100px"> <el-input v-model="item.ip" autocomplete="off"/>
<input v-model="item.ip" autocomplete="off" class="el-input__inner form-input"/> </el-form-item>
</div>
</div> </div>
<div style="width: 20%;float: left"> <div style="width: 30%;float: left">
<label class="el-form-item__label" style="padding-left: 20px">port</label> <el-form-item prop="port" label="Port" style="padding-left: 20px">
<div class="el-form-item__content" style="margin-left: 100px"> <el-input-number v-model="item.port" :min="1" :max="9999"></el-input-number>
<input v-model="item.port" autocomplete="off" type="number" class="el-input__inner form-input"/> </el-form-item>
</div>
</div> </div>
<div style="width: 20%;float: left"> <div style="width: 30%;float: left">
<label class="el-form-item__label" style="padding-left: 20px">最大并发数</label> <el-form-item prop="maxConcurrency" label="最大并发数" style="padding-left: 20px">
<div class="el-form-item__content" style="margin-left: 102px"> <el-input-number v-model="item.maxConcurrency" :min="1" :max="9999"></el-input-number>
<input v-model="item.maxConcurrency" autocomplete="off" type="number" </el-form-item>
class="el-input__inner form-input"/>
</div>
</div> </div>
<div class="op"> <div class="op">
<span class="box"> <span class="box">
...@@ -238,6 +227,7 @@ ...@@ -238,6 +227,7 @@
data() { data() {
return { return {
loading: false, loading: false,
testLoading: false,
createVisible: false, createVisible: false,
infoList: [], infoList: [],
updateVisible: false, updateVisible: false,
...@@ -264,6 +254,9 @@ ...@@ -264,6 +254,9 @@
], ],
description: [ description: [
{max: 60, message: '最大长度 60 个字符', trigger: 'blur'} {max: 60, message: '最大长度 60 个字符', trigger: 'blur'}
],
type: [
{required: true, message: '请选择资源类型', trigger: 'blur'}
] ]
} }
} }
...@@ -303,6 +296,28 @@ ...@@ -303,6 +296,28 @@
}); });
} }
}, },
validateResourceInfo() {
if (this.infoList.length <= 0) {
return {validate: false, msg: "资源池不能为空"}
}
let resultValidate = {validate: true, msg: "请完善数据"}
this.infoList.forEach(function (info) {
for (let key in info) {
if (info[key] != '0' && !info[key]) {
resultValidate.validate = false
return false;
}
}
if (!info.maxConcurrency) {
resultValidate.validate = false
return false;
}
});
return resultValidate;
},
buildPagePath(path) { buildPagePath(path) {
return path + "/" + this.currentPage + "/" + this.pageSize; return path + "/" + this.currentPage + "/" + this.pageSize;
}, },
...@@ -349,16 +364,29 @@ ...@@ -349,16 +364,29 @@
createTestResourcePool(createTestResourcePoolForm) { createTestResourcePool(createTestResourcePoolForm) {
this.$refs[createTestResourcePoolForm].validate(valide => { this.$refs[createTestResourcePoolForm].validate(valide => {
if (valide) { if (valide) {
this.form.info = JSON.stringify(this.infoList); let vri = this.validateResourceInfo();
this.$post("/testresourcepool/add", this.form) if (vri.validate) {
.then(() => { this.testLoading = true;
this.$message({ this.form.info = JSON.stringify(this.infoList);
type: 'success', this.$post("/testresourcepool/add", this.form)
message: '添加成功!' .then(() => {
}, this.$message({
this.createVisible = false, type: 'success',
this.initTableData()) message: '添加成功!'
},
this.createVisible = false,
this.initTableData());
this.testLoading = false;
});
} else {
this.$message({
type: 'warning',
message: vri.msg
}); });
this.testLoading = false;
return false;
}
} else { } else {
return false; return false;
} }
...@@ -367,17 +395,29 @@ ...@@ -367,17 +395,29 @@
updateTestResourcePool(updateTestResourcePoolForm) { updateTestResourcePool(updateTestResourcePoolForm) {
this.$refs[updateTestResourcePoolForm].validate(valide => { this.$refs[updateTestResourcePoolForm].validate(valide => {
if (valide) { if (valide) {
this.form.info = JSON.stringify(this.infoList); this.testLoading = true;
this.$post("/testresourcepool/update", this.form) let vri = this.validateResourceInfo();
.then(() => { if (vri.validate) {
this.$message({ this.form.info = JSON.stringify(this.infoList);
type: 'success', this.$post("/testresourcepool/update", this.form)
message: this.$t('commons.modify_success') .then(() => {
}, this.$message({
this.updateVisible = false, type: 'success',
this.initTableData(), message: this.$t('commons.modify_success')
self.loading = false) },
this.updateVisible = false,
this.initTableData(),
self.loading = false);
this.testLoading = false;
});
} else {
this.$message({
type: 'warning',
message: vri.msg
}); });
this.testLoading = false;
return false;
}
} else { } else {
return false; return false;
} }
...@@ -414,12 +454,29 @@ ...@@ -414,12 +454,29 @@
} }
.op { .op {
line-height: 40px;
float: left; float: left;
width: 16%; width: 10%;
} }
.box { .box {
padding-left: 5px; padding-left: 5px;
} }
.k8s-master {
width: 34%;
float: left
}
.k8s-token {
width: 36%;
float: left
}
.k8s-token .el-form-item__label {
padding-left: 20px;
}
.node-line {
clear: both;
}
</style> </style>
...@@ -65,7 +65,7 @@ ...@@ -65,7 +65,7 @@
</el-card> </el-card>
<el-dialog :title="$t('user.create')" :visible.sync="createVisible" width="30%" @closed="closeFunc" :destroy-on-close="true"> <el-dialog :title="$t('user.create')" :visible.sync="createVisible" width="30%" @closed="closeFunc" :destroy-on-close="true">
<el-form :model="form" label-position="left" label-width="100px" size="small" :rules="rule" ref="createUserForm"> <el-form :model="form" label-position="right" label-width="100px" size="small" :rules="rule" ref="createUserForm">
<el-form-item label="ID" prop="id"> <el-form-item label="ID" prop="id">
<el-input v-model="form.id" autocomplete="off"/> <el-input v-model="form.id" autocomplete="off"/>
</el-form-item> </el-form-item>
...@@ -85,7 +85,7 @@ ...@@ -85,7 +85,7 @@
</el-dialog> </el-dialog>
<el-dialog :title="$t('user.modify')" :visible.sync="updateVisible" width="30%" :destroy-on-close="true" @close="closeFunc"> <el-dialog :title="$t('user.modify')" :visible.sync="updateVisible" width="30%" :destroy-on-close="true" @close="closeFunc">
<el-form :model="form" label-position="left" label-width="100px" size="small" :rules="rule" ref="updateUserForm"> <el-form :model="form" label-position="right" label-width="100px" size="small" :rules="rule" ref="updateUserForm">
<el-form-item label="ID" prop="id"> <el-form-item label="ID" prop="id">
<el-input v-model="form.id" autocomplete="off" :disabled="true"/> <el-input v-model="form.id" autocomplete="off" :disabled="true"/>
</el-form-item> </el-form-item>
......
...@@ -50,7 +50,7 @@ ...@@ -50,7 +50,7 @@
</el-card> </el-card>
<el-dialog title="添加成员" :visible.sync="createVisible" width="30%" :destroy-on-close="true" @close="closeFunc"> <el-dialog title="添加成员" :visible.sync="createVisible" width="30%" :destroy-on-close="true" @close="closeFunc">
<el-form :model="form" ref="form" :rules="rules" label-position="left" label-width="100px" size="small"> <el-form :model="form" ref="form" :rules="rules" label-position="right" label-width="100px" size="small">
<el-form-item label="成员" prop="userIds"> <el-form-item label="成员" prop="userIds">
<el-select v-model="form.userIds" multiple :placeholder="$t('member.please_choose_member')" class="select-width"> <el-select v-model="form.userIds" multiple :placeholder="$t('member.please_choose_member')" class="select-width">
<el-option <el-option
...@@ -80,7 +80,7 @@ ...@@ -80,7 +80,7 @@
</el-dialog> </el-dialog>
<el-dialog title="修改成员" :visible.sync="updateVisible" width="30%" :destroy-on-close="true" @close="closeFunc"> <el-dialog title="修改成员" :visible.sync="updateVisible" width="30%" :destroy-on-close="true" @close="closeFunc">
<el-form :model="form" label-position="left" label-width="100px" size="small" ref="updateUserForm"> <el-form :model="form" label-position="right" label-width="100px" size="small" ref="updateUserForm">
<el-form-item label="ID" prop="id"> <el-form-item label="ID" prop="id">
<el-input v-model="form.id" autocomplete="off" :disabled="true"/> <el-input v-model="form.id" autocomplete="off" :disabled="true"/>
</el-form-item> </el-form-item>
......
...@@ -158,6 +158,8 @@ export default { ...@@ -158,6 +158,8 @@ export default {
'custom_http_code': 'Custom HTTP response success status code', 'custom_http_code': 'Custom HTTP response success status code',
'separated_by_commas': 'Separated by commas', 'separated_by_commas': 'Separated by commas',
'create': 'Create Test', 'create': 'Create Test',
'select_resource_pool': 'Please Select Resource Pool',
'resource_pool_is_null': 'Resource Pool is empty',
}, },
fuc_test: { fuc_test: {
'select_resource_pool': 'Please select resource pool' 'select_resource_pool': 'Please select resource pool'
......
...@@ -158,6 +158,8 @@ export default { ...@@ -158,6 +158,8 @@ export default {
'custom_http_code': '自定义 HTTP 响应成功状态码', 'custom_http_code': '自定义 HTTP 响应成功状态码',
'separated_by_commas': '按逗号分隔', 'separated_by_commas': '按逗号分隔',
'create': '创建测试', 'create': '创建测试',
'select_resource_pool': '请选择资源池',
'resource_pool_is_null': '资源池为空',
}, },
fuc_test: { fuc_test: {
'select_resource_pool': '请选择资源池' 'select_resource_pool': '请选择资源池'
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册