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

merge

......@@ -152,25 +152,13 @@
<artifactId>jmeter-plugins-casutg</artifactId>
<version>2.9</version>
</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>
<groupId>kg.apc</groupId>
<artifactId>jmeter-plugins-standard</artifactId>
<version>1.4.0</version>
<groupId>com.opencsv</groupId>
<artifactId>opencsv</artifactId>
<version>5.1</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
......
......@@ -6,6 +6,8 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication(exclude = {QuartzAutoConfiguration.class})
@ServletComponentScan
......@@ -14,4 +16,9 @@ public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
......@@ -9,8 +9,6 @@ public class FileMetadata implements Serializable {
private String type;
private String engine;
private Long createTime;
private Long updateTime;
......@@ -43,14 +41,6 @@ public class FileMetadata implements Serializable {
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() {
return createTime;
}
......
......@@ -314,76 +314,6 @@ public class FileMetadataExample {
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() {
addCriterion("create_time is null");
return (Criteria) this;
......
......@@ -5,7 +5,6 @@
<id column="id" jdbcType="VARCHAR" property="id" />
<result column="name" jdbcType="VARCHAR" property="name" />
<result column="type" jdbcType="VARCHAR" property="type" />
<result column="engine" jdbcType="VARCHAR" property="engine" />
<result column="create_time" jdbcType="BIGINT" property="createTime" />
<result column="update_time" jdbcType="BIGINT" property="updateTime" />
<result column="size" jdbcType="BIGINT" property="size" />
......@@ -69,7 +68,7 @@
</where>
</sql>
<sql id="Base_Column_List">
id, name, type, engine, create_time, update_time, size
id, name, type, create_time, update_time, size
</sql>
<select id="selectByExample" parameterType="io.metersphere.base.domain.FileMetadataExample" resultMap="BaseResultMap">
select
......@@ -103,11 +102,11 @@
</delete>
<insert id="insert" parameterType="io.metersphere.base.domain.FileMetadata">
insert into file_metadata (id, name, type,
engine, create_time, update_time,
size)
create_time, update_time, size
)
values (#{id,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR}, #{type,jdbcType=VARCHAR},
#{engine,jdbcType=VARCHAR}, #{createTime,jdbcType=BIGINT}, #{updateTime,jdbcType=BIGINT},
#{size,jdbcType=BIGINT})
#{createTime,jdbcType=BIGINT}, #{updateTime,jdbcType=BIGINT}, #{size,jdbcType=BIGINT}
)
</insert>
<insert id="insertSelective" parameterType="io.metersphere.base.domain.FileMetadata">
insert into file_metadata
......@@ -121,9 +120,6 @@
<if test="type != null">
type,
</if>
<if test="engine != null">
engine,
</if>
<if test="createTime != null">
create_time,
</if>
......@@ -144,9 +140,6 @@
<if test="type != null">
#{type,jdbcType=VARCHAR},
</if>
<if test="engine != null">
#{engine,jdbcType=VARCHAR},
</if>
<if test="createTime != null">
#{createTime,jdbcType=BIGINT},
</if>
......@@ -176,9 +169,6 @@
<if test="record.type != null">
type = #{record.type,jdbcType=VARCHAR},
</if>
<if test="record.engine != null">
engine = #{record.engine,jdbcType=VARCHAR},
</if>
<if test="record.createTime != null">
create_time = #{record.createTime,jdbcType=BIGINT},
</if>
......@@ -198,7 +188,6 @@
set id = #{record.id,jdbcType=VARCHAR},
name = #{record.name,jdbcType=VARCHAR},
type = #{record.type,jdbcType=VARCHAR},
engine = #{record.engine,jdbcType=VARCHAR},
create_time = #{record.createTime,jdbcType=BIGINT},
update_time = #{record.updateTime,jdbcType=BIGINT},
size = #{record.size,jdbcType=BIGINT}
......@@ -215,9 +204,6 @@
<if test="type != null">
type = #{type,jdbcType=VARCHAR},
</if>
<if test="engine != null">
engine = #{engine,jdbcType=VARCHAR},
</if>
<if test="createTime != null">
create_time = #{createTime,jdbcType=BIGINT},
</if>
......@@ -234,7 +220,6 @@
update file_metadata
set name = #{name,jdbcType=VARCHAR},
type = #{type,jdbcType=VARCHAR},
engine = #{engine,jdbcType=VARCHAR},
create_time = #{createTime,jdbcType=BIGINT},
update_time = #{updateTime,jdbcType=BIGINT},
size = #{size,jdbcType=BIGINT}
......
package io.metersphere.commons.constants;
public enum EngineType {
DOCKER, KUBERNETES
}
package io.metersphere.commons.constants;
public enum ResourceTypeEnum {
public enum ResourcePoolTypeEnum {
/**
* k8s 资源池
*/
......
......@@ -39,4 +39,13 @@ public class TestResourcePoolController {
Page<Object> page = PageHelper.startPage(goPage, pageSize, true);
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 {
private String testId;
private String testName;
private String namespace;
private String engineType;
private String fileType;
private String content;
private Map<String, Object> properties = new HashMap<>();
......@@ -37,14 +36,6 @@ public class EngineContext {
this.namespace = namespace;
}
public String getEngineType() {
return engineType;
}
public void setEngineType(String engineType) {
this.engineType = engineType;
}
public void addProperty(String key, Object value) {
this.properties.put(key, value);
}
......
......@@ -5,7 +5,8 @@ import com.alibaba.fastjson.JSONObject;
import io.metersphere.base.domain.FileContent;
import io.metersphere.base.domain.FileMetadata;
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.engine.docker.DockerTestEngine;
import io.metersphere.engine.kubernetes.KubernetesTestEngine;
......@@ -13,6 +14,7 @@ import io.metersphere.i18n.Translator;
import io.metersphere.parse.EngineSourceParser;
import io.metersphere.parse.EngineSourceParserFactory;
import io.metersphere.service.FileService;
import io.metersphere.service.TestResourcePoolService;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
......@@ -26,14 +28,35 @@ import java.util.Map;
@Service
public class EngineFactory {
private static FileService fileService;
private static TestResourcePoolService testResourcePoolService;
public static Engine createEngine(String engineType) {
final EngineType type = EngineType.valueOf(engineType);
public static Engine createEngine(LoadTestWithBLOBs loadTest) {
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) {
case DOCKER:
case NODE:
return new DockerTestEngine();
case KUBERNETES:
case K8S:
return new KubernetesTestEngine();
}
return null;
......@@ -48,7 +71,6 @@ public class EngineFactory {
engineContext.setTestId(loadTest.getId());
engineContext.setTestName(loadTest.getName());
engineContext.setNamespace(loadTest.getProjectId());
engineContext.setEngineType(fileMetadata.getEngine());
engineContext.setFileType(fileMetadata.getType());
if (!StringUtils.isEmpty(loadTest.getLoadConfiguration())) {
......@@ -86,4 +108,9 @@ public class EngineFactory {
private void setFileService(FileService fileService) {
EngineFactory.fileService = fileService;
}
@Resource
public void setTestResourcePoolService(TestResourcePoolService testResourcePoolService) {
EngineFactory.testResourcePoolService = testResourcePoolService;
}
}
package io.metersphere.engine.docker;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.controller.request.TestRequest;
import io.metersphere.engine.Engine;
import io.metersphere.engine.EngineContext;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.client.RestTemplate;
import java.util.HashMap;
import java.util.List;
public class DockerTestEngine implements Engine {
private EngineContext context;
RestTemplate restTemplate;
@Override
public boolean init(EngineContext context) {
this.restTemplate = CommonBeanFactory.getBean(RestTemplate.class);
// todo 初始化操作
this.context = context;
return true;
......@@ -22,8 +25,8 @@ public class DockerTestEngine implements Engine {
@Override
public void start() {
RestTemplate restTemplate = new RestTemplate();
// todo 运行测试
// RestTemplate restTemplate = new RestTemplate();
String testId = context.getTestId();
String content = context.getContent();
......@@ -34,6 +37,7 @@ public class DockerTestEngine implements Engine {
testRequest.setTestId(testId);
testRequest.setFileString(content);
// todo 判断测试状态
String taskStatusUri = "http://localhost:8082/jmeter/task/status/" + testId;
List containerList = restTemplate.getForObject(taskStatusUri, List.class);
for (int i = 0; i < containerList.size(); i++) {
......@@ -48,12 +52,13 @@ public class DockerTestEngine implements Engine {
@Override
public void stop() {
RestTemplate restTemplate = new RestTemplate();
// TODO 停止运行测试
// RestTemplate restTemplate = new RestTemplate();
String testId = context.getTestId();
String uri = "http://localhost:8082/jmeter/container/stop" + testId;
restTemplate.getForObject(uri, String.class);
String uri = "http://localhost:8082/jmeter/container/stop/" + testId;
restTemplate.postForObject(uri, "", String.class);
}
}
......@@ -4,6 +4,7 @@ import com.alibaba.fastjson.JSON;
import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.api.model.ObjectMeta;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.engine.Engine;
import io.metersphere.engine.EngineContext;
......@@ -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.provider.ClientCredential;
import io.metersphere.engine.kubernetes.provider.KubernetesProvider;
import io.metersphere.service.TestResourcePoolService;
import org.apache.commons.collections.MapUtils;
import java.util.HashMap;
public class KubernetesTestEngine implements Engine {
private EngineContext context;
private TestResourcePoolService testResourcePoolService;
@Override
public boolean init(EngineContext context) {
// todo 初始化操作
this.context = context;
this.testResourcePoolService = CommonBeanFactory.getBean(TestResourcePoolService.class);
return true;
}
......
......@@ -233,9 +233,11 @@ public class JmeterDocumentParser implements DocumentParser {
collectionProp.appendChild(createKafkaProp(document, "kafka.batch.size", kafkaProperties.getBatchSize()));
collectionProp.appendChild(createKafkaProp(document, "kafka.client.id", kafkaProperties.getClientId()));
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.name", context.getTestName()));
collectionProp.appendChild(createKafkaProp(document, "test.startTime", "" + System.currentTimeMillis()));
collectionProp.appendChild(createKafkaProp(document, "test.size", "1"));
elementProp.appendChild(collectionProp);
// 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;
import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.*;
import io.metersphere.base.mapper.ext.ExtFunctionalTestMapper;
import io.metersphere.commons.constants.EngineType;
import io.metersphere.commons.exception.MSException;
import io.metersphere.controller.request.testplan.*;
import io.metersphere.dto.FunctionalTestDTO;
import io.metersphere.engine.Engine;
import io.metersphere.engine.EngineFactory;
import io.metersphere.i18n.Translator;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
......@@ -94,8 +91,6 @@ public class FuctionalTestService {
fileMetadata.setCreateTime(System.currentTimeMillis());
fileMetadata.setUpdateTime(System.currentTimeMillis());
fileMetadata.setType("jmx");
// TODO engine 选择
fileMetadata.setEngine(EngineType.DOCKER.name());
fileMetadataMapper.insert(fileMetadata);
FileContent fileContent = new FileContent();
......
......@@ -6,7 +6,6 @@ import io.metersphere.base.mapper.FileMetadataMapper;
import io.metersphere.base.mapper.LoadTestFileMapper;
import io.metersphere.base.mapper.LoadTestMapper;
import io.metersphere.base.mapper.ext.ExtLoadTestMapper;
import io.metersphere.commons.constants.EngineType;
import io.metersphere.commons.constants.FileType;
import io.metersphere.commons.constants.TestStatus;
import io.metersphere.commons.exception.MSException;
......@@ -45,6 +44,8 @@ public class LoadTestService {
private LoadTestFileMapper loadTestFileMapper;
@Resource
private FileService fileService;
@Resource
private TestResourcePoolService testResourcePoolService;
public List<LoadTestDTO> list(QueryTestPlanRequest request) {
return extLoadTestMapper.list(request);
......@@ -102,8 +103,6 @@ public class LoadTestService {
fileMetadata.setUpdateTime(System.currentTimeMillis());
FileType fileType = getFileType(fileMetadata.getName());
fileMetadata.setType(fileType.name());
// TODO engine 选择
fileMetadata.setEngine(EngineType.DOCKER.name());
fileMetadataMapper.insert(fileMetadata);
FileContent fileContent = new FileContent();
......@@ -179,9 +178,8 @@ public class LoadTestService {
List<FileMetadata> csvFiles = fileMetadataList.stream().filter(f -> StringUtils.equalsIgnoreCase(f.getType(), FileType.CSV.name())).collect(Collectors.toList());
LogUtil.info("Load test started " + loadTest.getName());
// engine type (DOCKER|KUBERNETES)
// todo set type
final Engine engine = EngineFactory.createEngine(fileMetadata.getEngine());
// engine type (NODE|K8S)
final Engine engine = EngineFactory.createEngine(loadTest);
if (engine == null) {
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 {
public ReportDTO getReportTestAndProInfo(String reportId) {
return extLoadTestReportMapper.getReportTestAndProInfo(reportId);
}
public LoadTestReport getReport(String id) {
return loadTestReportMapper.selectByPrimaryKey(id);
}
}
package io.metersphere.service;
import com.alibaba.fastjson.JSON;
import io.metersphere.base.domain.TestResourcePool;
import io.metersphere.base.domain.TestResourcePoolExample;
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.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.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
import java.util.List;
......@@ -19,6 +30,8 @@ import java.util.UUID;
@Transactional(rollbackFor = Exception.class)
public class TestResourcePoolService {
private final static String nodeControllerUrl = "%s:%s/status";
@Resource
private TestResourcePoolMapper testResourcePoolMapper;
......@@ -27,6 +40,7 @@ public class TestResourcePoolService {
testResourcePool.setCreateTime(System.currentTimeMillis());
testResourcePool.setUpdateTime(System.currentTimeMillis());
testResourcePool.setStatus("1");
validateTestResourcePool(testResourcePool);
testResourcePoolMapper.insertSelective(testResourcePool);
return testResourcePool;
}
......@@ -37,6 +51,7 @@ public class TestResourcePoolService {
public void updateTestResourcePool(TestResourcePool testResourcePool) {
testResourcePool.setUpdateTime(System.currentTimeMillis());
validateTestResourcePool(testResourcePool);
testResourcePoolMapper.updateByPrimaryKeySelective(testResourcePool);
}
......@@ -47,4 +62,59 @@ public class TestResourcePoolService {
}
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` (
`id` varchar(64) NOT NULL COMMENT 'File ID',
`name` varchar(64) NOT NULL COMMENT 'File name',
`type` varchar(64) DEFAULT NULL COMMENT 'File type',
`engine` varchar(64) DEFAULT 'DOCKER' COMMENT 'engine type',
`size` bigint(13) NOT NULL COMMENT 'File size',
`create_time` bigint(13) NOT NULL COMMENT 'Create timestamp',
`update_time` bigint(13) NOT NULL COMMENT 'Update timestamp',
......
......@@ -45,10 +45,20 @@
</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>
</generatorConfiguration>
\ No newline at end of file
package io.metersphere;
import io.metersphere.commons.constants.JmeterReportType;
import kg.apc.jmeter.PluginsCMDWorker;
import org.apache.jmeter.util.JMeterUtils;
import com.opencsv.bean.CsvToBean;
import com.opencsv.bean.CsvToBeanBuilder;
import com.opencsv.bean.HeaderColumnNameMappingStrategy;
import com.opencsv.bean.MappingStrategy;
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 {
/*
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
public void test1() {
JMeterUtils.setJMeterHome("/opt/fit2cloud/apache-jmeter-5.2.1");
JMeterUtils.loadJMeterProperties("/opt/fit2cloud/apache-jmeter-5.2.1/bin/jmeter.properties");
PluginsCMDWorker worker = new PluginsCMDWorker();
worker.setPluginType(JmeterReportType.AggregateReport.name());
worker.addExportMode(2);
worker.setOutputCSVFile("/tmp/test0320.csv");
worker.setInputFile("/Users/liuruibin/Desktop/0316.jtl");
worker.doJob();
File csvFile = new File("/Users/liuruibin/Desktop/0316.jtl");
HeaderColumnNameMappingStrategy<Metric> ms = new HeaderColumnNameMappingStrategy<>();
ms.setType(Metric.class);
List<Metric> metrics = beanBuilderExample(csvFile.toPath(), ms);
metrics.forEach(c -> {
System.out.println(c.getTimestamp());
});
}
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 @@
return false;
}
if (!this.$refs.pressureConfig.validConfig()) {
return false;
}
if (!this.$refs.advancedConfig.validConfig()) {
return false;
}
......
......@@ -73,6 +73,21 @@
<div>{{$t('load_test.ramp_up_time_times')}}</div>
</el-form-item>
</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 :span="12">
<chart class="chart-container" ref="chart1" :options="orgOptions" :autoresize="true"></chart>
......@@ -89,6 +104,7 @@
const STEPS = "Steps";
const DURATION = "duration";
const RPS_LIMIT = "rpsLimit";
const RESOURCE_POOL = "resourcePoolId";
export default {
name: "PerformancePressureConfig",
......@@ -101,6 +117,8 @@
step: 10,
rpsLimit: 10,
orgOptions: {},
resourcePool: null,
resourcePools: [],
}
},
mounted() {
......@@ -110,6 +128,8 @@
} else {
this.calculateChart();
}
this.getResourcePools();
},
watch: {
'$route'(to, from) {
......@@ -125,11 +145,16 @@
}
},
methods: {
getResourcePools() {
this.$get('/testresourcepool/list/all', response => {
this.resourcePools = response.data;
})
},
getLoadConfig(testId) {
if (testId) {
this.$get('/testplan/get-load-config/' + testId, (response) => {
if (response.data && response.data != "") {
if (response.data) {
let data = JSON.parse(response.data);
data.forEach(d => {
......@@ -149,6 +174,9 @@
case RPS_LIMIT:
this.rpsLimit = d.value;
break;
case RESOURCE_POOL:
this.resourcePool = d.value;
break;
default:
break;
}
......@@ -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() {
/// todo:下面4个属性是jmeter ConcurrencyThreadGroup plugin的属性,这种硬编码不太好吧,在哪能转换这种属性?
return [
......@@ -259,7 +298,8 @@
{key: RAMP_UP, value: this.rampUpTime},
{key: STEPS, value: this.step},
{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 @@
</el-card>
<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-input v-model="form.name" autocomplete="off"></el-input>
</el-form-item>
......
......@@ -51,7 +51,7 @@
</el-card>
<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-select v-model="form.userIds" multiple :placeholder="$t('member.please_choose_member')" class="select-width">
<el-option
......@@ -81,7 +81,7 @@
</el-dialog>
<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-input v-model="form.id" autocomplete="off" :disabled="true"/>
</el-form-item>
......
......@@ -48,7 +48,7 @@
</el-card>
<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-input v-model="form.name" autocomplete="off"/>
</el-form-item>
......@@ -114,7 +114,7 @@
<!-- add workspace member dialog -->
<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-select v-model="memberForm.userIds" multiple :placeholder="$t('member.please_choose_member')" class="select-width">
<el-option
......@@ -145,7 +145,7 @@
<!-- update workspace member dialog -->
<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-input v-model="memberForm.id" autocomplete="off" :disabled="true"/>
</el-form-item>
......
......@@ -26,7 +26,7 @@
</el-table>
<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-input v-model="form.id" autocomplete="off" :disabled="true"/>
</el-form-item>
......
......@@ -101,7 +101,7 @@
<!-- add organization form -->
<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-input v-model="form.name" autocomplete="off"/>
</el-form-item>
......@@ -116,7 +116,7 @@
<!-- update organization form -->
<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-input v-model="form.name" autocomplete="off"/>
</el-form-item>
......@@ -131,7 +131,7 @@
<!-- add organization member form -->
<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-select v-model="memberForm.userIds" multiple :placeholder="$t('member.please_choose_member')" class="select-width">
<el-option
......@@ -162,7 +162,7 @@
<!-- update organization member form -->
<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-input v-model="memberForm.id" autocomplete="off" :disabled="true"/>
</el-form-item>
......
......@@ -53,7 +53,7 @@
<!-- add workspace dialog -->
<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-input v-model="form.name" autocomplete="off"/>
</el-form-item>
......@@ -78,7 +78,7 @@
<!-- update workspace dialog -->
<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-input v-model="form.name" autocomplete="off"/>
</el-form-item>
......@@ -154,7 +154,7 @@
<!-- add workspace member dialog -->
<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-select v-model="memberForm.userIds" multiple :placeholder="$t('member.please_choose_member')" class="select-width">
<el-option
......@@ -185,7 +185,7 @@
<!-- update workspace member dialog -->
<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-input v-model="memberForm.id" autocomplete="off" :disabled="true"/>
</el-form-item>
......
......@@ -69,8 +69,9 @@
</div>
</el-card>
<el-dialog title="创建资源池" :visible.sync="createVisible" width="70%" @closed="closeFunc" :destroy-on-close="true">
<el-form :model="form" label-position="left" label-width="100px" size="small" :rules="rule"
<el-dialog v-loading="testLoading" title="创建资源池" :visible.sync="createVisible" width="70%" @closed="closeFunc"
:destroy-on-close="true">
<el-form :model="form" label-position="right" label-width="100px" size="small" :rules="rule"
ref="createTestResourcePoolForm">
<el-form-item label="名称" prop="name">
<el-input v-model="form.name" autocomplete="off"/>
......@@ -85,46 +86,41 @@
</el-select>
</el-form-item>
<div v-for="(item,index) in infoList " :key="index">
<div class="current-row" v-if="form.type === 'K8S'">
<div style="width: 35%;float: left">
<label class="el-form-item__label">Master URL</label>
<div class="el-form-item__content" style="margin-left: 100px">
<input v-model="item.masterUrl" autocomplete="off" class="el-input__inner form-input"/>
</div>
</div>
<div style="width: 35%;float: left">
<label class="el-form-item__label" style="padding-left: 20px">Token</label>
<div class="el-form-item__content" style="margin-left: 100px">
<input v-model="item.token" autocomplete="off" class="el-input__inner form-input"/>
<div class="node-line" v-if="form.type === 'K8S'">
<div class="k8s-master">
<el-col :span="11">
</el-col>
<el-form-item prop="masterUrl" label="Master URL">
<el-input v-model="item.masterUrl" autocomplete="off"/>
</el-form-item>
</div>
<div class="k8s-token">
<el-form-item prop="token" label="Token">
<el-input v-model="item.token" show-password autocomplete="off"/>
</el-form-item>
</div>
<div style="width: 30%;float: left">
<label class="el-form-item__label" style="padding-left: 20px">最大并发数</label>
<div class="el-form-item__content" style="margin-left: 102px">
<input v-model="item.maxConcurrency" autocomplete="off" type="number"
class="el-input__inner form-input"/>
</div>
</div>
</div>
<div class="current-row" v-if="form.type === 'NODE'">
<div style="width: 42%;float: left">
<label class="el-form-item__label">IP</label>
<div class="el-form-item__content" style="margin-left: 100px">
<input v-model="item.ip" autocomplete="off" class="el-input__inner form-input"/>
</div>
<el-form-item prop="maxConcurrency" label="最大并发数">
<el-input-number v-model="item.maxConcurrency" :min="1" :max="9999"></el-input-number>
</el-form-item>
</div>
<div style="width: 20%;float: left">
<label class="el-form-item__label" style="padding-left: 20px">port</label>
<div class="el-form-item__content" style="margin-left: 100px">
<input v-model="item.port" autocomplete="off" type="number" class="el-input__inner form-input"/>
</div>
<div class="node-line" v-if="form.type === 'NODE'">
<div style="width: 30%;float: left">
<el-form-item prop="ip" label="IP">
<el-input v-model="item.ip" autocomplete="off"/>
</el-form-item>
</div>
<div style="width: 20%;float: left">
<label class="el-form-item__label" style="padding-left: 20px">最大并发数</label>
<div class="el-form-item__content" style="margin-left: 102px">
<input v-model="item.maxConcurrency" autocomplete="off" type="number"
class="el-input__inner form-input"/>
<div style="width: 30%;float: left">
<el-form-item prop="port" label="Port">
<el-input-number v-model="item.port" :min="1" :max="9999"></el-input-number>
</el-form-item>
</div>
<div style="width: 30%;float: left">
<el-form-item prop="maxConcurrency" label="最大并发数">
<el-input-number v-model="item.maxConcurrency" :min="1" :max="9999"></el-input-number>
</el-form-item>
</div>
<div class="op">
<span class="box">
......@@ -148,8 +144,9 @@
</span>
</el-dialog>
<el-dialog title="修改资源池" :visible.sync="updateVisible" width="70%" :destroy-on-close="true" @close="closeFunc">
<el-form :model="form" label-position="left" label-width="100px" size="small" :rules="rule"
<el-dialog v-loading="testLoading" title="修改资源池" :visible.sync="updateVisible" width="70%" :destroy-on-close="true"
@close="closeFunc">
<el-form :model="form" label-position="right" label-width="100px" size="small" :rules="rule"
ref="updateTestResourcePoolForm">
<el-form-item label="名称" prop="name">
<el-input v-model="form.name" autocomplete="off"/>
......@@ -158,52 +155,44 @@
<el-input v-model="form.description" autocomplete="off"/>
</el-form-item>
<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="NODE" value="NODE" label="独立节点">独立节点</el-option>
</el-select>
</el-form-item>
<div v-for="(item,index) in infoList " :key="index">
<div class="current-row" v-if="form.type === 'K8S'">
<div style="width: 35%;float: left">
<label class="el-form-item__label">Master URL</label>
<div class="el-form-item__content" style="margin-left: 100px">
<input v-model="item.masterUrl" autocomplete="off" class="el-input__inner form-input"/>
</div>
</div>
<div style="width: 35%;float: left">
<label class="el-form-item__label" style="padding-left: 20px">Token</label>
<div class="el-form-item__content" style="margin-left: 100px">
<input v-model="item.token" autocomplete="off" class="el-input__inner form-input"/>
<div class="node-line" v-if="form.type === 'K8S'">
<div class="k8s-master">
<el-form-item prop="masterUrl" label="Master URL">
<el-input v-model="item.masterUrl" autocomplete="off"/>
</el-form-item>
</div>
<div class="k8s-token">
<el-form-item prop="password" label="Token" style="padding-left: 20px">
<el-input v-model="item.token" show-password autocomplete="off"/>
</el-form-item>
</div>
<div style="width: 30%;float: left">
<label class="el-form-item__label" style="padding-left: 20px">最大并发数</label>
<div class="el-form-item__content" style="margin-left: 102px">
<input v-model="item.maxConcurrency" autocomplete="off" type="number"
class="el-input__inner form-input"/>
</div>
</div>
</div>
<div class="current-row" v-if="form.type === 'NODE'">
<div style="width: 42%;float: left">
<label class="el-form-item__label">IP</label>
<div class="el-form-item__content" style="margin-left: 100px">
<input v-model="item.ip" autocomplete="off" class="el-input__inner form-input"/>
</div>
<el-form-item prop="maxConcurrency" label="最大并发数" style="padding-left: 20px">
<el-input-number v-model="item.maxConcurrency" :min="1" :max="9999"></el-input-number>
</el-form-item>
</div>
<div style="width: 20%;float: left">
<label class="el-form-item__label" style="padding-left: 20px">port</label>
<div class="el-form-item__content" style="margin-left: 100px">
<input v-model="item.port" autocomplete="off" type="number" class="el-input__inner form-input"/>
</div>
<div class="node-line" v-if="form.type === 'NODE'">
<div style="width: 30%;float: left">
<el-form-item prop="ip" label="IP">
<el-input v-model="item.ip" autocomplete="off"/>
</el-form-item>
</div>
<div style="width: 20%;float: left">
<label class="el-form-item__label" style="padding-left: 20px">最大并发数</label>
<div class="el-form-item__content" style="margin-left: 102px">
<input v-model="item.maxConcurrency" autocomplete="off" type="number"
class="el-input__inner form-input"/>
<div style="width: 30%;float: left">
<el-form-item prop="port" label="Port" style="padding-left: 20px">
<el-input-number v-model="item.port" :min="1" :max="9999"></el-input-number>
</el-form-item>
</div>
<div style="width: 30%;float: left">
<el-form-item prop="maxConcurrency" label="最大并发数" style="padding-left: 20px">
<el-input-number v-model="item.maxConcurrency" :min="1" :max="9999"></el-input-number>
</el-form-item>
</div>
<div class="op">
<span class="box">
......@@ -238,6 +227,7 @@
data() {
return {
loading: false,
testLoading: false,
createVisible: false,
infoList: [],
updateVisible: false,
......@@ -264,6 +254,9 @@
],
description: [
{max: 60, message: '最大长度 60 个字符', trigger: 'blur'}
],
type: [
{required: true, message: '请选择资源类型', trigger: 'blur'}
]
}
}
......@@ -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) {
return path + "/" + this.currentPage + "/" + this.pageSize;
},
......@@ -349,6 +364,9 @@
createTestResourcePool(createTestResourcePoolForm) {
this.$refs[createTestResourcePoolForm].validate(valide => {
if (valide) {
let vri = this.validateResourceInfo();
if (vri.validate) {
this.testLoading = true;
this.form.info = JSON.stringify(this.infoList);
this.$post("/testresourcepool/add", this.form)
.then(() => {
......@@ -357,8 +375,18 @@
message: '添加成功!'
},
this.createVisible = false,
this.initTableData())
this.initTableData());
this.testLoading = false;
});
} else {
this.$message({
type: 'warning',
message: vri.msg
});
this.testLoading = false;
return false;
}
} else {
return false;
}
......@@ -367,6 +395,9 @@
updateTestResourcePool(updateTestResourcePoolForm) {
this.$refs[updateTestResourcePoolForm].validate(valide => {
if (valide) {
this.testLoading = true;
let vri = this.validateResourceInfo();
if (vri.validate) {
this.form.info = JSON.stringify(this.infoList);
this.$post("/testresourcepool/update", this.form)
.then(() => {
......@@ -376,8 +407,17 @@
},
this.updateVisible = false,
this.initTableData(),
self.loading = false)
self.loading = false);
this.testLoading = false;
});
} else {
this.$message({
type: 'warning',
message: vri.msg
});
this.testLoading = false;
return false;
}
} else {
return false;
}
......@@ -414,12 +454,29 @@
}
.op {
line-height: 40px;
float: left;
width: 16%;
width: 10%;
}
.box {
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>
......@@ -65,7 +65,7 @@
</el-card>
<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-input v-model="form.id" autocomplete="off"/>
</el-form-item>
......@@ -85,7 +85,7 @@
</el-dialog>
<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-input v-model="form.id" autocomplete="off" :disabled="true"/>
</el-form-item>
......
......@@ -50,7 +50,7 @@
</el-card>
<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-select v-model="form.userIds" multiple :placeholder="$t('member.please_choose_member')" class="select-width">
<el-option
......@@ -80,7 +80,7 @@
</el-dialog>
<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-input v-model="form.id" autocomplete="off" :disabled="true"/>
</el-form-item>
......
......@@ -158,6 +158,8 @@ export default {
'custom_http_code': 'Custom HTTP response success status code',
'separated_by_commas': 'Separated by commas',
'create': 'Create Test',
'select_resource_pool': 'Please Select Resource Pool',
'resource_pool_is_null': 'Resource Pool is empty',
},
fuc_test: {
'select_resource_pool': 'Please select resource pool'
......
......@@ -158,6 +158,8 @@ export default {
'custom_http_code': '自定义 HTTP 响应成功状态码',
'separated_by_commas': '按逗号分隔',
'create': '创建测试',
'select_resource_pool': '请选择资源池',
'resource_pool_is_null': '资源池为空',
},
fuc_test: {
'select_resource_pool': '请选择资源池'
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册