This commit is contained in:
chenjianxing 2020-03-18 18:37:07 +08:00
commit b6e3b104f3
40 changed files with 1205 additions and 425 deletions

View File

@ -18,9 +18,16 @@
<shiro.version>1.5.1</shiro.version>
<java.version>1.8</java.version>
<jmeter.version>5.2.1</jmeter.version>
<kubernetes-client.version>4.9.0</kubernetes-client.version>
</properties>
<dependencies>
<!-- kubernetes client-->
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>kubernetes-client</artifactId>
<version>${kubernetes-client.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
@ -57,7 +64,7 @@
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
<version>2.1.2</version>
</dependency>
<dependency>

View File

@ -9,12 +9,14 @@ public class FileMetadata implements Serializable {
private String type;
private Long size;
private String engine;
private Long createTime;
private Long updateTime;
private Long size;
private static final long serialVersionUID = 1L;
public String getId() {
@ -41,12 +43,12 @@ public class FileMetadata implements Serializable {
this.type = type == null ? null : type.trim();
}
public Long getSize() {
return size;
public String getEngine() {
return engine;
}
public void setSize(Long size) {
this.size = size;
public void setEngine(String engine) {
this.engine = engine == null ? null : engine.trim();
}
public Long getCreateTime() {
@ -64,4 +66,12 @@ public class FileMetadata implements Serializable {
public void setUpdateTime(Long updateTime) {
this.updateTime = updateTime;
}
public Long getSize() {
return size;
}
public void setSize(Long size) {
this.size = size;
}
}

View File

@ -314,63 +314,73 @@ public class FileMetadataExample {
return (Criteria) this;
}
public Criteria andSizeIsNull() {
addCriterion("size is null");
public Criteria andEngineIsNull() {
addCriterion("engine is null");
return (Criteria) this;
}
public Criteria andSizeIsNotNull() {
addCriterion("size is not null");
public Criteria andEngineIsNotNull() {
addCriterion("engine is not null");
return (Criteria) this;
}
public Criteria andSizeEqualTo(Long value) {
addCriterion("size =", value, "size");
public Criteria andEngineEqualTo(String value) {
addCriterion("engine =", value, "engine");
return (Criteria) this;
}
public Criteria andSizeNotEqualTo(Long value) {
addCriterion("size <>", value, "size");
public Criteria andEngineNotEqualTo(String value) {
addCriterion("engine <>", value, "engine");
return (Criteria) this;
}
public Criteria andSizeGreaterThan(Long value) {
addCriterion("size >", value, "size");
public Criteria andEngineGreaterThan(String value) {
addCriterion("engine >", value, "engine");
return (Criteria) this;
}
public Criteria andSizeGreaterThanOrEqualTo(Long value) {
addCriterion("size >=", value, "size");
public Criteria andEngineGreaterThanOrEqualTo(String value) {
addCriterion("engine >=", value, "engine");
return (Criteria) this;
}
public Criteria andSizeLessThan(Long value) {
addCriterion("size <", value, "size");
public Criteria andEngineLessThan(String value) {
addCriterion("engine <", value, "engine");
return (Criteria) this;
}
public Criteria andSizeLessThanOrEqualTo(Long value) {
addCriterion("size <=", value, "size");
public Criteria andEngineLessThanOrEqualTo(String value) {
addCriterion("engine <=", value, "engine");
return (Criteria) this;
}
public Criteria andSizeIn(List<Long> values) {
addCriterion("size in", values, "size");
public Criteria andEngineLike(String value) {
addCriterion("engine like", value, "engine");
return (Criteria) this;
}
public Criteria andSizeNotIn(List<Long> values) {
addCriterion("size not in", values, "size");
public Criteria andEngineNotLike(String value) {
addCriterion("engine not like", value, "engine");
return (Criteria) this;
}
public Criteria andSizeBetween(Long value1, Long value2) {
addCriterion("size between", value1, value2, "size");
public Criteria andEngineIn(List<String> values) {
addCriterion("engine in", values, "engine");
return (Criteria) this;
}
public Criteria andSizeNotBetween(Long value1, Long value2) {
addCriterion("size not between", value1, value2, "size");
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;
}
@ -493,6 +503,66 @@ public class FileMetadataExample {
addCriterion("update_time not between", value1, value2, "updateTime");
return (Criteria) this;
}
public Criteria andSizeIsNull() {
addCriterion("size is null");
return (Criteria) this;
}
public Criteria andSizeIsNotNull() {
addCriterion("size is not null");
return (Criteria) this;
}
public Criteria andSizeEqualTo(Long value) {
addCriterion("size =", value, "size");
return (Criteria) this;
}
public Criteria andSizeNotEqualTo(Long value) {
addCriterion("size <>", value, "size");
return (Criteria) this;
}
public Criteria andSizeGreaterThan(Long value) {
addCriterion("size >", value, "size");
return (Criteria) this;
}
public Criteria andSizeGreaterThanOrEqualTo(Long value) {
addCriterion("size >=", value, "size");
return (Criteria) this;
}
public Criteria andSizeLessThan(Long value) {
addCriterion("size <", value, "size");
return (Criteria) this;
}
public Criteria andSizeLessThanOrEqualTo(Long value) {
addCriterion("size <=", value, "size");
return (Criteria) this;
}
public Criteria andSizeIn(List<Long> values) {
addCriterion("size in", values, "size");
return (Criteria) this;
}
public Criteria andSizeNotIn(List<Long> values) {
addCriterion("size not in", values, "size");
return (Criteria) this;
}
public Criteria andSizeBetween(Long value1, Long value2) {
addCriterion("size between", value1, value2, "size");
return (Criteria) this;
}
public Criteria andSizeNotBetween(Long value1, Long value2) {
addCriterion("size not between", value1, value2, "size");
return (Criteria) this;
}
}
public static class Criteria extends GeneratedCriteria {

View File

@ -5,9 +5,10 @@
<id column="id" jdbcType="VARCHAR" property="id" />
<result column="name" jdbcType="VARCHAR" property="name" />
<result column="type" jdbcType="VARCHAR" property="type" />
<result column="size" jdbcType="BIGINT" property="size" />
<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" />
</resultMap>
<sql id="Example_Where_Clause">
<where>
@ -68,7 +69,7 @@
</where>
</sql>
<sql id="Base_Column_List">
id, name, type, size, create_time, update_time
id, name, type, engine, create_time, update_time, size
</sql>
<select id="selectByExample" parameterType="io.metersphere.base.domain.FileMetadataExample" resultMap="BaseResultMap">
select
@ -102,11 +103,11 @@
</delete>
<insert id="insert" parameterType="io.metersphere.base.domain.FileMetadata">
insert into file_metadata (id, name, type,
size, create_time, update_time
)
engine, create_time, update_time,
size)
values (#{id,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR}, #{type,jdbcType=VARCHAR},
#{size,jdbcType=BIGINT}, #{createTime,jdbcType=BIGINT}, #{updateTime,jdbcType=BIGINT}
)
#{engine,jdbcType=VARCHAR}, #{createTime,jdbcType=BIGINT}, #{updateTime,jdbcType=BIGINT},
#{size,jdbcType=BIGINT})
</insert>
<insert id="insertSelective" parameterType="io.metersphere.base.domain.FileMetadata">
insert into file_metadata
@ -120,8 +121,8 @@
<if test="type != null">
type,
</if>
<if test="size != null">
size,
<if test="engine != null">
engine,
</if>
<if test="createTime != null">
create_time,
@ -129,6 +130,9 @@
<if test="updateTime != null">
update_time,
</if>
<if test="size != null">
size,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="id != null">
@ -140,8 +144,8 @@
<if test="type != null">
#{type,jdbcType=VARCHAR},
</if>
<if test="size != null">
#{size,jdbcType=BIGINT},
<if test="engine != null">
#{engine,jdbcType=VARCHAR},
</if>
<if test="createTime != null">
#{createTime,jdbcType=BIGINT},
@ -149,6 +153,9 @@
<if test="updateTime != null">
#{updateTime,jdbcType=BIGINT},
</if>
<if test="size != null">
#{size,jdbcType=BIGINT},
</if>
</trim>
</insert>
<select id="countByExample" parameterType="io.metersphere.base.domain.FileMetadataExample" resultType="java.lang.Long">
@ -169,8 +176,8 @@
<if test="record.type != null">
type = #{record.type,jdbcType=VARCHAR},
</if>
<if test="record.size != null">
size = #{record.size,jdbcType=BIGINT},
<if test="record.engine != null">
engine = #{record.engine,jdbcType=VARCHAR},
</if>
<if test="record.createTime != null">
create_time = #{record.createTime,jdbcType=BIGINT},
@ -178,6 +185,9 @@
<if test="record.updateTime != null">
update_time = #{record.updateTime,jdbcType=BIGINT},
</if>
<if test="record.size != null">
size = #{record.size,jdbcType=BIGINT},
</if>
</set>
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
@ -188,9 +198,10 @@
set id = #{record.id,jdbcType=VARCHAR},
name = #{record.name,jdbcType=VARCHAR},
type = #{record.type,jdbcType=VARCHAR},
size = #{record.size,jdbcType=BIGINT},
engine = #{record.engine,jdbcType=VARCHAR},
create_time = #{record.createTime,jdbcType=BIGINT},
update_time = #{record.updateTime,jdbcType=BIGINT}
update_time = #{record.updateTime,jdbcType=BIGINT},
size = #{record.size,jdbcType=BIGINT}
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
</if>
@ -204,8 +215,8 @@
<if test="type != null">
type = #{type,jdbcType=VARCHAR},
</if>
<if test="size != null">
size = #{size,jdbcType=BIGINT},
<if test="engine != null">
engine = #{engine,jdbcType=VARCHAR},
</if>
<if test="createTime != null">
create_time = #{createTime,jdbcType=BIGINT},
@ -213,6 +224,9 @@
<if test="updateTime != null">
update_time = #{updateTime,jdbcType=BIGINT},
</if>
<if test="size != null">
size = #{size,jdbcType=BIGINT},
</if>
</set>
where id = #{id,jdbcType=VARCHAR}
</update>
@ -220,9 +234,10 @@
update file_metadata
set name = #{name,jdbcType=VARCHAR},
type = #{type,jdbcType=VARCHAR},
size = #{size,jdbcType=BIGINT},
engine = #{engine,jdbcType=VARCHAR},
create_time = #{createTime,jdbcType=BIGINT},
update_time = #{updateTime,jdbcType=BIGINT}
update_time = #{updateTime,jdbcType=BIGINT},
size = #{size,jdbcType=BIGINT}
where id = #{id,jdbcType=VARCHAR}
</update>
</mapper>

View File

@ -11,7 +11,7 @@
<result column="create_time" jdbcType="BIGINT" property="createTime" />
<result column="update_time" jdbcType="BIGINT" property="updateTime" />
<result column="language" jdbcType="VARCHAR" property="language" />
<collection property="roles" ofType="io.metersphere.base.domain.Role">
<collection property="roles" javaType="arraylist" ofType="io.metersphere.base.domain.Role">
<id column="rid" jdbcType="VARCHAR" property="id" />
<result column="rname" jdbcType="VARCHAR" property="name" />
</collection>

View File

@ -1,5 +1,5 @@
package io.metersphere.commons.constants;
public enum EngineType {
JMX
DOCKER, KUBERNETES
}

View File

@ -0,0 +1,5 @@
package io.metersphere.commons.constants;
public enum FileType {
JMX
}

View File

@ -0,0 +1,32 @@
package io.metersphere.controller.request;
public class TestRequest {
int size;
String fileString;
String testId;
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
public String getFileString() {
return fileString;
}
public void setFileString(String fileString) {
this.fileString = fileString;
}
public String getTestId() {
return testId;
}
public void setTestId(String testId) {
this.testId = testId;
}
}

View File

@ -1,21 +1,39 @@
package io.metersphere.engine;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
public class EngineContext {
private String engineId;
private String testId;
private String testName;
private String namespace;
private String engineType;
private InputStream inputStream;
private String fileType;
private String content;
private Map<String, Object> properties = new HashMap<>();
public String getEngineId() {
return engineId;
public String getTestId() {
return testId;
}
public void setEngineId(String engineId) {
this.engineId = engineId;
public void setTestId(String testId) {
this.testId = testId;
}
public String getTestName() {
return testName;
}
public void setTestName(String testName) {
this.testName = testName;
}
public String getNamespace() {
return namespace;
}
public void setNamespace(String namespace) {
this.namespace = namespace;
}
public String getEngineType() {
@ -26,14 +44,6 @@ public class EngineContext {
this.engineType = engineType;
}
public InputStream getInputStream() {
return inputStream;
}
public void setInputStream(InputStream inputStream) {
this.inputStream = inputStream;
}
public void addProperty(String key, Object value) {
this.properties.put(key, value);
}
@ -41,4 +51,20 @@ public class EngineContext {
public Object getProperty(String key) {
return this.properties.get(key);
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getFileType() {
return fileType;
}
public void setFileType(String fileType) {
this.fileType = fileType;
}
}

View File

@ -7,29 +7,34 @@ import io.metersphere.base.domain.FileMetadata;
import io.metersphere.base.domain.LoadTestWithBLOBs;
import io.metersphere.commons.constants.EngineType;
import io.metersphere.commons.exception.MSException;
import io.metersphere.engine.jmx.JmxEngine;
import io.metersphere.engine.docker.DockerTestEngine;
import io.metersphere.engine.kubernetes.KubernetesTestEngine;
import io.metersphere.parse.EngineSourceParser;
import io.metersphere.parse.EngineSourceParserFactory;
import org.apache.commons.lang3.StringUtils;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
public class EngineFactory {
public static Engine createEngine(String engineType) {
final EngineType type = EngineType.valueOf(engineType);
if (type == EngineType.JMX) {
return new JmxEngine();
switch (type) {
case DOCKER:
return new DockerTestEngine();
case KUBERNETES:
return new KubernetesTestEngine();
}
return null;
}
public static EngineContext createContext(LoadTestWithBLOBs loadTest, FileMetadata fileMetadata, FileContent fileContent) throws Exception {
final EngineContext engineContext = new EngineContext();
engineContext.setEngineId(loadTest.getId());
engineContext.setInputStream(new ByteArrayInputStream(fileContent.getFile()));
engineContext.setEngineType(fileMetadata.getType());
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())) {
final JSONArray jsonArray = JSONObject.parseArray(loadTest.getLoadConfiguration());
@ -40,14 +45,14 @@ public class EngineFactory {
}
}
final EngineSourceParser engineSourceParser = EngineSourceParserFactory.createEngineSourceParser(engineContext.getEngineType());
final EngineSourceParser engineSourceParser = EngineSourceParserFactory.createEngineSourceParser(engineContext.getFileType());
if (engineSourceParser == null) {
MSException.throwException("未知的文件类型!");
}
final InputStream inputStream = engineSourceParser.parse(engineContext, engineContext.getInputStream());
engineContext.setInputStream(inputStream);
String content = engineSourceParser.parse(engineContext, new ByteArrayInputStream(fileContent.getFile()));
engineContext.setContent(content);
return engineContext;
}

View File

@ -0,0 +1,59 @@
package io.metersphere.engine.docker;
import io.metersphere.commons.exception.MSException;
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;
@Override
public boolean init(EngineContext context) {
// todo 初始化操作
this.context = context;
return true;
}
@Override
public void start() {
RestTemplate restTemplate = new RestTemplate();
String testId = context.getTestId();
String content = context.getContent();
String uri = "http://localhost:8082/jmeter/container/start";
TestRequest testRequest = new TestRequest();
testRequest.setSize(1);
testRequest.setTestId(testId);
testRequest.setFileString(content);
String taskStatusUri = "http://localhost:8082/jmeter/task/status/" + testId;
List containerList = restTemplate.getForObject(taskStatusUri, List.class);
for (int i = 0; i < containerList.size(); i++) {
HashMap h = (HashMap) containerList.get(i);
if (StringUtils.equals((String)h.get("State"), "running")) {
MSException.throwException("the test is running!");
}
}
restTemplate.postForObject(uri, testRequest, String.class);
}
@Override
public void stop() {
RestTemplate restTemplate = new RestTemplate();
String testId = context.getTestId();
String uri = "http://localhost:8082/jmeter/container/stop" + testId;
restTemplate.getForObject(uri, String.class);
}
}

View File

@ -1,106 +0,0 @@
package io.metersphere.engine.jmx;
import io.metersphere.engine.Engine;
import io.metersphere.engine.EngineContext;
import io.metersphere.engine.EngineThread;
import io.metersphere.engine.jmx.client.DistributedRunner;
import io.metersphere.engine.jmx.client.JmeterProperties;
import org.apache.jmeter.JMeter;
import org.apache.jmeter.save.SaveService;
import org.apache.jmeter.services.FileServer;
import org.apache.jmeter.threads.ThreadGroup;
import org.apache.jorphan.collections.HashTree;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Set;
public class JmxEngine extends EngineThread implements Engine {
private static final Logger log = LoggerFactory.getLogger(JmxEngine.class);
/// todo从测试属性中读取
private final static Integer MAX_DURATION = 60;
/// todo从测试属性中读取
private final static String REMOTE_HOSTS = "127.0.0.1";
/// todojmeter home如何确定
private final static String jmeterHome = "/opt/fit2cloud/apache-jmeter-5.2.1";
private static Method readTreeMethod;
static {
try {
readTreeMethod = SaveService.class.getDeclaredMethod("readTree", InputStream.class, File.class);
readTreeMethod.setAccessible(true);
} catch (NoSuchMethodException e) {
// ignore
}
}
private EngineContext context;
private DistributedRunner runner;
private static void setMaxTestDuration(HashTree jmxTree) {
for (HashTree item : jmxTree.values()) {
Set treeKeys = item.keySet();
for (Object key : treeKeys) {
if (key instanceof ThreadGroup) {
((ThreadGroup) key).setProperty(ThreadGroup.SCHEDULER, true);
((ThreadGroup) key).setProperty(ThreadGroup.DURATION, MAX_DURATION);
}
}
}
}
@Override
public String getEngineName() {
return "JMX";
}
@Override
public boolean init(EngineContext context) {
this.context = context;
new JmeterProperties(JmxEngine.jmeterHome).initJmeterProperties();
FileServer.getFileServer().setBaseForScript(new File(JmxEngine.jmeterHome + File.separator + "nothing"));
final HashTree jmxTree = loadTree(this.context.getInputStream());
if (jmxTree == null) {
return false;
}
JMeter.convertSubTree(jmxTree, true);
setMaxTestDuration(jmxTree);
this.runner = new DistributedRunner(jmxTree, REMOTE_HOSTS);
return true;
}
@Override
public void run() {
try {
this.runner.run();
} catch (Throwable e) {
log.error("run test error, id: " + this.context.getEngineId(), e);
}
}
@Override
public void stop() {
super.stop(false);
this.runner.stop();
}
private HashTree loadTree(InputStream inputStream) {
try (BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream)) {
return (HashTree) readTreeMethod.invoke(null, bufferedInputStream, null);
} catch (Exception e) {
log.error("Failed to load tree", e);
}
return null;
}
}

View File

@ -1,137 +0,0 @@
package io.metersphere.engine.jmx.client;
import org.apache.jmeter.engine.ClientJMeterEngine;
import org.apache.jmeter.engine.JMeterEngine;
import org.apache.jmeter.report.dashboard.ReportGenerator;
import org.apache.jmeter.samplers.Remoteable;
import org.apache.jmeter.testelement.TestStateListener;
import org.apache.jmeter.util.JMeterUtils;
import org.apache.jorphan.collections.HashTree;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class DistributedRunner extends org.apache.jmeter.engine.DistributedRunner {
private static final String HOSTS_SEPARATOR = ",";
private HashTree jmxTree;
private String hosts;
// 脚本运行完成后是否停止jmeter-server
private boolean remoteStop = false;
public DistributedRunner(HashTree jmxTree, String hosts) {
this.jmxTree = jmxTree;
this.hosts = hosts;
}
public void run() {
final List<String> hosts = getRemoteHosts();
final ListenToTest listener = new ListenToTest(remoteStop, null);
jmxTree.add(jmxTree.getArray()[0], listener);
init(hosts, jmxTree);
listener.setStartedRemoteEngines(new ArrayList<>(getEngines()));
start();
}
private List<String> getRemoteHosts() {
StringTokenizer st = new StringTokenizer(hosts, HOSTS_SEPARATOR);
List<String> list = new LinkedList<>();
while (st.hasMoreElements()) {
list.add((String) st.nextElement());
}
return list;
}
private static class ListenToTest implements TestStateListener, Remoteable {
private final Logger log = LoggerFactory.getLogger(ListenToTest.class);
private final ReportGenerator reportGenerator;
private AtomicInteger startedRemoteEngines = new AtomicInteger(0);
private ConcurrentLinkedQueue<JMeterEngine> remoteEngines = new ConcurrentLinkedQueue<>();
private boolean remoteStop;
ListenToTest(boolean remoteStop, ReportGenerator reportGenerator) {
this.remoteStop = remoteStop;
this.reportGenerator = reportGenerator;
}
void setStartedRemoteEngines(List<JMeterEngine> engines) {
this.remoteEngines.clear();
this.remoteEngines.addAll(engines);
this.startedRemoteEngines = new AtomicInteger(remoteEngines.size());
}
@Override
// N.B. this is called by a daemon RMI thread from the remote host
public void testEnded(String host) {
final long now = System.currentTimeMillis();
log.info("Finished remote host: {} ({})", host, now);
if (startedRemoteEngines.decrementAndGet() <= 0) {
log.info("All remote engines have ended test, starting RemoteTestStopper thread");
Thread stopSoon = new Thread(() -> endTest(true), "RemoteTestStopper");
// the calling thread is a daemon; this thread must not be
// see Bug 59391
stopSoon.setDaemon(false);
stopSoon.start();
}
}
@Override
public void testEnded() {
endTest(false);
}
@Override
public void testStarted(String host) {
final long now = System.currentTimeMillis();
log.info("Started remote host: {} ({})", host, now);
}
@Override
public void testStarted() {
if (log.isInfoEnabled()) {
final long now = System.currentTimeMillis();
log.info("{} ({})", JMeterUtils.getResString("running_test"), now);//$NON-NLS-1$
}
}
private void endTest(boolean isDistributed) {
long now = System.currentTimeMillis();
if (isDistributed) {
log.info("Tidying up remote @ " + new Date(now) + " (" + now + ")");
} else {
log.info("Tidying up ... @ " + new Date(now) + " (" + now + ")");
}
if (isDistributed) {
if (remoteStop) {
log.info("Exiting remote servers:" + remoteEngines);
for (JMeterEngine engine : remoteEngines) {
log.info("Exiting remote server:" + engine);
engine.exit();
}
}
try {
TimeUnit.SECONDS.sleep(5); // Allow listeners to close files
} catch (InterruptedException ignored) {
Thread.currentThread().interrupt();
}
ClientJMeterEngine.tidyRMI(log);
}
if (reportGenerator != null) {
try {
log.info("Generating Dashboard");
reportGenerator.generate();
log.info("Dashboard generated");
} catch (Exception ex) {
System.err.println("Error generating the report: " + ex);
log.error("Error generating the report: {}", ex.getMessage(), ex);
}
}
log.info("... end of run");
}
}
}

View File

@ -1,70 +0,0 @@
package io.metersphere.engine.jmx.client;
import org.apache.jmeter.util.JMeterUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Map;
import java.util.Properties;
public class JmeterProperties extends Properties {
private final static Logger logger = LoggerFactory.getLogger(JmeterProperties.class);
private final String jmeterHome;
public JmeterProperties(String jmeterHome) {
this.jmeterHome = jmeterHome;
}
public void initJmeterProperties() {
JMeterUtils.loadJMeterProperties(getJmeterHomeBin() + File.separator + "jmeter.properties");
JMeterUtils.setJMeterHome(getJmeterHome());
JMeterUtils.initLocale();
Properties jmeterProps = JMeterUtils.getJMeterProperties();
// Add local JMeter properties, if the file is found
String userProp = JMeterUtils.getPropDefault("user.properties", "");
if (userProp.length() > 0) {
File file = JMeterUtils.findFile(userProp);
if (file.canRead()) {
try (FileInputStream fis = new FileInputStream(file)) {
Properties tmp = new Properties();
tmp.load(fis);
jmeterProps.putAll(tmp);
} catch (IOException e) {
logger.error("Failed to init jmeter properties", e);
}
}
}
// Add local system properties, if the file is found
String sysProp = JMeterUtils.getPropDefault("system.properties", "");
if (sysProp.length() > 0) {
File file = JMeterUtils.findFile(sysProp);
if (file.canRead()) {
try (FileInputStream fis = new FileInputStream(file)) {
System.getProperties().load(fis);
} catch (IOException e) {
logger.error("Failed to init jmeter properties", e);
}
}
}
jmeterProps.put("jmeter.version", JMeterUtils.getJMeterVersion());
for (Map.Entry<Object, Object> entry : jmeterProps.entrySet()) {
put(entry.getKey(), entry.getValue());
}
}
private String getJmeterHome() {
return jmeterHome;
}
private String getJmeterHomeBin() {
return getJmeterHome() + File.separator + "bin";
}
}

View File

@ -0,0 +1,77 @@
package io.metersphere.engine.kubernetes;
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.LogUtil;
import io.metersphere.engine.Engine;
import io.metersphere.engine.EngineContext;
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 java.util.HashMap;
public class KubernetesTestEngine implements Engine {
private EngineContext context;
@Override
public boolean init(EngineContext context) {
// todo 初始化操作
this.context = context;
return true;
}
@Override
public void start() {
if (context == null) {
LogUtil.warn("Please initial the engine.");
return;
}
// todo 运行测试
ClientCredential credential = new ClientCredential();
credential.setMasterUrl("https://172.16.10.93:6443");
KubernetesProvider kubernetesProvider = new KubernetesProvider(JSON.toJSONString(credential));
// create namespace
kubernetesProvider.confirmNamespace(context.getNamespace());
// create cm
try (KubernetesClient client = kubernetesProvider.getKubernetesClient()) {
String configMapName = context.getTestId() + "-files";
ConfigMap configMap = client.configMaps().inNamespace(context.getNamespace()).withName(configMapName).get();
if (configMap == null) {
ConfigMap item = new ConfigMap();
item.setMetadata(new ObjectMeta() {{
setName(configMapName);
}});
item.setData(new HashMap<String, String>() {{
put(context.getTestId() + ".jmx", context.getContent());
}});
client.configMaps().inNamespace(context.getNamespace()).create(item);
}
}
// create jmeter
try {
Jmeter jmeter = new Jmeter();
jmeter.setMetadata(new ObjectMeta() {{
setNamespace(context.getNamespace());
setName(context.getTestId());
}});
jmeter.setSpec(new JmeterSpec() {{
setReplicas(1);
setImage("registry.fit2cloud.com/metersphere/jmeter-master:0.0.2");
}});
LogUtil.info("Load test started. " + context.getTestId());
kubernetesProvider.applyCustomResource(jmeter);
} catch (Exception e) {
LogUtil.error(e);
}
}
@Override
public void stop() {
}
}

View File

@ -0,0 +1,36 @@
package io.metersphere.engine.kubernetes.crds;
import io.fabric8.kubernetes.client.CustomResource;
public class MeterSphereCustomResource extends CustomResource {
private String crd;
private Object spec;
private Object status;
public String getCrd() {
return crd;
}
public void setCrd(String crd) {
this.crd = crd;
}
public Object getSpec() {
return spec;
}
public void setSpec(Object spec) {
this.spec = spec;
}
public Object getStatus() {
return status;
}
public void setStatus(Object status) {
this.status = status;
}
}

View File

@ -0,0 +1,10 @@
package io.metersphere.engine.kubernetes.crds;
import io.fabric8.kubernetes.api.builder.Function;
import io.fabric8.kubernetes.client.CustomResourceDoneable;
public class MeterSphereCustomResourceDoneable<T extends MeterSphereCustomResource> extends CustomResourceDoneable<T> {
public MeterSphereCustomResourceDoneable(T resource, Function<T, T> function) {
super(resource, function);
}
}

View File

@ -0,0 +1,6 @@
package io.metersphere.engine.kubernetes.crds;
import io.fabric8.kubernetes.client.CustomResourceList;
public class MeterSphereCustomResourceList<T extends MeterSphereCustomResource> extends CustomResourceList<T> {
}

View File

@ -0,0 +1,33 @@
package io.metersphere.engine.kubernetes.crds.jmeter;
import io.metersphere.engine.kubernetes.crds.MeterSphereCustomResource;
public class Jmeter extends MeterSphereCustomResource {
public static final String CRD = "jmeters.metersphere.io";
public static final String KIND = "Jmeter";
private JmeterSpec spec;
private JmeterStatus status;
public Jmeter() {
this.setCrd(CRD);
this.setKind(KIND);
}
@Override
public JmeterSpec getSpec() {
return spec;
}
public void setSpec(JmeterSpec spec) {
this.spec = spec;
}
@Override
public JmeterStatus getStatus() {
return status;
}
public void setStatus(JmeterStatus status) {
this.status = status;
}
}

View File

@ -0,0 +1,10 @@
package io.metersphere.engine.kubernetes.crds.jmeter;
import io.fabric8.kubernetes.api.builder.Function;
import io.fabric8.kubernetes.client.CustomResourceDoneable;
public class JmeterDoneable extends CustomResourceDoneable<Jmeter> {
public JmeterDoneable(Jmeter resource, Function<Jmeter, Jmeter> function) {
super(resource, function);
}
}

View File

@ -0,0 +1,28 @@
package io.metersphere.engine.kubernetes.crds.jmeter;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import io.fabric8.kubernetes.api.model.KubernetesResource;
@JsonDeserialize
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class JmeterSpec implements KubernetesResource {
private int replicas = 1;
private String image;
public int getReplicas() {
return replicas;
}
public void setReplicas(int replicas) {
this.replicas = replicas;
}
public String getImage() {
return image;
}
public void setImage(String image) {
this.image = image;
}
}

View File

@ -0,0 +1,36 @@
package io.metersphere.engine.kubernetes.crds.jmeter;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import io.fabric8.kubernetes.api.model.KubernetesResource;
@JsonDeserialize
@JsonInclude(JsonInclude.Include.NON_NULL)
public class JmeterStatus implements KubernetesResource {
private String phase;
private String reason;
public String getPhase() {
return phase;
}
public void setPhase(String phase) {
this.phase = phase;
}
public String getReason() {
return reason;
}
public void setReason(String reason) {
this.reason = reason;
}
@Override
public String toString() {
return "JmeterStatus{" +
"phase='" + phase + '\'' +
", reason='" + reason + '\'' +
'}';
}
}

View File

@ -0,0 +1,267 @@
package io.metersphere.engine.kubernetes.provider;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.fabric8.kubernetes.api.model.*;
import io.fabric8.kubernetes.api.model.apiextensions.CustomResourceDefinition;
import io.fabric8.kubernetes.client.ConfigBuilder;
import io.fabric8.kubernetes.client.DefaultKubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.dsl.*;
import io.metersphere.engine.kubernetes.crds.MeterSphereCustomResource;
import io.metersphere.engine.kubernetes.crds.MeterSphereCustomResourceDoneable;
import io.metersphere.engine.kubernetes.crds.MeterSphereCustomResourceList;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import java.util.*;
import java.util.regex.Pattern;
public abstract class AbstractClientProvider {
private static ObjectMapper objectMapper = new ObjectMapper();
private String credential;
public AbstractClientProvider(String credential) {
setCredential(credential);
}
public String getCredential() {
return credential;
}
public void setCredential(String credential) {
this.credential = credential;
}
/**
* OpenShiftClient继承自KubernetesClientOpenShiftClient对OpenShift和kubernetes的共有资源也是用KubernetesClient代理的
* 所以可以在把client提取到抽象类
*/
public KubernetesClient getKubernetesClient() {
ClientCredential providerCredential = JSONObject.parseObject(getCredential(), ClientCredential.class);
io.fabric8.kubernetes.client.ConfigBuilder configBuilder = new ConfigBuilder();
configBuilder.withMasterUrl(providerCredential.getMasterUrl());
configBuilder.withOauthToken(providerCredential.getToken());
configBuilder.withTrustCerts(true);
//设置默认的 namespace null
configBuilder.withNamespace(null);
return new DefaultKubernetesClient(configBuilder.build());
}
public void validateCredential() {
try (KubernetesClient client = getKubernetesClient()) {
client.namespaces().list();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 确保指定的namespace存在不存在直接创建一个
*
* @param namespace namespace标识
* @return
*/
public synchronized String confirmNamespace(String namespace) {
KubernetesClient kubernetesClient = getKubernetesClient();
Namespace currentNamespace = kubernetesClient.namespaces().withName(namespace).get();
if (currentNamespace == null) {
Map<String, String> annotations = new HashMap<>();
Namespace newNamespace = new NamespaceBuilder()
.withNewMetadata()
.withName(namespace)
.withAnnotations(annotations)
.endMetadata()
.build();
currentNamespace = kubernetesClient.namespaces().createOrReplace(newNamespace);
}
return currentNamespace.getMetadata().getName();
}
/**
* 获取集群UUID当前集群UUID等于default namespace的UID
*
* @return
*/
public String getClusterUUID() {
KubernetesClient kubernetesClient = getKubernetesClient();
Namespace defaultNamespace = kubernetesClient.namespaces().withName("kube-system").get();
if (defaultNamespace == null) {
throw new RuntimeException("无法获取集群的kube-system namespace");
} else {
return defaultNamespace.getMetadata().getUid();
}
}
/**
* 确保docker registry存在
* 不存在 创建一个harbor-secret并修改serviceaccount default
*
* @param registry
*/
public void dockerRegistry(DockerRegistry registry) {
if (StringUtils.isEmpty(registry.getUsername()) ||
StringUtils.isEmpty(registry.getPassword()) ||
StringUtils.isEmpty(registry.getUrl())
) {
throw new RuntimeException("Please set the docker registry information");
}
String secretName = "docker-registry-ms-secret";
KubernetesClient kubernetesClient = getKubernetesClient();
Secret secretRegistry = new SecretBuilder()
.withNewMetadata().withName(secretName).endMetadata()
.addToData(".dockerconfigjson", DockerRegistryUtil.getDockerConfig(registry))
.withType("kubernetes.io/dockerconfigjson")
.build();
kubernetesClient.secrets().inNamespace(registry.getNamespace()).createOrReplace(secretRegistry);
//sa
ServiceAccount serviceAccount = kubernetesClient.serviceAccounts().inNamespace(registry.getNamespace())
.withName("default").get();
List<LocalObjectReference> imagePullSecrets = serviceAccount.getImagePullSecrets();
for (LocalObjectReference pullSecret : imagePullSecrets) {
if (secretName.equals(pullSecret.getName())) {
return;
}
}
LocalObjectReference localObjectReference = new LocalObjectReference(secretName);
imagePullSecrets.add(localObjectReference);
serviceAccount.setImagePullSecrets(imagePullSecrets);
kubernetesClient.serviceAccounts().inNamespace(registry.getNamespace())
.createOrReplace(serviceAccount);
}
public <T> T applyCustomResource(MeterSphereCustomResource customResource) throws Exception {
try (KubernetesClient kubernetesClient = getKubernetesClient()) {
CustomResourceDefinition crd = kubernetesClient.customResourceDefinitions().withName(customResource.getCrd()).get();
if (crd == null) {
throw new Exception("CRD does not exists.");
}
MixedOperation<MeterSphereCustomResource, MeterSphereCustomResourceList, MeterSphereCustomResourceDoneable, Resource<MeterSphereCustomResource, MeterSphereCustomResourceDoneable>>
operation = kubernetesClient.customResources(crd, MeterSphereCustomResource.class, MeterSphereCustomResourceList.class, MeterSphereCustomResourceDoneable.class);
MeterSphereCustomResource replace = operation.inNamespace(customResource.getMetadata().getNamespace()).createOrReplace(customResource);
return (T) objectMapper.readValue(objectMapper.writeValueAsString(replace), customResource.getClass());
}
}
public boolean deleteCustomResource(MeterSphereCustomResource customResource) {
try (KubernetesClient kubernetesClient = getKubernetesClient()) {
CustomResourceDefinition crd = kubernetesClient.customResourceDefinitions().withName(customResource.getCrd()).get();
MixedOperation<MeterSphereCustomResource, MeterSphereCustomResourceList, MeterSphereCustomResourceDoneable, Resource<MeterSphereCustomResource, MeterSphereCustomResourceDoneable>>
operation = kubernetesClient.customResources(crd, MeterSphereCustomResource.class, MeterSphereCustomResourceList.class, MeterSphereCustomResourceDoneable.class);
Boolean result = operation.inNamespace(customResource.getMetadata().getNamespace()).withName(customResource.getMetadata().getName()).cascading(true).delete();
return result == null ? false : result;
}
}
public <T> T getCustomResource(MeterSphereCustomResource customResource) throws Exception {
try (KubernetesClient kubernetesClient = getKubernetesClient()) {
CustomResourceDefinition crd = kubernetesClient.customResourceDefinitions().withName(customResource.getCrd()).get();
MixedOperation<MeterSphereCustomResource, MeterSphereCustomResourceList, MeterSphereCustomResourceDoneable, Resource<MeterSphereCustomResource, MeterSphereCustomResourceDoneable>>
operation = kubernetesClient.customResources(crd, MeterSphereCustomResource.class, MeterSphereCustomResourceList.class, MeterSphereCustomResourceDoneable.class);
MeterSphereCustomResource meterSphereCustomResource = operation.inNamespace(customResource.getMetadata().getNamespace()).withName(customResource.getMetadata().getName()).get();
if (meterSphereCustomResource == null) {
return null;
}
return (T) objectMapper.readValue(objectMapper.writeValueAsString(meterSphereCustomResource), customResource.getClass());
}
}
public <T> List<T> listCustomResource(MeterSphereCustomResource customResource) throws Exception {
try (KubernetesClient kubernetesClient = getKubernetesClient()) {
CustomResourceDefinition crd = kubernetesClient.customResourceDefinitions().withName(customResource.getCrd()).get();
MixedOperation<MeterSphereCustomResource, MeterSphereCustomResourceList, MeterSphereCustomResourceDoneable, Resource<MeterSphereCustomResource, MeterSphereCustomResourceDoneable>>
operation = kubernetesClient.customResources(crd, MeterSphereCustomResource.class, MeterSphereCustomResourceList.class, MeterSphereCustomResourceDoneable.class);
MeterSphereCustomResourceList list;
if (StringUtils.isNotEmpty(customResource.getMetadata().getNamespace())) {
list = operation.inNamespace(customResource.getMetadata().getNamespace()).list();
} else {
list = operation.inAnyNamespace().list();
}
List<T> resultList = new ArrayList<>();
for (Object cr : list.getItems()) {
resultList.add((T) objectMapper.readValue(objectMapper.writeValueAsString(cr), customResource.getClass()));
}
return resultList;
}
}
public boolean checkPVCNotExists(String namespace, String statefulsetName) {
KubernetesClient kubernetesClient = getKubernetesClient();
NonNamespaceOperation<PersistentVolumeClaim, PersistentVolumeClaimList, DoneablePersistentVolumeClaim, Resource<PersistentVolumeClaim, DoneablePersistentVolumeClaim>> operation = kubernetesClient.persistentVolumeClaims()
.inNamespace(namespace);
PersistentVolumeClaimList pvcList = operation.list(100, null);
Pattern compile = Pattern.compile(statefulsetName + "-\\d+");
return checkPVCNotExists(pvcList, compile, operation);
}
private boolean checkPVCNotExists(PersistentVolumeClaimList pvcList, Pattern compile, NonNamespaceOperation<PersistentVolumeClaim, PersistentVolumeClaimList, DoneablePersistentVolumeClaim, Resource<PersistentVolumeClaim, DoneablePersistentVolumeClaim>> operation) {
if (pvcList == null || CollectionUtils.isEmpty(pvcList.getItems())) {
return true;
}
Optional<PersistentVolumeClaim> claimOptional = pvcList.getItems().stream().filter(pvc -> compile.matcher(pvc.getMetadata().getName()).matches()).findAny();
if (claimOptional.isPresent()) {
return false;
} else if (StringUtils.isNotEmpty(pvcList.getMetadata().getContinue())) {
return checkPVCNotExists(operation.list(100, pvcList.getMetadata().getContinue()), compile, operation);
} else {
return true;
}
}
public boolean deletePVC(HasMetadata hasMetadata) {
KubernetesClient kubernetesClient = getKubernetesClient();
NonNamespaceOperation<PersistentVolumeClaim, PersistentVolumeClaimList, DoneablePersistentVolumeClaim, Resource<PersistentVolumeClaim, DoneablePersistentVolumeClaim>> operation = kubernetesClient.persistentVolumeClaims()
.inNamespace(hasMetadata.getMetadata().getNamespace());
if (MapUtils.isNotEmpty(hasMetadata.getMetadata().getLabels())) {
operation.withLabelSelector(new LabelSelector(null, hasMetadata.getMetadata().getLabels()));
}
if (StringUtils.isNotEmpty(hasMetadata.getMetadata().getName())) {
operation.withName(hasMetadata.getMetadata().getName());
}
Boolean delete = operation.delete();
return delete == null ? false : delete;
}
public boolean deleteNamespace(String namespace) {
KubernetesClient kubernetesClient = getKubernetesClient();
Boolean delete = kubernetesClient.namespaces().withName(namespace).delete();
return delete == null ? false : delete;
}
public String getLog(String namespace, String pod, String container, int tailingLines) {
try (KubernetesClient client = getKubernetesClient()) {
PrettyLoggable<String, LogWatch> loggable;
if (tailingLines > 0) {
loggable = client.pods().inNamespace(namespace).withName(pod).inContainer(container).tailingLines(tailingLines);
} else {
loggable = client.pods().inNamespace(namespace).withName(pod).inContainer(container);
}
return loggable.getLog();
}
}
}

View File

@ -0,0 +1,23 @@
package io.metersphere.engine.kubernetes.provider;
public class ClientCredential {
private String masterUrl;
private String token;
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;
}
}

View File

@ -0,0 +1,50 @@
package io.metersphere.engine.kubernetes.provider;
public class DockerRegistry {
private String url;
private String username;
private String password;
private String email;
private String namespace;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getNamespace() {
return namespace;
}
public void setNamespace(String namespace) {
this.namespace = namespace;
}
}

View File

@ -0,0 +1,24 @@
package io.metersphere.engine.kubernetes.provider;
import com.alibaba.fastjson.JSONObject;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
public class DockerRegistryUtil {
public static String getDockerConfig(DockerRegistry registry) {
Map<String, String> config = new HashMap<>();
config.put("username", registry.getUsername());
config.put("password", registry.getPassword());
config.put("auth", Base64.getEncoder().encodeToString((registry.getUsername() + ":" + registry.getPassword()).getBytes()));
JSONObject jb = new JSONObject();
jb.put(registry.getUrl(), config);
JSONObject result = new JSONObject();
result.put("auths", jb);
return Base64.getEncoder().encodeToString(result.toJSONString().getBytes());
}
}

View File

@ -0,0 +1,7 @@
package io.metersphere.engine.kubernetes.provider;
public class KubernetesProvider extends AbstractClientProvider {
public KubernetesProvider(String credential) {
super(credential);
}
}

View File

@ -5,5 +5,5 @@ import io.metersphere.engine.EngineContext;
import java.io.InputStream;
public interface EngineSourceParser {
InputStream parse(EngineContext context, InputStream source) throws Exception;
String parse(EngineContext context, InputStream source) throws Exception;
}

View File

@ -1,13 +1,13 @@
package io.metersphere.parse;
import io.metersphere.commons.constants.EngineType;
import io.metersphere.commons.constants.FileType;
import io.metersphere.parse.xml.XmlEngineSourceParse;
public class EngineSourceParserFactory {
public static EngineSourceParser createEngineSourceParser(String type) {
final EngineType engineType = EngineType.valueOf(type);
final FileType engineType = FileType.valueOf(type);
if (EngineType.JMX.equals(engineType)) {
if (FileType.JMX.equals(engineType)) {
return new XmlEngineSourceParse();
}

View File

@ -13,7 +13,7 @@ import java.io.InputStream;
public class XmlEngineSourceParse implements EngineSourceParser {
@Override
public InputStream parse(EngineContext context, InputStream source) throws Exception {
public String parse(EngineContext context, InputStream source) throws Exception {
final InputSource inputSource = new InputSource(source);
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
@ -21,7 +21,7 @@ public class XmlEngineSourceParse implements EngineSourceParser {
DocumentBuilder docBuilder = factory.newDocumentBuilder();
final Document document = docBuilder.parse(inputSource);
final DocumentParser documentParser = createDocumentParser(context.getEngineType());
final DocumentParser documentParser = createDocumentParser(context.getFileType());
return documentParser.parse(context, document);
}

View File

@ -6,5 +6,5 @@ import org.w3c.dom.Document;
import java.io.InputStream;
public interface DocumentParser {
InputStream parse(EngineContext context, Document document) throws Exception;
String parse(EngineContext context, Document document) throws Exception;
}

View File

@ -1,16 +1,18 @@
package io.metersphere.parse.xml.reader;
import io.metersphere.commons.constants.EngineType;
import io.metersphere.commons.constants.FileType;
import io.metersphere.parse.xml.reader.jmx.JmeterDocumentParser;
public class DocumentParserFactory {
public static DocumentParser createDocumentParser(String type) {
final EngineType engineType = EngineType.valueOf(type);
final FileType fileType = FileType.valueOf(type);
if (EngineType.JMX.equals(engineType)) {
return new JmeterDocumentParser();
switch (fileType) {
case JMX:
return new JmeterDocumentParser();
default:
break;
}
return null;
}
}

View File

@ -15,13 +15,11 @@ import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
public class JmeterDocumentParser implements DocumentParser {
private final static String HASH_TREE_ELEMENT = "hashTree";
private final static String TEST_PLAN = "TestPlan";
private final static String STRING_PROP = "stringProp";
private final static String COLLECTION_PROP = "collectionProp";
private final static String CONCURRENCY_THREAD_GROUP = "com.blazemeter.jmeter.threads.concurrency.ConcurrencyThreadGroup";
@ -32,7 +30,7 @@ public class JmeterDocumentParser implements DocumentParser {
private EngineContext context;
@Override
public InputStream parse(EngineContext context, Document document) throws Exception {
public String parse(EngineContext context, Document document) throws Exception {
this.context = context;
final Element jmeterTestPlan = document.getDocumentElement();
@ -49,18 +47,17 @@ public class JmeterDocumentParser implements DocumentParser {
}
}
return documentToInputStream(document);
return documentToString(document);
}
private InputStream documentToInputStream(Document document) throws TransformerException {
private String documentToString(Document document) throws TransformerException {
DOMSource domSource = new DOMSource(document);
StringWriter writer = new StringWriter();
StreamResult result = new StreamResult(writer);
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
transformer.transform(domSource, result);
final String resultStr = writer.toString();
return new ByteArrayInputStream(resultStr.getBytes(StandardCharsets.UTF_8));
return writer.toString();
}
private void parseHashTree(Element hashTree) {
@ -80,6 +77,8 @@ public class JmeterDocumentParser implements DocumentParser {
if (nodeNameEquals(ele, HASH_TREE_ELEMENT)) {
parseHashTree(ele);
} else if (nodeNameEquals(ele, TEST_PLAN)) {
processTearDownTestPlan(ele);
} else if (nodeNameEquals(ele, CONCURRENCY_THREAD_GROUP)) {
processConcurrencyThreadGroup(ele);
processCheckoutTimer(ele);
@ -100,12 +99,107 @@ public class JmeterDocumentParser implements DocumentParser {
}
}
private void processTearDownTestPlan(Element ele) {
/*<boolProp name="TestPlan.tearDown_on_shutdown">true</boolProp>*/
Document document = ele.getOwnerDocument();
Element tearDownSwitch = createBoolProp(document, "TestPlan.tearDown_on_shutdown", true);
ele.appendChild(tearDownSwitch);
Node hashTree = ele.getNextSibling();
while (!(hashTree instanceof Element)) {
hashTree = hashTree.getNextSibling();
}
/*
<PostThreadGroup guiclass="PostThreadGroupGui" testclass="PostThreadGroup" testname="tearDown Thread Group" enabled="true">
<stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
<elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true">
<boolProp name="LoopController.continue_forever">false</boolProp>
<stringProp name="LoopController.loops">1</stringProp>
</elementProp>
<stringProp name="ThreadGroup.num_threads">1</stringProp>
<stringProp name="ThreadGroup.ramp_time">1</stringProp>
<boolProp name="ThreadGroup.scheduler">false</boolProp>
<stringProp name="ThreadGroup.duration"></stringProp>
<stringProp name="ThreadGroup.delay"></stringProp>
<boolProp name="ThreadGroup.same_user_on_next_iteration">true</boolProp>
</PostThreadGroup>
*/
Element tearDownElement = document.createElement("PostThreadGroup");
tearDownElement.setAttribute("guiclass", "PostThreadGroupGui");
tearDownElement.setAttribute("testclass", "PostThreadGroup");
tearDownElement.setAttribute("testname", "tearDown Thread Group");
tearDownElement.setAttribute("enabled", "true");
tearDownElement.appendChild(createStringProp(document, "ThreadGroup.on_sample_error", "continue"));
tearDownElement.appendChild(createStringProp(document, "ThreadGroup.num_threads", "1"));
tearDownElement.appendChild(createStringProp(document, "ThreadGroup.ramp_time", "1"));
tearDownElement.appendChild(createStringProp(document, "ThreadGroup.duration", ""));
tearDownElement.appendChild(createStringProp(document, "ThreadGroup.delay", ""));
tearDownElement.appendChild(createBoolProp(document, "ThreadGroup.scheduler", false));
tearDownElement.appendChild(createBoolProp(document, "ThreadGroup.same_user_on_next_iteration", true));
Element elementProp = document.createElement("elementProp");
elementProp.setAttribute("name", "ThreadGroup.main_controller");
elementProp.setAttribute("elementType", "LoopController");
elementProp.setAttribute("guiclass", "LoopControlPanel");
elementProp.setAttribute("testclass", "LoopController");
elementProp.setAttribute("testname", "Loop Controller");
elementProp.setAttribute("enabled", "true");
elementProp.appendChild(createBoolProp(document, "LoopController.continue_forever", false));
elementProp.appendChild(createStringProp(document, "LoopController.loops", "1"));
tearDownElement.appendChild(elementProp);
hashTree.appendChild(tearDownElement);
Element tearDownHashTree = document.createElement(HASH_TREE_ELEMENT);
/*
<OnceOnlyController guiclass="OnceOnlyControllerGui" testclass="OnceOnlyController" testname="Once Only Controller" enabled="true"/>
*/
Element onceOnlyController = document.createElement("OnceOnlyController");
onceOnlyController.setAttribute("guiclass", "OnceOnlyControllerGui");
onceOnlyController.setAttribute("testclass", "OnceOnlyController");
onceOnlyController.setAttribute("testname", "Once Only Controller");
onceOnlyController.setAttribute("enabled", "true");
tearDownHashTree.appendChild(onceOnlyController);
/*
<hashTree>
<DebugSampler guiclass="TestBeanGUI" testclass="DebugSampler" testname="Debug Sampler" enabled="true">
<boolProp name="displayJMeterProperties">false</boolProp>
<boolProp name="displayJMeterVariables">true</boolProp>
<boolProp name="displaySystemProperties">false</boolProp>
</DebugSampler>
<hashTree/>
</hashTree>
*/
Element onceOnlyHashTree = document.createElement(HASH_TREE_ELEMENT);
Element debugSampler = document.createElement("DebugSampler");
debugSampler.setAttribute("guiclass", "TestBeanGUI");
debugSampler.setAttribute("testclass", "DebugSampler");
debugSampler.setAttribute("testname", "Debug Sampler");
debugSampler.setAttribute("enabled", "true");
debugSampler.appendChild(createBoolProp(document, "displayJMeterProperties", false));
debugSampler.appendChild(createBoolProp(document, "displayJMeterVariables", true));
debugSampler.appendChild(createBoolProp(document, "displaySystemProperties", false));
onceOnlyHashTree.appendChild(debugSampler);
// 添加空的 hashTree
onceOnlyHashTree.appendChild(document.createElement(HASH_TREE_ELEMENT));
tearDownHashTree.appendChild(onceOnlyHashTree);
hashTree.appendChild(tearDownHashTree);
// 添加backend listener
processCheckoutBackendListener(tearDownElement);
}
private Element createBoolProp(Document document, String name, boolean value) {
Element tearDownSwitch = document.createElement("boolProp");
tearDownSwitch.setAttribute("name", name);
tearDownSwitch.appendChild(document.createTextNode(String.valueOf(value)));
return tearDownSwitch;
}
private void processBackendListener(Element backendListener) {
KafkaProperties kafkaProperties = CommonBeanFactory.getBean(KafkaProperties.class);
Document document = backendListener.getOwnerDocument();
// 清空child
removeChildren(backendListener);
backendListener.appendChild(createStringProp(document, "classname", "io.github.rahulsinghai.jmeter.backendlistener.kafka.KafkaBackendClient"));
backendListener.appendChild(createStringProp(document, "QUEUE_SIZE", "5000"));
// elementProp
Element elementProp = document.createElement("elementProp");
elementProp.setAttribute("name", "arguments");
@ -139,6 +233,9 @@ 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
collectionProp.appendChild(createKafkaProp(document, "test.id", context.getTestId()));
collectionProp.appendChild(createKafkaProp(document, "test.name", context.getTestName()));
elementProp.appendChild(collectionProp);
// set elementProp
@ -263,6 +360,8 @@ public class JmeterDocumentParser implements DocumentParser {
collectionProp.appendChild(childCollectionProp);
timer.appendChild(collectionProp);
timerParent.appendChild(timer);
// 添加一个空的hashTree
timerParent.appendChild(document.createElement(HASH_TREE_ELEMENT));
}
private Element createStringProp(Document document, String name, String value) {

View File

@ -93,7 +93,9 @@ public class FuctionalTestService {
fileMetadata.setSize(file.getSize());
fileMetadata.setCreateTime(System.currentTimeMillis());
fileMetadata.setUpdateTime(System.currentTimeMillis());
fileMetadata.setType(EngineType.JMX.name());
fileMetadata.setType("jmx");
// TODO engine 选择
fileMetadata.setEngine(EngineType.DOCKER.name());
fileMetadataMapper.insert(fileMetadata);
FileContent fileContent = new FileContent();

View File

@ -1,10 +1,14 @@
package io.metersphere.service;
import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.*;
import io.metersphere.base.mapper.FileContentMapper;
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.exception.MSException;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.controller.request.testplan.*;
import io.metersphere.dto.LoadTestDTO;
import io.metersphere.engine.Engine;
@ -29,8 +33,6 @@ public class LoadTestService {
@Resource
private ExtLoadTestMapper extLoadTestMapper;
@Resource
private ProjectMapper projectMapper;
@Resource
private FileMetadataMapper fileMetadataMapper;
@Resource
private FileContentMapper fileContentMapper;
@ -51,7 +53,7 @@ public class LoadTestService {
public String save(SaveTestPlanRequest request, MultipartFile file) {
if (file == null) {
throw new IllegalArgumentException("文件不能为空!");
throw new IllegalArgumentException(Translator.get("file_cannot_be_null"));
}
final FileMetadata fileMetadata = saveFile(file);
@ -95,7 +97,9 @@ public class LoadTestService {
fileMetadata.setSize(file.getSize());
fileMetadata.setCreateTime(System.currentTimeMillis());
fileMetadata.setUpdateTime(System.currentTimeMillis());
fileMetadata.setType(EngineType.JMX.name());
fileMetadata.setType("JMX");
// TODO engine 选择
fileMetadata.setEngine(EngineType.DOCKER.name());
fileMetadataMapper.insert(fileMetadata);
FileContent fileContent = new FileContent();
@ -123,7 +127,7 @@ public class LoadTestService {
final LoadTestWithBLOBs loadTest = loadTestMapper.selectByPrimaryKey(request.getId());
if (loadTest == null) {
MSException.throwException("无法编辑测试,未找到测试:" + request.getId());
MSException.throwException(Translator.get("edit_load_test_not_found") + request.getId());
} else {
loadTest.setName(request.getName());
loadTest.setProjectId(request.getProjectId());
@ -141,23 +145,25 @@ public class LoadTestService {
public void run(RunTestPlanRequest request) {
final LoadTestWithBLOBs loadTest = loadTestMapper.selectByPrimaryKey(request.getId());
if (loadTest == null) {
MSException.throwException("无法运行测试,未找到测试:" + request.getId());
MSException.throwException(Translator.get("run_load_test_not_found") + request.getId());
}
final FileMetadata fileMetadata = fileService.getFileMetadataByTestId(request.getId());
if (fileMetadata == null) {
MSException.throwException("无法运行测试无法获取测试文件元信息测试ID" + request.getId());
MSException.throwException(Translator.get("run_load_test_file_not_found") + request.getId());
}
final FileContent fileContent = fileService.getFileContent(fileMetadata.getId());
if (fileContent == null) {
MSException.throwException("无法运行测试无法获取测试文件内容测试ID" + request.getId());
MSException.throwException(Translator.get("run_load_test_file_content_not_found") + request.getId());
}
System.out.println("开始运行:" + loadTest.getName());
final Engine engine = EngineFactory.createEngine(fileMetadata.getType());
LogUtil.info("Load test started " + loadTest.getName());
// engine type (DOCKER|KUBERNETES)
// todo set type
final Engine engine = EngineFactory.createEngine(fileMetadata.getEngine());
if (engine == null) {
MSException.throwException(String.format("无法运行测试未识别测试文件类型测试ID%s文件类型%s",
MSException.throwException(String.format("Test cannot be runtest ID%sfile type%s",
request.getId(),
fileMetadata.getType()));
}
@ -169,12 +175,12 @@ public class LoadTestService {
MSException.throwException(e);
}
if (!init) {
MSException.throwException(String.format("无法运行测试初始化运行环境失败测试ID%s", request.getId()));
MSException.throwException(Translator.get("run_load_test_file_init_error") + request.getId());
}
engine.start();
/// todo通过调用stop方法能够停止正在运行的engine但是如果部署了多个backend实例页面发送的停止请求如何定位到具体的engine
// todo通过调用stop方法能够停止正在运行的engine但是如果部署了多个backend实例页面发送的停止请求如何定位到具体的engine
}
public List<LoadTestDTO> recentTestPlans(QueryTestPlanRequest request) {

View File

@ -11,6 +11,7 @@ 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',

View File

@ -6,5 +6,11 @@
"workspace_name_is_null": "Workspace name cannot be null",
"workspace_name_already_exists": "The workspace name already exists",
"workspace_does_not_belong_to_user": "The current workspace does not belong to the current user",
"organization_does_not_belong_to_user": "The current organization does not belong to the current user"
"organization_does_not_belong_to_user": "The current organization does not belong to the current user",
"file_cannot_be_null": "File cannot be empty!",
"edit_load_test_not_found": "Cannot edit test, test not found:",
"run_load_test_not_found": "Cannot run test, test not found:",
"run_load_test_file_not_found": "Unable to run test, unable to get test file meta information, test ID:",
"run_load_test_file_content_not_found": "Cannot run test, cannot get test file content, test ID:",
"run_load_test_file_init_error": "Failed to run test, failed to initialize run environment, test ID:"
}

View File

@ -6,5 +6,11 @@
"workspace_name_is_null": "工作空间名不能为空",
"workspace_name_already_exists": "工作空间名已存在",
"workspace_does_not_belong_to_user": "当前工作空间不属于当前用户",
"organization_does_not_belong_to_user": "当前组织不属于当前用户"
"organization_does_not_belong_to_user": "当前组织不属于当前用户",
"file_cannot_be_null": "文件不能为空!",
"edit_load_test_not_found": "无法编辑测试,未找到测试:",
"run_load_test_not_found": "无法运行测试,未找到测试:",
"run_load_test_file_not_found": "无法运行测试无法获取测试文件元信息测试ID",
"run_load_test_file_content_not_found": "无法运行测试无法获取测试文件内容测试ID",
"run_load_test_file_init_error": "无法运行测试初始化运行环境失败测试ID"
}

View File

@ -0,0 +1,34 @@
package io.metersphere;
import io.fabric8.kubernetes.api.model.Pod;
import io.fabric8.kubernetes.api.model.PodList;
import io.fabric8.kubernetes.client.ConfigBuilder;
import io.fabric8.kubernetes.client.DefaultKubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClient;
import org.junit.Before;
import org.junit.Test;
public class BaseTest {
protected KubernetesClient kubernetesClient;
@Before
public void before() {
try {
ConfigBuilder configBuilder = new ConfigBuilder();
configBuilder.withMasterUrl("https://172.16.10.93:6443");
kubernetesClient = new DefaultKubernetesClient(configBuilder.build());
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
@Test
public void test1() {
PodList list = kubernetesClient.pods().list();
for (Pod item : list.getItems()) {
System.out.println(item);
}
}
}

View File

@ -27,6 +27,7 @@ import java.io.StringWriter;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class JmxFileParseTest {
private final static String HASH_TREE_ELEMENT = "hashTree";
private final static String TEST_PLAN = "TestPlan";
private final static String STRING_PROP = "stringProp";
private final static String CONCURRENCY_THREAD_GROUP = "com.blazemeter.jmeter.threads.concurrency.ConcurrencyThreadGroup";
private final static String VARIABLE_THROUGHPUT_TIMER = "kg.apc.jmeter.timers.VariableThroughputTimer";
@ -94,6 +95,8 @@ public class JmxFileParseTest {
if (nodeNameEquals(ele, HASH_TREE_ELEMENT)) {
parseHashTree(ele);
} else if (nodeNameEquals(ele, TEST_PLAN)) {
processTearDownTestPlan(ele);
} else if (nodeNameEquals(ele, CONCURRENCY_THREAD_GROUP)) {
processConcurrencyThreadGroup(ele);
processCheckoutTimer(ele);
@ -113,11 +116,106 @@ public class JmxFileParseTest {
}
}
private void processTearDownTestPlan(Element ele) {
/*<boolProp name="TestPlan.tearDown_on_shutdown">true</boolProp>*/
Document document = ele.getOwnerDocument();
Element tearDownSwitch = createBoolProp(document, "TestPlan.tearDown_on_shutdown", true);
ele.appendChild(tearDownSwitch);
Node hashTree = ele.getNextSibling();
while (!(hashTree instanceof Element)) {
hashTree = hashTree.getNextSibling();
}
/*
<PostThreadGroup guiclass="PostThreadGroupGui" testclass="PostThreadGroup" testname="tearDown Thread Group" enabled="true">
<stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
<elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true">
<boolProp name="LoopController.continue_forever">false</boolProp>
<stringProp name="LoopController.loops">1</stringProp>
</elementProp>
<stringProp name="ThreadGroup.num_threads">1</stringProp>
<stringProp name="ThreadGroup.ramp_time">1</stringProp>
<boolProp name="ThreadGroup.scheduler">false</boolProp>
<stringProp name="ThreadGroup.duration"></stringProp>
<stringProp name="ThreadGroup.delay"></stringProp>
<boolProp name="ThreadGroup.same_user_on_next_iteration">true</boolProp>
</PostThreadGroup>
*/
Element tearDownElement = document.createElement("PostThreadGroup");
tearDownElement.setAttribute("guiclass", "PostThreadGroupGui");
tearDownElement.setAttribute("testclass", "PostThreadGroup");
tearDownElement.setAttribute("testname", "tearDown Thread Group");
tearDownElement.setAttribute("enabled", "true");
tearDownElement.appendChild(createStringProp(document, "ThreadGroup.on_sample_error", "continue"));
tearDownElement.appendChild(createStringProp(document, "ThreadGroup.num_threads", "1"));
tearDownElement.appendChild(createStringProp(document, "ThreadGroup.ramp_time", "1"));
tearDownElement.appendChild(createStringProp(document, "ThreadGroup.duration", ""));
tearDownElement.appendChild(createStringProp(document, "ThreadGroup.delay", ""));
tearDownElement.appendChild(createBoolProp(document, "ThreadGroup.scheduler", false));
tearDownElement.appendChild(createBoolProp(document, "ThreadGroup.same_user_on_next_iteration", true));
Element elementProp = document.createElement("elementProp");
elementProp.setAttribute("name", "ThreadGroup.main_controller");
elementProp.setAttribute("elementType", "LoopController");
elementProp.setAttribute("guiclass", "LoopControlPanel");
elementProp.setAttribute("testclass", "LoopController");
elementProp.setAttribute("testname", "Loop Controller");
elementProp.setAttribute("enabled", "true");
elementProp.appendChild(createBoolProp(document, "LoopController.continue_forever", false));
elementProp.appendChild(createStringProp(document, "LoopController.loops", "1"));
tearDownElement.appendChild(elementProp);
hashTree.appendChild(tearDownElement);
Element tearDownHashTree = document.createElement(HASH_TREE_ELEMENT);
/*
<OnceOnlyController guiclass="OnceOnlyControllerGui" testclass="OnceOnlyController" testname="Once Only Controller" enabled="true"/>
*/
Element onceOnlyController = document.createElement("OnceOnlyController");
onceOnlyController.setAttribute("guiclass", "OnceOnlyControllerGui");
onceOnlyController.setAttribute("testclass", "OnceOnlyController");
onceOnlyController.setAttribute("testname", "Once Only Controller");
onceOnlyController.setAttribute("enabled", "true");
tearDownHashTree.appendChild(onceOnlyController);
/*
<hashTree>
<DebugSampler guiclass="TestBeanGUI" testclass="DebugSampler" testname="Debug Sampler" enabled="true">
<boolProp name="displayJMeterProperties">false</boolProp>
<boolProp name="displayJMeterVariables">true</boolProp>
<boolProp name="displaySystemProperties">false</boolProp>
</DebugSampler>
<hashTree/>
</hashTree>
*/
Element onceOnlyHashTree = document.createElement(HASH_TREE_ELEMENT);
Element debugSampler = document.createElement("DebugSampler");
debugSampler.setAttribute("guiclass", "TestBeanGUI");
debugSampler.setAttribute("testclass", "DebugSampler");
debugSampler.setAttribute("testname", "Debug Sampler");
debugSampler.setAttribute("enabled", "true");
debugSampler.appendChild(createBoolProp(document, "displayJMeterProperties", false));
debugSampler.appendChild(createBoolProp(document, "displayJMeterVariables", true));
debugSampler.appendChild(createBoolProp(document, "displaySystemProperties", false));
onceOnlyHashTree.appendChild(debugSampler);
// 添加空的 hashTree
onceOnlyHashTree.appendChild(document.createElement(HASH_TREE_ELEMENT));
tearDownHashTree.appendChild(onceOnlyHashTree);
hashTree.appendChild(tearDownHashTree);
// 添加backend listener
processCheckoutBackendListener(tearDownElement);
}
private Element createBoolProp(Document document, String name, boolean value) {
Element tearDownSwitch = document.createElement("boolProp");
tearDownSwitch.setAttribute("name", name);
tearDownSwitch.appendChild(document.createTextNode(String.valueOf(value)));
return tearDownSwitch;
}
private void processBackendListener(Element backendListener) {
Document document = backendListener.getOwnerDocument();
// 清空child
removeChildren(backendListener);
backendListener.appendChild(createStringProp(document, "classname", "io.github.rahulsinghai.jmeter.backendlistener.kafka.KafkaBackendClient"));
backendListener.appendChild(createStringProp(document, "QUEUE_SIZE", "5000"));
// elementProp
Element elementProp = document.createElement("elementProp");
elementProp.setAttribute("name", "arguments");
@ -151,6 +249,9 @@ public class JmxFileParseTest {
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
collectionProp.appendChild(createKafkaProp(document, "test.id", ""));
collectionProp.appendChild(createKafkaProp(document, "test.name", ""));
elementProp.appendChild(collectionProp);
// set elementProp