From 9b458f37319cf7cb7ba65acd08b3271b8fb6a07e Mon Sep 17 00:00:00 2001 From: liuruibin Date: Fri, 1 May 2020 10:00:04 +0800 Subject: [PATCH 001/109] fit2cloud -> metersphere --- src/main/resources/application.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 6c51bae..c45cedf 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,4 +1,4 @@ spring.application.name=node-controller -logging.file.path=/opt/fit2cloud/logs/${spring.application.name} +logging.file.path=/opt/metersphere/logs/${spring.application.name} server.port=8082 From 40e5212837e65401165ef1b878f80bcaabfdcd21 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Wed, 13 May 2020 11:07:22 +0800 Subject: [PATCH 002/109] get set --- .../metersphere/controller/request/DockerLoginRequest.java | 6 ++++-- .../java/io/metersphere/controller/request/TestRequest.java | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main/java/io/metersphere/controller/request/DockerLoginRequest.java b/src/main/java/io/metersphere/controller/request/DockerLoginRequest.java index fa01205..9767401 100644 --- a/src/main/java/io/metersphere/controller/request/DockerLoginRequest.java +++ b/src/main/java/io/metersphere/controller/request/DockerLoginRequest.java @@ -1,8 +1,10 @@ package io.metersphere.controller.request; -import lombok.Data; +import lombok.Getter; +import lombok.Setter; -@Data +@Getter +@Setter public class DockerLoginRequest { private String username; private String password; diff --git a/src/main/java/io/metersphere/controller/request/TestRequest.java b/src/main/java/io/metersphere/controller/request/TestRequest.java index f790f43..3ec0d3f 100644 --- a/src/main/java/io/metersphere/controller/request/TestRequest.java +++ b/src/main/java/io/metersphere/controller/request/TestRequest.java @@ -1,11 +1,13 @@ package io.metersphere.controller.request; -import lombok.Data; +import lombok.Getter; +import lombok.Setter; import java.util.HashMap; import java.util.Map; -@Data +@Getter +@Setter public class TestRequest extends DockerLoginRequest { private int size; From 81a2574764bffda3dc3a6d3625833b193ecde735 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=8C=AF?= Date: Thu, 4 Jun 2020 10:53:14 +0800 Subject: [PATCH 003/109] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=9E=84=E5=BB=BA?= =?UTF-8?q?=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Dockerfile b/Dockerfile index 644a0c6..19f885b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,6 +2,8 @@ FROM registry.fit2cloud.com/fit2cloud2/fabric8-java-alpine-openjdk8-jre MAINTAINER FIT2CLOUD +ARG MS_VERSION=dev + RUN mkdir -p /opt/apps ADD target/node-controller-1.0.jar /opt/apps @@ -10,6 +12,8 @@ ENV JAVA_APP_JAR=/opt/apps/node-controller-1.0.jar ENV AB_OFF=true +ENV MS_VERSION=${MS_VERSION} + ENV JAVA_OPTIONS=-Dfile.encoding=utf-8 CMD ["/deployments/run-java.sh"] From 86507eca3c623401e014b8c2d4de8b2fc369f5ae Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Mon, 15 Jun 2020 14:40:44 +0800 Subject: [PATCH 004/109] =?UTF-8?q?=E5=81=9C=E6=AD=A2=E5=AE=B9=E5=99=A8?= =?UTF-8?q?=E6=96=B9=E5=BC=8F=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/metersphere/service/JmeterOperateService.java | 2 +- src/main/java/io/metersphere/util/DockerClientService.java | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/metersphere/service/JmeterOperateService.java b/src/main/java/io/metersphere/service/JmeterOperateService.java index 743a623..e9505fd 100644 --- a/src/main/java/io/metersphere/service/JmeterOperateService.java +++ b/src/main/java/io/metersphere/service/JmeterOperateService.java @@ -129,7 +129,7 @@ public void stopContainer(String testId) { .withNameFilter(Collections.singletonList(testId)) .exec(); // container stop - list.forEach(container -> DockerClientService.stopContainer(dockerClient, container.getId())); + list.forEach(container -> DockerClientService.removeContainer(dockerClient, container.getId())); } public List taskStatus(String testId) { diff --git a/src/main/java/io/metersphere/util/DockerClientService.java b/src/main/java/io/metersphere/util/DockerClientService.java index 7222232..c429534 100644 --- a/src/main/java/io/metersphere/util/DockerClientService.java +++ b/src/main/java/io/metersphere/util/DockerClientService.java @@ -83,7 +83,10 @@ public static void stopContainer(DockerClient client, String containerId) { * @param containerId */ public static void removeContainer(DockerClient client, String containerId) { - client.removeContainerCmd(containerId).exec(); + client.removeContainerCmd(containerId) + .withForce(true) + .withRemoveVolumes(true) + .exec(); } } From b11bf2dc298125f5f48dfda069083712289d1446 Mon Sep 17 00:00:00 2001 From: shiziyuan9527 Date: Tue, 16 Jun 2020 17:20:49 +0800 Subject: [PATCH 005/109] =?UTF-8?q?=E5=88=A0=E9=99=A4=E5=AE=B9=E5=99=A8?= =?UTF-8?q?=E5=89=8D=E6=A3=80=E6=9F=A5=E5=AE=B9=E5=99=A8=E6=98=AF=E5=90=A6?= =?UTF-8?q?=E5=AD=98=E5=9C=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/JmeterOperateService.java | 4 +++- .../metersphere/util/DockerClientService.java | 17 +++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/metersphere/service/JmeterOperateService.java b/src/main/java/io/metersphere/service/JmeterOperateService.java index e9505fd..de1e3c5 100644 --- a/src/main/java/io/metersphere/service/JmeterOperateService.java +++ b/src/main/java/io/metersphere/service/JmeterOperateService.java @@ -77,7 +77,9 @@ public void onComplete() { try { FileUtils.forceDelete(new File(filePath)); LogUtil.info("Remove dir completed."); - DockerClientService.removeContainer(dockerClient, containerId); + if (DockerClientService.existContainer(dockerClient, containerId) > 0) { + DockerClientService.removeContainer(dockerClient, containerId); + } LogUtil.info("Remove container completed: " + containerId); } catch (IOException e) { LogUtil.error("Remove dir error: ", e); diff --git a/src/main/java/io/metersphere/util/DockerClientService.java b/src/main/java/io/metersphere/util/DockerClientService.java index c429534..79b0449 100644 --- a/src/main/java/io/metersphere/util/DockerClientService.java +++ b/src/main/java/io/metersphere/util/DockerClientService.java @@ -2,6 +2,7 @@ import com.github.dockerjava.api.DockerClient; import com.github.dockerjava.api.command.CreateContainerResponse; +import com.github.dockerjava.api.model.Container; import com.github.dockerjava.api.model.HostConfig; import com.github.dockerjava.api.model.Info; import com.github.dockerjava.core.DefaultDockerClientConfig; @@ -9,6 +10,9 @@ import io.metersphere.controller.request.DockerLoginRequest; import org.apache.commons.lang.StringUtils; +import java.util.Collections; +import java.util.List; + public class DockerClientService { /** @@ -89,4 +93,17 @@ public static void removeContainer(DockerClient client, String containerId) { .exec(); } + /** + * 容器是否存在 + * @param client + * @param containerId + */ + public static int existContainer(DockerClient client, String containerId) { + List list = client.listContainersCmd() + .withShowAll(true) + .withIdFilter(Collections.singleton(containerId)) + .exec(); + return list.size(); + } + } From 61a06bb02d6a012c763b7910919075d9b621062d Mon Sep 17 00:00:00 2001 From: shiziyuan9527 Date: Wed, 17 Jun 2020 11:01:50 +0800 Subject: [PATCH 006/109] =?UTF-8?q?=E5=90=AF=E5=8A=A8=E5=AE=B9=E5=99=A8?= =?UTF-8?q?=E5=89=8D=E6=A3=80=E6=9F=A5kafka?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/JmeterOperateService.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/main/java/io/metersphere/service/JmeterOperateService.java b/src/main/java/io/metersphere/service/JmeterOperateService.java index de1e3c5..8238282 100644 --- a/src/main/java/io/metersphere/service/JmeterOperateService.java +++ b/src/main/java/io/metersphere/service/JmeterOperateService.java @@ -17,6 +17,9 @@ import java.io.File; import java.io.IOException; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.Socket; import java.nio.charset.StandardCharsets; import java.util.*; import java.util.concurrent.TimeUnit; @@ -26,6 +29,9 @@ public class JmeterOperateService { public void startContainer(TestRequest testRequest) throws IOException { + String bootstrapServers = testRequest.getEnv().get("BOOTSTRAP_SERVERS"); + checkKafka(bootstrapServers); + LogUtil.info("Receive start container request, test id: {}", testRequest.getTestId()); DockerClient dockerClient = DockerClientService.connectDocker(testRequest); int size = testRequest.getSize(); @@ -90,6 +96,30 @@ public void onComplete() { }); } + private void checkKafka(String bootstrapServers) { + String[] servers = StringUtils.split(bootstrapServers, ","); + try { + for (String s : servers) { + String[] ipAndPort = s.split(":"); + //1,建立tcp + String ip = ipAndPort[0]; + int port = Integer.parseInt(ipAndPort[1]); + Socket soc = new Socket(); + soc.connect(new InetSocketAddress(ip, port), 1000); // 1s timeout + //2.输入内容 + String content = "1010"; + byte[] bs = content.getBytes(); + OutputStream os = soc.getOutputStream(); + os.write(bs); + //3.关闭 + soc.close(); + } + } catch (Exception e) { + LogUtil.error(e); + throw new RuntimeException("Failed to connect to Kafka"); + } + } + private String[] getEnvs(TestRequest testRequest) { Map env = testRequest.getEnv(); return env.keySet().stream().map(k -> k + "=" + env.get(k)).toArray(String[]::new); From f2e20db159710a8c6da3a76d1275fa5d4d315253 Mon Sep 17 00:00:00 2001 From: wangzhen-fit2cloud Date: Wed, 22 Jul 2020 19:41:37 +0800 Subject: [PATCH 007/109] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E5=8F=B7=E4=B8=BA=201.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 4 ++-- pom.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 19f885b..3f39b71 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,9 +6,9 @@ ARG MS_VERSION=dev RUN mkdir -p /opt/apps -ADD target/node-controller-1.0.jar /opt/apps +ADD target/node-controller-1.1.jar /opt/apps -ENV JAVA_APP_JAR=/opt/apps/node-controller-1.0.jar +ENV JAVA_APP_JAR=/opt/apps/node-controller-1.1.jar ENV AB_OFF=true diff --git a/pom.xml b/pom.xml index eaabcaf..a60ed5f 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.metersphere node-controller - 1.0 + 1.1 node-controller node-controller From 464747c5a073e7ebc4d41adc71ffe948ad01462e Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Tue, 11 Aug 2020 15:38:43 +0800 Subject: [PATCH 008/109] =?UTF-8?q?refactor:=20=E5=8E=BB=E6=8E=89=E4=B8=8D?= =?UTF-8?q?=E7=94=A8=E7=9A=84=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/metersphere/util/DockerClientService.java | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/main/java/io/metersphere/util/DockerClientService.java b/src/main/java/io/metersphere/util/DockerClientService.java index 79b0449..2b25f78 100644 --- a/src/main/java/io/metersphere/util/DockerClientService.java +++ b/src/main/java/io/metersphere/util/DockerClientService.java @@ -4,7 +4,6 @@ import com.github.dockerjava.api.command.CreateContainerResponse; import com.github.dockerjava.api.model.Container; import com.github.dockerjava.api.model.HostConfig; -import com.github.dockerjava.api.model.Info; import com.github.dockerjava.core.DefaultDockerClientConfig; import com.github.dockerjava.core.DockerClientBuilder; import io.metersphere.controller.request.DockerLoginRequest; @@ -21,11 +20,7 @@ public class DockerClientService { * @return */ public static DockerClient connectDocker() { - DockerClient dockerClient = DockerClientBuilder.getInstance().build(); - Info info = dockerClient.infoCmd().exec(); - LogUtil.info("docker的环境信息如下:================="); - LogUtil.info(info); - return dockerClient; + return DockerClientBuilder.getInstance().build(); } public static DockerClient connectDocker(DockerLoginRequest request) { @@ -37,11 +32,7 @@ public static DockerClient connectDocker(DockerLoginRequest request) { .withRegistryUsername(request.getUsername()) .withRegistryPassword(request.getPassword()) .build(); - DockerClient dockerClient = DockerClientBuilder.getInstance(config).build(); - Info info = dockerClient.infoCmd().exec(); - LogUtil.info("docker的环境信息如下:================="); - LogUtil.info(info); - return dockerClient; + return DockerClientBuilder.getInstance(config).build(); } /** @@ -95,6 +86,7 @@ public static void removeContainer(DockerClient client, String containerId) { /** * 容器是否存在 + * * @param client * @param containerId */ From 96bba101f670fc7f47fb604ceed0d78b43d1b2e4 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Fri, 21 Aug 2020 14:37:19 +0800 Subject: [PATCH 009/109] =?UTF-8?q?refactor:=20=E8=BE=93=E5=87=BA=20jmeter?= =?UTF-8?q?=20console=20=E7=9A=84=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../metersphere/service/JmeterOperateService.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/main/java/io/metersphere/service/JmeterOperateService.java b/src/main/java/io/metersphere/service/JmeterOperateService.java index 8238282..05c08bb 100644 --- a/src/main/java/io/metersphere/service/JmeterOperateService.java +++ b/src/main/java/io/metersphere/service/JmeterOperateService.java @@ -94,6 +94,21 @@ public void onComplete() { } }); }); + + containerIdList.forEach(containerId -> { + dockerClient.logContainerCmd(containerId) + .withFollowStream(true) + .withStdOut(true) + .withStdErr(true) + .withTailAll() + .exec(new InvocationBuilder.AsyncResultCallback() { + @Override + public void onNext(Frame item) { + LogUtil.info(new String(item.getPayload()).trim()); + } + }); + + }); } private void checkKafka(String bootstrapServers) { From 644299df6efbc55f604e915b1fab7dd3534db11b Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Mon, 31 Aug 2020 12:22:01 +0800 Subject: [PATCH 010/109] =?UTF-8?q?refactor:=20=E4=BB=A3=E7=A0=81=E7=BB=93?= =?UTF-8?q?=E6=9E=84=E8=B0=83=E6=95=B4=EF=BC=8C=E6=B7=BB=E5=8A=A0=20jmeter?= =?UTF-8?q?=20HEAP=20=E5=8F=82=E6=95=B0=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/io/metersphere/Application.java | 13 ------------- .../java/io/metersphere/node/Application.java | 19 +++++++++++++++++++ .../node/config/JmeterProperties.java | 13 +++++++++++++ .../controller/DockerClientController.java | 2 +- .../controller/JmeterOperateController.java | 6 +++--- .../request/DockerLoginRequest.java | 2 +- .../controller/request/TestRequest.java | 2 +- .../service/JmeterOperateService.java | 14 ++++++++++---- .../{ => node}/util/DockerClientService.java | 4 ++-- .../metersphere/{ => node}/util/LogUtil.java | 2 +- src/main/resources/logback.xml | 4 ++-- 11 files changed, 53 insertions(+), 28 deletions(-) delete mode 100644 src/main/java/io/metersphere/Application.java create mode 100644 src/main/java/io/metersphere/node/Application.java create mode 100644 src/main/java/io/metersphere/node/config/JmeterProperties.java rename src/main/java/io/metersphere/{ => node}/controller/DockerClientController.java (86%) rename src/main/java/io/metersphere/{ => node}/controller/JmeterOperateController.java (93%) rename src/main/java/io/metersphere/{ => node}/controller/request/DockerLoginRequest.java (79%) rename src/main/java/io/metersphere/{ => node}/controller/request/TestRequest.java (89%) rename src/main/java/io/metersphere/{ => node}/service/JmeterOperateService.java (95%) rename src/main/java/io/metersphere/{ => node}/util/DockerClientService.java (96%) rename src/main/java/io/metersphere/{ => node}/util/LogUtil.java (99%) diff --git a/src/main/java/io/metersphere/Application.java b/src/main/java/io/metersphere/Application.java deleted file mode 100644 index 010e94c..0000000 --- a/src/main/java/io/metersphere/Application.java +++ /dev/null @@ -1,13 +0,0 @@ -package io.metersphere; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -@SpringBootApplication -public class Application { - - public static void main(String[] args) { - SpringApplication.run(Application.class, args); - } - -} diff --git a/src/main/java/io/metersphere/node/Application.java b/src/main/java/io/metersphere/node/Application.java new file mode 100644 index 0000000..702a592 --- /dev/null +++ b/src/main/java/io/metersphere/node/Application.java @@ -0,0 +1,19 @@ +package io.metersphere.node; + +import io.metersphere.node.config.JmeterProperties; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.PropertySource; + +@EnableConfigurationProperties({ + JmeterProperties.class +}) +@PropertySource(value = {"file:/opt/metersphere/conf/metersphere.properties"}, encoding = "UTF-8", ignoreResourceNotFound = true) +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} diff --git a/src/main/java/io/metersphere/node/config/JmeterProperties.java b/src/main/java/io/metersphere/node/config/JmeterProperties.java new file mode 100644 index 0000000..38d9e4e --- /dev/null +++ b/src/main/java/io/metersphere/node/config/JmeterProperties.java @@ -0,0 +1,13 @@ +package io.metersphere.node.config; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = JmeterProperties.JMETER_PREFIX) +@Setter +@Getter +public class JmeterProperties { + public static final String JMETER_PREFIX = "jmeter"; + private String heap = "-Xms1g -Xmx1g -XX:MaxMetaspaceSize=256m"; +} diff --git a/src/main/java/io/metersphere/controller/DockerClientController.java b/src/main/java/io/metersphere/node/controller/DockerClientController.java similarity index 86% rename from src/main/java/io/metersphere/controller/DockerClientController.java rename to src/main/java/io/metersphere/node/controller/DockerClientController.java index 516f498..7dea07c 100644 --- a/src/main/java/io/metersphere/controller/DockerClientController.java +++ b/src/main/java/io/metersphere/node/controller/DockerClientController.java @@ -1,4 +1,4 @@ -package io.metersphere.controller; +package io.metersphere.node.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; diff --git a/src/main/java/io/metersphere/controller/JmeterOperateController.java b/src/main/java/io/metersphere/node/controller/JmeterOperateController.java similarity index 93% rename from src/main/java/io/metersphere/controller/JmeterOperateController.java rename to src/main/java/io/metersphere/node/controller/JmeterOperateController.java index 7c8d24c..5a89f47 100644 --- a/src/main/java/io/metersphere/controller/JmeterOperateController.java +++ b/src/main/java/io/metersphere/node/controller/JmeterOperateController.java @@ -1,9 +1,9 @@ -package io.metersphere.controller; +package io.metersphere.node.controller; import com.github.dockerjava.api.model.Container; -import io.metersphere.controller.request.TestRequest; -import io.metersphere.service.JmeterOperateService; +import io.metersphere.node.controller.request.TestRequest; +import io.metersphere.node.service.JmeterOperateService; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; diff --git a/src/main/java/io/metersphere/controller/request/DockerLoginRequest.java b/src/main/java/io/metersphere/node/controller/request/DockerLoginRequest.java similarity index 79% rename from src/main/java/io/metersphere/controller/request/DockerLoginRequest.java rename to src/main/java/io/metersphere/node/controller/request/DockerLoginRequest.java index 9767401..52c854b 100644 --- a/src/main/java/io/metersphere/controller/request/DockerLoginRequest.java +++ b/src/main/java/io/metersphere/node/controller/request/DockerLoginRequest.java @@ -1,4 +1,4 @@ -package io.metersphere.controller.request; +package io.metersphere.node.controller.request; import lombok.Getter; import lombok.Setter; diff --git a/src/main/java/io/metersphere/controller/request/TestRequest.java b/src/main/java/io/metersphere/node/controller/request/TestRequest.java similarity index 89% rename from src/main/java/io/metersphere/controller/request/TestRequest.java rename to src/main/java/io/metersphere/node/controller/request/TestRequest.java index 3ec0d3f..c063c36 100644 --- a/src/main/java/io/metersphere/controller/request/TestRequest.java +++ b/src/main/java/io/metersphere/node/controller/request/TestRequest.java @@ -1,4 +1,4 @@ -package io.metersphere.controller.request; +package io.metersphere.node.controller.request; import lombok.Getter; import lombok.Setter; diff --git a/src/main/java/io/metersphere/service/JmeterOperateService.java b/src/main/java/io/metersphere/node/service/JmeterOperateService.java similarity index 95% rename from src/main/java/io/metersphere/service/JmeterOperateService.java rename to src/main/java/io/metersphere/node/service/JmeterOperateService.java index 05c08bb..bba3126 100644 --- a/src/main/java/io/metersphere/service/JmeterOperateService.java +++ b/src/main/java/io/metersphere/node/service/JmeterOperateService.java @@ -1,4 +1,4 @@ -package io.metersphere.service; +package io.metersphere.node.service; import com.github.dockerjava.api.DockerClient; import com.github.dockerjava.api.command.WaitContainerResultCallback; @@ -7,14 +7,16 @@ import com.github.dockerjava.api.model.HostConfig; import com.github.dockerjava.api.model.Image; import com.github.dockerjava.core.InvocationBuilder; -import io.metersphere.controller.request.TestRequest; -import io.metersphere.util.DockerClientService; -import io.metersphere.util.LogUtil; +import io.metersphere.node.config.JmeterProperties; +import io.metersphere.node.controller.request.TestRequest; +import io.metersphere.node.util.DockerClientService; +import io.metersphere.node.util.LogUtil; import org.apache.commons.io.FileUtils; import org.apache.commons.lang.StringUtils; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; +import javax.annotation.Resource; import java.io.File; import java.io.IOException; import java.io.OutputStream; @@ -27,6 +29,8 @@ @Service public class JmeterOperateService { + @Resource + private JmeterProperties jmeterProperties; public void startContainer(TestRequest testRequest) throws IOException { String bootstrapServers = testRequest.getEnv().get("BOOTSTRAP_SERVERS"); @@ -137,6 +141,8 @@ private void checkKafka(String bootstrapServers) { private String[] getEnvs(TestRequest testRequest) { Map env = testRequest.getEnv(); + // HEAP + env.put("HEAP", jmeterProperties.getHeap()); return env.keySet().stream().map(k -> k + "=" + env.get(k)).toArray(String[]::new); } diff --git a/src/main/java/io/metersphere/util/DockerClientService.java b/src/main/java/io/metersphere/node/util/DockerClientService.java similarity index 96% rename from src/main/java/io/metersphere/util/DockerClientService.java rename to src/main/java/io/metersphere/node/util/DockerClientService.java index 2b25f78..ff7f083 100644 --- a/src/main/java/io/metersphere/util/DockerClientService.java +++ b/src/main/java/io/metersphere/node/util/DockerClientService.java @@ -1,4 +1,4 @@ -package io.metersphere.util; +package io.metersphere.node.util; import com.github.dockerjava.api.DockerClient; import com.github.dockerjava.api.command.CreateContainerResponse; @@ -6,7 +6,7 @@ import com.github.dockerjava.api.model.HostConfig; import com.github.dockerjava.core.DefaultDockerClientConfig; import com.github.dockerjava.core.DockerClientBuilder; -import io.metersphere.controller.request.DockerLoginRequest; +import io.metersphere.node.controller.request.DockerLoginRequest; import org.apache.commons.lang.StringUtils; import java.util.Collections; diff --git a/src/main/java/io/metersphere/util/LogUtil.java b/src/main/java/io/metersphere/node/util/LogUtil.java similarity index 99% rename from src/main/java/io/metersphere/util/LogUtil.java rename to src/main/java/io/metersphere/node/util/LogUtil.java index c955137..df77159 100644 --- a/src/main/java/io/metersphere/util/LogUtil.java +++ b/src/main/java/io/metersphere/node/util/LogUtil.java @@ -1,4 +1,4 @@ -package io.metersphere.util; +package io.metersphere.node.util; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index d5eae80..455a999 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -153,8 +153,8 @@ - - + + \ No newline at end of file From 0610b68d249e92d082c3f20714eb576a1eba3115 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Tue, 1 Sep 2020 16:03:35 +0800 Subject: [PATCH 011/109] =?UTF-8?q?refactor:=20=E4=BB=A3=E7=A0=81=E8=B0=83?= =?UTF-8?q?=E6=95=B4=EF=BC=8C=E5=90=AF=E5=8A=A8=E6=B5=8B=E8=AF=95=E6=97=B6?= =?UTF-8?q?=E6=A3=80=E6=B5=8B=E5=AE=B9=E5=99=A8=E6=98=AF=E5=90=A6=E5=AD=98?= =?UTF-8?q?=E5=9C=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../node/controller/request/TestRequest.java | 1 - .../node/service/JmeterOperateService.java | 111 ++++++++++-------- 2 files changed, 60 insertions(+), 52 deletions(-) diff --git a/src/main/java/io/metersphere/node/controller/request/TestRequest.java b/src/main/java/io/metersphere/node/controller/request/TestRequest.java index c063c36..19f6ade 100644 --- a/src/main/java/io/metersphere/node/controller/request/TestRequest.java +++ b/src/main/java/io/metersphere/node/controller/request/TestRequest.java @@ -10,7 +10,6 @@ @Setter public class TestRequest extends DockerLoginRequest { - private int size; private String fileString; private String testId; private String image; diff --git a/src/main/java/io/metersphere/node/service/JmeterOperateService.java b/src/main/java/io/metersphere/node/service/JmeterOperateService.java index bba3126..ee8916b 100644 --- a/src/main/java/io/metersphere/node/service/JmeterOperateService.java +++ b/src/main/java/io/metersphere/node/service/JmeterOperateService.java @@ -23,7 +23,10 @@ import java.net.InetSocketAddress; import java.net.Socket; import java.nio.charset.StandardCharsets; -import java.util.*; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -38,7 +41,6 @@ public void startContainer(TestRequest testRequest) throws IOException { LogUtil.info("Receive start container request, test id: {}", testRequest.getTestId()); DockerClient dockerClient = DockerClientService.connectDocker(testRequest); - int size = testRequest.getSize(); String testId = testRequest.getTestId(); String containerImage = testRequest.getImage(); @@ -59,60 +61,67 @@ public void startContainer(TestRequest testRequest) throws IOException { // 查找镜像 searchImage(dockerClient, testRequest.getImage()); + // 检查容器是否存在 + checkContainerExists(dockerClient, testId); + // 启动测试 + startContainer(testRequest, dockerClient, testId, containerImage, filePath); + } - ArrayList containerIdList = new ArrayList<>(); - for (int i = 0; i < size; i++) { - String containerName = testId + "-" + i; - // 创建 hostConfig - HostConfig hostConfig = HostConfig.newHostConfig(); - String[] envs = getEnvs(testRequest); - String containerId = DockerClientService.createContainers(dockerClient, containerName, containerImage, hostConfig, envs).getId(); - // 从主机复制文件到容器 - dockerClient.copyArchiveToContainerCmd(containerId) - .withHostResource(filePath) - .withDirChildrenOnly(true) - .withRemotePath("/test") - .exec(); - containerIdList.add(containerId); - } + private void startContainer(TestRequest testRequest, DockerClient dockerClient, String testId, String containerImage, String filePath) { + // 创建 hostConfig + HostConfig hostConfig = HostConfig.newHostConfig(); + String[] envs = getEnvs(testRequest); + String containerId = DockerClientService.createContainers(dockerClient, testId, containerImage, hostConfig, envs).getId(); + // 从主机复制文件到容器 + dockerClient.copyArchiveToContainerCmd(containerId) + .withHostResource(filePath) + .withDirChildrenOnly(true) + .withRemotePath("/test") + .exec(); - containerIdList.forEach(containerId -> { - DockerClientService.startContainer(dockerClient, containerId); - LogUtil.info("Container create started containerId: " + containerId); - dockerClient.waitContainerCmd(containerId) - .exec(new WaitContainerResultCallback() { - @Override - public void onComplete() { - // 清理文件夹 - try { - FileUtils.forceDelete(new File(filePath)); - LogUtil.info("Remove dir completed."); - if (DockerClientService.existContainer(dockerClient, containerId) > 0) { - DockerClientService.removeContainer(dockerClient, containerId); - } - LogUtil.info("Remove container completed: " + containerId); - } catch (IOException e) { - LogUtil.error("Remove dir error: ", e); + DockerClientService.startContainer(dockerClient, containerId); + LogUtil.info("Container create started containerId: " + containerId); + dockerClient.waitContainerCmd(containerId) + .exec(new WaitContainerResultCallback() { + @Override + public void onComplete() { + // 清理文件夹 + try { + FileUtils.forceDelete(new File(filePath)); + LogUtil.info("Remove dir completed."); + if (DockerClientService.existContainer(dockerClient, containerId) > 0) { + DockerClientService.removeContainer(dockerClient, containerId); } - LogUtil.info("completed...."); + LogUtil.info("Remove container completed: " + containerId); + } catch (IOException e) { + LogUtil.error("Remove dir error: ", e); } - }); - }); - - containerIdList.forEach(containerId -> { - dockerClient.logContainerCmd(containerId) - .withFollowStream(true) - .withStdOut(true) - .withStdErr(true) - .withTailAll() - .exec(new InvocationBuilder.AsyncResultCallback() { - @Override - public void onNext(Frame item) { - LogUtil.info(new String(item.getPayload()).trim()); - } - }); + LogUtil.info("completed...."); + } + }); + + dockerClient.logContainerCmd(containerId) + .withFollowStream(true) + .withStdOut(true) + .withStdErr(true) + .withTailAll() + .exec(new InvocationBuilder.AsyncResultCallback() { + @Override + public void onNext(Frame item) { + LogUtil.info(new String(item.getPayload()).trim()); + } + }); + } - }); + private void checkContainerExists(DockerClient dockerClient, String testId) { + List list = dockerClient.listContainersCmd() + .withShowAll(true) + .withStatusFilter(Arrays.asList("created", "restarting", "running", "paused", "exited")) + .withNameFilter(Collections.singletonList(testId)) + .exec(); + if (!CollectionUtils.isEmpty(list)) { + list.forEach(container -> DockerClientService.removeContainer(dockerClient, container.getId())); + } } private void checkKafka(String bootstrapServers) { From fb4a23620c2a172abc0977020dceaa55fd731dec Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Wed, 2 Sep 2020 13:42:43 +0800 Subject: [PATCH 012/109] =?UTF-8?q?refactor:=20=E6=B7=BB=E5=8A=A0=E4=BE=9D?= =?UTF-8?q?=E8=B5=96jar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pom.xml b/pom.xml index a60ed5f..cd0bbe3 100644 --- a/pom.xml +++ b/pom.xml @@ -19,6 +19,10 @@ + + org.springframework.boot + spring-boot-configuration-processor + org.springframework.boot spring-boot-starter-web From ccb0e9a8f82c71af27d9a2ca7b8712a9be0f13d5 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Mon, 14 Sep 2020 16:06:43 +0800 Subject: [PATCH 013/109] =?UTF-8?q?refactor:=20=E4=BC=98=E5=8C=96=E5=88=9B?= =?UTF-8?q?=E5=BB=BA=E5=AE=B9=E5=99=A8=E7=9A=84=E9=94=99=E8=AF=AF=E6=8F=90?= =?UTF-8?q?=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 5 +- .../controller/JmeterOperateController.java | 22 +----- .../RestControllerExceptionHandler.java | 18 +++++ .../node/controller/handler/ResultHolder.java | 73 +++++++++++++++++++ .../handler/ResultResponseBodyAdvice.java | 50 +++++++++++++ .../node/service/JmeterOperateService.java | 2 +- 6 files changed, 150 insertions(+), 20 deletions(-) create mode 100644 src/main/java/io/metersphere/node/controller/handler/RestControllerExceptionHandler.java create mode 100644 src/main/java/io/metersphere/node/controller/handler/ResultHolder.java create mode 100644 src/main/java/io/metersphere/node/controller/handler/ResultResponseBodyAdvice.java diff --git a/pom.xml b/pom.xml index cd0bbe3..f746ac3 100644 --- a/pom.xml +++ b/pom.xml @@ -47,7 +47,10 @@ org.projectlombok lombok - + + org.apache.commons + commons-lang3 + org.springframework.boot spring-boot-starter-test diff --git a/src/main/java/io/metersphere/node/controller/JmeterOperateController.java b/src/main/java/io/metersphere/node/controller/JmeterOperateController.java index 5a89f47..e8f16e1 100644 --- a/src/main/java/io/metersphere/node/controller/JmeterOperateController.java +++ b/src/main/java/io/metersphere/node/controller/JmeterOperateController.java @@ -20,32 +20,18 @@ public class JmeterOperateController { * 初始化测试任务,根据需求启动若干个 JMeter Engine 容器 */ @PostMapping("/container/start") - public void startContainer(@RequestBody TestRequest testRequest) throws IOException { + public String startContainer(@RequestBody TestRequest testRequest) throws IOException { jmeterOperateService.startContainer(testRequest); - } - - // 上传测试相关文件,将请求传过来的脚本、测试数据等文件,拷贝到上述容器中 - @PostMapping("/upload/task") - public void uploadFile(String jmxString) { - // 挂载数据到启动的容器 - // FileUtil.saveFile(jmxString, "/User/liyuhao/test", "ceshi2.jmx"); - } - - // 启动测试任务,控制上述容器执行 jmeter 相关命令开始进行测试 - @PostMapping("/exec/task") - public void taskPerform() { - // 执行 jmx - // docker run -it -v /Users/liyuhao/test:/test justb4/jmeter:latest -n -t /test/ceshi3.jmx - // dockerClient.startContainerCmd("18894c7755b1").exec(); - // dockerClient.execCreateCmd("18894c7755b1").withAttachStdout(true).withCmd("jmeter", "-n", "-t", "/test/*.jmx"); + return "OK"; } /** * 停止指定测试任务,控制上述容器停止指定的 JMeter 测试 */ @GetMapping("container/stop/{testId}") - public void stopContainer(@PathVariable String testId) { + public String stopContainer(@PathVariable String testId) { jmeterOperateService.stopContainer(testId); + return "OK"; } /** diff --git a/src/main/java/io/metersphere/node/controller/handler/RestControllerExceptionHandler.java b/src/main/java/io/metersphere/node/controller/handler/RestControllerExceptionHandler.java new file mode 100644 index 0000000..de54f00 --- /dev/null +++ b/src/main/java/io/metersphere/node/controller/handler/RestControllerExceptionHandler.java @@ -0,0 +1,18 @@ +package io.metersphere.node.controller.handler; + + +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + + +@RestControllerAdvice +public class RestControllerExceptionHandler { + + @ExceptionHandler(Exception.class) + public ResultHolder msExceptionHandler(HttpServletRequest request, HttpServletResponse response, Exception e) { + return ResultHolder.error(e.getMessage()); + } +} diff --git a/src/main/java/io/metersphere/node/controller/handler/ResultHolder.java b/src/main/java/io/metersphere/node/controller/handler/ResultHolder.java new file mode 100644 index 0000000..e3c1d2a --- /dev/null +++ b/src/main/java/io/metersphere/node/controller/handler/ResultHolder.java @@ -0,0 +1,73 @@ +package io.metersphere.node.controller.handler; + +import org.apache.commons.lang3.builder.ReflectionToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +public class ResultHolder { + public ResultHolder() { + this.success = true; + } + + private ResultHolder(Object data) { + this.data = data; + this.success = true; + } + + private ResultHolder(boolean success, String msg) { + this.success = success; + this.message = msg; + } + + private ResultHolder(boolean success, String msg, Object data) { + this.success = success; + this.message = msg; + this.data = data; + } + + // 请求是否成功 + private boolean success = false; + // 描述信息 + private String message; + // 返回数据 + private Object data = ""; + + public boolean isSuccess() { + return this.success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public Object getData() { + return data; + } + + public void setData(Object data) { + this.data = data; + } + + public static ResultHolder success(Object obj) { + return new ResultHolder(obj); + } + + public static ResultHolder error(String message) { + return new ResultHolder(false, message, null); + } + + public static ResultHolder error(String message, Object object) { + return new ResultHolder(false, message, object); + } + + public String toString() { + return ReflectionToStringBuilder.toString(this, ToStringStyle.SHORT_PREFIX_STYLE); + } +} diff --git a/src/main/java/io/metersphere/node/controller/handler/ResultResponseBodyAdvice.java b/src/main/java/io/metersphere/node/controller/handler/ResultResponseBodyAdvice.java new file mode 100644 index 0000000..e784219 --- /dev/null +++ b/src/main/java/io/metersphere/node/controller/handler/ResultResponseBodyAdvice.java @@ -0,0 +1,50 @@ +package io.metersphere.node.controller.handler; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.metersphere.node.util.LogUtil; +import org.springframework.core.MethodParameter; +import org.springframework.http.MediaType; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.StringHttpMessageConverter; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; + +/** + * 统一处理返回结果集 + */ +@RestControllerAdvice(value = {"io.metersphere.node"}) +public class ResultResponseBodyAdvice implements ResponseBodyAdvice { + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Override + public boolean supports(MethodParameter methodParameter, Class> converterType) { + return MappingJackson2HttpMessageConverter.class.isAssignableFrom(converterType) || StringHttpMessageConverter.class.isAssignableFrom(converterType); + } + + @Override + public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class> converterType, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) { + // 处理空值 + if (o == null && StringHttpMessageConverter.class.isAssignableFrom(converterType)) { + return null; + } + + if (!(o instanceof ResultHolder)) { + if (o instanceof String) { + ResultHolder success = ResultHolder.success(o); + try { + return objectMapper.writeValueAsString(success); + } catch (JsonProcessingException e) { + LogUtil.error(e); + return o; + } + } + return ResultHolder.success(o); + } + return o; + } + +} diff --git a/src/main/java/io/metersphere/node/service/JmeterOperateService.java b/src/main/java/io/metersphere/node/service/JmeterOperateService.java index ee8916b..48ef252 100644 --- a/src/main/java/io/metersphere/node/service/JmeterOperateService.java +++ b/src/main/java/io/metersphere/node/service/JmeterOperateService.java @@ -175,7 +175,7 @@ private void searchImage(DockerClient dockerClient, String imageName) { }).collect(Collectors.toList()); if (collect.size() == 0) { - throw new RuntimeException("Image Not Found."); + throw new RuntimeException("Image Not Found: " + imageName); } } From 6b614cdd2b9846d156f3d8aee67be997dec68925 Mon Sep 17 00:00:00 2001 From: BugKing Date: Mon, 21 Sep 2020 10:32:24 +0800 Subject: [PATCH 014/109] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 4 ++-- pom.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3f39b71..e779aa1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,9 +6,9 @@ ARG MS_VERSION=dev RUN mkdir -p /opt/apps -ADD target/node-controller-1.1.jar /opt/apps +ADD target/node-controller-1.3.jar /opt/apps -ENV JAVA_APP_JAR=/opt/apps/node-controller-1.1.jar +ENV JAVA_APP_JAR=/opt/apps/node-controller-1.3.jar ENV AB_OFF=true diff --git a/pom.xml b/pom.xml index a60ed5f..23bcc23 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.metersphere node-controller - 1.1 + 1.3 node-controller node-controller From 7f41d73e9f9ebbe9d1e9207b2159c5a8abc05eef Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Thu, 5 Nov 2020 16:01:24 +0800 Subject: [PATCH 015/109] =?UTF-8?q?feat:=20=E6=80=A7=E8=83=BD=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E6=94=AF=E6=8C=81=E4=B8=8A=E4=BC=A0jar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../metersphere/node/controller/request/TestRequest.java | 1 + .../metersphere/node/service/JmeterOperateService.java | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/src/main/java/io/metersphere/node/controller/request/TestRequest.java b/src/main/java/io/metersphere/node/controller/request/TestRequest.java index 19f6ade..74c7518 100644 --- a/src/main/java/io/metersphere/node/controller/request/TestRequest.java +++ b/src/main/java/io/metersphere/node/controller/request/TestRequest.java @@ -15,4 +15,5 @@ public class TestRequest extends DockerLoginRequest { private String image; private Map testData = new HashMap<>(); private Map env = new HashMap<>(); + private Map testJars = new HashMap<>(); } diff --git a/src/main/java/io/metersphere/node/service/JmeterOperateService.java b/src/main/java/io/metersphere/node/service/JmeterOperateService.java index 48ef252..124a0a2 100644 --- a/src/main/java/io/metersphere/node/service/JmeterOperateService.java +++ b/src/main/java/io/metersphere/node/service/JmeterOperateService.java @@ -59,6 +59,15 @@ public void startContainer(TestRequest testRequest) throws IOException { } } + // 保存 byte[] jar + Map jarFiles = testRequest.getTestJars(); + if (!CollectionUtils.isEmpty(jarFiles)) { + for (String k : jarFiles.keySet()) { + byte[] v = jarFiles.get(k); + FileUtils.writeByteArrayToFile(new File(filePath + File.separator + k), v); + } + } + // 查找镜像 searchImage(dockerClient, testRequest.getImage()); // 检查容器是否存在 From c9bf63ae90952eac5c9dd043278e14633d85aff0 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Wed, 11 Nov 2020 17:57:08 +0800 Subject: [PATCH 016/109] =?UTF-8?q?feat:=20=E6=80=A7=E8=83=BD=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E4=BF=9D=E5=AD=98jtl?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/JmeterOperateController.java | 17 +++++++++ .../node/controller/request/TestRequest.java | 1 + .../node/service/JmeterOperateService.java | 37 ++++++++++++++++--- 3 files changed, 50 insertions(+), 5 deletions(-) diff --git a/src/main/java/io/metersphere/node/controller/JmeterOperateController.java b/src/main/java/io/metersphere/node/controller/JmeterOperateController.java index e8f16e1..3ca6ed1 100644 --- a/src/main/java/io/metersphere/node/controller/JmeterOperateController.java +++ b/src/main/java/io/metersphere/node/controller/JmeterOperateController.java @@ -4,6 +4,9 @@ import com.github.dockerjava.api.model.Container; import io.metersphere.node.controller.request.TestRequest; import io.metersphere.node.service.JmeterOperateService; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; @@ -34,6 +37,20 @@ public String stopContainer(@PathVariable String testId) { return "OK"; } + @GetMapping("download/jtl/{reportId}") + public ResponseEntity downloadJtl(@PathVariable String reportId) { + byte[] bytes = jmeterOperateService.downloadJtl(reportId); + return ResponseEntity.ok() + .contentType(MediaType.parseMediaType("application/octet-stream")) + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + reportId + ".jtl" + "\"") + .body(bytes); + } + + @GetMapping("delete/jtl/{reportId}") + public boolean deleteJtl(@PathVariable String reportId) { + return jmeterOperateService.deleteJtl(reportId); + } + /** * 停止指定测试任务,控制上述容器停止指定的 JMeter 测试 */ diff --git a/src/main/java/io/metersphere/node/controller/request/TestRequest.java b/src/main/java/io/metersphere/node/controller/request/TestRequest.java index 74c7518..eb14292 100644 --- a/src/main/java/io/metersphere/node/controller/request/TestRequest.java +++ b/src/main/java/io/metersphere/node/controller/request/TestRequest.java @@ -12,6 +12,7 @@ public class TestRequest extends DockerLoginRequest { private String fileString; private String testId; + private String reportId; private String image; private Map testData = new HashMap<>(); private Map env = new HashMap<>(); diff --git a/src/main/java/io/metersphere/node/service/JmeterOperateService.java b/src/main/java/io/metersphere/node/service/JmeterOperateService.java index 124a0a2..c4e7b3d 100644 --- a/src/main/java/io/metersphere/node/service/JmeterOperateService.java +++ b/src/main/java/io/metersphere/node/service/JmeterOperateService.java @@ -12,14 +12,13 @@ import io.metersphere.node.util.DockerClientService; import io.metersphere.node.util.LogUtil; import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; import javax.annotation.Resource; -import java.io.File; -import java.io.IOException; -import java.io.OutputStream; +import java.io.*; import java.net.InetSocketAddress; import java.net.Socket; import java.nio.charset.StandardCharsets; @@ -34,6 +33,7 @@ public class JmeterOperateService { @Resource private JmeterProperties jmeterProperties; + private static final String rootPath = StringUtils.join(new String[]{"", "opt", "node-data"}, File.separator); public void startContainer(TestRequest testRequest) throws IOException { String bootstrapServers = testRequest.getEnv().get("BOOTSTRAP_SERVERS"); @@ -44,7 +44,7 @@ public void startContainer(TestRequest testRequest) throws IOException { String testId = testRequest.getTestId(); String containerImage = testRequest.getImage(); - String filePath = StringUtils.join(new String[]{"", "opt", "node-data", testId}, File.separator); + String filePath = StringUtils.join(new String[]{rootPath, testId}, File.separator); String fileName = testId + ".jmx"; @@ -96,13 +96,19 @@ private void startContainer(TestRequest testRequest, DockerClient dockerClient, public void onComplete() { // 清理文件夹 try { + String jtlFileName = testRequest.getReportId() + ".jtl"; + InputStream input = dockerClient + .copyArchiveFromContainerCmd(containerId, "/test/" + jtlFileName) + .exec(); + + IOUtils.copyLarge(input, new FileOutputStream(rootPath + File.separator + jtlFileName)); FileUtils.forceDelete(new File(filePath)); LogUtil.info("Remove dir completed."); if (DockerClientService.existContainer(dockerClient, containerId) > 0) { DockerClientService.removeContainer(dockerClient, containerId); } LogUtil.info("Remove container completed: " + containerId); - } catch (IOException e) { + } catch (Exception e) { LogUtil.error("Remove dir error: ", e); } LogUtil.info("completed...."); @@ -244,4 +250,25 @@ public void onNext(Frame item) { } return sb.toString(); } + + public byte[] downloadJtl(String reportId) { + try { + String jtlFileName = reportId + ".jtl"; + return IOUtils.toByteArray(new FileInputStream(rootPath + File.separator + jtlFileName)); + } catch (IOException e) { + LogUtil.error(e); + } + return new byte[0]; + } + + public boolean deleteJtl(String reportId) { + String jtlFileName = reportId + ".jtl"; + try { + FileUtils.forceDelete(new File(rootPath + File.separator + jtlFileName)); + return true; + } catch (IOException e) { + LogUtil.error(e); + } + return false; + } } From d725bae6f9cb8ebea1cfe761cea1d5a50bc8ac8c Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Thu, 12 Nov 2020 10:22:17 +0800 Subject: [PATCH 017/109] =?UTF-8?q?feat:=20=E6=80=A7=E8=83=BD=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E4=BF=9D=E5=AD=98jtl?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 2 +- .../node/service/JmeterOperateService.java | 39 +++++++++---------- .../node/util/DockerClientService.java | 5 ++- 3 files changed, 24 insertions(+), 22 deletions(-) diff --git a/pom.xml b/pom.xml index 876c73e..2e0f4df 100644 --- a/pom.xml +++ b/pom.xml @@ -41,7 +41,7 @@ com.github.docker-java docker-java - 3.2.0 + 3.2.5 org.projectlombok diff --git a/src/main/java/io/metersphere/node/service/JmeterOperateService.java b/src/main/java/io/metersphere/node/service/JmeterOperateService.java index c4e7b3d..228c0ec 100644 --- a/src/main/java/io/metersphere/node/service/JmeterOperateService.java +++ b/src/main/java/io/metersphere/node/service/JmeterOperateService.java @@ -2,10 +2,7 @@ import com.github.dockerjava.api.DockerClient; import com.github.dockerjava.api.command.WaitContainerResultCallback; -import com.github.dockerjava.api.model.Container; -import com.github.dockerjava.api.model.Frame; -import com.github.dockerjava.api.model.HostConfig; -import com.github.dockerjava.api.model.Image; +import com.github.dockerjava.api.model.*; import com.github.dockerjava.core.InvocationBuilder; import io.metersphere.node.config.JmeterProperties; import io.metersphere.node.controller.request.TestRequest; @@ -18,14 +15,14 @@ import org.springframework.util.CollectionUtils; import javax.annotation.Resource; -import java.io.*; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.Socket; import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -77,16 +74,18 @@ public void startContainer(TestRequest testRequest) throws IOException { } private void startContainer(TestRequest testRequest, DockerClient dockerClient, String testId, String containerImage, String filePath) { + List volumes = new ArrayList<>(); + List binds = new ArrayList<>(); + final Volume volume = new Volume("/test"); + volumes.add(volume); + final Bind bind = new Bind(filePath, volume); + binds.add(bind); // 创建 hostConfig HostConfig hostConfig = HostConfig.newHostConfig(); + hostConfig.withBinds(binds); + String[] envs = getEnvs(testRequest); - String containerId = DockerClientService.createContainers(dockerClient, testId, containerImage, hostConfig, envs).getId(); - // 从主机复制文件到容器 - dockerClient.copyArchiveToContainerCmd(containerId) - .withHostResource(filePath) - .withDirChildrenOnly(true) - .withRemotePath("/test") - .exec(); + String containerId = DockerClientService.createContainers(dockerClient, testId, containerImage, hostConfig, volumes, envs).getId(); DockerClientService.startContainer(dockerClient, containerId); LogUtil.info("Container create started containerId: " + containerId); @@ -97,11 +96,11 @@ public void onComplete() { // 清理文件夹 try { String jtlFileName = testRequest.getReportId() + ".jtl"; - InputStream input = dockerClient - .copyArchiveFromContainerCmd(containerId, "/test/" + jtlFileName) - .exec(); - IOUtils.copyLarge(input, new FileOutputStream(rootPath + File.separator + jtlFileName)); + File srcFile = new File(filePath + File.separator + jtlFileName); + File destDir = new File(rootPath + File.separator + jtlFileName); + + FileUtils.copyFile(srcFile, destDir); FileUtils.forceDelete(new File(filePath)); LogUtil.info("Remove dir completed."); if (DockerClientService.existContainer(dockerClient, containerId) > 0) { diff --git a/src/main/java/io/metersphere/node/util/DockerClientService.java b/src/main/java/io/metersphere/node/util/DockerClientService.java index ff7f083..c977795 100644 --- a/src/main/java/io/metersphere/node/util/DockerClientService.java +++ b/src/main/java/io/metersphere/node/util/DockerClientService.java @@ -4,6 +4,7 @@ import com.github.dockerjava.api.command.CreateContainerResponse; import com.github.dockerjava.api.model.Container; import com.github.dockerjava.api.model.HostConfig; +import com.github.dockerjava.api.model.Volume; import com.github.dockerjava.core.DefaultDockerClientConfig; import com.github.dockerjava.core.DockerClientBuilder; import io.metersphere.node.controller.request.DockerLoginRequest; @@ -41,11 +42,13 @@ public static DockerClient connectDocker(DockerLoginRequest request) { * @param client * @return */ - public static CreateContainerResponse createContainers(DockerClient client, String containerName, String imageName, HostConfig hostConfig, String... env) { + public static CreateContainerResponse createContainers(DockerClient client, String containerName, String imageName, + HostConfig hostConfig, List volumes, String... env) { CreateContainerResponse container = client.createContainerCmd(imageName) .withName(containerName) .withHostConfig(hostConfig) .withEnv(env) + .withVolumes(volumes) .exec(); return container; } From a74754ad8aa0f926e1e18c310812f7456f4fa07b Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Thu, 12 Nov 2020 10:44:09 +0800 Subject: [PATCH 018/109] =?UTF-8?q?feat:=20=E6=80=A7=E8=83=BD=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E4=BF=9D=E5=AD=98jtl?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/metersphere/node/service/JmeterOperateService.java | 2 +- .../java/io/metersphere/node/util/DockerClientService.java | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main/java/io/metersphere/node/service/JmeterOperateService.java b/src/main/java/io/metersphere/node/service/JmeterOperateService.java index 228c0ec..f34cf89 100644 --- a/src/main/java/io/metersphere/node/service/JmeterOperateService.java +++ b/src/main/java/io/metersphere/node/service/JmeterOperateService.java @@ -85,7 +85,7 @@ private void startContainer(TestRequest testRequest, DockerClient dockerClient, hostConfig.withBinds(binds); String[] envs = getEnvs(testRequest); - String containerId = DockerClientService.createContainers(dockerClient, testId, containerImage, hostConfig, volumes, envs).getId(); + String containerId = DockerClientService.createContainers(dockerClient, testId, containerImage, hostConfig, envs).getId(); DockerClientService.startContainer(dockerClient, containerId); LogUtil.info("Container create started containerId: " + containerId); diff --git a/src/main/java/io/metersphere/node/util/DockerClientService.java b/src/main/java/io/metersphere/node/util/DockerClientService.java index c977795..9d0766a 100644 --- a/src/main/java/io/metersphere/node/util/DockerClientService.java +++ b/src/main/java/io/metersphere/node/util/DockerClientService.java @@ -4,7 +4,6 @@ import com.github.dockerjava.api.command.CreateContainerResponse; import com.github.dockerjava.api.model.Container; import com.github.dockerjava.api.model.HostConfig; -import com.github.dockerjava.api.model.Volume; import com.github.dockerjava.core.DefaultDockerClientConfig; import com.github.dockerjava.core.DockerClientBuilder; import io.metersphere.node.controller.request.DockerLoginRequest; @@ -43,12 +42,11 @@ public static DockerClient connectDocker(DockerLoginRequest request) { * @return */ public static CreateContainerResponse createContainers(DockerClient client, String containerName, String imageName, - HostConfig hostConfig, List volumes, String... env) { + HostConfig hostConfig, String... env) { CreateContainerResponse container = client.createContainerCmd(imageName) .withName(containerName) .withHostConfig(hostConfig) .withEnv(env) - .withVolumes(volumes) .exec(); return container; } From 77e04657f8a0c503a3585994a61ece13153859a2 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Thu, 12 Nov 2020 11:02:17 +0800 Subject: [PATCH 019/109] =?UTF-8?q?refactor:=20=E5=9B=9E=E9=80=80=E5=88=B0?= =?UTF-8?q?=E4=BB=A5=E5=89=8D=E7=9A=84=E5=A4=8D=E5=88=B6=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../node/service/JmeterOperateService.java | 37 ++++++++++--------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/src/main/java/io/metersphere/node/service/JmeterOperateService.java b/src/main/java/io/metersphere/node/service/JmeterOperateService.java index f34cf89..c4e7b3d 100644 --- a/src/main/java/io/metersphere/node/service/JmeterOperateService.java +++ b/src/main/java/io/metersphere/node/service/JmeterOperateService.java @@ -2,7 +2,10 @@ import com.github.dockerjava.api.DockerClient; import com.github.dockerjava.api.command.WaitContainerResultCallback; -import com.github.dockerjava.api.model.*; +import com.github.dockerjava.api.model.Container; +import com.github.dockerjava.api.model.Frame; +import com.github.dockerjava.api.model.HostConfig; +import com.github.dockerjava.api.model.Image; import com.github.dockerjava.core.InvocationBuilder; import io.metersphere.node.config.JmeterProperties; import io.metersphere.node.controller.request.TestRequest; @@ -15,14 +18,14 @@ import org.springframework.util.CollectionUtils; import javax.annotation.Resource; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.OutputStream; +import java.io.*; import java.net.InetSocketAddress; import java.net.Socket; import java.nio.charset.StandardCharsets; -import java.util.*; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -74,18 +77,16 @@ public void startContainer(TestRequest testRequest) throws IOException { } private void startContainer(TestRequest testRequest, DockerClient dockerClient, String testId, String containerImage, String filePath) { - List volumes = new ArrayList<>(); - List binds = new ArrayList<>(); - final Volume volume = new Volume("/test"); - volumes.add(volume); - final Bind bind = new Bind(filePath, volume); - binds.add(bind); // 创建 hostConfig HostConfig hostConfig = HostConfig.newHostConfig(); - hostConfig.withBinds(binds); - String[] envs = getEnvs(testRequest); String containerId = DockerClientService.createContainers(dockerClient, testId, containerImage, hostConfig, envs).getId(); + // 从主机复制文件到容器 + dockerClient.copyArchiveToContainerCmd(containerId) + .withHostResource(filePath) + .withDirChildrenOnly(true) + .withRemotePath("/test") + .exec(); DockerClientService.startContainer(dockerClient, containerId); LogUtil.info("Container create started containerId: " + containerId); @@ -96,11 +97,11 @@ public void onComplete() { // 清理文件夹 try { String jtlFileName = testRequest.getReportId() + ".jtl"; + InputStream input = dockerClient + .copyArchiveFromContainerCmd(containerId, "/test/" + jtlFileName) + .exec(); - File srcFile = new File(filePath + File.separator + jtlFileName); - File destDir = new File(rootPath + File.separator + jtlFileName); - - FileUtils.copyFile(srcFile, destDir); + IOUtils.copyLarge(input, new FileOutputStream(rootPath + File.separator + jtlFileName)); FileUtils.forceDelete(new File(filePath)); LogUtil.info("Remove dir completed."); if (DockerClientService.existContainer(dockerClient, containerId) > 0) { From 215dc6416c1cb4cce920a73b7d81e7ece40c5cc8 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Thu, 12 Nov 2020 12:23:44 +0800 Subject: [PATCH 020/109] =?UTF-8?q?refactor:=20=E9=87=87=E7=94=A8=E6=8C=82?= =?UTF-8?q?=E5=8D=B7=E7=9A=84=E6=96=B9=E5=BC=8F=E5=A4=8D=E5=88=B6=E6=96=87?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../node/service/JmeterOperateService.java | 42 ++++++++++--------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/src/main/java/io/metersphere/node/service/JmeterOperateService.java b/src/main/java/io/metersphere/node/service/JmeterOperateService.java index c4e7b3d..569eed5 100644 --- a/src/main/java/io/metersphere/node/service/JmeterOperateService.java +++ b/src/main/java/io/metersphere/node/service/JmeterOperateService.java @@ -2,10 +2,7 @@ import com.github.dockerjava.api.DockerClient; import com.github.dockerjava.api.command.WaitContainerResultCallback; -import com.github.dockerjava.api.model.Container; -import com.github.dockerjava.api.model.Frame; -import com.github.dockerjava.api.model.HostConfig; -import com.github.dockerjava.api.model.Image; +import com.github.dockerjava.api.model.*; import com.github.dockerjava.core.InvocationBuilder; import io.metersphere.node.config.JmeterProperties; import io.metersphere.node.controller.request.TestRequest; @@ -18,7 +15,10 @@ import org.springframework.util.CollectionUtils; import javax.annotation.Resource; -import java.io.*; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.Socket; import java.nio.charset.StandardCharsets; @@ -33,7 +33,15 @@ public class JmeterOperateService { @Resource private JmeterProperties jmeterProperties; - private static final String rootPath = StringUtils.join(new String[]{"", "opt", "node-data"}, File.separator); + private static String ROOT_PATH; + + static { + ROOT_PATH = System.getenv("JMETER_DATA_PATH"); + LogUtil.info("JMETER_DATA_PATH: " + ROOT_PATH); + if (StringUtils.isBlank(ROOT_PATH)) { + ROOT_PATH = "/opt/metersphere/data/jmeter/"; + } + } public void startContainer(TestRequest testRequest) throws IOException { String bootstrapServers = testRequest.getEnv().get("BOOTSTRAP_SERVERS"); @@ -44,7 +52,7 @@ public void startContainer(TestRequest testRequest) throws IOException { String testId = testRequest.getTestId(); String containerImage = testRequest.getImage(); - String filePath = StringUtils.join(new String[]{rootPath, testId}, File.separator); + String filePath = StringUtils.join(new String[]{ROOT_PATH, testId}, File.separator); String fileName = testId + ".jmx"; @@ -79,14 +87,10 @@ public void startContainer(TestRequest testRequest) throws IOException { private void startContainer(TestRequest testRequest, DockerClient dockerClient, String testId, String containerImage, String filePath) { // 创建 hostConfig HostConfig hostConfig = HostConfig.newHostConfig(); + hostConfig.withBinds(Bind.parse(filePath + ":/test")); + String[] envs = getEnvs(testRequest); String containerId = DockerClientService.createContainers(dockerClient, testId, containerImage, hostConfig, envs).getId(); - // 从主机复制文件到容器 - dockerClient.copyArchiveToContainerCmd(containerId) - .withHostResource(filePath) - .withDirChildrenOnly(true) - .withRemotePath("/test") - .exec(); DockerClientService.startContainer(dockerClient, containerId); LogUtil.info("Container create started containerId: " + containerId); @@ -97,11 +101,11 @@ public void onComplete() { // 清理文件夹 try { String jtlFileName = testRequest.getReportId() + ".jtl"; - InputStream input = dockerClient - .copyArchiveFromContainerCmd(containerId, "/test/" + jtlFileName) - .exec(); - IOUtils.copyLarge(input, new FileOutputStream(rootPath + File.separator + jtlFileName)); + File srcFile = new File(filePath + File.separator + jtlFileName); + File destDir = new File(ROOT_PATH + File.separator + jtlFileName); + + FileUtils.copyFile(srcFile, destDir); FileUtils.forceDelete(new File(filePath)); LogUtil.info("Remove dir completed."); if (DockerClientService.existContainer(dockerClient, containerId) > 0) { @@ -254,7 +258,7 @@ public void onNext(Frame item) { public byte[] downloadJtl(String reportId) { try { String jtlFileName = reportId + ".jtl"; - return IOUtils.toByteArray(new FileInputStream(rootPath + File.separator + jtlFileName)); + return IOUtils.toByteArray(new FileInputStream(ROOT_PATH + File.separator + jtlFileName)); } catch (IOException e) { LogUtil.error(e); } @@ -264,7 +268,7 @@ public byte[] downloadJtl(String reportId) { public boolean deleteJtl(String reportId) { String jtlFileName = reportId + ".jtl"; try { - FileUtils.forceDelete(new File(rootPath + File.separator + jtlFileName)); + FileUtils.forceDelete(new File(ROOT_PATH + File.separator + jtlFileName)); return true; } catch (IOException e) { LogUtil.error(e); From 3149e9f767a6e343db8c93547cfc9352da8488ad Mon Sep 17 00:00:00 2001 From: BugKing Date: Wed, 25 Nov 2020 11:16:31 +0800 Subject: [PATCH 021/109] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 4 ++-- pom.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index e779aa1..27f2f7d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,9 +6,9 @@ ARG MS_VERSION=dev RUN mkdir -p /opt/apps -ADD target/node-controller-1.3.jar /opt/apps +ADD target/node-controller-1.5.jar /opt/apps -ENV JAVA_APP_JAR=/opt/apps/node-controller-1.3.jar +ENV JAVA_APP_JAR=/opt/apps/node-controller-1.5.jar ENV AB_OFF=true diff --git a/pom.xml b/pom.xml index 2e0f4df..1a08c4f 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.metersphere node-controller - 1.3 + 1.5 node-controller node-controller From b4deb946a3cf2e150268b117ffc400fa5b9ea12d Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Thu, 26 Nov 2020 11:20:50 +0800 Subject: [PATCH 022/109] =?UTF-8?q?refactor:=20=E6=89=A7=E8=A1=8C=E7=BB=93?= =?UTF-8?q?=E6=9D=9F=E5=90=8E=E6=B8=85=E7=90=86=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/metersphere/node/service/JmeterOperateService.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/main/java/io/metersphere/node/service/JmeterOperateService.java b/src/main/java/io/metersphere/node/service/JmeterOperateService.java index 569eed5..18c85c2 100644 --- a/src/main/java/io/metersphere/node/service/JmeterOperateService.java +++ b/src/main/java/io/metersphere/node/service/JmeterOperateService.java @@ -100,12 +100,6 @@ private void startContainer(TestRequest testRequest, DockerClient dockerClient, public void onComplete() { // 清理文件夹 try { - String jtlFileName = testRequest.getReportId() + ".jtl"; - - File srcFile = new File(filePath + File.separator + jtlFileName); - File destDir = new File(ROOT_PATH + File.separator + jtlFileName); - - FileUtils.copyFile(srcFile, destDir); FileUtils.forceDelete(new File(filePath)); LogUtil.info("Remove dir completed."); if (DockerClientService.existContainer(dockerClient, containerId) > 0) { From c668ad0b7807f230cbc5a1e514334c37d244c3c2 Mon Sep 17 00:00:00 2001 From: BugKing Date: Thu, 3 Dec 2020 19:24:36 +0800 Subject: [PATCH 023/109] =?UTF-8?q?build:=20=E5=A2=9E=E5=8A=A0=20action?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build-push.yml | 58 ++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 .github/workflows/build-push.yml diff --git a/.github/workflows/build-push.yml b/.github/workflows/build-push.yml new file mode 100644 index 0000000..a292545 --- /dev/null +++ b/.github/workflows/build-push.yml @@ -0,0 +1,58 @@ +name: Build Docker Image and Push + +on: + push: + branches: + - main + - v1* + pull_request: + branches: + - main + - v1* + + workflow_dispatch: + +jobs: + build_push: + runs-on: ubuntu-latest + name: Build Docker Image and Push + steps: + - uses: actions/checkout@v2 + - name: Inject slug/short variables + uses: rlespinasse/github-slug-action@v3.x + - name: Cache node modules + uses: actions/cache@v2 + env: + cache-name: cache-node-modules + with: + path: ~/.npm + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-build-${{ env.cache-name }}- + ${{ runner.os }}-build- + ${{ runner.os }}- + - name: Cache local Maven repository + uses: actions/cache@v2 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + - name: Set up JDK 1.8 + uses: actions/setup-java@v1 + with: + java-version: 1.8 + server-id: github # Value of the distributionManagement/repository/id field of the pom.xml + settings-path: ${{ github.workspace }} # location for the settings.xml file + + - name: Build with Maven + run: mvn -B package --file pom.xml + + - name: Push to Docker Hub + uses: docker/build-push-action@v1 + with: + username: metersphere + password: ${{ secrets.DOCKER_HUB_TOKEN }} + repository: metersphere/node-controller + tag_with_ref: true + build_args: MS_VERSION=${{ env.GITHUB_REF_SLUG }}-${{ env.GITHUB_SHA_SHORT }} From ee139124c156713ac0b45d6b360cb85b91c66f09 Mon Sep 17 00:00:00 2001 From: BugKing Date: Thu, 3 Dec 2020 19:29:17 +0800 Subject: [PATCH 024/109] =?UTF-8?q?build:=20=E4=BF=AE=E6=94=B9=20Dockerfil?= =?UTF-8?q?e=20=E5=9F=BA=E7=A1=80=E9=95=9C=E5=83=8F=E5=9C=B0=E5=9D=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index e779aa1..835c858 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM registry.fit2cloud.com/fit2cloud2/fabric8-java-alpine-openjdk8-jre +FROM metersphere/fabric8-java-alpine-openjdk8-jre MAINTAINER FIT2CLOUD From 7c8a97b3da406cecc712bf34da0cf4e73e73464b Mon Sep 17 00:00:00 2001 From: BugKing Date: Thu, 3 Dec 2020 19:34:24 +0800 Subject: [PATCH 025/109] =?UTF-8?q?build:=20=E6=9B=B4=E6=96=B0=20action?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build-push.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-push.yml b/.github/workflows/build-push.yml index a292545..57f0698 100644 --- a/.github/workflows/build-push.yml +++ b/.github/workflows/build-push.yml @@ -3,11 +3,11 @@ name: Build Docker Image and Push on: push: branches: - - main + - master - v1* pull_request: branches: - - main + - master - v1* workflow_dispatch: From f8849952f66458529248e4f5799363c5da7832d9 Mon Sep 17 00:00:00 2001 From: BugKing Date: Thu, 3 Dec 2020 19:24:36 +0800 Subject: [PATCH 026/109] =?UTF-8?q?build:=20=E5=A2=9E=E5=8A=A0=20action?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build-push.yml | 58 ++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 .github/workflows/build-push.yml diff --git a/.github/workflows/build-push.yml b/.github/workflows/build-push.yml new file mode 100644 index 0000000..a292545 --- /dev/null +++ b/.github/workflows/build-push.yml @@ -0,0 +1,58 @@ +name: Build Docker Image and Push + +on: + push: + branches: + - main + - v1* + pull_request: + branches: + - main + - v1* + + workflow_dispatch: + +jobs: + build_push: + runs-on: ubuntu-latest + name: Build Docker Image and Push + steps: + - uses: actions/checkout@v2 + - name: Inject slug/short variables + uses: rlespinasse/github-slug-action@v3.x + - name: Cache node modules + uses: actions/cache@v2 + env: + cache-name: cache-node-modules + with: + path: ~/.npm + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-build-${{ env.cache-name }}- + ${{ runner.os }}-build- + ${{ runner.os }}- + - name: Cache local Maven repository + uses: actions/cache@v2 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + - name: Set up JDK 1.8 + uses: actions/setup-java@v1 + with: + java-version: 1.8 + server-id: github # Value of the distributionManagement/repository/id field of the pom.xml + settings-path: ${{ github.workspace }} # location for the settings.xml file + + - name: Build with Maven + run: mvn -B package --file pom.xml + + - name: Push to Docker Hub + uses: docker/build-push-action@v1 + with: + username: metersphere + password: ${{ secrets.DOCKER_HUB_TOKEN }} + repository: metersphere/node-controller + tag_with_ref: true + build_args: MS_VERSION=${{ env.GITHUB_REF_SLUG }}-${{ env.GITHUB_SHA_SHORT }} From 0cd2e6cc911fd25c09291a2cd0b9ae04814fcb7c Mon Sep 17 00:00:00 2001 From: BugKing Date: Thu, 3 Dec 2020 19:29:17 +0800 Subject: [PATCH 027/109] =?UTF-8?q?build:=20=E4=BF=AE=E6=94=B9=20Dockerfil?= =?UTF-8?q?e=20=E5=9F=BA=E7=A1=80=E9=95=9C=E5=83=8F=E5=9C=B0=E5=9D=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 27f2f7d..b0e2a1b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM registry.fit2cloud.com/fit2cloud2/fabric8-java-alpine-openjdk8-jre +FROM metersphere/fabric8-java-alpine-openjdk8-jre MAINTAINER FIT2CLOUD From d3e0453b9e0893bfa00e6b87f704a16ba4992f95 Mon Sep 17 00:00:00 2001 From: BugKing Date: Thu, 10 Dec 2020 13:15:02 +0800 Subject: [PATCH 028/109] =?UTF-8?q?build:=20=E6=9B=B4=E6=96=B0=20docker=20?= =?UTF-8?q?=E9=95=9C=E5=83=8F=20tag=20=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build-push.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-push.yml b/.github/workflows/build-push.yml index 57f0698..2a07cb3 100644 --- a/.github/workflows/build-push.yml +++ b/.github/workflows/build-push.yml @@ -54,5 +54,5 @@ jobs: username: metersphere password: ${{ secrets.DOCKER_HUB_TOKEN }} repository: metersphere/node-controller - tag_with_ref: true + tags: ${{ env.GITHUB_REF_SLUG }} build_args: MS_VERSION=${{ env.GITHUB_REF_SLUG }}-${{ env.GITHUB_SHA_SHORT }} From 5b626e4b98fb96e728b5506990db059a24dc9721 Mon Sep 17 00:00:00 2001 From: BugKing Date: Thu, 17 Dec 2020 12:58:06 +0800 Subject: [PATCH 029/109] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 4 ++-- pom.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index b0e2a1b..9fd33f5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,9 +6,9 @@ ARG MS_VERSION=dev RUN mkdir -p /opt/apps -ADD target/node-controller-1.5.jar /opt/apps +ADD target/node-controller-1.6.jar /opt/apps -ENV JAVA_APP_JAR=/opt/apps/node-controller-1.5.jar +ENV JAVA_APP_JAR=/opt/apps/node-controller-1.6.jar ENV AB_OFF=true diff --git a/pom.xml b/pom.xml index 1a08c4f..5fd1ce9 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.metersphere node-controller - 1.5 + 1.6 node-controller node-controller From c6b0ffc51f95f8a08bcb366b7abf41593a7afda4 Mon Sep 17 00:00:00 2001 From: BugKing Date: Fri, 18 Dec 2020 13:47:30 +0800 Subject: [PATCH 030/109] =?UTF-8?q?build:=20=E5=A2=9E=E5=8A=A0=20Jenkinsfi?= =?UTF-8?q?le?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Jenkinsfile | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 Jenkinsfile diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..0daf2cb --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,28 @@ +pipeline { + agent { + node { + label 'master' + } + } + options { quietPeriod(2400) } + parameters { + string(name: 'IMAGE_NAME', defaultValue: 'ms-node-controller', description: '构建后的 Docker 镜像名称') + string(name: 'IMAGE_FREFIX', defaultValue: 'registry.cn-qingdao.aliyuncs.com/metersphere', description: '构建后的 Docker 镜像带仓库名的前缀') + } + stages { + stage('Build/Test') { + steps { + configFileProvider([configFile(fileId: 'metersphere-maven', targetLocation: 'settings.xml')]) { + sh "mvn clean package --settings ./settings.xml" + } + } + } + stage('Docker build & push') { + steps { + sh "docker build --build-arg MS_VERSION=\${TAG_NAME:-\$BRANCH_NAME}-b\${BUILD_NUMBER} -t ${IMAGE_NAME}:\${TAG_NAME:-\$BRANCH_NAME} ." + sh "docker tag ${IMAGE_NAME}:\${TAG_NAME:-\$BRANCH_NAME} ${IMAGE_FREFIX}/${IMAGE_NAME}:\${TAG_NAME:-\$BRANCH_NAME}" + sh "docker push ${IMAGE_FREFIX}/${IMAGE_NAME}:\${TAG_NAME:-\$BRANCH_NAME}" + } + } + } +} From 775a9d982f5afa79f662f4b015dd5c2dbff163b1 Mon Sep 17 00:00:00 2001 From: BugKing Date: Mon, 21 Dec 2020 10:45:36 +0800 Subject: [PATCH 031/109] =?UTF-8?q?build:=20Jenkinsfile=20=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E4=BC=81=E4=B8=9A=E5=BE=AE=E4=BF=A1=E9=80=9A=E7=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Jenkinsfile | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Jenkinsfile b/Jenkinsfile index 0daf2cb..aafe65f 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -24,5 +24,12 @@ pipeline { sh "docker push ${IMAGE_FREFIX}/${IMAGE_NAME}:\${TAG_NAME:-\$BRANCH_NAME}" } } + stage('Notification') { + steps { + withCredentials([string(credentialsId: 'wechat-bot-webhook', variable: 'WEBHOOK')]) { + qyWechatNotification failSend: true, mentionedId: '', mentionedMobile: '', webhookUrl: '${WEBHOOK}' + } + } + } } } From c41ee0a96c5d58c91188622101713a447d4f75b1 Mon Sep 17 00:00:00 2001 From: BugKing Date: Mon, 21 Dec 2020 10:46:11 +0800 Subject: [PATCH 032/109] =?UTF-8?q?build:=20Jenkinsfile=20=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E4=BC=81=E4=B8=9A=E5=BE=AE=E4=BF=A1=E9=80=9A=E7=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Jenkinsfile | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Jenkinsfile b/Jenkinsfile index 0daf2cb..aafe65f 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -24,5 +24,12 @@ pipeline { sh "docker push ${IMAGE_FREFIX}/${IMAGE_NAME}:\${TAG_NAME:-\$BRANCH_NAME}" } } + stage('Notification') { + steps { + withCredentials([string(credentialsId: 'wechat-bot-webhook', variable: 'WEBHOOK')]) { + qyWechatNotification failSend: true, mentionedId: '', mentionedMobile: '', webhookUrl: '${WEBHOOK}' + } + } + } } } From 8715a5cc6101f60aa149a86e71667e3ce53e7198 Mon Sep 17 00:00:00 2001 From: BugKing Date: Mon, 21 Dec 2020 14:07:12 +0800 Subject: [PATCH 033/109] =?UTF-8?q?build:=20Jenkinsfile=20=E9=80=9A?= =?UTF-8?q?=E7=9F=A5=E4=BD=BF=E7=94=A8=20post?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Jenkinsfile | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index aafe65f..f9e7194 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -24,11 +24,12 @@ pipeline { sh "docker push ${IMAGE_FREFIX}/${IMAGE_NAME}:\${TAG_NAME:-\$BRANCH_NAME}" } } - stage('Notification') { - steps { - withCredentials([string(credentialsId: 'wechat-bot-webhook', variable: 'WEBHOOK')]) { - qyWechatNotification failSend: true, mentionedId: '', mentionedMobile: '', webhookUrl: '${WEBHOOK}' - } + } + post('Notification') { + always { + sh "echo \$WEBHOOK\n" + withCredentials([string(credentialsId: 'wechat-bot-webhook', variable: 'WEBHOOK')]) { + qyWechatNotification failSend: true, mentionedId: '', mentionedMobile: '', webhookUrl: "$WEBHOOK" } } } From 666b840fce91add04ca6b028eed35e984f3844ee Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Mon, 21 Dec 2020 14:17:51 +0800 Subject: [PATCH 034/109] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=E4=BB=B7?= =?UTF-8?q?=E5=80=BC=E6=B5=8B=E8=AF=95=E6=96=87=E4=BB=B6=E7=9A=84=E6=96=B9?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/JmeterOperateController.java | 20 +---- .../node/controller/request/TestRequest.java | 6 -- .../node/service/JmeterOperateService.java | 83 +++---------------- 3 files changed, 13 insertions(+), 96 deletions(-) diff --git a/src/main/java/io/metersphere/node/controller/JmeterOperateController.java b/src/main/java/io/metersphere/node/controller/JmeterOperateController.java index 3ca6ed1..00359a0 100644 --- a/src/main/java/io/metersphere/node/controller/JmeterOperateController.java +++ b/src/main/java/io/metersphere/node/controller/JmeterOperateController.java @@ -4,13 +4,9 @@ import com.github.dockerjava.api.model.Container; import io.metersphere.node.controller.request.TestRequest; import io.metersphere.node.service.JmeterOperateService; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; -import java.io.IOException; import java.util.List; @RestController @@ -23,7 +19,7 @@ public class JmeterOperateController { * 初始化测试任务,根据需求启动若干个 JMeter Engine 容器 */ @PostMapping("/container/start") - public String startContainer(@RequestBody TestRequest testRequest) throws IOException { + public String startContainer(@RequestBody TestRequest testRequest) { jmeterOperateService.startContainer(testRequest); return "OK"; } @@ -37,20 +33,6 @@ public String stopContainer(@PathVariable String testId) { return "OK"; } - @GetMapping("download/jtl/{reportId}") - public ResponseEntity downloadJtl(@PathVariable String reportId) { - byte[] bytes = jmeterOperateService.downloadJtl(reportId); - return ResponseEntity.ok() - .contentType(MediaType.parseMediaType("application/octet-stream")) - .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + reportId + ".jtl" + "\"") - .body(bytes); - } - - @GetMapping("delete/jtl/{reportId}") - public boolean deleteJtl(@PathVariable String reportId) { - return jmeterOperateService.deleteJtl(reportId); - } - /** * 停止指定测试任务,控制上述容器停止指定的 JMeter 测试 */ diff --git a/src/main/java/io/metersphere/node/controller/request/TestRequest.java b/src/main/java/io/metersphere/node/controller/request/TestRequest.java index eb14292..ccb1729 100644 --- a/src/main/java/io/metersphere/node/controller/request/TestRequest.java +++ b/src/main/java/io/metersphere/node/controller/request/TestRequest.java @@ -9,12 +9,6 @@ @Getter @Setter public class TestRequest extends DockerLoginRequest { - - private String fileString; - private String testId; - private String reportId; private String image; - private Map testData = new HashMap<>(); private Map env = new HashMap<>(); - private Map testJars = new HashMap<>(); } diff --git a/src/main/java/io/metersphere/node/service/JmeterOperateService.java b/src/main/java/io/metersphere/node/service/JmeterOperateService.java index 18c85c2..7565e56 100644 --- a/src/main/java/io/metersphere/node/service/JmeterOperateService.java +++ b/src/main/java/io/metersphere/node/service/JmeterOperateService.java @@ -2,26 +2,23 @@ import com.github.dockerjava.api.DockerClient; import com.github.dockerjava.api.command.WaitContainerResultCallback; -import com.github.dockerjava.api.model.*; +import com.github.dockerjava.api.model.Container; +import com.github.dockerjava.api.model.Frame; +import com.github.dockerjava.api.model.HostConfig; +import com.github.dockerjava.api.model.Image; import com.github.dockerjava.core.InvocationBuilder; import io.metersphere.node.config.JmeterProperties; import io.metersphere.node.controller.request.TestRequest; import io.metersphere.node.util.DockerClientService; import io.metersphere.node.util.LogUtil; -import org.apache.commons.io.FileUtils; -import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; import javax.annotation.Resource; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.Socket; -import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -33,61 +30,28 @@ public class JmeterOperateService { @Resource private JmeterProperties jmeterProperties; - private static String ROOT_PATH; - static { - ROOT_PATH = System.getenv("JMETER_DATA_PATH"); - LogUtil.info("JMETER_DATA_PATH: " + ROOT_PATH); - if (StringUtils.isBlank(ROOT_PATH)) { - ROOT_PATH = "/opt/metersphere/data/jmeter/"; - } - } - - public void startContainer(TestRequest testRequest) throws IOException { - String bootstrapServers = testRequest.getEnv().get("BOOTSTRAP_SERVERS"); + public void startContainer(TestRequest testRequest) { + Map env = testRequest.getEnv(); + String bootstrapServers = env.get("BOOTSTRAP_SERVERS"); checkKafka(bootstrapServers); - LogUtil.info("Receive start container request, test id: {}", testRequest.getTestId()); + LogUtil.info("Receive start container request, test id: {}", env.get("TEST_ID")); DockerClient dockerClient = DockerClientService.connectDocker(testRequest); - String testId = testRequest.getTestId(); String containerImage = testRequest.getImage(); - String filePath = StringUtils.join(new String[]{ROOT_PATH, testId}, File.separator); - String fileName = testId + ".jmx"; - - - // 每个测试生成一个文件夹 - FileUtils.writeStringToFile(new File(filePath + File.separator + fileName), testRequest.getFileString(), StandardCharsets.UTF_8); - // 保存测试数据文件 - Map testData = testRequest.getTestData(); - if (!CollectionUtils.isEmpty(testData)) { - for (String k : testData.keySet()) { - String v = testData.get(k); - FileUtils.writeStringToFile(new File(filePath + File.separator + k), v, StandardCharsets.UTF_8); - } - } - - // 保存 byte[] jar - Map jarFiles = testRequest.getTestJars(); - if (!CollectionUtils.isEmpty(jarFiles)) { - for (String k : jarFiles.keySet()) { - byte[] v = jarFiles.get(k); - FileUtils.writeByteArrayToFile(new File(filePath + File.separator + k), v); - } - } // 查找镜像 searchImage(dockerClient, testRequest.getImage()); // 检查容器是否存在 - checkContainerExists(dockerClient, testId); + checkContainerExists(dockerClient, env.get("TEST_ID")); // 启动测试 - startContainer(testRequest, dockerClient, testId, containerImage, filePath); + startContainer(testRequest, dockerClient, env.get("TEST_ID"), containerImage); } - private void startContainer(TestRequest testRequest, DockerClient dockerClient, String testId, String containerImage, String filePath) { + private void startContainer(TestRequest testRequest, DockerClient dockerClient, String testId, String containerImage) { // 创建 hostConfig HostConfig hostConfig = HostConfig.newHostConfig(); - hostConfig.withBinds(Bind.parse(filePath + ":/test")); String[] envs = getEnvs(testRequest); String containerId = DockerClientService.createContainers(dockerClient, testId, containerImage, hostConfig, envs).getId(); @@ -100,14 +64,12 @@ private void startContainer(TestRequest testRequest, DockerClient dockerClient, public void onComplete() { // 清理文件夹 try { - FileUtils.forceDelete(new File(filePath)); - LogUtil.info("Remove dir completed."); if (DockerClientService.existContainer(dockerClient, containerId) > 0) { DockerClientService.removeContainer(dockerClient, containerId); } LogUtil.info("Remove container completed: " + containerId); } catch (Exception e) { - LogUtil.error("Remove dir error: ", e); + LogUtil.error("Remove container error: ", e); } LogUtil.info("completed...."); } @@ -248,25 +210,4 @@ public void onNext(Frame item) { } return sb.toString(); } - - public byte[] downloadJtl(String reportId) { - try { - String jtlFileName = reportId + ".jtl"; - return IOUtils.toByteArray(new FileInputStream(ROOT_PATH + File.separator + jtlFileName)); - } catch (IOException e) { - LogUtil.error(e); - } - return new byte[0]; - } - - public boolean deleteJtl(String reportId) { - String jtlFileName = reportId + ".jtl"; - try { - FileUtils.forceDelete(new File(ROOT_PATH + File.separator + jtlFileName)); - return true; - } catch (IOException e) { - LogUtil.error(e); - } - return false; - } } From 43a2f41f2e6d1a09f2d4b867d008fab3fb15ea73 Mon Sep 17 00:00:00 2001 From: BugKing Date: Mon, 21 Dec 2020 14:33:58 +0800 Subject: [PATCH 035/109] =?UTF-8?q?build:=20=E4=BF=AE=E6=94=B9=E9=9D=99?= =?UTF-8?q?=E9=BB=98=E6=97=B6=E9=97=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index f9e7194..7f01aaf 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -4,7 +4,7 @@ pipeline { label 'master' } } - options { quietPeriod(2400) } + options { quietPeriod(1200) } parameters { string(name: 'IMAGE_NAME', defaultValue: 'ms-node-controller', description: '构建后的 Docker 镜像名称') string(name: 'IMAGE_FREFIX', defaultValue: 'registry.cn-qingdao.aliyuncs.com/metersphere', description: '构建后的 Docker 镜像带仓库名的前缀') From 0c79f27002814e491245eab45d86dd4f75dea245 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Mon, 21 Dec 2020 15:25:53 +0800 Subject: [PATCH 036/109] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=E4=BB=B7?= =?UTF-8?q?=E5=80=BC=E6=B5=8B=E8=AF=95=E6=96=87=E4=BB=B6=E7=9A=84=E6=96=B9?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/metersphere/node/service/JmeterOperateService.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/io/metersphere/node/service/JmeterOperateService.java b/src/main/java/io/metersphere/node/service/JmeterOperateService.java index 7565e56..8b989f2 100644 --- a/src/main/java/io/metersphere/node/service/JmeterOperateService.java +++ b/src/main/java/io/metersphere/node/service/JmeterOperateService.java @@ -125,8 +125,6 @@ private void checkKafka(String bootstrapServers) { private String[] getEnvs(TestRequest testRequest) { Map env = testRequest.getEnv(); - // HEAP - env.put("HEAP", jmeterProperties.getHeap()); return env.keySet().stream().map(k -> k + "=" + env.get(k)).toArray(String[]::new); } From 5ec119acee3dcc4ef7b1166e6ba73a8f068d97fe Mon Sep 17 00:00:00 2001 From: BugKing Date: Mon, 21 Dec 2020 16:52:51 +0800 Subject: [PATCH 037/109] xx --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index b2f6ac8..1d31971 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,7 +1,7 @@ pipeline { agent { node { - label 'master' + label 'metersphere' } } options { quietPeriod(1200) } From 34cc77f80ba3040f59ed6dd3fad8f844766a5eef Mon Sep 17 00:00:00 2001 From: BugKing Date: Mon, 21 Dec 2020 16:53:52 +0800 Subject: [PATCH 038/109] =?UTF-8?q?build:=20=E4=BF=AE=E6=94=B9=20Jenkinsfi?= =?UTF-8?q?le=20=E6=9E=84=E5=BB=BA=E8=8A=82=E7=82=B9=20label?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index b2f6ac8..1d31971 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,7 +1,7 @@ pipeline { agent { node { - label 'master' + label 'metersphere' } } options { quietPeriod(1200) } From 4fc618c21c7d2eaa7b9e2e58599921d3879ce526 Mon Sep 17 00:00:00 2001 From: BugKing Date: Mon, 21 Dec 2020 16:55:18 +0800 Subject: [PATCH 039/109] =?UTF-8?q?build:=20=E4=BF=AE=E6=94=B9=E9=9D=99?= =?UTF-8?q?=E9=BB=98=E6=97=B6=E9=97=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 1d31971..755b724 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -4,7 +4,7 @@ pipeline { label 'metersphere' } } - options { quietPeriod(1200) } + options { quietPeriod(60) } parameters { string(name: 'IMAGE_NAME', defaultValue: 'ms-node-controller', description: '构建后的 Docker 镜像名称') string(name: 'IMAGE_FREFIX', defaultValue: 'registry.cn-qingdao.aliyuncs.com/metersphere', description: '构建后的 Docker 镜像带仓库名的前缀') From 00c2cf250c091e5efb758d4d249941d8056913f7 Mon Sep 17 00:00:00 2001 From: BugKing Date: Mon, 21 Dec 2020 16:56:05 +0800 Subject: [PATCH 040/109] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E9=9D=99=E9=BB=98?= =?UTF-8?q?=E6=97=B6=E9=97=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 755b724..b967dba 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -4,7 +4,7 @@ pipeline { label 'metersphere' } } - options { quietPeriod(60) } + options { quietPeriod(600) } parameters { string(name: 'IMAGE_NAME', defaultValue: 'ms-node-controller', description: '构建后的 Docker 镜像名称') string(name: 'IMAGE_FREFIX', defaultValue: 'registry.cn-qingdao.aliyuncs.com/metersphere', description: '构建后的 Docker 镜像带仓库名的前缀') From 246c8183a8d72a8ca9b3d4ea28945df273523df5 Mon Sep 17 00:00:00 2001 From: BugKing Date: Wed, 23 Dec 2020 09:59:48 +0800 Subject: [PATCH 041/109] =?UTF-8?q?build:=20=E9=95=9C=E5=83=8F=E5=86=85?= =?UTF-8?q?=E7=9A=84=E7=89=88=E6=9C=AC=E5=8F=B7=E4=BD=BF=E7=94=A8=20commit?= =?UTF-8?q?=20ID?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Jenkinsfile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index b967dba..fa8deb4 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -7,7 +7,7 @@ pipeline { options { quietPeriod(600) } parameters { string(name: 'IMAGE_NAME', defaultValue: 'ms-node-controller', description: '构建后的 Docker 镜像名称') - string(name: 'IMAGE_FREFIX', defaultValue: 'registry.cn-qingdao.aliyuncs.com/metersphere', description: '构建后的 Docker 镜像带仓库名的前缀') + string(name: 'IMAGE_PREFIX', defaultValue: 'registry.cn-qingdao.aliyuncs.com/metersphere', description: '构建后的 Docker 镜像带仓库名的前缀') } stages { stage('Build/Test') { @@ -19,9 +19,9 @@ pipeline { } stage('Docker build & push') { steps { - sh "docker build --build-arg MS_VERSION=\${TAG_NAME:-\$BRANCH_NAME}-b\${BUILD_NUMBER} -t ${IMAGE_NAME}:\${TAG_NAME:-\$BRANCH_NAME} ." - sh "docker tag ${IMAGE_NAME}:\${TAG_NAME:-\$BRANCH_NAME} ${IMAGE_FREFIX}/${IMAGE_NAME}:\${TAG_NAME:-\$BRANCH_NAME}" - sh "docker push ${IMAGE_FREFIX}/${IMAGE_NAME}:\${TAG_NAME:-\$BRANCH_NAME}" + sh "docker build --build-arg MS_VERSION=\${TAG_NAME:-\$BRANCH_NAME}-\${GIT_COMMIT:0:8} -t ${IMAGE_NAME}:\${TAG_NAME:-\$BRANCH_NAME} ." + sh "docker tag ${IMAGE_NAME}:\${TAG_NAME:-\$BRANCH_NAME} ${IMAGE_PREFIX}/${IMAGE_NAME}:\${TAG_NAME:-\$BRANCH_NAME}" + sh "docker push ${IMAGE_PREFIX}/${IMAGE_NAME}:\${TAG_NAME:-\$BRANCH_NAME}" } } stage('Notification') { From 740dc32c186d9816e64204a8bef1239ea2ab8cf6 Mon Sep 17 00:00:00 2001 From: BugKing Date: Wed, 6 Jan 2021 13:38:45 +0800 Subject: [PATCH 042/109] =?UTF-8?q?build:=20Jenkinsfile=20=E5=8E=BB?= =?UTF-8?q?=E6=8E=89=20parameter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Jenkinsfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index fa8deb4..3b07ce5 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -5,9 +5,9 @@ pipeline { } } options { quietPeriod(600) } - parameters { - string(name: 'IMAGE_NAME', defaultValue: 'ms-node-controller', description: '构建后的 Docker 镜像名称') - string(name: 'IMAGE_PREFIX', defaultValue: 'registry.cn-qingdao.aliyuncs.com/metersphere', description: '构建后的 Docker 镜像带仓库名的前缀') + environment { + IMAGE_NAME = 'metersphere' + IMAGE_PREFIX = 'registry.cn-qingdao.aliyuncs.com/metersphere' } stages { stage('Build/Test') { From 9e9455447aaf7394c27f36cba83b4d35a2fbd0c9 Mon Sep 17 00:00:00 2001 From: BugKing Date: Wed, 6 Jan 2021 14:01:46 +0800 Subject: [PATCH 043/109] =?UTF-8?q?build:=20=E4=BF=AE=E6=94=B9=20Jenkinsfi?= =?UTF-8?q?le=20=E9=95=9C=E5=83=8F=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 3b07ce5..1106dc0 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -6,7 +6,7 @@ pipeline { } options { quietPeriod(600) } environment { - IMAGE_NAME = 'metersphere' + IMAGE_NAME = 'ms-node-controller' IMAGE_PREFIX = 'registry.cn-qingdao.aliyuncs.com/metersphere' } stages { From cda41a930718163c4cd2c5d197b57de0b4c7d7fa Mon Sep 17 00:00:00 2001 From: BugKing Date: Wed, 27 Jan 2021 15:05:58 +0800 Subject: [PATCH 044/109] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 4 ++-- pom.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 9fd33f5..8582b1c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,9 +6,9 @@ ARG MS_VERSION=dev RUN mkdir -p /opt/apps -ADD target/node-controller-1.6.jar /opt/apps +ADD target/node-controller-1.7.jar /opt/apps -ENV JAVA_APP_JAR=/opt/apps/node-controller-1.6.jar +ENV JAVA_APP_JAR=/opt/apps/node-controller-1.7.jar ENV AB_OFF=true diff --git a/pom.xml b/pom.xml index 5fd1ce9..4032cd9 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.metersphere node-controller - 1.6 + 1.7 node-controller node-controller From 164591f05242a01cd686ee6565f9cf04d76855d2 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Mon, 22 Feb 2021 10:10:12 +0800 Subject: [PATCH 045/109] =?UTF-8?q?refactor:=20=E5=8E=BB=E6=8E=89=E4=B8=8D?= =?UTF-8?q?=E7=94=A8=E7=9A=84=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/io/metersphere/node/Application.java | 5 ----- .../metersphere/node/config/JmeterProperties.java | 13 ------------- .../node/service/JmeterOperateService.java | 12 +++++------- 3 files changed, 5 insertions(+), 25 deletions(-) delete mode 100644 src/main/java/io/metersphere/node/config/JmeterProperties.java diff --git a/src/main/java/io/metersphere/node/Application.java b/src/main/java/io/metersphere/node/Application.java index 702a592..35b186f 100644 --- a/src/main/java/io/metersphere/node/Application.java +++ b/src/main/java/io/metersphere/node/Application.java @@ -1,14 +1,9 @@ package io.metersphere.node; -import io.metersphere.node.config.JmeterProperties; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.PropertySource; -@EnableConfigurationProperties({ - JmeterProperties.class -}) @PropertySource(value = {"file:/opt/metersphere/conf/metersphere.properties"}, encoding = "UTF-8", ignoreResourceNotFound = true) @SpringBootApplication public class Application { diff --git a/src/main/java/io/metersphere/node/config/JmeterProperties.java b/src/main/java/io/metersphere/node/config/JmeterProperties.java deleted file mode 100644 index 38d9e4e..0000000 --- a/src/main/java/io/metersphere/node/config/JmeterProperties.java +++ /dev/null @@ -1,13 +0,0 @@ -package io.metersphere.node.config; - -import lombok.Getter; -import lombok.Setter; -import org.springframework.boot.context.properties.ConfigurationProperties; - -@ConfigurationProperties(prefix = JmeterProperties.JMETER_PREFIX) -@Setter -@Getter -public class JmeterProperties { - public static final String JMETER_PREFIX = "jmeter"; - private String heap = "-Xms1g -Xmx1g -XX:MaxMetaspaceSize=256m"; -} diff --git a/src/main/java/io/metersphere/node/service/JmeterOperateService.java b/src/main/java/io/metersphere/node/service/JmeterOperateService.java index 8b989f2..7a0c7c4 100644 --- a/src/main/java/io/metersphere/node/service/JmeterOperateService.java +++ b/src/main/java/io/metersphere/node/service/JmeterOperateService.java @@ -7,7 +7,6 @@ import com.github.dockerjava.api.model.HostConfig; import com.github.dockerjava.api.model.Image; import com.github.dockerjava.core.InvocationBuilder; -import io.metersphere.node.config.JmeterProperties; import io.metersphere.node.controller.request.TestRequest; import io.metersphere.node.util.DockerClientService; import io.metersphere.node.util.LogUtil; @@ -15,7 +14,6 @@ import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; -import javax.annotation.Resource; import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.Socket; @@ -28,15 +26,15 @@ @Service public class JmeterOperateService { - @Resource - private JmeterProperties jmeterProperties; public void startContainer(TestRequest testRequest) { Map env = testRequest.getEnv(); + String testId = env.get("TEST_ID"); + LogUtil.info("Receive start container request, test id: {}", testId); String bootstrapServers = env.get("BOOTSTRAP_SERVERS"); + // 检查kafka连通性 checkKafka(bootstrapServers); - LogUtil.info("Receive start container request, test id: {}", env.get("TEST_ID")); DockerClient dockerClient = DockerClientService.connectDocker(testRequest); String containerImage = testRequest.getImage(); @@ -44,9 +42,9 @@ public void startContainer(TestRequest testRequest) { // 查找镜像 searchImage(dockerClient, testRequest.getImage()); // 检查容器是否存在 - checkContainerExists(dockerClient, env.get("TEST_ID")); + checkContainerExists(dockerClient, testId); // 启动测试 - startContainer(testRequest, dockerClient, env.get("TEST_ID"), containerImage); + startContainer(testRequest, dockerClient, testId, containerImage); } private void startContainer(TestRequest testRequest, DockerClient dockerClient, String testId, String containerImage) { From a92cf980336e51998f5ffffaf84d129648190212 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Thu, 18 Mar 2021 13:14:26 +0800 Subject: [PATCH 046/109] =?UTF-8?q?refactor:=20=E4=BD=BF=E7=94=A8host?= =?UTF-8?q?=E7=BD=91=E7=BB=9C=E5=90=AF=E5=8A=A8=E5=AE=B9=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/metersphere/node/service/JmeterOperateService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/metersphere/node/service/JmeterOperateService.java b/src/main/java/io/metersphere/node/service/JmeterOperateService.java index 7a0c7c4..b3e6668 100644 --- a/src/main/java/io/metersphere/node/service/JmeterOperateService.java +++ b/src/main/java/io/metersphere/node/service/JmeterOperateService.java @@ -50,7 +50,7 @@ public void startContainer(TestRequest testRequest) { private void startContainer(TestRequest testRequest, DockerClient dockerClient, String testId, String containerImage) { // 创建 hostConfig HostConfig hostConfig = HostConfig.newHostConfig(); - + hostConfig.withNetworkMode("host"); String[] envs = getEnvs(testRequest); String containerId = DockerClientService.createContainers(dockerClient, testId, containerImage, hostConfig, envs).getId(); From 08f682132b213ab22f0a58a4a74618650c21521a Mon Sep 17 00:00:00 2001 From: BugKing Date: Wed, 24 Mar 2021 19:16:24 +0800 Subject: [PATCH 047/109] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 4 ++-- pom.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 8582b1c..cea8bc7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,9 +6,9 @@ ARG MS_VERSION=dev RUN mkdir -p /opt/apps -ADD target/node-controller-1.7.jar /opt/apps +ADD target/node-controller-1.8.jar /opt/apps -ENV JAVA_APP_JAR=/opt/apps/node-controller-1.7.jar +ENV JAVA_APP_JAR=/opt/apps/node-controller-1.8.jar ENV AB_OFF=true diff --git a/pom.xml b/pom.xml index 4032cd9..8505672 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.metersphere node-controller - 1.7 + 1.8 node-controller node-controller From d72681108ec0c3ba3939f87f8ce3d2c55084c59c Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Fri, 16 Apr 2021 17:56:04 +0800 Subject: [PATCH 048/109] =?UTF-8?q?feat(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96):=20=E6=8E=A5=E5=8F=A3=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E5=8C=96=E6=89=A7=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 333 ++++ src/main/java/io/metersphere/Application.java | 31 + .../api/config/AppStartListener.java | 65 + .../metersphere/api/config/KafkaConfig.java | 17 + .../api/config/KafkaProperties.java | 51 + .../controller/JmeterExecuteController.java | 31 + .../api/controller/request/RunRequest.java | 14 + .../api/jmeter/APIBackendListenerClient.java | 246 +++ .../metersphere/api/jmeter/JMeterService.java | 86 + .../io/metersphere/api/jmeter/JMeterVars.java | 115 ++ .../metersphere/api/jmeter/LocalRunner.java | 24 + .../api/jmeter/constants/ApiRunMode.java | 5 + .../api/jmeter/constants/RequestType.java | 12 + .../api/jmeter/module/RequestResult.java | 47 + .../module/ResponseAssertionResult.java | 13 + .../api/jmeter/module/ResponseResult.java | 32 + .../api/jmeter/module/ScenarioResult.java | 50 + .../api/jmeter/module/TestResult.java | 67 + .../api/jmeter/utils/CommonBeanFactory.java | 34 + .../api/jmeter/utils/FileUtils.java | 32 + .../api/jmeter/utils/JmeterProperties.java | 21 + .../api/jmeter/utils/MSException.java | 24 + .../api/jmeter/utils/RunModeConfig.java | 11 + .../api/service/JmeterExecuteService.java | 73 + .../api/service/LoadTestProducer.java | 53 + .../java/io/metersphere/node/Application.java | 14 - .../java/org/apache/jmeter/NewDriver.java | 378 ++++ .../jmeter/assertions/JSONPathAssertion.java | 229 +++ .../apache/jmeter/reporters/ResultAction.java | 98 + .../apache/jmeter/samplers/SampleResult.java | 1623 +++++++++++++++++ .../jmeter/util/JSR223BeanInfoSupport.java | 93 + .../apache/jmeter/util/JSR223TestElement.java | 387 ++++ .../apache/jorphan/collections/HashTree.java | 1095 +++++++++++ src/main/resources/application.properties | 27 + .../resources/jmeter/bin/jmeter.properties | 1346 ++++++++++++++ .../jmeter/bin/saveservice.properties | 420 +++++ .../resources/jmeter/bin/upgrade.properties | 123 ++ src/main/resources/jmeter/bin/user.properties | 147 ++ 38 files changed, 7453 insertions(+), 14 deletions(-) create mode 100644 src/main/java/io/metersphere/Application.java create mode 100644 src/main/java/io/metersphere/api/config/AppStartListener.java create mode 100644 src/main/java/io/metersphere/api/config/KafkaConfig.java create mode 100644 src/main/java/io/metersphere/api/config/KafkaProperties.java create mode 100644 src/main/java/io/metersphere/api/controller/JmeterExecuteController.java create mode 100644 src/main/java/io/metersphere/api/controller/request/RunRequest.java create mode 100644 src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java create mode 100644 src/main/java/io/metersphere/api/jmeter/JMeterService.java create mode 100644 src/main/java/io/metersphere/api/jmeter/JMeterVars.java create mode 100644 src/main/java/io/metersphere/api/jmeter/LocalRunner.java create mode 100644 src/main/java/io/metersphere/api/jmeter/constants/ApiRunMode.java create mode 100644 src/main/java/io/metersphere/api/jmeter/constants/RequestType.java create mode 100644 src/main/java/io/metersphere/api/jmeter/module/RequestResult.java create mode 100644 src/main/java/io/metersphere/api/jmeter/module/ResponseAssertionResult.java create mode 100644 src/main/java/io/metersphere/api/jmeter/module/ResponseResult.java create mode 100644 src/main/java/io/metersphere/api/jmeter/module/ScenarioResult.java create mode 100644 src/main/java/io/metersphere/api/jmeter/module/TestResult.java create mode 100644 src/main/java/io/metersphere/api/jmeter/utils/CommonBeanFactory.java create mode 100644 src/main/java/io/metersphere/api/jmeter/utils/FileUtils.java create mode 100644 src/main/java/io/metersphere/api/jmeter/utils/JmeterProperties.java create mode 100644 src/main/java/io/metersphere/api/jmeter/utils/MSException.java create mode 100644 src/main/java/io/metersphere/api/jmeter/utils/RunModeConfig.java create mode 100644 src/main/java/io/metersphere/api/service/JmeterExecuteService.java create mode 100644 src/main/java/io/metersphere/api/service/LoadTestProducer.java delete mode 100644 src/main/java/io/metersphere/node/Application.java create mode 100644 src/main/java/org/apache/jmeter/NewDriver.java create mode 100644 src/main/java/org/apache/jmeter/assertions/JSONPathAssertion.java create mode 100644 src/main/java/org/apache/jmeter/reporters/ResultAction.java create mode 100644 src/main/java/org/apache/jmeter/samplers/SampleResult.java create mode 100644 src/main/java/org/apache/jmeter/util/JSR223BeanInfoSupport.java create mode 100644 src/main/java/org/apache/jmeter/util/JSR223TestElement.java create mode 100644 src/main/java/org/apache/jorphan/collections/HashTree.java create mode 100644 src/main/resources/jmeter/bin/jmeter.properties create mode 100644 src/main/resources/jmeter/bin/saveservice.properties create mode 100644 src/main/resources/jmeter/bin/upgrade.properties create mode 100644 src/main/resources/jmeter/bin/user.properties diff --git a/pom.xml b/pom.xml index 8505672..127e7fa 100644 --- a/pom.xml +++ b/pom.xml @@ -16,6 +16,9 @@ 1.8 + 5.4.1 + 1.1.3 + 2.7.8 @@ -62,6 +65,336 @@ + + + + org.flywaydb + flyway-core + + + mysql + mysql-connector-java + runtime + + + com.github.pagehelper + pagehelper + 5.0.3 + + + + org.apache.commons + commons-collections4 + 4.1 + + + org.apache.commons + commons-text + 1.8 + + + commons-codec + commons-codec + + + + com.alibaba + fastjson + 1.2.72 + + + + + org.springdoc + springdoc-openapi-ui + 1.5.6 + + + + org.apache.jmeter + ApacheJMeter_http + ${jmeter.version} + + + org.apache.logging.log4j + log4j-slf4j-impl + + + xstream + com.thoughtworks.xstream + + + + + + com.thoughtworks.xstream + xstream + 1.4.16 + + + + org.python + jython-standalone + 2.7.0 + + + + org.apache.jmeter + ApacheJMeter_functions + ${jmeter.version} + + + + org.apache.jmeter + ApacheJMeter_jdbc + ${jmeter.version} + + + + org.apache.jmeter + ApacheJMeter_tcp + ${jmeter.version} + + + org.apache.jmeter + ApacheJMeter_java + ${jmeter.version} + + + + com.microsoft.sqlserver + mssql-jdbc + 7.4.1.jre8 + + + + org.postgresql + postgresql + 42.2.14 + + + + com.oracle.database.jdbc + ojdbc8 + 19.7.0.0 + + + + + org.apache.dubbo + dubbo + ${dubbo.version} + + + org.apache.zookeeper + zookeeper + 3.4.14 + + + slf4j-log4j12 + org.slf4j + + + + + org.apache.curator + curator-framework + 4.0.1 + + + org.apache.curator + curator-recipes + 4.0.1 + + + + + org.apache.dubbo + dubbo-registry-nacos + ${dubbo.version} + + + com.alibaba.nacos + nacos-api + ${nacos.version} + + + com.alibaba.nacos + nacos-client + ${nacos.version} + + + + com.alibaba + easyexcel + 2.1.7 + + + + io.metersphere + jmeter-plugins-dubbo + 2.7.12 + + + + + org.springframework.boot + spring-boot-starter-data-ldap + + + + + io.swagger.parser.v3 + swagger-parser + 2.0.24 + + + + + org.codehaus.groovy + groovy-jsr223 + 2.5.10 + + + + + org.mozilla + rhino-engine + 1.7.13 + + + + org.jsoup + jsoup + 1.10.3 + + + + com.atlassian.commonmark + commonmark + 0.15.2 + + + + org.apache.commons + commons-compress + 1.20 + + + + org.dom4j + dom4j + 2.1.3 + + + + jaxen + jaxen + 1.2.0 + + + net.sourceforge.htmlcleaner + htmlcleaner + 2.24 + + + + org.json + json + 20171018 + + + + com.aliyun + alibaba-dingtalk-service-sdk + 1.0.1 + + + org.apache.httpcomponents + httpclient + 4.5.6 + + + + net.oneandone.reflections8 + reflections8 + 0.11.7 + + + + io.fabric8 + kubernetes-client + 4.13.0 + + + com.github.fge + json-schema-validator + 2.2.6 + + + + org.apache.jmeter + ApacheJMeter_bolt + ${jmeter.version} + + + org.apache.jmeter + ApacheJMeter_jms + ${jmeter.version} + + + org.apache.jmeter + ApacheJMeter_ftp + ${jmeter.version} + + + org.apache.jmeter + ApacheJMeter_junit + ${jmeter.version} + + + org.apache.jmeter + ApacheJMeter_ldap + ${jmeter.version} + + + org.apache.jmeter + ApacheJMeter_mail + ${jmeter.version} + + + org.apache.jmeter + ApacheJMeter_components + ${jmeter.version} + + + org.apache.jmeter + ApacheJMeter_native + ${jmeter.version} + + + + com.jayway.jsonpath + json-path + 2.5.0 + + + org.aspectj + aspectjweaver + 1.9.6 + + + commons-collections + commons-collections + 3.2.2 + + + org.springframework.kafka + spring-kafka + + + + diff --git a/src/main/java/io/metersphere/Application.java b/src/main/java/io/metersphere/Application.java new file mode 100644 index 0000000..d304b57 --- /dev/null +++ b/src/main/java/io/metersphere/Application.java @@ -0,0 +1,31 @@ +package io.metersphere; + +import io.metersphere.api.config.KafkaProperties; +import io.metersphere.api.jmeter.utils.CommonBeanFactory; +import io.metersphere.api.jmeter.utils.JmeterProperties; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.web.servlet.ServletComponentScan; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.PropertySource; + +@ServletComponentScan +@EnableConfigurationProperties({ + KafkaProperties.class, + JmeterProperties.class, +}) +@PropertySource(value = {"file:/opt/metersphere/conf/metersphere.properties"}, encoding = "UTF-8", ignoreResourceNotFound = true) +@SpringBootApplication +public class Application { + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + + @Bean + @ConditionalOnMissingBean + public CommonBeanFactory commonBeanFactory() { + return new CommonBeanFactory(); + } +} diff --git a/src/main/java/io/metersphere/api/config/AppStartListener.java b/src/main/java/io/metersphere/api/config/AppStartListener.java new file mode 100644 index 0000000..fd4b171 --- /dev/null +++ b/src/main/java/io/metersphere/api/config/AppStartListener.java @@ -0,0 +1,65 @@ +package io.metersphere.api.config; + +import io.metersphere.api.jmeter.JMeterService; +import io.metersphere.api.jmeter.utils.FileUtils; +import io.metersphere.api.jmeter.utils.MSException; +import io.metersphere.node.util.LogUtil; +import org.apache.jmeter.NewDriver; +import org.python.core.Options; +import org.python.util.PythonInterpreter; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.net.MalformedURLException; + +@Component +public class AppStartListener implements ApplicationListener { + + @Resource + private JMeterService jMeterService; + + @Value("${jmeter.home}") + private String jmeterHome; + + @Override + public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { + System.out.println("================= NODE 应用启动 ================="); + System.setProperty("jmeter.home", jmeterHome); + loadJars(); + initPythonEnv(); + } + + /** + * 解决接口测试-无法导入内置python包 + */ + private void initPythonEnv() { + //解决无法加载 PyScriptEngineFactory + Options.importSite = false; + try { + PythonInterpreter interp = new PythonInterpreter(); + String path = jMeterService.getJmeterHome(); + System.out.println("sys.path: " + path); + path += "/lib/ext/jython-standalone.jar/Lib"; + interp.exec("import sys"); + interp.exec("sys.path.append(\"" + path + "\")"); + } catch (Exception e) { + e.printStackTrace(); + LogUtil.error(e.getMessage(), e); + } + } + + /** + * 加载jar包 + */ + private void loadJars() { + try { + NewDriver.addPath(FileUtils.JAR_FILE_DIR); + } catch (MalformedURLException e) { + LogUtil.error(e.getMessage(), e); + MSException.throwException(e.getMessage()); + } + } +} diff --git a/src/main/java/io/metersphere/api/config/KafkaConfig.java b/src/main/java/io/metersphere/api/config/KafkaConfig.java new file mode 100644 index 0000000..80ce8a6 --- /dev/null +++ b/src/main/java/io/metersphere/api/config/KafkaConfig.java @@ -0,0 +1,17 @@ +package io.metersphere.api.config; + +import org.apache.kafka.clients.admin.NewTopic; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.kafka.config.TopicBuilder; + +@Configuration +public class KafkaConfig { + private final String testTopic = "ms-api-exec-topic"; + + @Bean + public NewTopic apiExecTopic() { + return TopicBuilder.name(testTopic) + .build(); + } +} diff --git a/src/main/java/io/metersphere/api/config/KafkaProperties.java b/src/main/java/io/metersphere/api/config/KafkaProperties.java new file mode 100644 index 0000000..e52b95a --- /dev/null +++ b/src/main/java/io/metersphere/api/config/KafkaProperties.java @@ -0,0 +1,51 @@ +package io.metersphere.api.config; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = KafkaProperties.KAFKA_PREFIX) +@Getter +@Setter +public class KafkaProperties { + public static final String KAFKA_PREFIX = "kafka"; + + private String acks = "0"; // 不要设置all + private String expectedDelayEndTime = "30000"; // 30s + private String topic; + private String fields; + private String timestamp; + private String bootstrapServers; + private String sampleFilter; + private String testMode; + private String parseAllReqHeaders; + private String parseAllResHeaders; + private String compressionType; + private String batchSize; + private String clientId; + private String connectionsMaxIdleMs; + private KafkaProperties.Ssl ssl = new KafkaProperties.Ssl(); + private KafkaProperties.Log log = new KafkaProperties.Log(); + + @Getter + @Setter + public static class Ssl { + private String enabled = "false"; + private String keyPassword; + private String keystoreLocation; + private String keystorePassword; + private String keystoreType; + private String truststoreLocation; + private String truststorePassword; + private String truststoreType; + private String protocol; + private String enabledProtocols; + private String provider; + } + + @Getter + @Setter + public static class Log { + private String topic; + } +} diff --git a/src/main/java/io/metersphere/api/controller/JmeterExecuteController.java b/src/main/java/io/metersphere/api/controller/JmeterExecuteController.java new file mode 100644 index 0000000..03a9c1b --- /dev/null +++ b/src/main/java/io/metersphere/api/controller/JmeterExecuteController.java @@ -0,0 +1,31 @@ +package io.metersphere.api.controller; + +import com.alibaba.fastjson.JSON; +import io.metersphere.api.controller.request.RunRequest; +import io.metersphere.api.service.JmeterExecuteService; +import io.metersphere.node.util.LogUtil; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Resource; + +@RestController +@RequestMapping("/jmeter") +public class JmeterExecuteController { + + @Resource + private JmeterExecuteService jmeterExecuteService; + + @PostMapping(value = "/api/run", consumes = {"multipart/form-data"}) + public String apiRun(@RequestParam(value = "files") MultipartFile[] bodyFiles, @RequestParam(value = "jarFiles") MultipartFile[] jarFiles, String request) { + LogUtil.info("接收到测试请求 start "); + RunRequest runRequest = JSON.parseObject(request, RunRequest.class); + return jmeterExecuteService.run(runRequest, bodyFiles, jarFiles); + } + + @GetMapping("/status") + public String getStatus() { + return "OK"; + } + +} diff --git a/src/main/java/io/metersphere/api/controller/request/RunRequest.java b/src/main/java/io/metersphere/api/controller/request/RunRequest.java new file mode 100644 index 0000000..f0e6408 --- /dev/null +++ b/src/main/java/io/metersphere/api/controller/request/RunRequest.java @@ -0,0 +1,14 @@ +package io.metersphere.api.controller.request; + +import io.metersphere.api.jmeter.utils.RunModeConfig; +import lombok.Data; + +@Data +public class RunRequest { + private String testId; + private String userId; + private boolean isDebug; + private String runMode; + private String jmx; + private RunModeConfig config; +} diff --git a/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java b/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java new file mode 100644 index 0000000..c78488a --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java @@ -0,0 +1,246 @@ +package io.metersphere.api.jmeter; + +import com.alibaba.fastjson.JSON; +import io.metersphere.api.service.LoadTestProducer; +import io.metersphere.api.jmeter.constants.ApiRunMode; +import io.metersphere.api.jmeter.constants.RequestType; +import io.metersphere.api.jmeter.module.*; +import io.metersphere.api.jmeter.utils.CommonBeanFactory; +import io.metersphere.node.util.LogUtil; +import org.apache.commons.lang3.StringUtils; +import org.apache.dubbo.common.utils.CollectionUtils; +import org.apache.jmeter.assertions.AssertionResult; +import org.apache.jmeter.protocol.http.sampler.HTTPSampleResult; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.visualizers.backend.AbstractBackendListenerClient; +import org.apache.jmeter.visualizers.backend.BackendListenerContext; +import org.springframework.http.HttpMethod; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.io.Serializable; +import java.util.*; + +/** + * JMeter BackendListener扩展, jmx脚本中使用 + */ +public class APIBackendListenerClient extends AbstractBackendListenerClient implements Serializable { + + public final static String TEST_ID = "ms.test.id"; + + public final static String TEST_REPORT_NAME = "ms.test.report.name"; + + private final static String THREAD_SPLIT = " "; + + private final static String ID_SPLIT = "-"; + + private final List queue = new ArrayList<>(); + + private String runMode = ApiRunMode.RUN.name(); + + private String userId; + + private boolean isDebug; + + private LoadTestProducer loadTestProducer; + + /** + * 测试ID + */ + private String testId; + + /** + * 只有合并报告是这个有值 + */ + private String reportName; + + /** + * 获得控制台内容 + */ + private PrintStream oldPrintStream = System.out; + private ByteArrayOutputStream bos = new ByteArrayOutputStream(); + + private void setConsole() { + // 设置新的out + System.setOut(new PrintStream(bos)); + } + + private String getConsole() { + System.setOut(oldPrintStream); + return bos.toString(); + } + + @Override + public void setupTest(BackendListenerContext context) throws Exception { + setConsole(); + setParam(context); + super.setupTest(context); + } + + + @Override + public void handleSampleResults(List sampleResults, BackendListenerContext context) { + queue.addAll(sampleResults); + } + + @Override + public void teardownTest(BackendListenerContext context) throws Exception { + TestResult testResult = new TestResult(); + testResult.setTestId(testId); + testResult.setTotal(queue.size()); + testResult.setReportName(this.reportName); + testResult.setDebug(this.isDebug); + testResult.setUserId(this.userId); + loadTestProducer = CommonBeanFactory.getBean(LoadTestProducer.class); + // 一个脚本里可能包含多个场景(ThreadGroup),所以要区分开,key: 场景Id + final Map scenarios = new LinkedHashMap<>(); + queue.forEach(result -> { + // 线程名称: <场景名> <场景Index>-<请求Index>, 例如:Scenario 2-1 + String scenarioName = StringUtils.substringBeforeLast(result.getThreadName(), THREAD_SPLIT); + String index = StringUtils.substringAfterLast(result.getThreadName(), THREAD_SPLIT); + String scenarioId = StringUtils.substringBefore(index, ID_SPLIT); + ScenarioResult scenarioResult; + if (!scenarios.containsKey(scenarioId)) { + scenarioResult = new ScenarioResult(); + try { + scenarioResult.setId(Integer.parseInt(scenarioId)); + } catch (Exception e) { + scenarioResult.setId(0); + LogUtil.error("场景ID转换异常: " + e.getMessage()); + } + scenarioResult.setName(scenarioName); + scenarios.put(scenarioId, scenarioResult); + } else { + scenarioResult = scenarios.get(scenarioId); + } + + if (result.isSuccessful()) { + scenarioResult.addSuccess(); + testResult.addSuccess(); + } else { + scenarioResult.addError(result.getErrorCount()); + testResult.addError(result.getErrorCount()); + } + + RequestResult requestResult = getRequestResult(result); + scenarioResult.getRequestResults().add(requestResult); + scenarioResult.addResponseTime(result.getTime()); + + testResult.addPassAssertions(requestResult.getPassAssertions()); + testResult.addTotalAssertions(requestResult.getTotalAssertions()); + + scenarioResult.addPassAssertions(requestResult.getPassAssertions()); + scenarioResult.addTotalAssertions(requestResult.getTotalAssertions()); + }); + testResult.getScenarios().addAll(scenarios.values()); + testResult.getScenarios().sort(Comparator.comparing(ScenarioResult::getId)); + testResult.setRunMode(this.runMode); + // 推送执行结果 + loadTestProducer.sendMessage(JSON.toJSONString(testResult)); + queue.clear(); + super.teardownTest(context); + } + + private RequestResult getRequestResult(SampleResult result) { + RequestResult requestResult = new RequestResult(); + requestResult.setId(result.getSamplerId()); + requestResult.setName(result.getSampleLabel()); + requestResult.setUrl(result.getUrlAsString()); + requestResult.setMethod(getMethod(result)); + requestResult.setBody(result.getSamplerData()); + requestResult.setHeaders(result.getRequestHeaders()); + requestResult.setRequestSize(result.getSentBytes()); + requestResult.setStartTime(result.getStartTime()); + requestResult.setEndTime(result.getEndTime()); + requestResult.setTotalAssertions(result.getAssertionResults().length); + requestResult.setSuccess(result.isSuccessful()); + requestResult.setError(result.getErrorCount()); + if (result instanceof HTTPSampleResult) { + HTTPSampleResult res = (HTTPSampleResult) result; + requestResult.setCookies(res.getCookies()); + } + + for (SampleResult subResult : result.getSubResults()) { + requestResult.getSubRequestResults().add(getRequestResult(subResult)); + } + ResponseResult responseResult = requestResult.getResponseResult(); + responseResult.setBody(result.getResponseDataAsString()); + responseResult.setHeaders(result.getResponseHeaders()); + responseResult.setLatency(result.getLatency()); + responseResult.setResponseCode(result.getResponseCode()); + responseResult.setResponseSize(result.getResponseData().length); + responseResult.setResponseTime(result.getTime()); + responseResult.setResponseMessage(result.getResponseMessage()); + if (JMeterVars.get(result.hashCode()) != null && CollectionUtils.isNotEmpty(JMeterVars.get(result.hashCode()).entrySet())) { + StringBuilder builder = new StringBuilder(); + for (Map.Entry entry : JMeterVars.get(result.hashCode()).entrySet()) { + builder.append(entry.getKey()).append(":").append(entry.getValue()).append("\n"); + } + if (StringUtils.isNotEmpty(builder)) { + responseResult.setVars(builder.toString()); + } + JMeterVars.remove(result.hashCode()); + } + for (AssertionResult assertionResult : result.getAssertionResults()) { + ResponseAssertionResult responseAssertionResult = getResponseAssertionResult(assertionResult); + if (responseAssertionResult.isPass()) { + requestResult.addPassAssertions(); + } + //xpath 提取错误会添加断言错误 + if (StringUtils.isBlank(responseAssertionResult.getMessage()) || + (StringUtils.isNotBlank(responseAssertionResult.getName()) && !responseAssertionResult.getName().endsWith("XPath2Extractor"))) { + responseResult.getAssertions().add(responseAssertionResult); + } + } + responseResult.setConsole(getConsole()); + + return requestResult; + } + + private String getMethod(SampleResult result) { + String body = result.getSamplerData(); + // Dubbo Protocol + String start = "RPC Protocol: "; + String end = "://"; + if (StringUtils.contains(body, start)) { + String protocol = StringUtils.substringBetween(body, start, end); + if (StringUtils.isNotEmpty(protocol)) { + return protocol.toUpperCase(); + } + return RequestType.DUBBO; + } else if (StringUtils.contains(result.getResponseHeaders(), "url:jdbc")) { + return "SQL"; + } else { + // Http Method + String method = StringUtils.substringBefore(body, " "); + for (HttpMethod value : HttpMethod.values()) { + if (StringUtils.equals(method, value.name())) { + return method; + } + } + return "Request"; + } + } + + private void setParam(BackendListenerContext context) { + this.testId = context.getParameter(TEST_ID); + this.reportName = context.getParameter(TEST_REPORT_NAME); + this.runMode = context.getParameter("runMode"); + this.isDebug = StringUtils.equals(context.getParameter("DEBUG"), "DEBUG") ? true : false; + this.userId = context.getParameter("USER_ID"); + if (StringUtils.isBlank(this.runMode)) { + this.runMode = ApiRunMode.RUN.name(); + } + } + + private ResponseAssertionResult getResponseAssertionResult(AssertionResult assertionResult) { + ResponseAssertionResult responseAssertionResult = new ResponseAssertionResult(); + responseAssertionResult.setName(assertionResult.getName()); + responseAssertionResult.setPass(!assertionResult.isFailure() && !assertionResult.isError()); + if (!responseAssertionResult.isPass()) { + responseAssertionResult.setMessage(assertionResult.getFailureMessage()); + } + return responseAssertionResult; + } + +} diff --git a/src/main/java/io/metersphere/api/jmeter/JMeterService.java b/src/main/java/io/metersphere/api/jmeter/JMeterService.java new file mode 100644 index 0000000..670023d --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/JMeterService.java @@ -0,0 +1,86 @@ +package io.metersphere.api.jmeter; + +import io.metersphere.api.controller.request.RunRequest; +import io.metersphere.api.jmeter.utils.JmeterProperties; +import io.metersphere.api.jmeter.utils.MSException; +import io.metersphere.node.util.LogUtil; +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.visualizers.backend.BackendListener; +import org.apache.jorphan.collections.HashTree; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.stereotype.Service; + +import javax.annotation.PostConstruct; +import javax.annotation.Resource; +import java.io.File; +import java.lang.reflect.Field; + +@Service +public class JMeterService { + + @Resource + private JmeterProperties jmeterProperties; + + @PostConstruct + public void init() { + String JMETER_HOME = getJmeterHome(); + + String JMETER_PROPERTIES = JMETER_HOME + "/bin/jmeter.properties"; + JMeterUtils.loadJMeterProperties(JMETER_PROPERTIES); + JMeterUtils.setJMeterHome(JMETER_HOME); + JMeterUtils.setLocale(LocaleContextHolder.getLocale()); + } + + public String getJmeterHome() { + String home = getClass().getResource("/").getPath() + "jmeter"; + try { + File file = new File(home); + if (file.exists()) { + return home; + } else { + return jmeterProperties.getHome(); + } + } catch (Exception e) { + return jmeterProperties.getHome(); + } + } + + public static HashTree getHashTree(Object scriptWrapper) throws Exception { + Field field = scriptWrapper.getClass().getDeclaredField("testPlan"); + field.setAccessible(true); + return (HashTree) field.get(scriptWrapper); + } + + + private void addBackendListener(HashTree testPlan, RunRequest request) { + BackendListener backendListener = new BackendListener(); + backendListener.setName(request.getTestId()); + Arguments arguments = new Arguments(); + if (request.getConfig() != null && request.getConfig().getMode().equals("serial") && request.getConfig().getReportType().equals("setReport")) { + arguments.addArgument(APIBackendListenerClient.TEST_REPORT_NAME, request.getConfig().getReportName()); + } + arguments.addArgument(APIBackendListenerClient.TEST_ID, request.getTestId()); + if (StringUtils.isNotBlank(request.getRunMode())) { + arguments.addArgument("runMode", request.getRunMode()); + } + arguments.addArgument("DEBUG", request.isDebug() ? "DEBUG" : "RUN"); + arguments.addArgument("USER_ID", request.getUserId()); + backendListener.setArguments(arguments); + backendListener.setClassname(APIBackendListenerClient.class.getCanonicalName()); + testPlan.add(testPlan.getArray()[0], backendListener); + } + + public void runSerial(RunRequest request, HashTree testPlan) { + try { + init(); + addBackendListener(testPlan, request); + LocalRunner runner = new LocalRunner(testPlan); + runner.run(); + } catch (Exception e) { + LogUtil.error(e.getMessage(), e); + MSException.throwException("读取脚本失败"); + } + } +} diff --git a/src/main/java/io/metersphere/api/jmeter/JMeterVars.java b/src/main/java/io/metersphere/api/jmeter/JMeterVars.java new file mode 100644 index 0000000..ae468d3 --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/JMeterVars.java @@ -0,0 +1,115 @@ +package io.metersphere.api.jmeter; + +import com.alibaba.fastjson.JSON; +import io.github.ningyu.jmeter.plugin.dubbo.sample.DubboSample; +import org.apache.dubbo.common.utils.CollectionUtils; +import org.apache.jmeter.extractor.JSR223PostProcessor; +import org.apache.jmeter.extractor.RegexExtractor; +import org.apache.jmeter.extractor.XPath2Extractor; +import org.apache.jmeter.extractor.json.jsonpath.JSONPostProcessor; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy; +import org.apache.jmeter.protocol.jdbc.sampler.JDBCSampler; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jorphan.collections.HashTree; +import org.springframework.util.StringUtils; + +import java.util.*; + +public class JMeterVars { + + private JMeterVars() { + } + + /** + * 数据和线程变量保持一致 + */ + private static Map variables = new HashMap<>(); + + /** + * 线程执行过程调用提取变量值 + * + * @param testId + * @param vars + * @param extract + */ + public static void addVars(Integer testId, JMeterVariables vars, String extract) { + JMeterVariables vs = variables.get(testId); + if (vs == null) { + vs = new JMeterVariables(); + } + if (!StringUtils.isEmpty(extract) && vars != null) { + List extracts = Arrays.asList(extract.split(";")); + if (CollectionUtils.isNotEmpty(extracts)) { + for (String item : extracts) { + String nrKey = item + "_matchNr"; + Object nr = vars.get(nrKey); + if (nr != null) { + int nrv = 0; + try { + nrv = Integer.valueOf(String.valueOf(nr)); + } catch (Exception e) { + } + if (nrv > 0) { + List data = new ArrayList<>(); + for (int i = 1; i < nrv + 1; i++) { + data.add(vars.get(item + "_" + i)); + } + String array = JSON.toJSONString(data); + vars.put(item, array); + } + } + vs.put(item, vars.get(item) == null ? "" : vars.get(item)); + } + vs.remove("TESTSTART.MS"); // 标示变量移除 + } + } + + variables.put(testId, vs); + } + + /** + * 处理所有请求,有提取变量的请求增加后置脚本提取变量值 + * + * @param tree + */ + public static void addJSR223PostProcessor(HashTree tree) { + for (Object key : tree.keySet()) { + HashTree node = tree.get(key); + if (key instanceof HTTPSamplerProxy || key instanceof DubboSample || key instanceof JDBCSampler) { + StringJoiner extract = new StringJoiner(";"); + for (Object child : node.keySet()) { + if (child instanceof RegexExtractor) { + RegexExtractor regexExtractor = (RegexExtractor) child; + extract.add(regexExtractor.getRefName()); + } else if (child instanceof XPath2Extractor) { + XPath2Extractor regexExtractor = (XPath2Extractor) child; + extract.add(regexExtractor.getRefName()); + } else if (child instanceof JSONPostProcessor) { + JSONPostProcessor regexExtractor = (JSONPostProcessor) child; + extract.add(regexExtractor.getRefNames()); + } + } + + if (Optional.ofNullable(extract).orElse(extract).length() > 0) { + JSR223PostProcessor shell = new JSR223PostProcessor(); + shell.setEnabled(true); + shell.setProperty("script", "io.metersphere.api.jmeter.JMeterVars.addVars(prev.hashCode(),vars," + "\"" + extract.toString() + "\"" + ");"); + node.add(shell); + } + } + + if (node != null) { + addJSR223PostProcessor(node); + } + } + } + + public static JMeterVariables get(Integer key) { + return variables.get(key); + } + + public static void remove(Integer key) { + variables.remove(key); + } + +} diff --git a/src/main/java/io/metersphere/api/jmeter/LocalRunner.java b/src/main/java/io/metersphere/api/jmeter/LocalRunner.java new file mode 100644 index 0000000..3db1bb7 --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/LocalRunner.java @@ -0,0 +1,24 @@ +package io.metersphere.api.jmeter; + +import org.apache.jmeter.engine.JMeterEngine; +import org.apache.jmeter.engine.JMeterEngineException; +import org.apache.jmeter.engine.StandardJMeterEngine; +import org.apache.jorphan.collections.HashTree; + +public class LocalRunner { + private final HashTree jmxTree; + + public LocalRunner(HashTree jmxTree) { + this.jmxTree = jmxTree; + } + + public void run() { + JMeterEngine engine = new StandardJMeterEngine(); + engine.configure(jmxTree); + try { + engine.runTest(); + } catch (JMeterEngineException e) { + engine.stopTest(true); + } + } +} diff --git a/src/main/java/io/metersphere/api/jmeter/constants/ApiRunMode.java b/src/main/java/io/metersphere/api/jmeter/constants/ApiRunMode.java new file mode 100644 index 0000000..2fcccaf --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/constants/ApiRunMode.java @@ -0,0 +1,5 @@ +package io.metersphere.api.jmeter.constants; + +public enum ApiRunMode { + RUN, DEBUG, DEFINITION, SCENARIO, API_PLAN, JENKINS_API_PLAN, JENKINS, SCENARIO_PLAN, API, SCHEDULE_API_PLAN, SCHEDULE_SCENARIO,SCHEDULE_SCENARIO_PLAN, SCHEDULE_PERFORMANCE_TEST +} diff --git a/src/main/java/io/metersphere/api/jmeter/constants/RequestType.java b/src/main/java/io/metersphere/api/jmeter/constants/RequestType.java new file mode 100644 index 0000000..377746b --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/constants/RequestType.java @@ -0,0 +1,12 @@ +package io.metersphere.api.jmeter.constants; + +public class RequestType { + + public static final String HTTP = "HTTP"; + + public static final String DUBBO = "DUBBO"; + + public static final String SQL = "SQL"; + + public static final String TCP = "TCP"; +} diff --git a/src/main/java/io/metersphere/api/jmeter/module/RequestResult.java b/src/main/java/io/metersphere/api/jmeter/module/RequestResult.java new file mode 100644 index 0000000..d9cec5c --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/module/RequestResult.java @@ -0,0 +1,47 @@ +package io.metersphere.api.jmeter.module; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class RequestResult { + // 请求ID + private String id; + + private String name; + + private String url; + + private String method; + + private long requestSize; + + private long startTime; + + private long endTime; + + private int error; + + private boolean success; + + private String headers; + + private String cookies; + + private String body; + + private int totalAssertions = 0; + + private int passAssertions = 0; + + private final List subRequestResults = new ArrayList<>(); + + private final ResponseResult responseResult = new ResponseResult(); + + public void addPassAssertions() { + this.passAssertions++; + } + +} diff --git a/src/main/java/io/metersphere/api/jmeter/module/ResponseAssertionResult.java b/src/main/java/io/metersphere/api/jmeter/module/ResponseAssertionResult.java new file mode 100644 index 0000000..05c4512 --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/module/ResponseAssertionResult.java @@ -0,0 +1,13 @@ +package io.metersphere.api.jmeter.module; + +import lombok.Data; + +@Data +public class ResponseAssertionResult { + + private String name; + + private String message; + + private boolean pass; +} diff --git a/src/main/java/io/metersphere/api/jmeter/module/ResponseResult.java b/src/main/java/io/metersphere/api/jmeter/module/ResponseResult.java new file mode 100644 index 0000000..7e4ad10 --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/module/ResponseResult.java @@ -0,0 +1,32 @@ +package io.metersphere.api.jmeter.module; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + + +@Data +public class ResponseResult { + + private String responseCode; + + private String responseMessage; + + private long responseTime; + + private long latency; + + private long responseSize; + + private String headers; + + private String body; + + private String vars; + + private String console; + + private final List assertions = new ArrayList<>(); + +} diff --git a/src/main/java/io/metersphere/api/jmeter/module/ScenarioResult.java b/src/main/java/io/metersphere/api/jmeter/module/ScenarioResult.java new file mode 100644 index 0000000..3be1644 --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/module/ScenarioResult.java @@ -0,0 +1,50 @@ +package io.metersphere.api.jmeter.module; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class ScenarioResult { + + private Integer id; + + private String name; + + private long responseTime; + + private int error = 0; + + private int success = 0; + + private int totalAssertions = 0; + + private int passAssertions = 0; + + private List requestResults = new ArrayList<>(); + + public void addResponseTime(long time) { + this.responseTime += time; + } + + public void addError(int count) { + this.error += count; + } + + public void addSuccess() { + this.success++; + } + + public void addTotalAssertions(int count) { + this.totalAssertions += count; + } + + public void addPassAssertions(int count) { + this.passAssertions += count; + } + + public int getTotal() { + return error + success; + } +} diff --git a/src/main/java/io/metersphere/api/jmeter/module/TestResult.java b/src/main/java/io/metersphere/api/jmeter/module/TestResult.java new file mode 100644 index 0000000..60ed9b0 --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/module/TestResult.java @@ -0,0 +1,67 @@ +package io.metersphere.api.jmeter.module; + +import lombok.Data; +import org.apache.commons.lang3.StringUtils; +import org.apache.dubbo.common.utils.CollectionUtils; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class TestResult { + + private String testId; + + private boolean isDebug; + + private String userId; + + private String runMode; + + private String reportName; + + private int success = 0; + + private int error = 0; + + private int total = 0; + + private int totalAssertions = 0; + + private int passAssertions = 0; + + private List scenarios = new ArrayList<>(); + + public void addError(int count) { + this.error += count; + } + + public void addSuccess() { + this.success++; + } + + public void addTotalAssertions(int count) { + this.totalAssertions += count; + } + + public void addPassAssertions(int count) { + this.passAssertions += count; + } + + private static final String SEPARATOR = "<->"; + + public void addScenario(ScenarioResult result) { + if (result != null && CollectionUtils.isNotEmpty(result.getRequestResults())) { + result.getRequestResults().forEach(item -> { + if (StringUtils.isNotEmpty(item.getName()) && item.getName().indexOf(SEPARATOR) != -1) { + String array[] = item.getName().split(SEPARATOR); + item.setName(array[1] + array[0]); + item.getSubRequestResults().forEach(subItem -> { + subItem.setName(array[0]); + }); + } + }); + scenarios.add(result); + } + } +} diff --git a/src/main/java/io/metersphere/api/jmeter/utils/CommonBeanFactory.java b/src/main/java/io/metersphere/api/jmeter/utils/CommonBeanFactory.java new file mode 100644 index 0000000..edf156f --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/utils/CommonBeanFactory.java @@ -0,0 +1,34 @@ +package io.metersphere.api.jmeter.utils; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; + +public class CommonBeanFactory implements ApplicationContextAware { + private static ApplicationContext context; + + public CommonBeanFactory() { + } + + public void setApplicationContext(ApplicationContext ctx) throws BeansException { + context = ctx; + } + + public static Object getBean(String beanName) { + try { + return context != null && !StringUtils.isBlank(beanName) ? context.getBean(beanName) : null; + } catch (BeansException e) { + return null; + } + } + + public static T getBean(Class className) { + try { + return context != null && className != null ? context.getBean(className) : null; + } catch (BeansException e) { + return null; + } + } +} + diff --git a/src/main/java/io/metersphere/api/jmeter/utils/FileUtils.java b/src/main/java/io/metersphere/api/jmeter/utils/FileUtils.java new file mode 100644 index 0000000..6a47e83 --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/utils/FileUtils.java @@ -0,0 +1,32 @@ +package io.metersphere.api.jmeter.utils; + +import io.metersphere.node.util.LogUtil; +import org.aspectj.util.FileUtil; +import org.springframework.web.multipart.MultipartFile; + +import java.io.*; + +public class FileUtils { + public static final String BODY_FILE_DIR = "/opt/metersphere/data/body"; + public static final String JAR_FILE_DIR = "/opt/metersphere/data/node/jar"; + + public static void createBodyFiles(MultipartFile[] bodyFiles,String path) { + if (bodyFiles != null && bodyFiles.length > 0) { + File testDir = new File(path); + if (!testDir.exists()) { + testDir.mkdirs(); + } + for (int i = 0; i < bodyFiles.length; i++) { + MultipartFile item = bodyFiles[i]; + File file = new File(path + "/" + item.getOriginalFilename()); + try (InputStream in = item.getInputStream(); OutputStream out = new FileOutputStream(file)) { + file.createNewFile(); + FileUtil.copyStream(in, out); + } catch (IOException e) { + LogUtil.error(e); + MSException.throwException("文件处理异常"); + } + } + } + } +} diff --git a/src/main/java/io/metersphere/api/jmeter/utils/JmeterProperties.java b/src/main/java/io/metersphere/api/jmeter/utils/JmeterProperties.java new file mode 100644 index 0000000..9fd5eaa --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/utils/JmeterProperties.java @@ -0,0 +1,21 @@ +package io.metersphere.api.jmeter.utils; + + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = JmeterProperties.JMETER_PREFIX) +@Setter +@Getter +public class JmeterProperties { + + public static final String JMETER_PREFIX = "jmeter"; + + private String image; + + private String home; + + private String heap = "-Xms1g -Xmx1g -XX:MaxMetaspaceSize=256m"; + private String gcAlgo = "-XX:+UseG1GC -XX:MaxGCPauseMillis=100 -XX:G1ReservePercent=20"; +} diff --git a/src/main/java/io/metersphere/api/jmeter/utils/MSException.java b/src/main/java/io/metersphere/api/jmeter/utils/MSException.java new file mode 100644 index 0000000..688df74 --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/utils/MSException.java @@ -0,0 +1,24 @@ +package io.metersphere.api.jmeter.utils; + +public class MSException extends RuntimeException { + + private MSException(String message) { + super(message); + } + + private MSException(Throwable t) { + super(t); + } + + public static void throwException(String message) { + throw new MSException(message); + } + + public static MSException getException(String message) { + throw new MSException(message); + } + + public static void throwException(Throwable t) { + throw new MSException(t); + } +} diff --git a/src/main/java/io/metersphere/api/jmeter/utils/RunModeConfig.java b/src/main/java/io/metersphere/api/jmeter/utils/RunModeConfig.java new file mode 100644 index 0000000..8330e8a --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/utils/RunModeConfig.java @@ -0,0 +1,11 @@ +package io.metersphere.api.jmeter.utils; + +import lombok.Data; + +@Data +public class RunModeConfig { + private String mode; + private String reportType; + private String reportName; + private boolean onSampleError; +} diff --git a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java new file mode 100644 index 0000000..f34f576 --- /dev/null +++ b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java @@ -0,0 +1,73 @@ +package io.metersphere.api.service; + +import io.metersphere.api.controller.request.RunRequest; +import io.metersphere.api.jmeter.JMeterService; +import io.metersphere.api.jmeter.utils.FileUtils; +import io.metersphere.api.jmeter.utils.MSException; +import io.metersphere.node.util.LogUtil; +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.NewDriver; +import org.apache.jmeter.save.SaveService; +import org.apache.jorphan.collections.HashTree; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Resource; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.net.MalformedURLException; + +@Service +public class JmeterExecuteService { + @Resource + private JMeterService jMeterService; + @Resource + LoadTestProducer loadTestProducer; + + private static InputStream getStrToStream(String sInputString) { + if (StringUtils.isNotEmpty(sInputString)) { + try { + ByteArrayInputStream tInputStringStream = new ByteArrayInputStream(sInputString.getBytes()); + return tInputStringStream; + } catch (Exception ex) { + ex.printStackTrace(); + MSException.throwException("生成脚本异常"); + } + } + return null; + } + + private void loadJar(String path) { + try { + NewDriver.addPath(path); + } catch (MalformedURLException e) { + LogUtil.error(e.getMessage(), e); + MSException.throwException(e.getMessage()); + } + } + + public String run(RunRequest request, MultipartFile[] bodyFiles, MultipartFile[] jarFiles) { + if (request == null || request.getJmx() == null) { + return "执行文件为空,无法执行!"; + } + LogUtil.info(request.getJmx()); + // 检查KAFKA + loadTestProducer.checkKafka(); + // 生成附件/JAR文件 + FileUtils.createBodyFiles(bodyFiles, FileUtils.BODY_FILE_DIR); + FileUtils.createBodyFiles(jarFiles, FileUtils.JAR_FILE_DIR); + try { + this.loadJar(FileUtils.JAR_FILE_DIR); + // 生成执行脚本 + InputStream inputSource = getStrToStream(request.getJmx()); + Object scriptWrapper = SaveService.loadElement(inputSource); + HashTree testPlan = JMeterService.getHashTree(scriptWrapper); + // 开始执行 + jMeterService.runSerial(request, testPlan); + } catch (Exception e) { + LogUtil.error(e.getMessage()); + return e.getMessage(); + } + return "SUCCESS"; + } +} diff --git a/src/main/java/io/metersphere/api/service/LoadTestProducer.java b/src/main/java/io/metersphere/api/service/LoadTestProducer.java new file mode 100644 index 0000000..0b323c3 --- /dev/null +++ b/src/main/java/io/metersphere/api/service/LoadTestProducer.java @@ -0,0 +1,53 @@ +package io.metersphere.api.service; + +import io.metersphere.api.config.KafkaProperties; +import io.metersphere.api.jmeter.utils.CommonBeanFactory; +import io.metersphere.api.jmeter.utils.MSException; +import io.metersphere.node.util.LogUtil; +import org.apache.commons.lang.StringUtils; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.Socket; + +@Service +public class LoadTestProducer { + private static final String topic = "ms-api-exec-topic"; + + public void checkKafka() { + KafkaProperties kafkaProperties = CommonBeanFactory.getBean(KafkaProperties.class); + String[] servers = StringUtils.split(kafkaProperties.getBootstrapServers(), ","); + try { + for (String s : servers) { + String[] ipAndPort = s.split(":"); + //1,建立tcp + String ip = ipAndPort[0]; + int port = Integer.parseInt(ipAndPort[1]); + Socket soc = new Socket(); + // 1s timeout + soc.connect(new InetSocketAddress(ip, port), 1000); + //2.输入内容 + String content = "1010"; + byte[] bs = content.getBytes(); + OutputStream os = soc.getOutputStream(); + os.write(bs); + //3.关闭 + soc.close(); + } + } catch (Exception e) { + LogUtil.error(e); + MSException.throwException("Failed to connect to Kafka"); + } + } + + + @Resource + private KafkaTemplate kafkaTemplate; + + public void sendMessage(String report) { + this.kafkaTemplate.send(topic, report); + } +} diff --git a/src/main/java/io/metersphere/node/Application.java b/src/main/java/io/metersphere/node/Application.java deleted file mode 100644 index 35b186f..0000000 --- a/src/main/java/io/metersphere/node/Application.java +++ /dev/null @@ -1,14 +0,0 @@ -package io.metersphere.node; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.PropertySource; - -@PropertySource(value = {"file:/opt/metersphere/conf/metersphere.properties"}, encoding = "UTF-8", ignoreResourceNotFound = true) -@SpringBootApplication -public class Application { - - public static void main(String[] args) { - SpringApplication.run(Application.class, args); - } -} diff --git a/src/main/java/org/apache/jmeter/NewDriver.java b/src/main/java/org/apache/jmeter/NewDriver.java new file mode 100644 index 0000000..fa24fcd --- /dev/null +++ b/src/main/java/org/apache/jmeter/NewDriver.java @@ -0,0 +1,378 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter; + +import java.io.File; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.reflect.Method; +import java.net.MalformedURLException; +import java.net.URL; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.text.SimpleDateFormat; +import java.util.*; + +/** + * Main class for JMeter - sets up initial classpath and the loader. + * + */ +public final class NewDriver { + + private static final String CLASSPATH_SEPARATOR = File.pathSeparator; + + private static final String OS_NAME = System.getProperty("os.name");// $NON-NLS-1$ + + private static final String OS_NAME_LC = OS_NAME.toLowerCase(java.util.Locale.ENGLISH); + + private static final String JAVA_CLASS_PATH = "java.class.path";// $NON-NLS-1$ + + private static final String JMETER_LOGFILE_SYSTEM_PROPERTY = "jmeter.logfile";// $NON-NLS-1$ + + private static final String HEADLESS_MODE_PROPERTY = "java.awt.headless";// $NON-NLS-1$ + /** The class loader to use for loading JMeter classes. */ + private static final DynamicClassLoader loader; + + /** The directory JMeter is installed in. */ + private static final String JMETER_INSTALLATION_DIRECTORY; + + private static final List EXCEPTIONS_IN_INIT = new ArrayList<>(); + + // 将当前类加载器设置为 loader ,解决由系统类加载器加载的 JMeter 无法动态加载 jar 包问题 + public static void setContextClassLoader() { + Thread.currentThread().setContextClassLoader(loader); + } + + public static void loaderClass(String name) { + try { + loader.loadClass(name); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + } + + static { + final List jars = new LinkedList<>(); + final String initiaClasspath = System.getProperty(JAVA_CLASS_PATH); + + // Find JMeter home dir from the initial classpath + String tmpDir; + + //只从 jmeter.home 加载 + tmpDir = System.getProperty("jmeter.home","");// Allow override $NON-NLS-1$ $NON-NLS-2$ + if (tmpDir.length() == 0) { + File userDir = new File(System.getProperty("user.dir"));// $NON-NLS-1$ + tmpDir = userDir.getAbsoluteFile().getParent(); + } + + JMETER_INSTALLATION_DIRECTORY=tmpDir; + + /* + * Does the system support UNC paths? If so, may need to fix them up + * later + */ + boolean usesUNC = OS_NAME_LC.startsWith("windows");// $NON-NLS-1$ + + // Add standard jar locations to initial classpath + StringBuilder classpath = new StringBuilder(); + File[] libDirs = new File[] { new File(JMETER_INSTALLATION_DIRECTORY + File.separator + "lib"),// $NON-NLS-1$ $NON-NLS-2$ + new File(JMETER_INSTALLATION_DIRECTORY + File.separator + "lib" + File.separator + "ext"),// $NON-NLS-1$ $NON-NLS-2$ + new File(JMETER_INSTALLATION_DIRECTORY + File.separator + "lib" + File.separator + "junit")};// $NON-NLS-1$ $NON-NLS-2$ + for (File libDir : libDirs) { + File[] libJars = libDir.listFiles((dir, name) -> name.endsWith(".jar")); + if (libJars == null) { + new Throwable("Could not access " + libDir).printStackTrace(); // NOSONAR No logging here + continue; + } + Arrays.sort(libJars); // Bug 50708 Ensure predictable order of jars + for (File libJar : libJars) { + try { + String s = libJar.getPath(); + + // Fix path to allow the use of UNC URLs + if (usesUNC) { + if (s.startsWith("\\\\") && !s.startsWith("\\\\\\")) {// $NON-NLS-1$ $NON-NLS-2$ + s = "\\\\" + s;// $NON-NLS-1$ + } else if (s.startsWith("//") && !s.startsWith("///")) {// $NON-NLS-1$ $NON-NLS-2$ + s = "//" + s;// $NON-NLS-1$ + } + } // usesUNC + + jars.add(new File(s).toURI().toURL());// See Java bug 4496398 + classpath.append(CLASSPATH_SEPARATOR); + classpath.append(s); + } catch (MalformedURLException e) { // NOSONAR + EXCEPTIONS_IN_INIT.add(new Exception("Error adding jar:"+libJar.getAbsolutePath(), e)); + } + } + } + + // ClassFinder needs the classpath + System.setProperty(JAVA_CLASS_PATH, initiaClasspath + classpath.toString()); + loader = AccessController.doPrivileged( + (PrivilegedAction) () -> + new DynamicClassLoader(jars.toArray(new URL[jars.size()])) + ); + } + + /** + * Prevent instantiation. + */ + private NewDriver() { + } + + /** + * Generate an array of jar files located in a directory. + * Jar files located in sub directories will not be added. + * + * @param dir to search for the jar files. + */ + private static File[] listJars(File dir) { + if (dir.isDirectory()) { + return dir.listFiles((f, name) -> { + if (name.endsWith(".jar")) {// $NON-NLS-1$ + File jar = new File(f, name); + return jar.isFile() && jar.canRead(); + } + return false; + }); + } + return new File[0]; + } + + /** + * Add a URL to the loader classpath only; does not update the system classpath. + * + * @param path to be added. + * @throws MalformedURLException when path points to an invalid url + */ + public static void addURL(String path) throws MalformedURLException { + File furl = new File(path); + loader.addURL(furl.toURI().toURL()); // See Java bug 4496398 + File[] jars = listJars(furl); + for (File jar : jars) { + loader.addURL(jar.toURI().toURL()); // See Java bug 4496398 + } + } + + /** + * Add a URL to the loader classpath only; does not update the system + * classpath. + * + * @param url + * The {@link URL} to add to the classpath + */ + public static void addURL(URL url) { + loader.addURL(url); + } + + /** + * Add a directory or jar to the loader and system classpaths. + * + * @param path + * to add to the loader and system classpath + * @throws MalformedURLException + * if path can not be transformed to a valid + * {@link URL} + */ + public static void addPath(String path) throws MalformedURLException { + File file = new File(path); + // Ensure that directory URLs end in "/" + if (file.isDirectory() && !path.endsWith("/")) {// $NON-NLS-1$ + file = new File(path + "/");// $NON-NLS-1$ + } + loader.addURL(file.toURI().toURL()); // See Java bug 4496398 + StringBuilder sb = new StringBuilder(System.getProperty(JAVA_CLASS_PATH)); + sb.append(CLASSPATH_SEPARATOR); + sb.append(path); + File[] jars = listJars(file); + for (File jar : jars) { + loader.addURL(jar.toURI().toURL()); // See Java bug 4496398 + sb.append(CLASSPATH_SEPARATOR); + sb.append(jar.getPath()); + } + + // ClassFinder needs this + System.setProperty(JAVA_CLASS_PATH,sb.toString()); + } + + /** + * Get the directory where JMeter is installed. This is the absolute path + * name. + * + * @return the directory where JMeter is installed. + */ + public static String getJMeterDir() { + return JMETER_INSTALLATION_DIRECTORY; + } + + /** + * The main program which actually runs JMeter. + * + * @param args + * the command line arguments + */ + public static void main(String[] args) { + if(!EXCEPTIONS_IN_INIT.isEmpty()) { + System.err.println("Configuration error during init, see exceptions:"+exceptionsToString(EXCEPTIONS_IN_INIT)); // NOSONAR Intentional System.err use + } else { + Thread.currentThread().setContextClassLoader(loader); + + + setLoggingProperties(args); + + try { + // Only set property if it has not been set explicitely + if(System.getProperty(HEADLESS_MODE_PROPERTY) == null && shouldBeHeadless(args)) { + System.setProperty(HEADLESS_MODE_PROPERTY, "true"); + } + Class initialClass = loader.loadClass("org.apache.jmeter.JMeter");// $NON-NLS-1$ + Object instance = initialClass.getDeclaredConstructor().newInstance(); + Method startup = initialClass.getMethod("start", new Class[] { new String[0].getClass() });// $NON-NLS-1$ + startup.invoke(instance, new Object[] { args }); + } catch(Throwable e){ // NOSONAR We want to log home directory in case of exception + e.printStackTrace(); // NOSONAR No logger at this step + System.err.println("JMeter home directory was detected as: "+JMETER_INSTALLATION_DIRECTORY); // NOSONAR Intentional System.err use + } + } + } + + /** + * @param exceptionsInInit List of {@link Exception} + * @return String + */ + private static String exceptionsToString(List exceptionsInInit) { + StringBuilder builder = new StringBuilder(); + for (Exception exception : exceptionsInInit) { + StringWriter stringWriter = new StringWriter(); + PrintWriter printWriter = new PrintWriter(stringWriter); + exception.printStackTrace(printWriter); // NOSONAR + builder.append(stringWriter.toString()) + .append("\r\n"); + } + return builder.toString(); + } + + /* + * Set logging related system properties. + */ + private static void setLoggingProperties(String[] args) { + String jmLogFile = getCommandLineArgument(args, 'j', "jmeterlogfile");// $NON-NLS-1$ $NON-NLS-2$ + + if (jmLogFile != null && !jmLogFile.isEmpty()) { + jmLogFile = replaceDateFormatInFileName(jmLogFile); + System.setProperty(JMETER_LOGFILE_SYSTEM_PROPERTY, jmLogFile);// $NON-NLS-1$ + } else if (System.getProperty(JMETER_LOGFILE_SYSTEM_PROPERTY) == null) {// $NON-NLS-1$ + System.setProperty(JMETER_LOGFILE_SYSTEM_PROPERTY, "jmeter.log");// $NON-NLS-1$ $NON-NLS-2$ + } + + String jmLogConf = getCommandLineArgument(args, 'i', "jmeterlogconf");// $NON-NLS-1$ $NON-NLS-2$ + File logConfFile = null; + + if (jmLogConf != null && !jmLogConf.isEmpty()) { + logConfFile = new File(jmLogConf); + } else if (System.getProperty("log4j.configurationFile") == null) {// $NON-NLS-1$ + logConfFile = new File("log4j2.xml");// $NON-NLS-1$ + if (!logConfFile.isFile()) { + logConfFile = new File(JMETER_INSTALLATION_DIRECTORY, "jmeter.bin" + File.separator + "log4j2.xml");// $NON-NLS-1$ $NON-NLS-2$ + } + } + + if (logConfFile != null) { + System.setProperty("log4j.configurationFile", logConfFile.toURI().toString());// $NON-NLS-1$ + } + } + + private static boolean shouldBeHeadless(String[] args) { + for (String arg : args) { + if("-n".equals(arg) || "-s".equals(arg) || "-g".equals(arg)) { + return true; + } + } + return false; + } + /* + * Find command line argument option value by the id and name. + */ + private static String getCommandLineArgument(String[] args, int id, String name) { + final String shortArgName = "-" + ((char) id);// $NON-NLS-1$ + final String longArgName = "--" + name;// $NON-NLS-1$ + + String value = null; + + for (int i = 0; i < args.length; i++) { + if ((shortArgName.equals(args[i]) && i < args.length - 1) + || longArgName.equals(args[i])) { + if (!args[i + 1].startsWith("-")) {// $NON-NLS-1$ + value = args[i + 1]; + } + break; + } else if (!shortArgName.equals(args[i]) && args[i].startsWith(shortArgName)) { + value = args[i].substring(shortArgName.length()); + break; + } + } + + return value; + } + + /* + * If the fileName contains at least one set of paired single-quotes, reformat using DateFormat + */ + private static String replaceDateFormatInFileName(String fileName) { + try { + StringBuilder builder = new StringBuilder(); + + final Date date = new Date(); + int fromIndex = 0; + int begin = fileName.indexOf('\'', fromIndex);// $NON-NLS-1$ + int end; + + String format; + SimpleDateFormat dateFormat; + + while (begin != -1) { + builder.append(fileName.substring(fromIndex, begin)); + + fromIndex = begin + 1; + end = fileName.indexOf('\'', fromIndex);// $NON-NLS-1$ + if (end == -1) { + throw new IllegalArgumentException("Invalid pairs of single-quotes in the file name: " + fileName);// $NON-NLS-1$ + } + + format = fileName.substring(begin + 1, end); + dateFormat = new SimpleDateFormat(format); + builder.append(dateFormat.format(date)); + + fromIndex = end + 1; + begin = fileName.indexOf('\'', fromIndex);// $NON-NLS-1$ + } + + if (fromIndex < fileName.length() - 1) { + builder.append(fileName.substring(fromIndex)); + } + + return builder.toString(); + } catch (Exception ex) { + System.err.println("Error replacing date format in file name:"+fileName+", error:"+ex.getMessage()); // NOSONAR + } + + return fileName; + } +} diff --git a/src/main/java/org/apache/jmeter/assertions/JSONPathAssertion.java b/src/main/java/org/apache/jmeter/assertions/JSONPathAssertion.java new file mode 100644 index 0000000..4b832e4 --- /dev/null +++ b/src/main/java/org/apache/jmeter/assertions/JSONPathAssertion.java @@ -0,0 +1,229 @@ +// +// Source code recreated from a .class file by IntelliJ IDEA +// (powered by Fernflower decompiler) +// + +package org.apache.jmeter.assertions; + +import com.jayway.jsonpath.JsonPath; +import com.jayway.jsonpath.Predicate; +import net.minidev.json.JSONArray; +import net.minidev.json.JSONObject; +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.ThreadListener; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.oro.text.regex.Pattern; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.Serializable; +import java.text.DecimalFormat; +import java.util.Map; + +public class JSONPathAssertion extends AbstractTestElement implements Serializable, Assertion, ThreadListener { + private static final Logger log = LoggerFactory.getLogger(JSONPathAssertion.class); + private static final long serialVersionUID = 2L; + public static final String JSONPATH = "JSON_PATH"; + public static final String EXPECTEDVALUE = "EXPECTED_VALUE"; + public static final String JSONVALIDATION = "JSONVALIDATION"; + public static final String EXPECT_NULL = "EXPECT_NULL"; + public static final String INVERT = "INVERT"; + public static final String ISREGEX = "ISREGEX"; + private static ThreadLocal decimalFormatter = ThreadLocal.withInitial(JSONPathAssertion::createDecimalFormat); + + public JSONPathAssertion() { + } + + private static DecimalFormat createDecimalFormat() { + DecimalFormat decimalFormatter = new DecimalFormat("#.#"); + decimalFormatter.setMaximumFractionDigits(340); + decimalFormatter.setMinimumFractionDigits(1); + return decimalFormatter; + } + + public String getOption() { + return getPropertyAsString("ASS_OPTION"); + } + + public String getJsonPath() { + return this.getPropertyAsString("JSON_PATH"); + } + + public void setJsonPath(String jsonPath) { + this.setProperty("JSON_PATH", jsonPath); + } + + public String getExpectedValue() { + return this.getPropertyAsString("EXPECTED_VALUE"); + } + + public void setExpectedValue(String expectedValue) { + this.setProperty("EXPECTED_VALUE", expectedValue); + } + + public void setJsonValidationBool(boolean jsonValidation) { + this.setProperty("JSONVALIDATION", jsonValidation); + } + + public void setExpectNull(boolean val) { + this.setProperty("EXPECT_NULL", val); + } + + public boolean isExpectNull() { + return this.getPropertyAsBoolean("EXPECT_NULL"); + } + + public boolean isJsonValidationBool() { + return this.getPropertyAsBoolean("JSONVALIDATION"); + } + + public void setInvert(boolean invert) { + this.setProperty("INVERT", invert); + } + + public boolean isInvert() { + return this.getPropertyAsBoolean("INVERT"); + } + + public void setIsRegex(boolean flag) { + this.setProperty("ISREGEX", flag); + } + + public boolean isUseRegex() { + return this.getPropertyAsBoolean("ISREGEX", true); + } + + private void doAssert(String jsonString) { + Object value = JsonPath.read(jsonString, this.getJsonPath(), new Predicate[0]); + if (this.isJsonValidationBool()) { + if (value instanceof JSONArray) { + if (this.arrayMatched((JSONArray) value)) { + return; + } + } else if (this.isExpectNull() && value == null || this.isEquals(value)) { + return; + } + + if (this.isExpectNull()) { + throw new IllegalStateException(String.format("Value expected to be null, but found '%s'", value)); + } else { + String msg; + if (this.isUseRegex()) { + msg = "Value expected to match regexp '%s', but it did not match: '%s'"; + } else { + msg = "Value expected to be '%s', but found '%s'"; + } + + throw new IllegalStateException(String.format(msg, this.getExpectedValue(), objectToString(value))); + } + } + } + + private boolean arrayMatched(JSONArray value) { + if (value.isEmpty() && "[]".equals(this.getExpectedValue())) { + return true; + } else { + Object[] var2 = value.toArray(); + int var3 = var2.length; + + for (int var4 = 0; var4 < var3; ++var4) { + Object subj = var2[var4]; + if (subj == null && this.isExpectNull() || this.isEquals(subj)) { + return true; + } + } + + return this.isEquals(value); + } + } + + private boolean isEquals(Object subj) { + String str = objectToString(subj); + if (this.isUseRegex()) { + Pattern pattern = JMeterUtils.getPatternCache().getPattern(this.getExpectedValue()); + return JMeterUtils.getMatcher().matches(str, pattern); + } else { + if (StringUtils.isNotEmpty(getOption())) { + boolean refFlag = false; + switch (getOption()) { + case "CONTAINS": + refFlag = str.contains(getExpectedValue()); + break; + case "NOT_CONTAINS": + refFlag = !str.contains(getExpectedValue()); + break; + case "EQUALS": + refFlag = str.equals(getExpectedValue()); + break; + case "NOT_EQUALS": + refFlag = !str.contains(getExpectedValue()); + break; + } + return refFlag; + } + return str.equals(this.getExpectedValue()); + } + } + + public AssertionResult getResult(SampleResult samplerResult) { + AssertionResult result = new AssertionResult(this.getName()); + String responseData = samplerResult.getResponseDataAsString(); + if (responseData.isEmpty()) { + return result.setResultForNull(); + } else { + result.setFailure(false); + result.setFailureMessage(""); + if (!this.isInvert()) { + try { + this.doAssert(responseData); + } catch (Exception var6) { + log.debug("Assertion failed", var6); + result.setFailure(true); + result.setFailureMessage(var6.getMessage()); + } + } else { + try { + this.doAssert(responseData); + result.setFailure(true); + if (this.isJsonValidationBool()) { + if (this.isExpectNull()) { + result.setFailureMessage("Failed that JSONPath " + this.getJsonPath() + " not matches null"); + } else { + result.setFailureMessage("Failed that JSONPath " + this.getJsonPath() + " not matches " + this.getExpectedValue()); + } + } else { + result.setFailureMessage("Failed that JSONPath not exists: " + this.getJsonPath()); + } + } catch (Exception var5) { + log.debug("Assertion failed, as expected", var5); + } + } + + return result; + } + } + + public static String objectToString(Object subj) { + String str; + if (subj == null) { + str = "null"; + } else if (subj instanceof Map) { + str = (new JSONObject((Map) subj)).toJSONString(); + } else if (!(subj instanceof Double) && !(subj instanceof Float)) { + str = subj.toString(); + } else { + str = ((DecimalFormat) decimalFormatter.get()).format(subj); + } + + return str; + } + + public void threadStarted() { + } + + public void threadFinished() { + decimalFormatter.remove(); + } +} diff --git a/src/main/java/org/apache/jmeter/reporters/ResultAction.java b/src/main/java/org/apache/jmeter/reporters/ResultAction.java new file mode 100644 index 0000000..10e611e --- /dev/null +++ b/src/main/java/org/apache/jmeter/reporters/ResultAction.java @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.reporters; + +import org.apache.jmeter.samplers.SampleEvent; +import org.apache.jmeter.samplers.SampleListener; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.OnErrorTestElement; +import org.apache.jmeter.threads.JMeterContext.TestLogicalAction; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.Serializable; + +/** + * ResultAction - take action based on the status of the last Result + */ +public class ResultAction extends OnErrorTestElement implements Serializable, SampleListener { + + private static final long serialVersionUID = 242L; + + private static final Logger log = LoggerFactory.getLogger(ResultAction.class); + + /** + * Constructor is initially called once for each occurrence in the test plan + * For GUI, several more instances are created Then clear is called at start + * of test Called several times during test startup The name will not + * necessarily have been set at this point. + */ + public ResultAction() { + super(); + } + + /** + * Examine the sample(s) and take appropriate action + * + * @see SampleListener#sampleOccurred(SampleEvent) + */ + @Override + public void sampleOccurred(SampleEvent e) { + SampleResult s = e.getResult(); + if (log.isDebugEnabled()) { + log.debug("ResultStatusHandler {} for {} OK? {}", getName(), s.getSampleLabel(), s.isSuccessful()); + } + if (!s.isSuccessful()) { + if (isStopTestNow()) { + s.setStopTestNow(true); + } else if (isStopTest()) { + s.setStopTest(true); + } else if (isStopThread()) { + s.setStopThread(true); + } else if (isStartNextThreadLoop()) { + s.setTestLogicalAction(TestLogicalAction.START_NEXT_ITERATION_OF_THREAD); + } else if (isStartNextIterationOfCurrentLoop()) { + s.setTestLogicalAction(TestLogicalAction.START_NEXT_ITERATION_OF_CURRENT_LOOP); + } else if (isBreakCurrentLoop()) { + s.setTestLogicalAction(TestLogicalAction.BREAK_CURRENT_LOOP); + } + } else { + if (getErrorAction() == 1000) { + s.setTestLogicalAction(TestLogicalAction.BREAK_CURRENT_LOOP); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public void sampleStarted(SampleEvent e) { + // not used + } + + /** + * {@inheritDoc} + */ + @Override + public void sampleStopped(SampleEvent e) { + // not used + } + +} diff --git a/src/main/java/org/apache/jmeter/samplers/SampleResult.java b/src/main/java/org/apache/jmeter/samplers/SampleResult.java new file mode 100644 index 0000000..ee44117 --- /dev/null +++ b/src/main/java/org/apache/jmeter/samplers/SampleResult.java @@ -0,0 +1,1623 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.samplers; + +import org.apache.jmeter.assertions.AssertionResult; +import org.apache.jmeter.gui.Searchable; +import org.apache.jmeter.testelement.TestPlan; +import org.apache.jmeter.threads.JMeterContext.TestLogicalAction; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.util.JOrphanUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.Serializable; +import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; + +// For unit tests, @see TestSampleResult + +/** + * This is a nice packaging for the various information returned from taking a + * sample of an entry. + */ +public class SampleResult implements Serializable, Cloneable, Searchable { + + private static final long serialVersionUID = 241L; + + // Needs to be accessible from Test code + static Logger log = LoggerFactory.getLogger(SampleResult.class); + + /** + * The default encoding to be used if not overridden. + * The value is ISO-8859-1. + */ + public static final String DEFAULT_HTTP_ENCODING = StandardCharsets.ISO_8859_1.name(); + + private static final String OK_CODE = Integer.toString(HttpURLConnection.HTTP_OK); + private static final String OK_MSG = "OK"; // $NON-NLS-1$ + private static final String INVALID_CALL_SEQUENCE_MSG = "Invalid call sequence"; // $NON-NLS-1$ + + + // Bug 33196 - encoding ISO-8859-1 is only suitable for Western countries + // However the suggested System.getProperty("file.encoding") is Cp1252 on + // Windows + // So use a new property with the original value as default + // needs to be accessible from test code + /** + * The default encoding to be used to decode the responseData byte array. + * The value is defined by the property "sampleresult.default.encoding" + * with a default of DEFAULT_HTTP_ENCODING if that is not defined. + */ + protected static final String DEFAULT_ENCODING + = JMeterUtils.getPropDefault("sampleresult.default.encoding", // $NON-NLS-1$ + DEFAULT_HTTP_ENCODING); + + /** + * The default used by {@link #setResponseData(String, String)} + */ + private static final String DEFAULT_CHARSET = Charset.defaultCharset().name(); + + /** + * Data type value ({@value}) indicating that the response data is text. + * + * @see #getDataType + * @see #setDataType(String) + */ + public static final String TEXT = "text"; // $NON-NLS-1$ + + /** + * Data type value ({@value}) indicating that the response data is binary. + * + * @see #getDataType + * @see #setDataType(String) + */ + public static final String BINARY = "jmeter.bin"; // $NON-NLS-1$ + + private static final boolean DISABLE_SUBRESULTS_RENAMING = JMeterUtils.getPropDefault("subresults.disable_renaming", false); + + // List of types that are known to be binary + private static final String[] BINARY_TYPES = { + "image/", //$NON-NLS-1$ + "audio/", //$NON-NLS-1$ + "video/", //$NON-NLS-1$ + }; + + // List of types that are known to be ascii, although they may appear to be binary + private static final String[] NON_BINARY_TYPES = { + "audio/x-mpegurl", //$NON-NLS-1$ (HLS Media Manifest) + "audio/mpegurl", //$NON-NLS-1$ (HLS Media Manifest) + "video/f4m", //$NON-NLS-1$ (Flash Media Manifest) + "image/svg+xml" //$NON-NLS-1$ (SVG is xml) + }; + + + /** + * empty array which can be returned instead of null + */ + private static final byte[] EMPTY_BA = new byte[0]; + + private static final SampleResult[] EMPTY_SR = new SampleResult[0]; + + private static final AssertionResult[] EMPTY_AR = new AssertionResult[0]; + + private static final boolean START_TIMESTAMP = + JMeterUtils.getPropDefault("sampleresult.timestamp.start", false); // $NON-NLS-1$ + + /** + * Allow read-only access from test code + */ + private static final boolean USE_NANO_TIME = + JMeterUtils.getPropDefault("sampleresult.useNanoTime", true); // $NON-NLS-1$ + + /** + * How long between checks of nanotime; default 5000ms; set to <=0 to disable the thread + */ + private static final long NANOTHREAD_SLEEP = + JMeterUtils.getPropDefault("sampleresult.nanoThreadSleep", 5000); // $NON-NLS-1$ + + private static final String NULL_FILENAME = "NULL"; + + static { + if (START_TIMESTAMP) { + log.info("Note: Sample TimeStamps are START times"); + } else { + log.info("Note: Sample TimeStamps are END times"); + } + log.info("sampleresult.default.encoding is set to {}", DEFAULT_ENCODING); + log.info("sampleresult.useNanoTime={}", USE_NANO_TIME); + log.info("sampleresult.nanoThreadSleep={}", NANOTHREAD_SLEEP); + + if (USE_NANO_TIME && NANOTHREAD_SLEEP > 0) { + // Make sure we start with a reasonable value + NanoOffset.nanoOffset = System.currentTimeMillis() - SampleResult.sampleNsClockInMs(); + NanoOffset nanoOffset = new NanoOffset(); + nanoOffset.setDaemon(true); + nanoOffset.setName("NanoOffset"); + nanoOffset.start(); + } + } + + private String samplerId; + + public String getSamplerId() { + return this.samplerId; + } + + private SampleSaveConfiguration saveConfig; + + private SampleResult parent; + + private byte[] responseData = EMPTY_BA; + + private String responseCode = "";// Never return null + + private String label = "";// Never return null + + /** + * Filename used by ResultSaver + */ + private String resultFileName = ""; + + /** + * The data used by the sampler + */ + private String samplerData; + + private String threadName = ""; // Never return null + + private String responseMessage = ""; + + private String responseHeaders = ""; // Never return null + + private String requestHeaders = ""; + + /** + * timeStamp == 0 means either not yet initialised or no stamp available (e.g. when loading a results file) + * the time stamp - can be start or end + */ + private long timeStamp = 0; + + private long startTime = 0; + + private long endTime = 0; + + private long idleTime = 0;// Allow for non-sample time + + /** + * Start of pause (if any) + */ + private long pauseTime = 0; + + private List assertionResults; + + private List subResults; + + /** + * The data type of the sample + * + * @see #getDataType() + * @see #setDataType(String) + * @see #TEXT + * @see #BINARY + */ + private String dataType = ""; // Don't return null if not set + + private boolean success; + + /** + * Files that this sample has been saved in. + * In Non GUI mode and when best utils is used, size never exceeds 1, + * but as a compromise set it to 2 + */ + private final Set files = ConcurrentHashMap.newKeySet(2); + + // TODO do contentType and/or dataEncoding belong in HTTPSampleResult instead? + private String dataEncoding;// (is this really the character set?) e.g. + // ISO-8895-1, UTF-8 + + private String contentType = ""; // e.g. text/html; charset=utf-8 + + /** + * elapsed time + */ + private long elapsedTime = 0; + + /** + * time to first response + */ + private long latency = 0; + + /** + * time to end connecting + */ + private long connectTime = 0; + + /** + * Way to signal what to do on Test + */ + private TestLogicalAction testLogicalAction = TestLogicalAction.CONTINUE; + + /** + * Should thread terminate? + */ + private boolean stopThread = false; + + /** + * Should test terminate? + */ + private boolean stopTest = false; + + /** + * Should test terminate abruptly? + */ + private boolean stopTestNow = false; + + private int sampleCount = 1; + + private long bytes = 0; // Allows override of sample size in case sampler does not want to store all the data + + private int headersSize = 0; + + private long bodySize = 0; + + /** + * Currently active threads in this thread group + */ + private volatile int groupThreads = 0; + + /** + * Currently active threads in all thread groups + */ + private volatile int allThreads = 0; + + private final long nanoTimeOffset; + + // Allow testcode access to the settings + final boolean useNanoTime; + + final long nanoThreadSleep; + + private long sentBytes; + + private URL location; + + private transient boolean ignore; + + private transient int subResultIndex; + + /** + * Cache for responseData as string to avoid multiple computations + */ + private transient volatile String responseDataAsString; + + public SampleResult() { + this(USE_NANO_TIME, NANOTHREAD_SLEEP); + } + + // Allow test code to change the default useNanoTime setting + SampleResult(boolean nanoTime) { + this(nanoTime, NANOTHREAD_SLEEP); + } + + // Allow test code to change the default useNanoTime and nanoThreadSleep settings + SampleResult(boolean nanoTime, long nanoThreadSleep) { + this.elapsedTime = 0; + this.useNanoTime = nanoTime; + this.nanoThreadSleep = nanoThreadSleep; + this.nanoTimeOffset = initOffset(); + // 增加请求ID的获取 + Sampler sampler = JMeterContextService.getContext().getCurrentSampler(); + if (sampler != null) { + this.samplerId = sampler.getPropertyAsString("MS-ID"); + } + + } + + /** + * Copy constructor. + * + * @param res existing sample result + */ + public SampleResult(SampleResult res) { + this(); + allThreads = res.allThreads;//OK + assertionResults = res.assertionResults; + bytes = res.bytes; + headersSize = res.headersSize; + bodySize = res.bodySize; + contentType = res.contentType;//OK + dataEncoding = res.dataEncoding;//OK + dataType = res.dataType;//OK + endTime = res.endTime;//OK + // files is created automatically, and applies per instance + groupThreads = res.groupThreads;//OK + idleTime = res.idleTime; + label = res.label;//OK + latency = res.latency; + connectTime = res.connectTime; + location = res.location;//OK + parent = res.parent; + pauseTime = res.pauseTime; + requestHeaders = res.requestHeaders;//OK + responseCode = res.responseCode;//OK + responseData = res.responseData;//OK + responseDataAsString = null; + responseHeaders = res.responseHeaders;//OK + responseMessage = res.responseMessage;//OK + + // Don't copy this; it is per instance resultFileName = res.resultFileName; + + sampleCount = res.sampleCount; + samplerData = res.samplerData; + saveConfig = res.saveConfig; + sentBytes = res.sentBytes; + startTime = res.startTime;//OK + stopTest = res.stopTest; + stopTestNow = res.stopTestNow; + stopThread = res.stopThread; + testLogicalAction = res.testLogicalAction; + subResults = res.subResults; + success = res.success;//OK + threadName = res.threadName;//OK + elapsedTime = res.elapsedTime; + timeStamp = res.timeStamp; + } + + /** + * Create a sample with a specific elapsed time but don't allow the times to + * be changed later + *

+ * (only used by HTTPSampleResult) + * + * @param elapsed time + * @param atend create the sample finishing now, else starting now + */ + protected SampleResult(long elapsed, boolean atend) { + this(); + long now = currentTimeInMillis(); + if (atend) { + setTimes(now - elapsed, now); + } else { + setTimes(now, now + elapsed); + } + } + + /** + * Allow users to create a sample with specific timestamp and elapsed times + * for cloning purposes, but don't allow the times to be changed later + *

+ * Currently used by CSVSaveService and + * StatisticalSampleResult + * + * @param stamp this may be a start time or an end time (both in + * milliseconds) + * @param elapsed time in milliseconds + */ + public SampleResult(long stamp, long elapsed) { + this(); + stampAndTime(stamp, elapsed); + } + + private long initOffset() { + if (useNanoTime) { + return nanoThreadSleep > 0 ? NanoOffset.getNanoOffset() : System.currentTimeMillis() - sampleNsClockInMs(); + } else { + return Long.MIN_VALUE; + } + } + + /** + * @param propertiesToSave The propertiesToSave to set. + */ + public void setSaveConfig(SampleSaveConfiguration propertiesToSave) { + this.saveConfig = propertiesToSave; + } + + public SampleSaveConfiguration getSaveConfig() { + return saveConfig; + } + + public boolean isStampedAtStart() { + return START_TIMESTAMP; + } + + /** + * Create a sample with specific start and end times for test purposes, but + * don't allow the times to be changed later + *

+ * (used by StatVisualizerModel.Test) + * + * @param start start time in milliseconds since unix epoch + * @param end end time in milliseconds since unix epoch + * @return sample with given start and end time + */ + public static SampleResult createTestSample(long start, long end) { + SampleResult res = new SampleResult(); + res.setStartTime(start); + res.setEndTime(end); + return res; + } + + /** + * Create a sample with a specific elapsed time for test purposes, but don't + * allow the times to be changed later + * + * @param elapsed - desired elapsed time in milliseconds + * @return sample that starts 'now' and ends elapsed milliseconds later + */ + public static SampleResult createTestSample(long elapsed) { + long now = System.currentTimeMillis(); + return createTestSample(now, now + elapsed); + } + + private static long sampleNsClockInMs() { + return System.nanoTime() / 1000000; + } + + /** + * Helper method to get 1 ms resolution timing. + * + * @return the current time in milliseconds + * @throws RuntimeException when useNanoTime is true but + * nanoTimeOffset is not set + */ + public long currentTimeInMillis() { + if (useNanoTime) { + if (nanoTimeOffset == Long.MIN_VALUE) { + throw new IllegalStateException("Invalid call; nanoTimeOffset has not been set"); + } + return sampleNsClockInMs() + nanoTimeOffset; + } + return System.currentTimeMillis(); + } + + // Helper method to maintain timestamp relationships + private void stampAndTime(long stamp, long elapsed) { + if (START_TIMESTAMP) { + startTime = stamp; + endTime = stamp + elapsed; + } else { + startTime = stamp - elapsed; + endTime = stamp; + } + timeStamp = stamp; + elapsedTime = elapsed; + } + + /** + * For use by SaveService only. + * + * @param stamp this may be a start time or an end time (both in milliseconds) + * @param elapsed time in milliseconds + * @throws RuntimeException when startTime or endTime has been + * set already + */ + public void setStampAndTime(long stamp, long elapsed) { + if (startTime != 0 || endTime != 0) { + throw new IllegalStateException("Calling setStampAndTime() after start/end times have been set"); + } + stampAndTime(stamp, elapsed); + } + + /** + * Set the "marked" flag to show that the result has been written to the file. + * + * @param filename the name of the file + * @return true if the result was previously marked + */ + public boolean markFile(String filename) { + return !files.add(filename != null ? filename : NULL_FILENAME); + } + + public String getResponseCode() { + return responseCode; + } + + /** + * Set response code to OK, i.e. "200" + */ + public void setResponseCodeOK() { + responseCode = OK_CODE; + } + + public void setResponseCode(String code) { + responseCode = code; + } + + public boolean isResponseCodeOK() { + return responseCode.equals(OK_CODE); + } + + public String getResponseMessage() { + return responseMessage; + } + + public void setResponseMessage(String msg) { + responseMessage = msg; + } + + public void setResponseMessageOK() { + responseMessage = OK_MSG; + } + + /** + * Set result statuses OK - shorthand method to set: + *

    + *
  • ResponseCode
  • + *
  • ResponseMessage
  • + *
  • Successful status
  • + *
+ */ + public void setResponseOK() { + setResponseCodeOK(); + setResponseMessageOK(); + setSuccessful(true); + } + + public String getThreadName() { + return threadName; + } + + public void setThreadName(String threadName) { + this.threadName = threadName; + } + + /** + * Get the sample timestamp, which may be either the start time or the end time. + * + * @return timeStamp in milliseconds + * @see #getStartTime() + * @see #getEndTime() + */ + public long getTimeStamp() { + return timeStamp; + } + + public String getSampleLabel() { + return label; + } + + /** + * Get the sample label for use in summary reports etc. + * + * @param includeGroup whether to include the thread group name + * @return the label + */ + public String getSampleLabel(boolean includeGroup) { + if (includeGroup) { + return threadName.substring(0, threadName.lastIndexOf(' ')) + ":" + label; + } + return label; + } + + public void setSampleLabel(String label) { + this.label = label; + } + + public void addAssertionResult(AssertionResult assertResult) { + if (assertionResults == null) { + assertionResults = new ArrayList<>(); + } + assertionResults.add(assertResult); + } + + /** + * Gets the assertion results associated with this sample. + * + * @return an array containing the assertion results for this sample. + * Returns empty array if there are no assertion results. + */ + public AssertionResult[] getAssertionResults() { + if (assertionResults == null) { + return EMPTY_AR; + } + return assertionResults.toArray(new AssertionResult[assertionResults.size()]); + } + + /** + * Add a subresult and adjust the parent byte count and end-time. + * + * @param subResult the {@link SampleResult} to be added + */ + public void addSubResult(SampleResult subResult) { + addSubResult(subResult, isRenameSampleLabel()); + } + + /** + * see https://bz.apache.org/bugzilla/show_bug.cgi?id=63055 + * + * @return true if TestPlan is in functional mode or property subresults.disable_renaming is true + */ + public static boolean isRenameSampleLabel() { + return !(TestPlan.getFunctionalMode() || DISABLE_SUBRESULTS_RENAMING); + } + + /** + * Add a subresult and adjust the parent byte count and end-time. + * + * @param subResult the {@link SampleResult} to be added + * @param renameSubResults boolean do we rename subResults based on position + */ + public void addSubResult(SampleResult subResult, boolean renameSubResults) { + if (subResult == null) { + // see https://bz.apache.org/bugzilla/show_bug.cgi?id=54778 + return; + } + String tn = getThreadName(); + if (tn.length() == 0) { + tn = Thread.currentThread().getName(); + this.setThreadName(tn); + } + subResult.setThreadName(tn); + + // Extend the time to the end of the added sample + setEndTime(Math.max(getEndTime(), subResult.getEndTime() + nanoTimeOffset - subResult.nanoTimeOffset)); // Bug 51855 + // Include the byte count for the added sample + setBytes(getBytesAsLong() + subResult.getBytesAsLong()); + setSentBytes(getSentBytes() + subResult.getSentBytes()); + setHeadersSize(getHeadersSize() + subResult.getHeadersSize()); + setBodySize(getBodySizeAsLong() + subResult.getBodySizeAsLong()); + addRawSubResult(subResult, renameSubResults); + } + + /** + * Add a subresult to the collection without updating any parent fields. + * + * @param subResult the {@link SampleResult} to be added + */ + public void addRawSubResult(SampleResult subResult) { + storeSubResult(subResult, isRenameSampleLabel()); + } + + /** + * Add a subresult to the collection without updating any parent fields. + * + * @param subResult the {@link SampleResult} to be added + */ + private void addRawSubResult(SampleResult subResult, boolean renameSubResults) { + storeSubResult(subResult, renameSubResults); + } + + /** + * Add a subresult read from a results file. + *

+ * As for {@link SampleResult#addSubResult(SampleResult) + * addSubResult(SampleResult)}, except that the fields don't need to be + * accumulated + * + * @param subResult the {@link SampleResult} to be added + */ + public void storeSubResult(SampleResult subResult) { + storeSubResult(subResult, isRenameSampleLabel()); + } + + /** + * Add a subresult read from a results file. + *

+ * As for {@link SampleResult#addSubResult(SampleResult) + * addSubResult(SampleResult)}, except that the fields don't need to be + * accumulated + * + * @param subResult the {@link SampleResult} to be added + * @param renameSubResults boolean do we rename subResults based on position + */ + private void storeSubResult(SampleResult subResult, boolean renameSubResults) { + if (subResults == null) { + subResults = new ArrayList<>(); + } + if (renameSubResults) { + subResult.setSampleLabel(getSampleLabel() + "-" + subResultIndex++); + } + subResults.add(subResult); + subResult.setParent(this); + } + + /** + * Gets the subresults associated with this sample. + * + * @return an array containing the subresults for this sample. Returns an + * empty array if there are no subresults. + */ + public SampleResult[] getSubResults() { + if (subResults == null) { + return EMPTY_SR; + } + return subResults.toArray(new SampleResult[subResults.size()]); + } + + /** + * Sets the responseData attribute of the SampleResult object. + *

+ * If the parameter is null, then the responseData is set to an empty byte array. + * This ensures that getResponseData() can never be null. + * + * @param response the new responseData value + */ + public void setResponseData(byte[] response) { + responseDataAsString = null; + responseData = response == null ? EMPTY_BA : response; + } + + /** + * Sets the responseData attribute of the SampleResult object. + * Should only be called after setting the dataEncoding (if necessary) + * + * @param response the new responseData value (String) + * @deprecated - only intended for use from BeanShell code + */ + @Deprecated + public void setResponseData(String response) { + responseDataAsString = null; + try { + responseData = response.getBytes(getDataEncodingWithDefault()); + } catch (UnsupportedEncodingException e) { + log.warn("Could not convert string, using default encoding. " + e.getLocalizedMessage()); + responseData = response.getBytes(Charset.defaultCharset()); // N.B. default charset is used deliberately here + } + } + + /** + * Sets the encoding and responseData attributes of the SampleResult object. + * + * @param response the new responseData value (String) + * @param encoding the encoding to set and then use (if null, use platform default) + */ + public void setResponseData(final String response, final String encoding) { + responseDataAsString = null; + String encodeUsing = encoding != null ? encoding : DEFAULT_CHARSET; + try { + responseData = response.getBytes(encodeUsing); + setDataEncoding(encodeUsing); + } catch (UnsupportedEncodingException e) { + log.warn("Could not convert string using '" + encodeUsing + + "', using default encoding: " + DEFAULT_CHARSET, e); + responseData = response.getBytes(Charset.defaultCharset()); // N.B. default charset is used deliberately here + setDataEncoding(DEFAULT_CHARSET); + } + } + + /** + * Gets the responseData attribute of the SampleResult object. + *

+ * Note that some samplers may not store all the data, in which case + * getResponseData().length will be incorrect. + *

+ * Instead, always use {@link #getBytes()} to obtain the sample result byte count. + *

+ * + * @return the responseData value (cannot be null) + */ + public byte[] getResponseData() { + return responseData; + } + + /** + * Gets the responseData of the SampleResult object as a String + * + * @return the responseData value as a String, converted according to the encoding + */ + public String getResponseDataAsString() { + try { + if (responseDataAsString == null) { + responseDataAsString = new String(responseData, getDataEncodingWithDefault()); + } + return responseDataAsString; + } catch (UnsupportedEncodingException e) { + log.warn("Using platform default as " + getDataEncodingWithDefault() + " caused " + e); + return new String(responseData, Charset.defaultCharset()); // N.B. default charset is used deliberately here + } + } + + public void setSamplerData(String s) { + samplerData = s; + } + + public String getSamplerData() { + return samplerData; + } + + /** + * Get the time it took this sample to occur. + * + * @return elapsed time in milliseconds + */ + public long getTime() { + return elapsedTime; + } + + public boolean isSuccessful() { + return success; + } + + /** + * Sets the data type of the sample. + * + * @param dataType String containing {@link #BINARY} or {@link #TEXT} + * @see #BINARY + * @see #TEXT + */ + public void setDataType(String dataType) { + this.dataType = dataType; + } + + /** + * Returns the data type of the sample. + * + * @return String containing {@link #BINARY} or {@link #TEXT} or the empty string + * @see #BINARY + * @see #TEXT + */ + public String getDataType() { + return dataType; + } + + /** + * Extract and save the DataEncoding and DataType from the parameter provided. + * Does not save the full content Type. + * + * @param ct - content type (may be null) + * @see #setContentType(String) which should be used to save the full content-type string + */ + public void setEncodingAndType(String ct) { + if (ct != null) { + // Extract charset and store as DataEncoding + // N.B. The meta tag: + // + // is now processed by HTTPSampleResult#getDataEncodingWithDefault + final String charsetPrefix = "charset="; // $NON-NLS-1$ + int cset = ct.toLowerCase(java.util.Locale.ENGLISH).indexOf(charsetPrefix); + if (cset >= 0) { + String charSet = ct.substring(cset + charsetPrefix.length()); + // handle: ContentType: text/plain; charset=ISO-8859-1; format=flowed + int semiColon = charSet.indexOf(';'); + if (semiColon >= 0) { + charSet = charSet.substring(0, semiColon); + } + // Check for quoted string + if (charSet.startsWith("\"") || charSet.startsWith("\'")) { // $NON-NLS-1$ + setDataEncoding(charSet.substring(1, charSet.length() - 1)); // remove quotes + } else { + setDataEncoding(charSet); + } + } + if (isBinaryType(ct)) { + setDataType(BINARY); + } else { + setDataType(TEXT); + } + } + } + + /* + * Determine if content-type is known to be binary, i.e. not displayable as text. + * + * @param ct content type + * @return true if content-type is of type binary. + */ + public static boolean isBinaryType(String ct) { + for (String entry : NON_BINARY_TYPES) { + if (ct.startsWith(entry)) { + return false; + } + } + for (String binaryType : BINARY_TYPES) { + if (ct.startsWith(binaryType)) { + return true; + } + } + return false; + } + + /** + * Sets the successful attribute of the SampleResult object. + * + * @param success the new successful value + */ + public void setSuccessful(boolean success) { + this.success = success; + } + + /** + * Returns the display name. + * + * @return display name of this sample result + */ + @Override + public String toString() { + return getSampleLabel(); + } + + /** + * Returns the dataEncoding or the default if no dataEncoding was provided. + * + * @return the value of the dataEncoding or DEFAULT_ENCODING + */ + public String getDataEncodingWithDefault() { + return getDataEncodingWithDefault(DEFAULT_ENCODING); + } + + /** + * Returns the dataEncoding or the default if no dataEncoding was provided. + * + * @param defaultEncoding the default to be applied + * @return the value of the dataEncoding or the provided default + */ + protected String getDataEncodingWithDefault(String defaultEncoding) { + if (dataEncoding != null && dataEncoding.length() > 0) { + return dataEncoding; + } + return defaultEncoding; + } + + /** + * Returns the dataEncoding. May be null or the empty String. + * + * @return the value of the dataEncoding + */ + public String getDataEncodingNoDefault() { + return dataEncoding; + } + + /** + * Sets the dataEncoding. + * + * @param dataEncoding the dataEncoding to set, e.g. ISO-8895-1, UTF-8 + */ + public void setDataEncoding(String dataEncoding) { + this.dataEncoding = dataEncoding; + } + + /** + * @return whether to stop the test waiting for current running Sampler to end + */ + public boolean isStopTest() { + return stopTest; + } + + /** + * @return whether to stop the test now interrupting current running samplers + */ + public boolean isStopTestNow() { + return stopTestNow; + } + + /** + * @return whether to stop this thread + */ + public boolean isStopThread() { + return stopThread; + } + + public void setStopTest(boolean b) { + stopTest = b; + } + + public void setStopTestNow(boolean b) { + stopTestNow = b; + } + + public void setStopThread(boolean b) { + stopThread = b; + } + + /** + * @return the request headers + */ + public String getRequestHeaders() { + return requestHeaders; + } + + /** + * @return the response headers + */ + public String getResponseHeaders() { + return responseHeaders; + } + + /** + * @param string - + * request headers + */ + public void setRequestHeaders(String string) { + requestHeaders = string; + } + + /** + * @param string - + * response headers + */ + public void setResponseHeaders(String string) { + responseHeaders = string; + } + + /** + * @return the full content type - e.g. text/html [;charset=utf-8 ] + */ + public String getContentType() { + return contentType; + } + + /** + * Get the media type from the Content Type + * + * @return the media type - e.g. text/html (without charset, if any) + */ + public String getMediaType() { + return JOrphanUtils.trim(contentType, " ;").toLowerCase(java.util.Locale.ENGLISH); + } + + /** + * Stores the content-type string, e.g. text/xml; charset=utf-8 + * + * @param string the content-type to be set + * @see #setEncodingAndType(String) which can be used to extract the charset. + */ + public void setContentType(String string) { + contentType = string; + } + + /** + * @return idleTime + */ + public long getIdleTime() { + return idleTime; + } + + /** + * @return the end time + */ + public long getEndTime() { + return endTime; + } + + /** + * @return the start time + */ + public long getStartTime() { + return startTime; + } + + /* + * Helper methods N.B. setStartTime must be called before setEndTime + * + * setStartTime is used by HTTPSampleResult to clone the parent sampler and + * allow the original start time to be kept + */ + protected final void setStartTime(long start) { + startTime = start; + if (START_TIMESTAMP) { + timeStamp = startTime; + } + } + + public void setEndTime(long end) { + endTime = end; + if (!START_TIMESTAMP) { + timeStamp = endTime; + } + if (startTime == 0) { + log.error("setEndTime must be called after setStartTime", new Throwable(INVALID_CALL_SEQUENCE_MSG)); + } else { + elapsedTime = endTime - startTime - idleTime; + } + } + + /** + * Set idle time pause. + * For use by SampleResultConverter/CSVSaveService. + * + * @param idle long + */ + public void setIdleTime(long idle) { + idleTime = idle; + } + + private void setTimes(long start, long end) { + setStartTime(start); + setEndTime(end); + } + + /** + * Record the start time of a sample + */ + public void sampleStart() { + if (startTime == 0) { + setStartTime(currentTimeInMillis()); + } else { + log.error("sampleStart called twice", new Throwable(INVALID_CALL_SEQUENCE_MSG)); + } + } + + /** + * Record the end time of a sample and calculate the elapsed time + */ + public void sampleEnd() { + if (endTime == 0) { + setEndTime(currentTimeInMillis()); + } else { + log.error("sampleEnd called twice", new Throwable(INVALID_CALL_SEQUENCE_MSG)); + } + } + + /** + * Pause a sample + */ + public void samplePause() { + if (pauseTime != 0) { + log.error("samplePause called twice", new Throwable(INVALID_CALL_SEQUENCE_MSG)); + } + pauseTime = currentTimeInMillis(); + } + + /** + * Resume a sample + */ + public void sampleResume() { + if (pauseTime == 0) { + log.error("sampleResume without samplePause", new Throwable(INVALID_CALL_SEQUENCE_MSG)); + } + idleTime += currentTimeInMillis() - pauseTime; + pauseTime = 0; + } + + /** + * When a Sampler is working as a monitor + * + * @param monitor flag whether this sampler is working as a monitor + * @deprecated since 3.2 NOOP + */ + @Deprecated + public void setMonitor(boolean monitor) { + // NOOP + } + + /** + * If the sampler is a monitor, method will return true. + * + * @return true if the sampler is a monitor + * @deprecated since 3.2 always return false + */ + @Deprecated + public boolean isMonitor() { + return false; + } + + /** + * The statistical sample sender aggregates several samples to save on + * transmission costs. + * + * @param count number of samples represented by this instance + */ + public void setSampleCount(int count) { + sampleCount = count; + } + + /** + * return the sample count. by default, the value is 1. + * + * @return the sample count + */ + public int getSampleCount() { + return sampleCount; + } + + /** + * Returns the count of errors. + * + * @return 0 - or 1 if the sample failed + *

+ * TODO do we need allow for nested samples? + */ + public int getErrorCount() { + return success ? 0 : 1; + } + + public void setErrorCount(int i) {// for reading from CSV files + // ignored currently + } + + /* + * TODO: error counting needs to be sorted out. + * + * At present the Statistical Sampler tracks errors separately + * It would make sense to move the error count here, but this would + * mean lots of changes. + * It's also tricky maintaining the count - it can't just be incremented/decremented + * when the success flag is set as this may be done multiple times. + * The work-round for now is to do the work in the StatisticalSampleResult, + * which overrides this method. + * Note that some JMS samplers also create samples with > 1 sample count + * Also the Transaction Controller probably needs to be changed to do + * proper sample and error accounting. + * The purpose of this work-round is to allow at least minimal support for + * errors in remote statistical batch mode. + * + */ + + /** + * In the event the sampler does want to pass back the actual contents, we + * still want to calculate the throughput. The bytes are the bytes of the + * response data. + * + * @param length the number of bytes of the response data for this sample + */ + public void setBytes(long length) { + bytes = length; + } + + /** + * In the event the sampler does want to pass back the actual contents, we + * still want to calculate the throughput. The bytes are the bytes of the + * response data. + * + * @param length the number of bytes of the response data for this sample + * @deprecated use setBytes(long) + */ + @Deprecated + public void setBytes(int length) { + setBytes((long) length); + } + + /** + * @param sentBytesCount long sent bytes + */ + public void setSentBytes(long sentBytesCount) { + sentBytes = sentBytesCount; + } + + /** + * @return the sentBytes + */ + public long getSentBytes() { + return sentBytes; + } + + /** + * return the bytes returned by the response. + * + * @return byte count + * @deprecated use getBytesAsLong + */ + @Deprecated + public int getBytes() { + return (int) getBytesAsLong(); + } + + /** + * return the bytes returned by the response. + * + * @return byte count + */ + public long getBytesAsLong() { + long tmpSum = this.getHeadersSize() + this.getBodySizeAsLong(); + return tmpSum == 0 ? bytes : tmpSum; + } + + /** + * @return Returns the latency. + */ + public long getLatency() { + return latency; + } + + /** + * Set the time to the first response + */ + public void latencyEnd() { + latency = currentTimeInMillis() - startTime - idleTime; + } + + /** + * This is only intended for use by SampleResultConverter! + * + * @param latency The latency to set. + */ + public void setLatency(long latency) { + this.latency = latency; + } + + /** + * @return Returns the connect time. + */ + public long getConnectTime() { + return connectTime; + } + + /** + * Set the time to the end of connecting + */ + public void connectEnd() { + connectTime = currentTimeInMillis() - startTime - idleTime; + } + + /** + * This is only intended for use by SampleResultConverter! + * + * @param time The connect time to set. + */ + public void setConnectTime(long time) { + this.connectTime = time; + } + + /** + * This is only intended for use by SampleResultConverter! + * + * @param timeStamp The timeStamp to set. + */ + public void setTimeStamp(long timeStamp) { + this.timeStamp = timeStamp; + } + + + public void setURL(URL location) { + this.location = location; + } + + public URL getURL() { + return location; + } + + /** + * Get a String representation of the URL (if defined). + * + * @return ExternalForm of URL, or empty string if url is null + */ + public String getUrlAsString() { + return location == null ? "" : location.toExternalForm(); + } + + /** + * @return Returns the parent. + */ + public SampleResult getParent() { + return parent; + } + + /** + * @param parent The parent to set. + */ + public void setParent(SampleResult parent) { + this.parent = parent; + } + + public String getResultFileName() { + return resultFileName; + } + + public void setResultFileName(String resultFileName) { + this.resultFileName = resultFileName; + } + + public int getGroupThreads() { + return groupThreads; + } + + public void setGroupThreads(int n) { + this.groupThreads = n; + } + + public int getAllThreads() { + return allThreads; + } + + public void setAllThreads(int n) { + this.allThreads = n; + } + + // Bug 47394 + + /** + * Allow custom SampleSenders to drop unwanted assertionResults + */ + public void removeAssertionResults() { + this.assertionResults = null; + } + + /** + * Allow custom SampleSenders to drop unwanted subResults + */ + public void removeSubResults() { + this.subResults = null; + } + + /** + * Set the headers size in bytes + * + * @param size the number of bytes of the header + */ + public void setHeadersSize(int size) { + this.headersSize = size; + } + + /** + * Get the headers size in bytes + * + * @return the headers size + */ + public int getHeadersSize() { + return headersSize; + } + + /** + * @return the body size in bytes + * @deprecated replaced by getBodySizeAsLong() + */ + @Deprecated + public int getBodySize() { + return (int) getBodySizeAsLong(); + } + + /** + * @return the body size in bytes + */ + public long getBodySizeAsLong() { + return bodySize == 0 ? responseData.length : bodySize; + } + + /** + * @param bodySize the body size to set + */ + public void setBodySize(long bodySize) { + this.bodySize = bodySize; + } + + /** + * @param bodySize the body size to set + * @deprecated use setBodySize(long) + */ + @Deprecated + public void setBodySize(int bodySize) { + this.bodySize = bodySize; + } + + private static class NanoOffset extends Thread { + + private static volatile long nanoOffset; + + static long getNanoOffset() { + return nanoOffset; + } + + @Override + public void run() { + // Wait longer than a clock pulse (generally 10-15ms) + getOffset(30L); // Catch an early clock pulse to reduce slop. + while (true) { + getOffset(NANOTHREAD_SLEEP); // Can now afford to wait a bit longer between checks + } + } + + private static void getOffset(long wait) { + try { + TimeUnit.MILLISECONDS.sleep(wait); + long clock = System.currentTimeMillis(); + long nano = SampleResult.sampleNsClockInMs(); + nanoOffset = clock - nano; + } catch (InterruptedException ignore) { + // ignored + Thread.currentThread().interrupt(); + } + } + } + + /** + * @return the startNextThreadLoop + * @deprecated use {@link SampleResult#getTestLogicalAction()} + */ + @Deprecated + public boolean isStartNextThreadLoop() { + return testLogicalAction == TestLogicalAction.START_NEXT_ITERATION_OF_THREAD; + } + + /** + * @param startNextThreadLoop the startNextLoop to set + * @deprecated use SampleResult#setTestLogicalAction(TestLogicalAction) + */ + @Deprecated + public void setStartNextThreadLoop(boolean startNextThreadLoop) { + if (startNextThreadLoop) { + testLogicalAction = TestLogicalAction.START_NEXT_ITERATION_OF_THREAD; + } else { + testLogicalAction = TestLogicalAction.CONTINUE; + } + } + + /** + * Clean up cached data + */ + public void cleanAfterSample() { + this.responseDataAsString = null; + } + + @Override + public Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException e) { + throw new IllegalStateException("This should not happen"); + } + } + + @Override + public List getSearchableTokens() throws Exception { + List datasToSearch = new ArrayList<>(4); + datasToSearch.add(getSampleLabel()); + datasToSearch.add(getResponseDataAsString()); + datasToSearch.add(getRequestHeaders()); + datasToSearch.add(getResponseHeaders()); + return datasToSearch; + } + + /** + * @return boolean true if this SampleResult should not be sent to Listeners + */ + public boolean isIgnore() { + return ignore; + } + + /** + * Call this method to tell JMeter to ignore this SampleResult by Listeners + */ + public void setIgnore() { + this.ignore = true; + } + + /** + * @return String first non null assertion failure message if assertionResults is not null, null otherwise + */ + public String getFirstAssertionFailureMessage() { + String message = null; + AssertionResult[] results = getAssertionResults(); + + if (results != null) { + // Find the first non-null message + for (AssertionResult result : results) { + message = result.getFailureMessage(); + if (message != null) { + break; + } + } + } + return message; + } + + /** + * @return the testLogicalAction + */ + public TestLogicalAction getTestLogicalAction() { + return testLogicalAction; + } + + /** + * @param testLogicalAction the testLogicalAction to set + */ + public void setTestLogicalAction(TestLogicalAction testLogicalAction) { + this.testLogicalAction = testLogicalAction; + } +} diff --git a/src/main/java/org/apache/jmeter/util/JSR223BeanInfoSupport.java b/src/main/java/org/apache/jmeter/util/JSR223BeanInfoSupport.java new file mode 100644 index 0000000..67568d3 --- /dev/null +++ b/src/main/java/org/apache/jmeter/util/JSR223BeanInfoSupport.java @@ -0,0 +1,93 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.util; + +import org.apache.jmeter.testbeans.TestBean; + +import javax.script.ScriptEngineFactory; +import javax.script.ScriptEngineManager; +import java.util.*; +import java.util.Map.Entry; + +/** + * 解决JSR233加载 ScriptEngineFactory 空指针问题 + */ +public abstract class JSR223BeanInfoSupport extends ScriptingBeanInfoSupport { + + private static final String[] LANGUAGE_TAGS; + + /** + * Will be removed in next version following 3.2 + * @deprecated use {@link JSR223BeanInfoSupport#getLanguageNames()} + */ + @Deprecated + public static final String[][] LANGUAGE_NAMES; // NOSONAR Kept for backward compatibility + + private static final String[][] CONSTANT_LANGUAGE_NAMES; + + static { + Map nameMap = new HashMap<>(); + ScriptEngineManager sem = new ScriptEngineManager(); + final List engineFactories = sem.getEngineFactories(); + for(ScriptEngineFactory fact : engineFactories){ + List names = fact.getNames(); + for(String shortName : names) { + if (shortName != null) { + nameMap.put(shortName.toLowerCase(Locale.ENGLISH), fact); + } + } + } + LANGUAGE_TAGS = nameMap.keySet().toArray(new String[nameMap.size()]); + Arrays.sort(LANGUAGE_TAGS); + CONSTANT_LANGUAGE_NAMES = new String[nameMap.size()][2]; + int i = 0; + for(Entry me : nameMap.entrySet()) { + final String key = me.getKey(); + CONSTANT_LANGUAGE_NAMES[i][0] = key; + final ScriptEngineFactory fact = me.getValue(); + CONSTANT_LANGUAGE_NAMES[i++][1] = key + + " (" // $NON-NLS-1$ + + fact.getLanguageName() + " " + fact.getLanguageVersion() // $NON-NLS-1$ + + " / " // $NON-NLS-1$ + + fact.getEngineName() + " " + fact.getEngineVersion() // $NON-NLS-1$ + + ")"; // $NON-NLS-1$ + } + + LANGUAGE_NAMES = getLanguageNames(); // NOSONAR Kept for backward compatibility + } + + private static final ResourceBundle NAME_BUNDLE = new ListResourceBundle() { + @Override + protected Object[][] getContents() { + return CONSTANT_LANGUAGE_NAMES; + } + }; + + protected JSR223BeanInfoSupport(Class beanClass) { + super(beanClass, LANGUAGE_TAGS, NAME_BUNDLE); + } + + /** + * @return String array of 2 columns array containing Script engine short name / Script Language details + */ + public static final String[][] getLanguageNames() { + return CONSTANT_LANGUAGE_NAMES.clone(); + } + +} diff --git a/src/main/java/org/apache/jmeter/util/JSR223TestElement.java b/src/main/java/org/apache/jmeter/util/JSR223TestElement.java new file mode 100644 index 0000000..d7cd59c --- /dev/null +++ b/src/main/java/org/apache/jmeter/util/JSR223TestElement.java @@ -0,0 +1,387 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.util; + +import io.metersphere.api.jmeter.utils.FileUtils; +import io.metersphere.node.util.LogUtil; +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.collections.map.LRUMap; +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.NewDriver; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testelement.TestStateListener; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jorphan.util.JOrphanUtils; +import org.codehaus.groovy.jsr223.GroovyScriptEngineImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.script.*; +import java.io.*; +import java.util.Collections; +import java.util.Map; +import java.util.Properties; + + +/** + * Base class for JSR223 Test elements + *

+ * 将当前类加载器设置为 loader ,解决由系统类加载器加载的 JMeter 无法动态加载 jar 包问题 + * 同时隔离在 beanshell 中访问由系统类加载器加载的其他类 + */ +public abstract class JSR223TestElement extends ScriptingTestElement + implements Serializable, TestStateListener { + private static final long serialVersionUID = 232L; + + private static final Logger logger = LoggerFactory.getLogger(JSR223TestElement.class); + /** + * Cache of compiled scripts + */ + @SuppressWarnings("unchecked") // LRUMap does not support generics (yet) + private static final Map compiledScriptsCache = + Collections.synchronizedMap( + new LRUMap(JMeterUtils.getPropDefault("jsr223.compiled_scripts_cache_size", 100))); + + /** + * If not empty then script in ScriptText will be compiled and cached + */ + private String cacheKey = ""; + + /** + * md5 of the script, used as an unique key for the cache + */ + private String scriptMd5 = null; + + /** + * Initialization On Demand Holder pattern + */ + private static class LazyHolder { + private LazyHolder() { + super(); + } + + public static final ScriptEngineManager INSTANCE = new ScriptEngineManager(); + } + + /** + * @return ScriptEngineManager singleton + */ + public static ScriptEngineManager getInstance() { + return LazyHolder.INSTANCE; + } + + public JSR223TestElement() { + super(); + } + + /** + * @return {@link ScriptEngine} for language defaulting to groovy if language is not set + * @throws ScriptException when no {@link ScriptEngine} could be found + */ + protected ScriptEngine getScriptEngine() throws ScriptException { + String lang = getScriptLanguageWithDefault(); + ScriptEngine scriptEngine = getInstance().getEngineByName(lang); + if (scriptEngine == null) { + throw new ScriptException("Cannot find engine named: '" + lang + "', ensure you set language field in JSR223 Test Element: " + getName()); + } + + return scriptEngine; + } + + /** + * @return script language or DEFAULT_SCRIPT_LANGUAGE if none is set + */ + private String getScriptLanguageWithDefault() { + String lang = getScriptLanguage(); + if (StringUtils.isNotEmpty(lang)) { + return lang; + } + return DEFAULT_SCRIPT_LANGUAGE; + } + + /** + * Populate variables to be passed to scripts + * + * @param bindings Bindings + */ + protected void populateBindings(Bindings bindings) { + final String label = getName(); + final String fileName = getFilename(); + final String scriptParameters = getParameters(); + // Use actual class name for log + final Logger elementLogger = LoggerFactory.getLogger(getClass().getName() + "." + getName()); + bindings.put("log", elementLogger); // $NON-NLS-1$ (this name is fixed) + bindings.put("Label", label); // $NON-NLS-1$ (this name is fixed) + bindings.put("FileName", fileName); // $NON-NLS-1$ (this name is fixed) + bindings.put("Parameters", scriptParameters); // $NON-NLS-1$ (this name is fixed) + String[] args = JOrphanUtils.split(scriptParameters, " ");//$NON-NLS-1$ + bindings.put("args", args); // $NON-NLS-1$ (this name is fixed) + // Add variables for access to context and variables + JMeterContext jmctx = JMeterContextService.getContext(); + bindings.put("ctx", jmctx); // $NON-NLS-1$ (this name is fixed) + JMeterVariables vars = jmctx.getVariables(); + bindings.put("vars", vars); // $NON-NLS-1$ (this name is fixed) + Properties props = JMeterUtils.getJMeterProperties(); + bindings.put("props", props); // $NON-NLS-1$ (this name is fixed) + // For use in debugging: + bindings.put("OUT", System.out); // NOSONAR $NON-NLS-1$ (this name is fixed) + + // Most subclasses will need these: + Sampler sampler = jmctx.getCurrentSampler(); + bindings.put("sampler", sampler); // $NON-NLS-1$ (this name is fixed) + SampleResult prev = jmctx.getPreviousResult(); + bindings.put("prev", prev); // $NON-NLS-1$ (this name is fixed) + } + + + /** + * This method will run inline script or file script with special behaviour for file script: + * - If ScriptEngine implements Compilable script will be compiled and cached + * - If not if will be run + * + * @param scriptEngine ScriptEngine + * @param pBindings {@link Bindings} might be null + * @return Object returned by script + * @throws IOException when reading the script fails + * @throws ScriptException when compiling or evaluation of the script fails + */ + protected Object processFileOrScript(ScriptEngine scriptEngine, final Bindings pBindings) + throws IOException, ScriptException { + + this.loadGroovyJar(scriptEngine); + + Bindings bindings = pBindings; + if (bindings == null) { + bindings = scriptEngine.createBindings(); + } + populateBindings(bindings); + File scriptFile = new File(getFilename()); + // Hack: bsh-2.0b5.jar BshScriptEngine implements Compilable but throws + // "java.lang.Error: unimplemented" + boolean supportsCompilable = scriptEngine instanceof Compilable + && !("bsh.engine.BshScriptEngine".equals(scriptEngine.getClass().getName())); // NOSONAR // $NON-NLS-1$ + + ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); + // 将当前类加载器设置为 loader ,解决由系统类加载器加载的 JMeter 无法动态加载 jar 包问题 + // 同时隔离在 beanshell 中访问由系统类加载器加载的其他类 + NewDriver.setContextClassLoader(); + + try { + if (!StringUtils.isEmpty(getFilename())) { + if (scriptFile.exists() && scriptFile.canRead()) { + if (supportsCompilable) { + String newCacheKey = getScriptLanguage() + "#" + // $NON-NLS-1$ + scriptFile.getAbsolutePath() + "#" + // $NON-NLS-1$ + scriptFile.lastModified(); + CompiledScript compiledScript = compiledScriptsCache.get(newCacheKey); + if (compiledScript == null) { + synchronized (compiledScriptsCache) { + compiledScript = compiledScriptsCache.get(newCacheKey); + if (compiledScript == null) { + // TODO Charset ? + try (BufferedReader fileReader = new BufferedReader(new FileReader(scriptFile), + (int) scriptFile.length())) { + compiledScript = ((Compilable) scriptEngine).compile(fileReader); + compiledScriptsCache.put(newCacheKey, compiledScript); + } + } + } + } + return compiledScript.eval(bindings); + } else { + // TODO Charset ? + try (BufferedReader fileReader = new BufferedReader(new FileReader(scriptFile), + (int) scriptFile.length())) { + return scriptEngine.eval(fileReader, bindings); + } + } + } else { + throw new ScriptException("Script file '" + scriptFile.getAbsolutePath() + + "' does not exist or is unreadable for element:" + getName()); + } + } else if (!StringUtils.isEmpty(getScript())) { + if (supportsCompilable && + !ScriptingBeanInfoSupport.FALSE_AS_STRING.equals(cacheKey)) { + computeScriptMD5(); + CompiledScript compiledScript = compiledScriptsCache.get(this.scriptMd5); + if (compiledScript == null) { + synchronized (compiledScriptsCache) { + compiledScript = compiledScriptsCache.get(this.scriptMd5); + if (compiledScript == null) { + compiledScript = ((Compilable) scriptEngine).compile(getScript()); + compiledScriptsCache.put(this.scriptMd5, compiledScript); + } + } + } + + return compiledScript.eval(bindings); + } else { + return scriptEngine.eval(getScript(), bindings); + } + } else { + throw new ScriptException("Both script file and script text are empty for element:" + getName()); + } + } catch (ScriptException ex) { + Throwable rootCause = ex.getCause(); + if (isStopCondition(rootCause)) { + throw (RuntimeException) ex.getCause(); + } else { + throw ex; + } + } finally { + // 将当前类加载器设置回来,避免加载无法加载到系统类加载加载类 + Thread.currentThread().setContextClassLoader(contextClassLoader); + } + } + + + /** + * groovy 使用的是自己的类加载器, + * 这里再执行脚本前,使用 groovy的加载器加载jar包, + * 解决groovy脚本无法使用jar包的问题 + * + * @param scriptEngine + * @Auth jianxing + */ + public static void loadGroovyJar(ScriptEngine scriptEngine) { + if (scriptEngine instanceof GroovyScriptEngineImpl) { + GroovyScriptEngineImpl groovyScriptEngine = (GroovyScriptEngineImpl) scriptEngine; + + try { + File file = new File(FileUtils.JAR_FILE_DIR); + if (file.isFile()) { + groovyScriptEngine.getClassLoader().addURL(file.toURI().toURL()); + } else { + File[] files = file.listFiles(); + for (File f : files) { + groovyScriptEngine.getClassLoader().addURL(f.toURI().toURL()); + } + } + } catch (Exception e) { + e.printStackTrace(); + LogUtil.error(e.getMessage(), e); + } + } + } + + /** + * @return boolean true if element is not compilable or if compilation succeeds + * @throws IOException if script is missing + * @throws ScriptException if compilation fails + */ + public boolean compile() + throws ScriptException, IOException { + String lang = getScriptLanguageWithDefault(); + ScriptEngine scriptEngine = getInstance().getEngineByName(lang); + boolean supportsCompilable = scriptEngine instanceof Compilable + && !("bsh.engine.BshScriptEngine".equals(scriptEngine.getClass().getName())); // NOSONAR // $NON-NLS-1$ + if (!supportsCompilable) { + return true; + } + if (!StringUtils.isEmpty(getScript())) { + try { + ((Compilable) scriptEngine).compile(getScript()); + return true; + } catch (ScriptException e) { // NOSONAR + logger.error("Error compiling script for test element {}, error:{}", getName(), e.getMessage()); + return false; + } + } else { + File scriptFile = new File(getFilename()); + try (BufferedReader fileReader = new BufferedReader(new FileReader(scriptFile), + (int) scriptFile.length())) { + try { + ((Compilable) scriptEngine).compile(fileReader); + return true; + } catch (ScriptException e) { // NOSONAR + logger.error("Error compiling script for test element {}, error:{}", getName(), e.getMessage()); + return false; + } + } + } + } + + /** + * compute MD5 if it is null + */ + private void computeScriptMD5() { + // compute the md5 of the script if needed + if (scriptMd5 == null) { + scriptMd5 = DigestUtils.md5Hex(getScript()); + } + } + + /** + * @return the cacheKey + */ + public String getCacheKey() { + return cacheKey; + } + + /** + * @param cacheKey the cacheKey to set + */ + public void setCacheKey(String cacheKey) { + this.cacheKey = cacheKey; + } + + /** + * @see org.apache.jmeter.testelement.TestStateListener#testStarted() + */ + @Override + public void testStarted() { + // NOOP + } + + /** + * @see org.apache.jmeter.testelement.TestStateListener#testStarted(java.lang.String) + */ + @Override + public void testStarted(String host) { + // NOOP + } + + /** + * @see org.apache.jmeter.testelement.TestStateListener#testEnded() + */ + @Override + public void testEnded() { + testEnded(""); + } + + /** + * @see org.apache.jmeter.testelement.TestStateListener#testEnded(java.lang.String) + */ + @Override + public void testEnded(String host) { + compiledScriptsCache.clear(); + this.scriptMd5 = null; + } + + public String getScriptLanguage() { + return scriptLanguage; + } + + public void setScriptLanguage(String s) { + scriptLanguage = s; + } +} diff --git a/src/main/java/org/apache/jorphan/collections/HashTree.java b/src/main/java/org/apache/jorphan/collections/HashTree.java new file mode 100644 index 0000000..828ab58 --- /dev/null +++ b/src/main/java/org/apache/jorphan/collections/HashTree.java @@ -0,0 +1,1095 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jorphan.collections; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.*; + +/** + * This class is used to create a tree structure of objects. Each element in the + * tree is also a key to the next node down in the tree. It provides many ways + * to add objects and branches, as well as many ways to retrieve. + *

+ * HashTree implements the Map interface for convenience reasons. The main + * difference between a Map and a HashTree is that the HashTree organizes the + * data into a recursive tree structure, and provides the means to manipulate + * that structure. + *

+ * Of special interest is the {@link #traverse(HashTreeTraverser)} method, which + * provides an expedient way to traverse any HashTree by implementing the + * {@link HashTreeTraverser} interface in order to perform some operation on the + * tree, or to extract information from the tree. + * + * @see HashTreeTraverser + * @see SearchByClass + */ +public class HashTree implements Serializable, Map, Cloneable { + + private static final long serialVersionUID = 240L; + + // Used for the RuntimeException to short-circuit the traversal + private static final String FOUND = "found"; // $NON-NLS-1$ + + // N.B. The keys can be either JMeterTreeNode or TestElement + protected final Map data; + + /** + * Creates an empty new HashTree. + */ + public HashTree() { + this(null, null); + } + + /** + * Allow subclasses to provide their own Map. + * @param _map {@link Map} to use + */ + protected HashTree(Map _map) { + this(_map, null); + } + + /** + * Creates a new HashTree and adds the given object as a top-level node. + * + * @param key + * name of the new top-level node + */ + public HashTree(Object key) { + this(new LinkedHashMap(), key); + } + + /** + * Uses the new HashTree if not null and adds the given object as a + * top-level node if not null + * + * @param _map + * the map to be used. If null a new {@link LinkedHashMap} + * will be created + * @param key + * the object to be used as the key for the root node (may be + * null, in which case no root node will be created) + */ + private HashTree(Map _map, Object key) { + if(_map != null) { + data = _map; + } else { + data = new LinkedHashMap<>(); + } + if(key != null) { + data.put(key, new HashTree()); + } + } + + /** + * The Map given must also be a HashTree, otherwise an + * UnsupportedOperationException is thrown. If it is a HashTree, this is + * like calling the add(HashTree) method. + * + * @see #add(HashTree) + * @see Map#putAll(Map) + */ + @Override + public void putAll(Map map) { + if (map instanceof HashTree) { + this.add((HashTree) map); + } else { + throw new UnsupportedOperationException("can only putAll other HashTree objects"); + } + } + + /** + * Exists to satisfy the Map interface. + * + * @see Map#entrySet() + */ + @Override + public Set> entrySet() { + return data.entrySet(); + } + + /** + * Implemented as required by the Map interface, but is not very useful + * here. All 'values' in a HashTree are HashTree's themselves. + * + * @param value + * Object to be tested as a value. + * @return True if the HashTree contains the value, false otherwise. + * @see Map#containsValue(Object) + */ + @Override + public boolean containsValue(Object value) { + return data.containsValue(value); + } + + /** + * This is the same as calling HashTree.add(key,value). + * + * @param key + * to use + * @param value + * to store against key + * @see Map#put(Object, Object) + */ + @Override + public HashTree put(Object key, HashTree value) { + HashTree previous = data.get(key); + add(key, value); + return previous; + } + + /** + * Clears the HashTree of all contents. + * + * @see Map#clear() + */ + @Override + public void clear() { + data.clear(); + } + + /** + * Returns a collection of all the sub-trees of the current tree. + * + * @see Map#values() + */ + @Override + public Collection values() { + return data.values(); + } + + /** + * Adds a key as a node at the current level and then adds the given + * HashTree to that new node. + * + * @param key + * key to create in this tree + * @param subTree + * sub tree to add to the node created for the first argument. + */ + public void add(Object key, HashTree subTree) { + add(key).add(subTree); + } + + /** + * Adds all the nodes and branches of the given tree to this tree. Is like + * merging two trees. Duplicates are ignored. + * + * @param newTree the tree to be added + */ + public void add(HashTree newTree) { + for (Object item : newTree.list()) { + add(item).add(newTree.getTree(item)); + } + } + + /** + * Creates a new HashTree and adds all the objects in the given collection + * as top-level nodes in the tree. + * + * @param keys + * a collection of objects to be added to the created HashTree. + */ + public HashTree(Collection keys) { + data = new LinkedHashMap<>(); + for (Object o : keys) { + data.put(o, new HashTree()); + } + } + + /** + * Creates a new HashTree and adds all the objects in the given array as + * top-level nodes in the tree. + * + * @param keys + * array with names for the new top-level nodes + */ + public HashTree(Object[] keys) { + data = new LinkedHashMap<>(); + for (Object key : keys) { + data.put(key, new HashTree()); + } + } + + /** + * If the HashTree contains the given object as a key at the top level, then + * a true result is returned, otherwise false. + * + * @param o + * Object to be tested as a key. + * @return True if the HashTree contains the key, false otherwise. + * @see Map#containsKey(Object) + */ + @Override + public boolean containsKey(Object o) { + return data.containsKey(o); + } + + /** + * If the HashTree is empty, true is returned, false otherwise. + * + * @return True if HashTree is empty, false otherwise. + */ + @Override + public boolean isEmpty() { + return data.isEmpty(); + } + + /** + * Sets a key and it's value in the HashTree. It actually sets up a key, and + * then creates a node for the key and sets the value to the new node, as a + * key. Any previous nodes that existed under the given key are lost. + * + * @param key + * key to be set up + * @param value + * value to be set up as a key in the secondary node + */ + public void set(Object key, Object value) { + data.put(key, createNewTree(value)); + } + + /** + * Sets a key into the current tree and assigns it a HashTree as its + * subtree. Any previous entries under the given key are removed. + * + * @param key + * key to be set up + * @param t + * HashTree that the key maps to + */ + public void set(Object key, HashTree t) { + data.put(key, t); + } + + /** + * Sets a key and its values in the HashTree. It sets up a key in the + * current node, and then creates a node for that key, and sets all the + * values in the array as keys in the new node. Any keys previously held + * under the given key are lost. + * + * @param key + * Key to be set up + * @param values + * Array of objects to be added as keys in the secondary node + */ + public void set(Object key, Object[] values) { + data.put(key, createNewTree(Arrays.asList(values))); + } + + /** + * Sets a key and its values in the HashTree. It sets up a key in the + * current node, and then creates a node for that key, and set all the + * values in the array as keys in the new node. Any keys previously held + * under the given key are removed. + * + * @param key + * key to be set up + * @param values + * Collection of objects to be added as keys in the secondary + * node + */ + public void set(Object key, Collection values) { + data.put(key, createNewTree(values)); + } + + /** + * Sets a series of keys into the HashTree. It sets up the first object in + * the key array as a key in the current node, recurses into the next + * HashTree node through that key and adds the second object in the array. + * Continues recursing in this manner until the end of the first array is + * reached, at which point all the values of the second array are set as + * keys to the bottom-most node. All previous keys of that bottom-most node + * are removed. + * + * @param treePath + * array of keys to put into HashTree + * @param values + * array of values to be added as keys to bottom-most node + */ + public void set(Object[] treePath, Object[] values) { + if (treePath != null && values != null) { + set(Arrays.asList(treePath), Arrays.asList(values)); + } + } + + /** + * Sets a series of keys into the HashTree. It sets up the first object in + * the key array as a key in the current node, recurses into the next + * HashTree node through that key and adds the second object in the array. + * Continues recursing in this manner until the end of the first array is + * reached, at which point all the values of the Collection of values are + * set as keys to the bottom-most node. Any keys previously held by the + * bottom-most node are lost. + * + * @param treePath + * array of keys to put into HashTree + * @param values + * Collection of values to be added as keys to bottom-most node + */ + public void set(Object[] treePath, Collection values) { + if (treePath != null) { + set(Arrays.asList(treePath), values); + } + } + + /** + * Sets a series of keys into the HashTree. It sets up the first object in + * the key list as a key in the current node, recurses into the next + * HashTree node through that key and adds the second object in the list. + * Continues recursing in this manner until the end of the first list is + * reached, at which point all the values of the array of values are set as + * keys to the bottom-most node. Any previously existing keys of that bottom + * node are removed. + * + * @param treePath + * collection of keys to put into HashTree + * @param values + * array of values to be added as keys to bottom-most node + */ + public void set(Collection treePath, Object[] values) { + HashTree tree = addTreePath(treePath); + tree.set(Arrays.asList(values)); + } + + /** + * Sets the nodes of the current tree to be the objects of the given + * collection. Any nodes previously in the tree are removed. + * + * @param values + * Collection of objects to set as nodes. + */ + public void set(Collection values) { + clear(); + this.add(values); + } + + /** + * Sets a series of keys into the HashTree. It sets up the first object in + * the key list as a key in the current node, recurses into the next + * HashTree node through that key and adds the second object in the list. + * Continues recursing in this manner until the end of the first list is + * reached, at which point all the values of the Collection of values are + * set as keys to the bottom-most node. Any previously existing keys of that + * bottom node are lost. + * + * @param treePath + * list of keys to put into HashTree + * @param values + * collection of values to be added as keys to bottom-most node + */ + public void set(Collection treePath, Collection values) { + HashTree tree = addTreePath(treePath); + tree.set(values); + } + + /** + * Adds an key into the HashTree at the current level. If a HashTree exists + * for the key already, no new tree will be added + * + * @param key + * key to be added to HashTree + * @return newly generated tree, if no tree was found for the given key; + * existing key otherwise + */ + public HashTree add(Object key) { + if (!data.containsKey(key)) { + HashTree newTree = createNewTree(); + data.put(key, newTree); + return newTree; + } + return getTree(key); + } + + /** + * Adds all the given objects as nodes at the current level. + * + * @param keys + * Array of Keys to be added to HashTree. + */ + public void add(Object[] keys) { + for (Object key : keys) { + add(key); + } + } + + /** + * Adds a bunch of keys into the HashTree at the current level. + * + * @param keys + * Collection of Keys to be added to HashTree. + */ + public void add(Collection keys) { + for (Object o : keys) { + add(o); + } + } + + /** + * Adds a key and it's value in the HashTree. The first argument becomes a + * node at the current level, and the second argument becomes a node of it. + * + * @param key + * key to be added + * @param value + * value to be added as a key in the secondary node + * @return HashTree for which value is the key + */ + public HashTree add(Object key, Object value) { + return add(key).add(value); + } + + /** + * Adds a key and it's values in the HashTree. The first argument becomes a + * node at the current level, and adds all the values in the array to the + * new node. + * + * @param key + * key to be added + * @param values + * array of objects to be added as keys in the secondary node + */ + public void add(Object key, Object[] values) { + add(key).add(values); + } + + /** + * Adds a key as a node at the current level and then adds all the objects + * in the second argument as nodes of the new node. + * + * @param key + * key to be added + * @param values + * Collection of objects to be added as keys in the secondary + * node + */ + public void add(Object key, Collection values) { + add(key).add(values); + } + + /** + * Adds a series of nodes into the HashTree using the given path. The first + * argument is an array that represents a path to a specific node in the + * tree. If the path doesn't already exist, it is created (the objects are + * added along the way). At the path, all the objects in the second argument + * are added as nodes. + * + * @param treePath + * an array of objects representing a path + * @param values + * array of values to be added as keys to bottom-most node + */ + public void add(Object[] treePath, Object[] values) { + if (treePath != null) { + add(Arrays.asList(treePath), Arrays.asList(values)); + } + } + + /** + * Adds a series of nodes into the HashTree using the given path. The first + * argument is an array that represents a path to a specific node in the + * tree. If the path doesn't already exist, it is created (the objects are + * added along the way). At the path, all the objects in the second argument + * are added as nodes. + * + * @param treePath + * an array of objects representing a path + * @param values + * collection of values to be added as keys to bottom-most node + */ + public void add(Object[] treePath, Collection values) { + if (treePath != null) { + add(Arrays.asList(treePath), values); + } + } + + public HashTree add(Object[] treePath, Object value) { + return add(Arrays.asList(treePath), value); + } + + /** + * Adds a series of nodes into the HashTree using the given path. The first + * argument is a List that represents a path to a specific node in the tree. + * If the path doesn't already exist, it is created (the objects are added + * along the way). At the path, all the objects in the second argument are + * added as nodes. + * + * @param treePath + * a list of objects representing a path + * @param values + * array of values to be added as keys to bottom-most node + */ + public void add(Collection treePath, Object[] values) { + HashTree tree = addTreePath(treePath); + tree.add(Arrays.asList(values)); + } + + /** + * Adds a series of nodes into the HashTree using the given path. The first + * argument is a List that represents a path to a specific node in the tree. + * If the path doesn't already exist, it is created (the objects are added + * along the way). At the path, the object in the second argument is added + * as a node. + * + * @param treePath + * a list of objects representing a path + * @param value + * Object to add as a node to bottom-most node + * @return HashTree for which value is the key + */ + public HashTree add(Collection treePath, Object value) { + HashTree tree = addTreePath(treePath); + return tree.add(value); + } + + /** + * Adds a series of nodes into the HashTree using the given path. The first + * argument is a SortedSet that represents a path to a specific node in the + * tree. If the path doesn't already exist, it is created (the objects are + * added along the way). At the path, all the objects in the second argument + * are added as nodes. + * + * @param treePath + * a SortedSet of objects representing a path + * @param values + * Collection of values to be added as keys to bottom-most node + */ + public void add(Collection treePath, Collection values) { + HashTree tree = addTreePath(treePath); + tree.add(values); + } + + protected HashTree addTreePath(Collection treePath) { + HashTree tree = this; + for (Object temp : treePath) { + tree = tree.add(temp); + } + return tree; + } + + /** + * Gets the HashTree mapped to the given key. + * + * @param key + * Key used to find appropriate HashTree() + * @return the HashTree for key + */ + public HashTree getTree(Object key) { + return data.get(key); + } + + /** + * Returns the HashTree object associated with the given key. Same as + * calling {@link #getTree(Object)}. + * + * @see Map#get(Object) + */ + @Override + public HashTree get(Object key) { + return getTree(key); + } + + /** + * Gets the HashTree object mapped to the last key in the array by recursing + * through the HashTree structure one key at a time. + * + * @param treePath + * array of keys. + * @return HashTree at the end of the recursion. + */ + public HashTree getTree(Object[] treePath) { + if (treePath != null) { + return getTree(Arrays.asList(treePath)); + } + return this; + } + + /** + * Create a clone of this HashTree. This is not a deep clone (i.e., the + * contents of the tree are not cloned). + * + */ + @Override + public Object clone() { + HashTree newTree = new HashTree(); + cloneTree(newTree); + return newTree; + } + + protected void cloneTree(HashTree newTree) { + for (Object key : list()) { + newTree.set(key, (HashTree) getTree(key).clone()); + } + } + + /** + * Creates a new tree. This method exists to allow inheriting classes to + * generate the appropriate types of nodes. For instance, when a node is + * added, it's value is a HashTree. Rather than directly calling the + * HashTree() constructor, the createNewTree() method is called. Inheriting + * classes should override these methods and create the appropriate subclass + * of HashTree. + * + * @return HashTree + */ + protected HashTree createNewTree() { + return new HashTree(); + } + + /** + * Creates a new tree. This method exists to allow inheriting classes to + * generate the appropriate types of nodes. For instance, when a node is + * added, it's value is a HashTree. Rather than directly calling the + * HashTree() constructor, the createNewTree() method is called. Inheriting + * classes should override these methods and create the appropriate subclass + * of HashTree. + * + * @param key + * object to use as the key for the top level + * + * @return newly created {@link HashTree} + */ + protected HashTree createNewTree(Object key) { + return new HashTree(key); + } + + /** + * Creates a new tree. This method exists to allow inheriting classes to + * generate the appropriate types of nodes. For instance, when a node is + * added, it's value is a HashTree. Rather than directly calling the + * HashTree() constructor, the createNewTree() method is called. Inheriting + * classes should override these methods and create the appropriate subclass + * of HashTree. + * + * @param values objects to be added to the new {@link HashTree} + * + * @return newly created {@link HashTree} + */ + protected HashTree createNewTree(Collection values) { + return new HashTree(values); + } + + /** + * Gets the HashTree object mapped to the last key in the SortedSet by + * recursing through the HashTree structure one key at a time. + * + * @param treePath + * Collection of keys + * @return HashTree at the end of the recursion + */ + public HashTree getTree(Collection treePath) { + return getTreePath(treePath); + } + + /** + * Gets a Collection of all keys in the current HashTree node. If the + * HashTree represented a file system, this would be like getting a + * collection of all the files in the current folder. + * + * @return Set of all keys in this HashTree + */ + public Collection list() { + return data.keySet(); + } + + /** + * Gets a Set of all keys in the HashTree mapped to the given key of the + * current HashTree object (in other words, one level down. If the HashTree + * represented a file system, this would like getting a list of all files in + * a sub-directory (of the current directory) specified by the key argument. + * + * @param key + * key used to find HashTree to get list of + * @return Set of all keys in found HashTree. + */ + public Collection list(Object key) { + HashTree temp = data.get(key); + if (temp != null) { + return temp.list(); + } + return new HashSet<>(); + } + + /** + * Removes the entire branch specified by the given key. + * + * @see Map#remove(Object) + */ + @Override + public HashTree remove(Object key) { + return data.remove(key); + } + + /** + * Recurses down into the HashTree structure using each subsequent key in the + * array of keys, and returns the Set of keys of the HashTree object at the + * end of the recursion. If the HashTree represented a file system, this + * would be like getting a list of all the files in a directory specified by + * the treePath, relative from the current directory. + * + * @param treePath + * Array of keys used to recurse into HashTree structure + * @return Set of all keys found in end HashTree + */ + public Collection list(Object[] treePath) { + if (treePath != null) { + return list(Arrays.asList(treePath)); + } + return list(); + } + + /** + * Recurses down into the HashTree structure using each subsequent key in the + * List of keys, and returns the Set of keys of the HashTree object at the + * end of the recursion. If the HashTree represented a file system, this + * would be like getting a list of all the files in a directory specified by + * the treePath, relative from the current directory. + * + * @param treePath + * List of keys used to recurse into HashTree structure + * @return Set of all keys found in end HashTree + */ + public Collection list(Collection treePath) { + HashTree tree = getTreePath(treePath); + if (tree != null) { + return tree.list(); + } + return new HashSet<>(); + } + + /** + * Finds the given current key, and replaces it with the given new key. Any + * tree structure found under the original key is moved to the new key. + * + * @param currentKey name of the key to be replaced + * @param newKey name of the new key + */ + public void replaceKey(Object currentKey, Object newKey) { + HashTree tree = getTree(currentKey); + data.remove(currentKey); + data.put(newKey, tree); + } + + /** + * Gets an array of all keys in the current HashTree node. If the HashTree + * represented a file system, this would be like getting an array of all the + * files in the current folder. + * + * @return array of all keys in this HashTree. + */ + public Object[] getArray() { + return data.keySet().toArray(); + } + + /** + * Gets an array of all keys in the HashTree mapped to the given key of the + * current HashTree object (in other words, one level down). If the HashTree + * represented a file system, this would like getting a list of all files in + * a sub-directory (of the current directory) specified by the key argument. + * + * @param key + * key used to find HashTree to get list of + * @return array of all keys in found HashTree + */ + public Object[] getArray(Object key) { + HashTree t = getTree(key); + if (t != null) { + return t.getArray(); + } + return null; + } + + /** + * Recurses down into the HashTree structure using each subsequent key in the + * array of keys, and returns an array of keys of the HashTree object at the + * end of the recursion. If the HashTree represented a file system, this + * would be like getting a list of all the files in a directory specified by + * the treePath, relative from the current directory. + * + * @param treePath + * array of keys used to recurse into HashTree structure + * @return array of all keys found in end HashTree + */ + public Object[] getArray(Object[] treePath) { + if (treePath != null) { + return getArray(Arrays.asList(treePath)); + } + return getArray(); + } + + /** + * Recurses down into the HashTree structure using each subsequent key in the + * treePath argument, and returns an array of keys of the HashTree object at + * the end of the recursion. If the HashTree represented a file system, this + * would be like getting a list of all the files in a directory specified by + * the treePath, relative from the current directory. + * + * @param treePath + * list of keys used to recurse into HashTree structure + * @return array of all keys found in end HashTree + */ + public Object[] getArray(Collection treePath) { + HashTree tree = getTreePath(treePath); + return (tree != null) ? tree.getArray() : null; + } + + protected HashTree getTreePath(Collection treePath) { + HashTree tree = this; + for (Object aTreePath : treePath) { + tree = tree.getTree(aTreePath); + if (tree == null) { + return null; + } + } + return tree; + } + + /** + * Returns a hashcode for this HashTree. + * + * @see Object#hashCode() + */ + @Override + public int hashCode() { + return data.hashCode() * 7; + } + + /** + * Compares all objects in the tree and verifies that the two trees contain + * the same objects at the same tree levels. Returns true if they do, false + * otherwise. + * + * @param o + * Object to be compared against + * @see Object#equals(Object) + */ + @Override + public boolean equals(Object o) { + if (!(o instanceof HashTree)) { + return false; + } + HashTree oo = (HashTree) o; + if (oo.size() != this.size()) { + return false; + } + return data.equals(oo.data); + } + + /** + * Returns a Set of all the keys in the top-level of this HashTree. + * + * @see Map#keySet() + */ + @Override + public Set keySet() { + return data.keySet(); + } + + /** + * Searches the HashTree structure for the given key. If it finds the key, + * it returns the HashTree mapped to the key. If it finds nothing, it + * returns null. + * + * @param key + * Key to search for + * @return HashTree mapped to key, if found, otherwise null + */ + public HashTree search(Object key) { + HashTree result = getTree(key); + if (result != null) { + return result; + } + TreeSearcher searcher = new TreeSearcher(key); + try { + traverse(searcher); + } catch (RuntimeException e) { + if (!e.getMessage().equals(FOUND)){ + throw e; + } + // do nothing - means object is found + } + return searcher.getResult(); + } + + /** + * Method readObject. + * + * @param ois + * the stream to read the objects from + * @throws ClassNotFoundException + * when the class for the deserialization can not be found + * @throws IOException + * when I/O error occurs + */ + private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { + ois.defaultReadObject(); + } + + private void writeObject(ObjectOutputStream oos) throws IOException { + oos.defaultWriteObject(); + } + + /** + * Returns the number of top-level entries in the HashTree. + * + * @see Map#size() + */ + @Override + public int size() { + return data.size(); + } + + /** + * Allows any implementation of the HashTreeTraverser interface to easily + * traverse (depth-first) all the nodes of the HashTree. The Traverser + * implementation will be given notification of each node visited. + * + * @see HashTreeTraverser + * @param visitor + * the visitor that wants to traverse the tree + */ + public void traverse(HashTreeTraverser visitor) { + for (Object item : list()) { + visitor.addNode(item, getTree(item)); + getTree(item).traverseInto(visitor); + } + } + + /** + * The recursive method that accomplishes the tree-traversal and performs + * the callbacks to the HashTreeTraverser. + * + * @param visitor + * the {@link HashTreeTraverser} to be notified + */ + private void traverseInto(HashTreeTraverser visitor) { + if (list().isEmpty()) { + visitor.processPath(); + } else { + for (Object item : list()) { + final HashTree treeItem = getTree(item); + visitor.addNode(item, treeItem); + treeItem.traverseInto(visitor); + } + } + visitor.subtractNode(); + } + + /** + * Generate a printable representation of the tree. + * + * @return a representation of the tree + */ + @Override + public String toString() { + ConvertToString converter = new ConvertToString(); + try { + traverse(converter); + } catch (Exception e) { // Just in case + converter.reportError(e); + } + return converter.toString(); + } + + private static class TreeSearcher implements HashTreeTraverser { + + private final Object target; + + private HashTree result; + + public TreeSearcher(Object t) { + target = t; + } + + public HashTree getResult() { + return result; + } + + /** {@inheritDoc} */ + @Override + public void addNode(Object node, HashTree subTree) { + result = subTree.getTree(target); + if (result != null) { + // short circuit traversal when found + throw new RuntimeException(FOUND); + } + } + + /** {@inheritDoc} */ + @Override + public void processPath() { + // Not used + } + + /** {@inheritDoc} */ + @Override + public void subtractNode() { + // Not used + } + } + + private static class ConvertToString implements HashTreeTraverser { + private final StringBuilder string = new StringBuilder(getClass().getName() + "{"); + + private final StringBuilder spaces = new StringBuilder(); + + private int depth = 0; + + @Override + public void addNode(Object key, HashTree subTree) { + depth++; + string.append("\n").append(getSpaces()).append(key); + string.append(" {"); + } + + @Override + public void subtractNode() { + string.append("\n" + getSpaces() + "}"); + depth--; + } + + @Override + public void processPath() { + // NOOP + } + + @Override + public String toString() { + string.append("\n}"); + return string.toString(); + } + + void reportError(Throwable t){ + string.append("Error: ").append(t.toString()); + } + + private String getSpaces() { + if (spaces.length() < depth * 2) { + while (spaces.length() < depth * 2) { + spaces.append(" "); + } + } else if (spaces.length() > depth * 2) { + spaces.setLength(depth * 2); + } + return spaces.toString(); + } + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index c45cedf..70b2f6a 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -2,3 +2,30 @@ spring.application.name=node-controller logging.file.path=/opt/metersphere/logs/${spring.application.name} server.port=8082 +#kafka +spring.kafka.bootstrap-servers=${kafka.bootstrap-servers} +spring.kafka.consumer.group-id=metersphere_group_id +kafka.fields= +kafka.timestamp=yyyy-MM-dd'T'HH:mm:ss.SSSZZ +kafka.sample-filter= +kafka.test-mode=info +kafka.parse-all-req-headers=false +kafka.parse-all-res-headers=false +kafka.compression-type= +kafka.batch-size=16384 +kafka.client-id=JMeterKafkaBackendListener +kafka.connections-max-idle-ms=180000 +kafka.ssl.enabled=false +kafka.ssl.key-password= +kafka.ssl.keystore-location= +kafka.ssl.keystore-password= +kafka.ssl.truststore-location= +kafka.ssl.truststore-password= +kafka.ssl.enabled-protocols=TLSv1.2,TLSv1.1,TLSv1 +kafka.ssl.keystore-type=JKS +kafka.ssl.protocol=TLS +kafka.ssl.provider= +kafka.ssl.truststore-type= + +# jmeter +jmeter.home=/opt/jmeter diff --git a/src/main/resources/jmeter/bin/jmeter.properties b/src/main/resources/jmeter/bin/jmeter.properties new file mode 100644 index 0000000..38e815f --- /dev/null +++ b/src/main/resources/jmeter/bin/jmeter.properties @@ -0,0 +1,1346 @@ +################################################################################ +# Apache JMeter Property file +################################################################################ + +## Licensed to the Apache Software Foundation (ASF) under one or more +## contributor license agreements. See the NOTICE file distributed with +## this work for additional information regarding copyright ownership. +## The ASF licenses this file to You under the Apache License, Version 2.0 +## (the "License"); you may not use this file except in compliance with +## the License. You may obtain a copy of the License at +## +## http://www.apache.org/licenses/LICENSE-2.0 +## +## Unless required by applicable law or agreed to in writing, software +## distributed under the License is distributed on an "AS IS" BASIS, +## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +## See the License for the specific language governing permissions and +## limitations under the License. + +################################################################################ +# +# THIS FILE SHOULD NOT BE MODIFIED +# +# This avoids having to re-apply the modifications when upgrading JMeter +# Instead only user.properties should be modified: +# 1/ copy the property you want to modify to user.properties from jmeter.properties +# 2/ Change its value there +# +################################################################################ + +# JMeter properties are described in the file +# http://jmeter.apache.org/usermanual/properties_reference.html +# A local copy can be found in +# printable_docs/usermanual/properties_reference.html + +#Preferred GUI language. Comment out to use the JVM default locale's language. +#language=en + + +# Additional locale(s) to add to the displayed list. +# The current default list is: en, fr, de, no, es, tr, ja, zh_CN, zh_TW, pl, pt_BR +# [see JMeterMenuBar#makeLanguageMenu()] +# The entries are a comma-separated list of language names +#locales.add=zu + + +#--------------------------------------------------------------------------- +# XML Parser +#--------------------------------------------------------------------------- + +# Path to a Properties file containing Namespace mapping in the form +# prefix=Namespace +# Example: +# ns=http://biz.aol.com/schema/2006-12-18 +#xpath.namespace.config= + + +# XPath2 query cache for storing compiled XPath queries +#xpath2query.parser.cache.size=400 + +#--------------------------------------------------------------------------- +# SSL configuration +#--------------------------------------------------------------------------- + +## SSL System properties are now in system.properties + +# JMeter no longer converts javax.xxx property entries in this file into System properties. +# These must now be defined in the system.properties file or on the command-line. +# The system.properties file gives more flexibility. + +# By default, SSL session contexts are now created per-thread, rather than being shared. +# The original behaviour can be enabled by setting the JMeter property to true +#https.sessioncontext.shared=false + +# Be aware that https default protocol may vary depending on the version of JVM +# See https://blogs.oracle.com/java-platform-group/entry/diagnosing_tls_ssl_and_https +# See https://bz.apache.org/bugzilla/show_bug.cgi?id=58236 +# Default HTTPS protocol level: +#https.default.protocol=TLS +# This may need to be changed here (or in user.properties) to: +#https.default.protocol=SSLv3 + +# List of protocols to enable. You may have to select only a subset if you find issues with target server. +# This is needed when server does not support Socket version negotiation, this can lead to: +# javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated +# java.net.SocketException: Connection reset +# see https://bz.apache.org/bugzilla/show_bug.cgi?id=54759 +#https.socket.protocols=SSLv2Hello SSLv3 TLSv1 + +# Control if we allow reuse of cached SSL context between iterations +# set the value to 'false' to reset the SSL context each iteration +# Deprecated since 5.0 +#https.use.cached.ssl.context=true + +# +# Reset HTTP State when starting a new Thread Group iteration which means: +# true means next iteration is associated to a new user +# false means next iteration is associated to same user +# true involves: +# - Closing opened connection +# - resetting SSL State +#httpclient.reset_state_on_thread_group_iteration=true + +# Start and end index to be used with keystores with many entries +# The default is to use entry 0, i.e. the first +#https.keyStoreStartIndex=0 +#https.keyStoreEndIndex=0 + +#--------------------------------------------------------------------------- +# Look and Feel configuration +#--------------------------------------------------------------------------- + +#Classname of the Swing default UI +# +# The LAF classnames that are available are now displayed as ToolTip text +# when hovering over the Options/Look and Feel selection list. +# +# You can either use a full class name, as shown below, +# or one of the strings "System" or "CrossPlatform" which means +# JMeter will use the corresponding string returned by UIManager.getLookAndFeelClassName() + +# LAF can be overridden by os.name (lowercased, spaces replaced by '_') +# Sample os.name LAF: +#jmeter.laf.windows_xp=javax.swing.plaf.metal.MetalLookAndFeel + +# Failing that, the OS family = os.name, but only up to first space: +# Sample OS family LAF: +#jmeter.laf.windows=com.sun.java.swing.plaf.windows.WindowsLookAndFeel + +# Custom settings for Mac using System LAF if you don't want to use Darcula +#jmeter.laf.mac=System + +# Failing that, the JMeter default laf can be defined: +#jmeter.laf=System + +# If none of the above jmeter.laf properties are defined, JMeter uses the CrossPlatform LAF. +# This is because the CrossPlatform LAF generally looks better than the System LAF. +# See https://bz.apache.org/bugzilla/show_bug.cgi?id=52026 for details +# N.B. the laf can be defined in user.properties. + +# LoggerPanel display +# default to false +#jmeter.loggerpanel.display=false + +# Enable LogViewer Panel to receive log event even if closed +# Enabled since 2.12 +# Note this has some impact on performances, but as GUI mode must +# not be used for Load Test it is acceptable +#jmeter.loggerpanel.enable_when_closed=true + +# Max lines kept in LoggerPanel, default to 1000 chars +# 0 means no limit +#jmeter.loggerpanel.maxlength=1000 + +# Interval period in ms to process the queue of events of the listeners +#jmeter.gui.refresh_period=500 + +# HiDPI mode (default: false) +# Activate a 'pseudo'-hidpi mode. Allows to increase size of some UI elements +# which are not correctly managed by JVM with high resolution screens in Linux or Windows +#jmeter.hidpi.mode=false +# To enable pseudo-hidpi mode change to true +#jmeter.hidpi.mode=true +# HiDPI scale factor +#jmeter.hidpi.scale.factor=1.0 +# Suggested value for HiDPI +#jmeter.hidpi.scale.factor=2.0 + +# Toolbar display +# Toolbar icon definitions +#jmeter.toolbar.icons=org/apache/jmeter/images/toolbar/icons-toolbar.properties +# Toolbar list +#jmeter.toolbar=new,open,close,save,save_as_testplan,|,cut,copy,paste,|,expand,collapse,toggle,|,test_start,test_stop,test_shutdown,|,test_start_remote_all,test_stop_remote_all,test_shutdown_remote_all,|,test_clear,test_clear_all,|,search,search_reset,|,function_helper,help +# Toolbar icons default size: 22x22. Available sizes are: 22x22, 32x32, 48x48 +#jmeter.toolbar.icons.size=22x22 +# Suggested value for HiDPI +#jmeter.toolbar.icons.size=48x48 + +# Icon definitions +# default: +#jmeter.icons=org/apache/jmeter/images/icon.properties +# alternate: +#jmeter.icons=org/apache/jmeter/images/icon_1.properties +# Historical icon set (deprecated) +#jmeter.icons=org/apache/jmeter/images/icon_old.properties + +# Tree icons default size: 19x19. Available sizes are: 19x19, 24x24, 32x32, 48x48 +# Useful for HiDPI display (see below) +#jmeter.tree.icons.size=19x19 +# Suggested value for HiDPI screen like 3200x1800: +#jmeter.tree.icons.size=32x32 + +#Components to not display in JMeter GUI (GUI class name or static label) +# These elements are deprecated and will be removed in next version: +# MongoDB Script, MongoDB Source Config, Monitor Results +# BSF Elements +not_in_menu=org.apache.jmeter.protocol.mongodb.sampler.MongoScriptSampler,org.apache.jmeter.protocol.mongodb.config.MongoSourceElement,\ + org.apache.jmeter.timers.BSFTimer,org.apache.jmeter.modifiers.BSFPreProcessor,org.apache.jmeter.extractor.BSFPostProcessor,org.apache.jmeter.assertions.BSFAssertion,\ + org.apache.jmeter.visualizers.BSFListener,org.apache.jmeter.protocol.java.sampler.BSFSampler,\ + org.apache.jmeter.protocol.http.control.gui.SoapSamplerGui + +# Number of items in undo history +# Feature is disabled by default (0) due to known and not fixed bugs: +# https://bz.apache.org/bugzilla/show_bug.cgi?id=57043 +# https://bz.apache.org/bugzilla/show_bug.cgi?id=57039 +# https://bz.apache.org/bugzilla/show_bug.cgi?id=57040 +# Set it to a number > 0 (25 can be a good default) +# The bigger it is, the more it consumes memory +#undo.history.size=0 + +# Hotkeys to add JMeter components, will add elements when you press Ctrl+0 .. Ctrl+9 (Command+0 .. Command+9 on Mac) +gui.quick_0=ThreadGroupGui +gui.quick_1=HttpTestSampleGui +gui.quick_2=RegexExtractorGui +gui.quick_3=AssertionGui +gui.quick_4=ConstantTimerGui +gui.quick_5=TestActionGui +gui.quick_6=JSR223PostProcessor +gui.quick_7=JSR223PreProcessor +gui.quick_8=DebugSampler +gui.quick_9=ViewResultsFullVisualizer + + +#--------------------------------------------------------------------------- +# JMX Backup configuration +#--------------------------------------------------------------------------- +#Enable auto backups of the .jmx file when a test plan is saved. +#When enabled, before the .jmx is saved, it will be backed up to the directory pointed +#by the jmeter.gui.action.save.backup_directory property (see below). Backup file names are built +#after the jmx file being saved. For example, saving test-plan.jmx will create a test-plan-000012.jmx +#in the backup directory provided that the last created backup file is test-plan-000011.jmx. +#Default value is true indicating that auto backups are enabled +#jmeter.gui.action.save.backup_on_save=true + +#Set the backup directory path where JMX backups will be created upon save in the GUI. +#If not set (what it defaults to) then backup files will be created in +#a sub-directory of the JMeter base installation. The default directory is ${JMETER_HOME}/backups +#If set and the directory does not exist, it will be created. +#jmeter.gui.action.save.backup_directory= + +#Set the maximum time (in hours) that backup files should be preserved since the save time. +#By default no expiration time is set which means we keep backups for ever. +#jmeter.gui.action.save.keep_backup_max_hours=0 + +#Set the maximum number of backup files that should be preserved. By default 10 backups will be preserved. +#Setting this to zero will cause the backups to not being deleted (unless keep_backup_max_hours is set to a non zero value) +#jmeter.gui.action.save.keep_backup_max_count=10 + +#Enable auto saving of the .jmx file before start run a test plan +#When enabled, before the run, the .jmx will be saved and also backed up to the directory pointed +#save_automatically_before_run=true + +#--------------------------------------------------------------------------- +# Remote hosts and RMI configuration +#--------------------------------------------------------------------------- + +# Remote Hosts - comma delimited +remote_hosts=127.0.0.1 +#remote_hosts=localhost:1099,localhost:2010 + +# RMI port to be used by the server (must start rmiregistry with same port) +#server_port=1099 + +# To change the port to (say) 1234: +# On the server(s) +# - set server_port=1234 +# - start rmiregistry with port 1234 +# On Windows this can be done by: +# SET SERVER_PORT=1234 +# JMETER-SERVER +# +# On Unix: +# SERVER_PORT=1234 jmeter-server +# +# On the client: +# - set remote_hosts=server:1234 + +# Parameter that controls the RMI port used by RemoteSampleListenerImpl (The Controller) +# Default value is 0 which means port is randomly assigned +# You may need to open Firewall port on the Controller machine +#client.rmi.localport=0 + +# When distributed test is starting, there may be several attempts to initialize +# remote engines. By default, only single try is made. Increase following property +# to make it retry for additional times +#client.tries=1 + +# If there is initialization retries, following property sets delay between attempts +#client.retries_delay=5000 + +# When all initialization tries was made, test will fail if some remote engines are failed +# Set following property to true to ignore failed nodes and proceed with test +#client.continue_on_fail=false + +# To change the default port (1099) used to access the server: +#server.rmi.port=1234 + +# To use a specific port for the JMeter server engine, define +# the following property before starting the server: +#server.rmi.localport=4000 + +# The jmeter server creates by default the RMI registry as part of the server process. +# To stop the server creating the RMI registry: +#server.rmi.create=false + +# Define the following property to cause JMeter to exit after the first test +#server.exitaftertest=true + +# +# Configuration of Secure RMI connection +# +# Type of keystore : JKS +#server.rmi.ssl.keystore.type=JKS +# +# Keystore file that contains private key +#server.rmi.ssl.keystore.file=rmi_keystore.jks +# +# Password of Keystore +#server.rmi.ssl.keystore.password=changeit +# +# Key alias +#server.rmi.ssl.keystore.alias=rmi +# +# Type of truststore : JKS +#server.rmi.ssl.truststore.type=JKS +# +# Keystore file that contains certificate +#server.rmi.ssl.truststore.file=rmi_keystore.jks +# +# Password of Trust store +#server.rmi.ssl.truststore.password=changeit +# +# Set this if you don't want to use SSL for RMI +#server.rmi.ssl.disable=false +#--------------------------------------------------------------------------- +# Include Controller +#--------------------------------------------------------------------------- + +# Prefix used by IncludeController when building file name +#includecontroller.prefix= + +#--------------------------------------------------------------------------- +# Shared HTTP configuration between HC4 and Java Implementations +#--------------------------------------------------------------------------- + +# +# Should JMeter add to POST request content-type header if missing: +# Content-Type: application/x-www-form-urlencoded +# Was true before version 5.0 +#post_add_content_type_if_missing=false + +#--------------------------------------------------------------------------- +# HTTP Java configuration +#--------------------------------------------------------------------------- + +# Number of connection retries performed by HTTP Java sampler before giving up +# 0 means no retry since version 3.0 +#http.java.sampler.retries=0 + +#--------------------------------------------------------------------------- +# Following properties apply to Apache HttpClient +#--------------------------------------------------------------------------- + +# set the socket timeout (or use the parameter http.socket.timeout) +# for AJP Sampler implementation. +# Value is in milliseconds +#httpclient.timeout=0 +# 0 == no timeout + +# Set the http version (defaults to 1.1) +#httpclient.version=1.1 (or use the parameter http.protocol.version) + +# Define characters per second > 0 to emulate slow connections +#httpclient.socket.http.cps=0 +#httpclient.socket.https.cps=0 + +#Enable loopback protocol +#httpclient.loopback=true + +# Define the local host address to be used for multi-homed hosts +#httpclient.localaddress=1.2.3.4 + +#--------------------------------------------------------------------------- +# AuthManager Kerberos configuration +#--------------------------------------------------------------------------- + +# AuthManager Kerberos configuration +# Name of application module used in jaas.conf +#kerberos_jaas_application=JMeter + +# Should ports be stripped from urls before constructing SPNs +# for SPNEGO authentication +#kerberos.spnego.strip_port=true + +# Should credentials be delegated to webservers when using +# SPNEGO authentication +#kerberos.spnego.delegate_cred=false + +#--------------------------------------------------------------------------- +# Apache HttpComponents HTTPClient configuration (HTTPClient4) +#--------------------------------------------------------------------------- + +# define a properties file for overriding Apache HttpClient parameters +# Uncomment this line if you put anything in hc.parameters file +#hc.parameters.file=hc.parameters + +# If true, default HC4 User-Agent will not be added +#httpclient4.default_user_agent_disabled=false + +# Preemptively send Authorization Header when BASIC auth is used +#httpclient4.auth.preemptive=true + +# Number of retries to attempt (default 0) +#httpclient4.retrycount=0 + +# true if it's OK to retry requests that have been sent +# This will retry Idempotent and non Idempotent requests +# This should usually be false, but it can be useful +# when testing against some Load Balancers like Amazon ELB +#httpclient4.request_sent_retry_enabled=false + +# Idle connection timeout (Milliseconds) to apply if the server does not send +# Keep-Alive headers (default 0) +# Set this > 0 to compensate for servers that don't send a Keep-Alive header +# If <= 0, idle timeout will only apply if the server sends a Keep-Alive header +#httpclient4.idletimeout=0 + +# Check connections if the elapsed time (Milliseconds) since the last +# use of the connection exceed this value +#httpclient4.validate_after_inactivity=1700 + +# TTL (in Milliseconds) represents an absolute value. +# No matter what, the connection will not be re-used beyond its TTL. +#httpclient4.time_to_live=2000 + +# Max size in bytes of PUT body to retain in result sampler. +# Bigger results will be clipped. +#httpclient4.max_body_retain_size=32768 + +# Ignore EOFException that some edgy application may emit to signal end of GZIP stream +# Defaults to false +#httpclient4.gzip_relax_mode=false + +# Ignore EOFException that some edgy application may emit to signal end of Deflated stream +# Defaults to false +#httpclient4.deflate_relax_mode=false + +#--------------------------------------------------------------------------- +# HTTP Cache Manager configuration +#--------------------------------------------------------------------------- +# +# Space or comma separated list of methods that can be cached +#cacheable_methods=GET +# N.B. This property is currently a temporary solution for Bug 56162 + +# Since 2.12, JMeter does not create anymore a Sample Result with 204 response +# code for a resource found in cache which is inline with what browser do. +#cache_manager.cached_resource_mode=RETURN_NO_SAMPLE + +# You can choose between 3 modes: +# RETURN_NO_SAMPLE (default) +# RETURN_200_CACHE +# RETURN_CUSTOM_STATUS + +# Those mode have the following behaviours: +# RETURN_NO_SAMPLE: +# this mode returns no Sample Result, it has no additional configuration + +# RETURN_200_CACHE: +# this mode will return Sample Result with response code to 200 and +# response message to "(ex cache)", you can modify response message by setting +# RETURN_200_CACHE.message=(ex cache) + +# RETURN_CUSTOM_STATUS: +# This mode lets you select what response code and message you want to return, +# if you use this mode you need to set those properties +# RETURN_CUSTOM_STATUS.code= +# RETURN_CUSTOM_STATUS.message= + +#--------------------------------------------------------------------------- +# Results file configuration +#--------------------------------------------------------------------------- + +# This section helps determine how result data will be saved. +# The commented out values are the defaults. + +# legitimate values: xml, csv, db. Only xml and csv are currently supported. +#jmeter.save.saveservice.output_format=csv + +# The below properties are true when field should be saved; false otherwise +# +# assertion_results_failure_message only affects CSV output +#jmeter.save.saveservice.assertion_results_failure_message=true +# +# legitimate values: none, first, all +#jmeter.save.saveservice.assertion_results=none +# +#jmeter.save.saveservice.data_type=true +#jmeter.save.saveservice.label=true +#jmeter.save.saveservice.response_code=true +# response_data is not currently supported for CSV output +#jmeter.save.saveservice.response_data=false +# Save ResponseData for failed samples +#jmeter.save.saveservice.response_data.on_error=false +#jmeter.save.saveservice.response_message=true +#jmeter.save.saveservice.successful=true +#jmeter.save.saveservice.thread_name=true +#jmeter.save.saveservice.time=true +#jmeter.save.saveservice.subresults=true +#jmeter.save.saveservice.assertions=true +#jmeter.save.saveservice.latency=true +# Only available with HttpClient4 +#jmeter.save.saveservice.connect_time=true +#jmeter.save.saveservice.samplerData=false +#jmeter.save.saveservice.responseHeaders=false +#jmeter.save.saveservice.requestHeaders=false +#jmeter.save.saveservice.encoding=false +#jmeter.save.saveservice.bytes=true +# Only available with HttpClient4 +#jmeter.save.saveservice.sent_bytes=true +#jmeter.save.saveservice.url=true +#jmeter.save.saveservice.filename=false +#jmeter.save.saveservice.hostname=false +#jmeter.save.saveservice.thread_counts=true +#jmeter.save.saveservice.sample_count=false +#jmeter.save.saveservice.idle_time=true + +# Timestamp format - this only affects CSV output files +# legitimate values: none, ms, or a format suitable for SimpleDateFormat +#jmeter.save.saveservice.timestamp_format=ms +#jmeter.save.saveservice.timestamp_format=yyyy/MM/dd HH:mm:ss.SSS + +# For use with Comma-separated value (CSV) files or other formats +# where the fields' values are separated by specified delimiters. +# Default: +#jmeter.save.saveservice.default_delimiter=, +# For TAB, one can use: +#jmeter.save.saveservice.default_delimiter=\t + +# Only applies to CSV format files: +# Print field names as first line in CSV +#jmeter.save.saveservice.print_field_names=true + +# Optional list of JMeter variable names whose values are to be saved in the result data files. +# Use commas to separate the names. For example: +#sample_variables=SESSION_ID,REFERENCE +# N.B. The current implementation saves the values in XML as attributes, +# so the names must be valid XML names. +# By default JMeter sends the variable to all servers +# to ensure that the correct data is available at the client. + +# Optional xml processing instruction for line 2 of the file: +# Example: +#jmeter.save.saveservice.xml_pi= +# Default value: +#jmeter.save.saveservice.xml_pi= + +# Prefix used to identify filenames that are relative to the current base +#jmeter.save.saveservice.base_prefix=~/ + +# AutoFlush on each line written in XML or CSV output +# Setting this to true will result in less test results data loss in case of Crash +# but with impact on performances, particularly for intensive tests (low or no pauses) +# Since JMeter 2.10, this is false by default +#jmeter.save.saveservice.autoflush=false + +#--------------------------------------------------------------------------- +# Settings that affect SampleResults +#--------------------------------------------------------------------------- + +# Save the start time stamp instead of the end +# This also affects the timestamp stored in result files +sampleresult.timestamp.start=true + +# Whether to use System.nanoTime() - otherwise only use System.currentTimeMillis() +#sampleresult.useNanoTime=true + +# Use a background thread to calculate the nanoTime offset +# Set this to <= 0 to disable the background thread +#sampleresult.nanoThreadSleep=5000 + +# Since version 5.0 JMeter has a new SubResult Naming Policy which numbers subresults by default +# This property if set to true discards renaming policy. This can be required if you're using JMeter for functional testing. +# Defaults to: false +#subresults.disable_renaming=false + +#--------------------------------------------------------------------------- +# Upgrade property +#--------------------------------------------------------------------------- + +# File that holds a record of name changes for backward compatibility issues +upgrade_properties=/bin/upgrade.properties + +#--------------------------------------------------------------------------- +# JMeter Test Script recorder configuration +# +# N.B. The element was originally called the Proxy recorder, which is why the +# properties have the prefix "proxy". +#--------------------------------------------------------------------------- + +# If the recorder detects a gap of at least 5s (default) between HTTP requests, +# it assumes that the user has clicked a new URL +#proxy.pause=5000 + +# Add numeric suffix to Sampler names (default true) +#proxy.number.requests=true + +# List of URL patterns that will be added to URL Patterns to exclude +# Separate multiple lines with ; +#proxy.excludes.suggested=.*\\.(bmp|css|js|gif|ico|jpe?g|png|swf|woff|woff2) + +# Change the default HTTP Sampler (currently HttpClient4) +# Java: +#jmeter.httpsampler=HTTPSampler +#or +#jmeter.httpsampler=Java +# +# HttpClient4.x +#jmeter.httpsampler=HttpClient4 + +# By default JMeter tries to be more lenient with RFC2616 redirects and allows +# relative paths. +# If you want to test strict conformance, set this value to true +# When the property is true, JMeter follows http://tools.ietf.org/html/rfc3986#section-5.2 +#jmeter.httpclient.strict_rfc2616=false + +# Default content-type include filter to use +#proxy.content_type_include=text/html|text/plain|text/xml +# Default content-type exclude filter to use +#proxy.content_type_exclude=image/.*|text/css|application/.* + +# Default headers to remove from Header Manager elements +# (Cookie and Authorization are always removed) +#proxy.headers.remove=If-Modified-Since,If-None-Match,Host + +# Binary content-type handling +# These content-types will be handled by saving the request in a file: +#proxy.binary.types=application/x-amf,application/x-java-serialized-object,binary/octet-stream +# The files will be saved in this directory: +#proxy.binary.directory=user.dir +# The files will be created with this file filesuffix: +#proxy.binary.filesuffix=.binary + +#--------------------------------------------------------------------------- +# Test Script Recorder certificate configuration +#--------------------------------------------------------------------------- + +#proxy.cert.directory= +#proxy.cert.file=proxyserver.jks +#proxy.cert.type=JKS +#proxy.cert.keystorepass=password +#proxy.cert.keypassword=password +#proxy.cert.factory=SunX509 +# define this property if you wish to use your own keystore +#proxy.cert.alias= +# The default validity for certificates created by JMeter +#proxy.cert.validity=7 +# Use dynamic key generation (if supported by JMeter/JVM) +# If false, will revert to using a single key with no certificate +#proxy.cert.dynamic_keys=true + +#--------------------------------------------------------------------------- +# Test Script Recorder miscellaneous configuration +#--------------------------------------------------------------------------- + +# Whether to attempt disabling of samples that resulted from redirects +# where the generated samples use auto-redirection +#proxy.redirect.disabling=true + +# SSL configuration +#proxy.ssl.protocol=TLS + +#--------------------------------------------------------------------------- +# JMeter Proxy configuration +#--------------------------------------------------------------------------- +# use command-line flags for user-name and password +#http.proxyDomain=NTLM domain, if required by HTTPClient sampler + +#--------------------------------------------------------------------------- +# HTTPSampleResponse Parser configuration +#--------------------------------------------------------------------------- + +# Space-separated list of parser groups +HTTPResponse.parsers=htmlParser wmlParser cssParser +# for each parser, there should be a parser.types and a parser.className property + +# CSS Parser based on ph-css +cssParser.className=org.apache.jmeter.protocol.http.parser.CssParser +cssParser.types=text/css + +# CSS parser LRU cache size +# This cache stores the URLs found in a CSS to avoid continuously parsing the CSS +# By default the cache size is 400 +# It can be disabled by setting its value to 0 +#css.parser.cache.size=400 + +# Let the CSS Parser ignore all css errors +#css.parser.ignore_all_css_errors=true + +#--------------------------------------------------------------------------- +# HTML Parser configuration +#--------------------------------------------------------------------------- + +# Define the HTML parser to be used. +# Default parser: +# This new parser (since 2.10) should perform better than all others +# see https://bz.apache.org/bugzilla/show_bug.cgi?id=55632 +# Do not comment this property +htmlParser.className=org.apache.jmeter.protocol.http.parser.LagartoBasedHtmlParser + +# Other parsers: +# Default parser before 2.10 +#htmlParser.className=org.apache.jmeter.protocol.http.parser.JTidyHTMLParser +# Note that Regexp extractor may detect references that have been commented out. +# In many cases it will work OK, but you should be aware that it may generate +# additional references. +#htmlParser.className=org.apache.jmeter.protocol.http.parser.RegexpHTMLParser +# This parser is based on JSoup, it should be the most accurate but less +# performant than LagartoBasedHtmlParser +#htmlParser.className=org.apache.jmeter.protocol.http.parser.JsoupBasedHtmlParser + +#Used by HTTPSamplerBase to associate htmlParser with content types below +htmlParser.types=text/html application/xhtml+xml application/xml text/xml + +#--------------------------------------------------------------------------- +# WML Parser configuration +#--------------------------------------------------------------------------- + +wmlParser.className=org.apache.jmeter.protocol.http.parser.RegexpHTMLParser + +#Used by HTTPSamplerBase to associate wmlParser with content types below +wmlParser.types=text/vnd.wap.wml + +#--------------------------------------------------------------------------- +# Remote batching configuration +#--------------------------------------------------------------------------- +# How is Sample sender implementations configured: +# - true (default) means client configuration will be used +# - false means server configuration will be used +#sample_sender_client_configured=true + +# By default when Stripping modes are used JMeter since 3.1 will strip +# response even for SampleResults in error. +# If you want to revert to previous behaviour (no stripping of Responses in error) +# set this property to false +#sample_sender_strip_also_on_error=true + +# Remote batching support +# Since JMeter 2.9, default is MODE_STRIPPED_BATCH, which returns samples in +# batch mode (every 100 samples or every minute by default) +# Note also that MODE_STRIPPED_BATCH strips response data from SampleResult, so if you need it change to +# another mode +# Batch returns samples in batches +# Statistical returns sample summary statistics +# mode can also be the class name of an implementation of org.apache.jmeter.samplers.SampleSender +#mode=Standard +#mode=Batch +#mode=Statistical +#Set to true to key statistical samples on threadName rather than threadGroup +#key_on_threadname=false +#mode=Stripped +#mode=StrippedBatch +#mode=org.example.load.MySampleSender +# +#num_sample_threshold=100 +# Value is in milliseconds +#time_threshold=60000 +# +# Asynchronous sender; uses a queue and background worker process to return the samples +#mode=Asynch +# default queue size +#asynch.batch.queue.size=100 +# Same as Asynch but strips response data from SampleResult +#mode=StrippedAsynch +# +# DiskStore: Serialises the samples to disk, rather than saving in memory +#mode=DiskStore +# Same as DiskStore but strips response data from SampleResult +#mode=StrippedDiskStore +# Note: the mode is currently resolved on the client; +# other properties (e.g. time_threshold) are resolved on the server. + +#--------------------------------------------------------------------------- +# JDBC Request configuration +#--------------------------------------------------------------------------- + +# String used to indicate a null value +#jdbcsampler.nullmarker=]NULL[ +# +# Max size of BLOBs and CLOBs to store in JDBC sampler. Result will be cut off +#jdbcsampler.max_retain_result_size=65536 + +# Database validation query +# based in https://stackoverflow.com/questions/10684244/dbcp-validationquery-for-different-databases list +jdbc.config.check.query=select 1 from INFORMATION_SCHEMA.SYSTEM_USERS|select 1 from dual|select 1 from sysibm.sysdummy1|select 1|select 1 from rdb$database +jdbc.config.jdbc.driver.class=com.mysql.jdbc.Driver|org.postgresql.Driver|oracle.jdbc.OracleDriver|com.ingres.jdbc.IngresDriver|com.microsoft.sqlserver.jdbc.SQLServerDriver|com.microsoft.jdbc.sqlserver.SQLServerDriver|org.apache.derby.jdbc.ClientDriver|org.hsqldb.jdbc.JDBCDriver|com.ibm.db2.jcc.DB2Driver|org.apache.derby.jdbc.ClientDriver|org.h2.Driver|org.firebirdsql.jdbc.FBDriver|org.mariadb.jdbc.Driver|org.sqlite.JDBC|net.sourceforge.jtds.jdbc.Driver|com.exasol.jdbc.EXADriver + +#--------------------------------------------------------------------------- +# OS Process Sampler configuration +#--------------------------------------------------------------------------- +# Polling to see if process has finished its work, used when a timeout is configured on sampler +#os_sampler.poll_for_timeout=100 + +#--------------------------------------------------------------------------- +# TCP Sampler configuration +#--------------------------------------------------------------------------- + +# The default handler class +#tcp.handler=TCPClientImpl +# +# eolByte = byte value for end of line +# set this to a value outside the range -128 to +127 to skip eol checking +#tcp.eolByte=1000 +# +# TCP Charset, used by org.apache.jmeter.protocol.tcp.sampler.TCPClientImpl +# default to Platform defaults charset as returned by Charset.defaultCharset().name() +#tcp.charset= +# +# status.prefix and suffix = strings that enclose the status response code +#tcp.status.prefix=Status= +#tcp.status.suffix=. +# +# status.properties = property file to convert codes to messages +#tcp.status.properties=mytestfiles/tcpstatus.properties + +# The length prefix used by LengthPrefixedBinaryTCPClientImpl implementation +# defaults to 2 bytes. +#tcp.binarylength.prefix.length=2 + +#--------------------------------------------------------------------------- +# Summariser - Generate Summary Results - configuration (mainly applies to non-GUI mode) +#--------------------------------------------------------------------------- +# +# Comment the following property to disable the default non-GUI summariser +# [or change the value to rename it] +# (applies to non-GUI mode only) +summariser.name=summary +# +# interval between summaries (in seconds) default 30 seconds +#summariser.interval=30 +# +# Write messages to log file +#summariser.log=true +# +# Write messages to System.out +#summariser.out=true + +# Ignore SampleResults generated by TransactionControllers +# defaults to true +#summariser.ignore_transaction_controller_sample_result=true + + +#--------------------------------------------------------------------------- +# Aggregate Report and Aggregate Graph - configuration +#--------------------------------------------------------------------------- +# +# Percentiles to display in reports +# Can be float value between 0 and 100 +# First percentile to display, defaults to 90% +#aggregate_rpt_pct1=90 +# Second percentile to display, defaults to 95% +#aggregate_rpt_pct2=95 +# Second percentile to display, defaults to 99% +#aggregate_rpt_pct3=99 + +#--------------------------------------------------------------------------- +# BackendListener - configuration +#--------------------------------------------------------------------------- +# +# Backend metrics window mode (fixed=fixed-size window, timed=time boxed) +#backend_metrics_window_mode=fixed +# Backend metrics sliding window size for Percentiles, Min, Max +#backend_metrics_window=100 + +# Backend metrics sliding window size for Percentiles, Min, Max +# when backend_metrics_window_mode is timed +# Setting this value too high can lead to OOM +#backend_metrics_large_window=5000 + +######################## +# Graphite Backend +######################## +# Send interval in second +# Defaults to 1 second +#backend_graphite.send_interval=1 + +######################## +# Influx Backend +######################## + +# Send interval in second +# Defaults to 5 seconds +#backend_influxdb.send_interval=5 +#Influxdb timeouts +#backend_influxdb.connection_timeout=1000 +#backend_influxdb.socket_timeout=3000 +#backend_influxdb.connection_request_timeout=100 + +#--------------------------------------------------------------------------- +# BeanShell configuration +#--------------------------------------------------------------------------- + +# BeanShell Server properties +# +# Define the port number as non-zero to start the http server on that port +#beanshell.server.port=9000 +# The telnet server will be started on the next port + +# +# Define the server initialisation file +beanshell.server.file=../extras/startup.bsh + +# +# Define a file to be processed at startup +# This is processed using its own interpreter. +#beanshell.init.file= + +# +# Define the intialisation files for BeanShell Sampler, Function and other BeanShell elements +# N.B. Beanshell test elements do not share interpreters. +# Each element in each thread has its own interpreter. +# This is retained between samples. +#beanshell.sampler.init=BeanShellSampler.bshrc +#beanshell.function.init=BeanShellFunction.bshrc +#beanshell.assertion.init=BeanShellAssertion.bshrc +#beanshell.listener.init=etc +#beanshell.postprocessor.init=etc +#beanshell.preprocessor.init=etc +#beanshell.timer.init=etc + +# The file BeanShellListeners.bshrc contains sample definitions +# of Test and Thread Listeners. + +#--------------------------------------------------------------------------- +# JSR-223 function +#--------------------------------------------------------------------------- + +# Path to JSR-223 file containing script to call on JMeter startup +# JMeter will try to guess the engine to use by the extension +# of the name of the file. +# This script can use pre-defined variables: +# log : Logger to log any message +# props : JMeter Property +# OUT : System.OUT +#jsr223.init.file= + +#--------------------------------------------------------------------------- +# Groovy function +#--------------------------------------------------------------------------- + +#Path to Groovy file containing utility functions to make available to __groovy function +#groovy.utilities= + +# Example +#groovy.utilities=jmeter.bin/utility.groovy + +#--------------------------------------------------------------------------- +# MailerModel configuration +#--------------------------------------------------------------------------- + +# Number of successful samples before a message is sent +#mailer.successlimit=2 +# +# Number of failed samples before a message is sent +#mailer.failurelimit=2 + +#--------------------------------------------------------------------------- +# CSVRead configuration +#--------------------------------------------------------------------------- + +# CSVRead delimiter setting (default ",") +# Make sure that there are no trailing spaces or tabs after the delimiter +# characters, or these will be included in the list of valid delimiters +#csvread.delimiter=, +#csvread.delimiter=; +#csvread.delimiter=! +#csvread.delimiter=~ +# The following line has a tab after the = +#csvread.delimiter= + +#--------------------------------------------------------------------------- +# __time() function configuration +# +# The properties below can be used to redefine the default formats +#--------------------------------------------------------------------------- +#time.YMD=yyyyMMdd +#time.HMS=HHmmss +#time.YMDHMS=yyyyMMdd-HHmmss +#time.USER1= +#time.USER2= + +#--------------------------------------------------------------------------- +# CSV DataSet configuration +#--------------------------------------------------------------------------- + +# String to return at EOF (if recycle not used) +#csvdataset.eofstring= +#list in https://docs.oracle.com/javase/8/docs/technotes/guides/intl/encoding.doc.html +csvdataset.file.encoding_list=UTF-8|UTF-16|ISO-8859-15|US-ASCII + + +#--------------------------------------------------------------------------- +# LDAP Sampler configuration +#--------------------------------------------------------------------------- +# Maximum number of search results returned by a search that will be sorted +# to guarantee a stable ordering (if more results then this limit are returned +# then no sorting is done). Set to 0 to turn off all sorting, in which case +# "Equals" response assertions will be very likely to fail against search results. +# +#ldapsampler.max_sorted_results=1000 + +# Number of characters to log for each of three sections (starting matching section, diff section, +# ending matching section where not all sections will appear for all diffs) diff display when an Equals +# assertion fails. So a value of 100 means a maximum of 300 characters of diff text will be displayed +# (+ a number of extra characters like "..." and "[[["/"]]]" which are used to decorate it). +#assertion.equals_section_diff_len=100 +# test written out to log to signify start/end of diff delta +#assertion.equals_diff_delta_start=[[[ +#assertion.equals_diff_delta_end=]]] + +#--------------------------------------------------------------------------- +# Miscellaneous configuration +#--------------------------------------------------------------------------- + +# Size of cache used by CSS Selector Extractor (for JODD implementation only) +# to store parsed CSS Selector expressions. +#cssselector.parser.cache.size=400 + + +# Used to control what happens when you start a test and +# have listeners that could overwrite existing result files +# Possible values: +# ASK : Ask user +# APPEND : Append results to existing file +# DELETE : Delete existing file and start a new file +#resultcollector.action_if_file_exists=ASK + +# If defined, then start the mirror server on the port +#mirror.server.port=8081 + +# ORO PatternCacheLRU size +#oro.patterncache.size=1000 + +#TestBeanGui +# +#propertyEditorSearchPath=null + +# Turn expert mode on/off: expert mode will show expert-mode beans and properties +#jmeter.expertMode=true + +# Max size of bytes stored in memory per SampleResult +# Ensure you don't exceed max capacity of a Java Array and remember +# that the higher it is, the higher JMeter will consume heap +# Defaults to 0, which means no truncation +#httpsampler.max_bytes_to_store_per_request=0 + +# Max size of buffer in bytes used when reading responses +# Defaults to 64k +#httpsampler.max_buffer_size=66560 + +# Maximum redirects to follow in a single sequence (default 20) +#httpsampler.max_redirects=20 +# Maximum frame/iframe nesting depth (default 5) +#httpsampler.max_frame_depth=5 + +# Revert to BUG 51939 behaviour (no separate container for embedded resources) by setting the following false: +#httpsampler.separate.container=true + +# If embedded resources download fails due to missing resources or other reasons, if this property is true +# Parent sample will not be marked as failed +#httpsampler.ignore_failed_embedded_resources=false + +#keep alive time for the parallel download threads (in seconds) +#httpsampler.parallel_download_thread_keepalive_inseconds=60 + +# Don't keep the embedded resources response data : just keep the size and the md5 +# default to false +#httpsampler.embedded_resources_use_md5=false + +# List of extra HTTP methods that should be available in select box +#httpsampler.user_defined_methods=VERSION-CONTROL,REPORT,CHECKOUT,CHECKIN,UNCHECKOUT,MKWORKSPACE,UPDATE,LABEL,MERGE,BASELINE-CONTROL,MKACTIVITY + +# The encoding to be used if none is provided (default ISO-8859-1) +sampleresult.default.encoding=UTF-8 + +# CookieManager behaviour - should cookies with null/empty values be deleted? +# Default is true. Use false to revert to original behaviour +#CookieManager.delete_null_cookies=true + +# CookieManager behaviour - should variable cookies be allowed? +# Default is true. Use false to revert to original behaviour +#CookieManager.allow_variable_cookies=true + +# CookieManager behaviour - should Cookies be stored as variables? +# Default is false +#CookieManager.save.cookies=false + +# CookieManager behaviour - prefix to add to cookie name before storing it as a variable +# Default is COOKIE_; to remove the prefix, define it as one or more spaces +#CookieManager.name.prefix= + +# CookieManager behaviour - check received cookies are valid before storing them? +# Default is true. Use false to revert to previous behaviour +#CookieManager.check.cookies=true + +# Netscape HTTP Cookie file +cookies=cookies + +# Ability to switch to Nashorn as default Javascript Engine used by MsIfController and __javaScript function +# JMeter works as following: +# - JDK >= 8 and javascript.use_rhino=false or not set : Nashorn +# - JDK >= 8 and javascript.use_rhino=true: Rhino +# If you want to use Rhino on JDK8, set this property to true +#javascript.use_rhino=false + +# Number of milliseconds to wait for a thread to stop +#jmeterengine.threadstop.wait=5000 + +#Whether to invoke System.exit(0) in server exit code after stopping RMI +#jmeterengine.remote.system.exit=false + +# Whether to call System.exit(1) on failure to stop threads in non-GUI mode. +# This only takes effect if the test was explicitly requested to stop. +# If this is disabled, it may be necessary to kill the JVM externally +#jmeterengine.stopfail.system.exit=true + +# Whether to force call System.exit(0) at end of test in non-GUI mode, even if +# there were no failures and the test was not explicitly asked to stop. +# Without this, the JVM may never exit if there are other threads spawned by +# the test which never exit. +#jmeterengine.force.system.exit=false + +# How long to pause (in ms) in the daemon thread before reporting that the JVM has failed to exit. +# If the value is <= 0, the JMeter does not start the daemon thread +#jmeter.exit.check.pause=2000 + +# If running non-GUI, then JMeter listens on the following port for a shutdown message. +# To disable, set the port to 1000 or less. +#jmeterengine.nongui.port=4445 +# +# If the initial port is busy, keep trying until this port is reached +# (to disable searching, set the value less than or equal to the .port property) +#jmeterengine.nongui.maxport=4455 + +# How often to check for shutdown during ramp-up (milliseconds) +#jmeterthread.rampup.granularity=1000 + +#Should JMeter expand the tree when loading a test plan? +# default value is false since JMeter 2.7 +#onload.expandtree=false + +#JSyntaxTextArea configuration +#jsyntaxtextarea.wrapstyleword=true +#jsyntaxtextarea.linewrap=true +#jsyntaxtextarea.codefolding=true +# Set 0 to disable undo feature in JSyntaxTextArea +#jsyntaxtextarea.maxundos=50 +# Change the font on the (JSyntax) Text Areas. (Useful for HiDPI screens) +#jsyntaxtextarea.font.family=Hack +#jsyntaxtextarea.font.size=14 + +# Set this to false to disable the use of JSyntaxTextArea for the Console Logger panel +#loggerpanel.usejsyntaxtext=true + +# Maximum size of HTML page that can be displayed; default=10 mbytes +# Set to 0 to disable the size check and display the whole response +#view.results.tree.max_size=10485760 + +# Order of Renderers in View Results Tree +# Note full class names should be used for non jmeter core renderers +# For JMeter core renderers, class names start with . and are automatically +# prefixed with org.apache.jmeter.visualizers +view.results.tree.renderers_order=.RenderAsText,.RenderAsRegexp,.RenderAsBoundaryExtractor,.RenderAsCssJQuery,.RenderAsXPath,org.apache.jmeter.extractor.json.render.RenderAsJsonRenderer,.RenderAsHTML,.RenderAsHTMLFormatted,.RenderAsHTMLWithEmbedded,.RenderAsDocument,.RenderAsJSON,.RenderAsXML + +# Maximum number of results in the results tree +# Set to 0 to store all results (might consume a lot of memory) +#view.results.tree.max_results=500 + +# Maximum size of Document that can be parsed by Tika engine; defaut=10 * 1024 * 1024 (10MB) +# Set to 0 to disable the size check +#document.max_size=0 + +#JMS options +# Enable the following property to stop JMS Point-to-Point Sampler from using +# the properties java.naming.security.[principal|credentials] when creating the queue connection +#JMSSampler.useSecurity.properties=false + +# Set the following value to true in order to skip the delete confirmation dialogue +#confirm.delete.skip=false + +# Used by JSR223 elements +# Size of compiled scripts cache +#jsr223.compiled_scripts_cache_size=100 + +#--------------------------------------------------------------------------- +# Classpath configuration +#--------------------------------------------------------------------------- + +# List of directories (separated by ;) to search for additional JMeter plugin classes, +# for example new GUI elements and samplers. +# Any jar file in such a directory will be automatically included, +# jar files in sub directories are ignored. +# The given value is in addition to any jars found in the lib/ext directory. +# Do not use this for utility or plugin dependency jars. +#search_paths=/app1/lib;/app2/lib + +# List of directories that JMeter will search for utility and plugin dependency classes. +# Use your platform path separator to separate multiple paths. +# Any jar file in such a directory will be automatically included, +# jar files in sub directories are ignored. +# The given value is in addition to any jars found in the lib directory. +# All entries will be added to the class path of the system class loader +# and also to the path of the JMeter internal loader. +# Paths with spaces may cause problems for the JVM +#user.classpath=../classes;../lib + +# List of directories (separated by ;) that JMeter will search for utility +# and plugin dependency classes. +# Any jar file in such a directory will be automatically included, +# jar files in sub directories are ignored. +# The given value is in addition to any jars found in the lib directory +# or given by the user.classpath property. +# All entries will be added to the path of the JMeter internal loader only. +# For plugin dependencies this property should be used instead of user.classpath. +#plugin_dependency_paths=../dependencies/lib;../app1/;../app2/ + +# Classpath finder +# ================ +# The classpath finder currently needs to load every single JMeter class to find +# the classes it needs. +# For non-GUI mode, it's only necessary to scan for Function classes, but all classes +# are still loaded. +# All current Function classes include ".function." in their name, +# and none include ".gui." in the name, so the number of unwanted classes loaded can be +# reduced by checking for these. However, if a valid function class name does not match +# these restrictions, it will not be loaded. If problems are encountered, then comment +# or change the following properties: +classfinder.functions.contain=.functions. +classfinder.functions.notContain=.gui. + + +#--------------------------------------------------------------------------- +# Additional property files to load +#--------------------------------------------------------------------------- + +# Should JMeter automatically load additional JMeter properties? +# File name to look for (comment to disable) +user.properties=user.properties + +# Should JMeter automatically load additional system properties? +# File name to look for (comment to disable) +system.properties=system.properties + +# Comma separated list of files that contain reference to templates and their description +# Path must be relative to JMeter root folder +#template.files=/jmeter.bin/templates/templates.xml + + +#--------------------------------------------------------------------------- +# Thread Group Validation feature +#--------------------------------------------------------------------------- + +# Validation is the name of the feature used to rapidly validate a Thread Group runs fine +# Default implementation is org.apache.jmeter.gui.action.validation.TreeClonerForValidation +# It runs validation without timers, with 1 thread, 1 iteration and Startup Delay set to 0 +# You can implement your own policy that must extend org.apache.jmeter.engine.TreeCloner +# JMeter will instantiate it and use it to create the Tree used to run validation on Thread Group +#testplan_validation.tree_cloner_class=org.apache.jmeter.validation.ComponentTreeClonerForValidation + +# Number of threads to use to validate a Thread Group +#testplan_validation.nb_threads_per_thread_group=1 + +# Ignore BackendListener when validating the thread group of plan +#testplan_validation.ignore_backends=true + +# Ignore timers when validating the thread group of plan +#testplan_validation.ignore_timers=true + +# Number of iterations to use to validate a Thread Group +#testplan_validation.number_iterations=1 + +# Force throuput controllers that work in percentage mode to be a 100% +# Disabled by default +#testplan_validation.tpc_force_100_pct=false + +#--------------------------------------------------------------------------- +# Think Time configuration +#--------------------------------------------------------------------------- + +# +# Apply a factor on computed pauses by the following Timers: +# - Gaussian Random Timer +# - Uniform Random Timer +# - Poisson Random Timer +# +#timer.factor=1.0f + +# Default implementation that create the Timer structure to add to Test Plan +# Implementation of interface org.apache.jmeter.gui.action.thinktime.ThinkTimeCreator +#think_time_creator.impl=org.apache.jmeter.thinktime.DefaultThinkTimeCreator + +# Default Timer GUI class added to Test Plan by DefaultThinkTimeCreator +#think_time_creator.default_timer_implementation=org.apache.jmeter.timers.gui.UniformRandomTimerGui + +# Default constant pause of Timer +#think_time_creator.default_constant_pause=1000 + +# Default range pause of Timer +#think_time_creator.default_range=100 + + +# Change this parameter if you want to override the APDEX satisfaction threshold. +jmeter.reportgenerator.apdex_satisfied_threshold=500 + +# Change this parameter if you want to override the APDEX tolerance threshold. +jmeter.reportgenerator.apdex_tolerated_threshold=1500 + +# Timeout in milliseconds for Report generation when using Tools > Generate HTML report +#generate_report_ui.generation_timeout=120000 +#--------------------------------------------------------------------------- +# Naming Policy configuration +#--------------------------------------------------------------------------- + +# Prefix used when naming elements +#naming_policy.prefix= +# Suffix used when naming elements +#naming_policy.suffix= + +# Implementation of interface org.apache.jmeter.gui.action.TreeNodeNamingPolicy +#naming_policy.impl=org.apache.jmeter.gui.action.impl.DefaultTreeNodeNamingPolicy + +#--------------------------------------------------------------------------- +# Help Documentation +#--------------------------------------------------------------------------- + +# Switch that allows using Local documentation opened in JMeter GUI +# By default we use Online documentation opened in Browser +#help.local=false + +#--------------------------------------------------------------------------- +# Documentation generation +#--------------------------------------------------------------------------- + +# Path to XSL file used to generate Schematic View of Test Plan +# When empty, JMeter will use the embedded one in src/core/org/apache/jmeter/gui/action/schematic.xsl +#docgeneration.schematic_xsl= diff --git a/src/main/resources/jmeter/bin/saveservice.properties b/src/main/resources/jmeter/bin/saveservice.properties new file mode 100644 index 0000000..fd64d6b --- /dev/null +++ b/src/main/resources/jmeter/bin/saveservice.properties @@ -0,0 +1,420 @@ +#--------------------------------------------------------- +# SAVESERVICE PROPERTIES - JMETER INTERNAL USE ONLY +#--------------------------------------------------------- +## Licensed to the Apache Software Foundation (ASF) under one or more +## contributor license agreements. See the NOTICE file distributed with +## this work for additional information regarding copyright ownership. +## The ASF licenses this file to You under the Apache License, Version 2.0 +## (the "License"); you may not use this file except in compliance with +## the License. You may obtain a copy of the License at +## +## http://www.apache.org/licenses/LICENSE-2.0 +## +## Unless required by applicable law or agreed to in writing, software +## distributed under the License is distributed on an "AS IS" BASIS, +## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +## See the License for the specific language governing permissions and +## limitations under the License. +# This file is used to define how XStream (de-)serializes classnames +# in JMX test plan files. +# FOR JMETER INTERNAL USE ONLY +#--------------------------------------------------------- +# N.B. To ensure backward compatibility, please do NOT change or delete any entries +# New entries can be added as necessary. +# +# Note that keys starting with an underscore are special, +# and are not used as aliases. +# +# Please keep the entries in alphabetical order within the sections +# to reduce the likelihood of duplicates +# +# version number of this file is now computed by a sha1 sum, so no need for +# an explicit _file_version property anymore. +# +# For this sha1 sum we ignore every newline character. It can be computed +# by the following command: +# +# cat jmeter.bin/saveservice.properties | perl -ne 'chomp; print' | sha1sum +# +# Be aware, that every change in this file will change the sha1 sum! +# +# Conversion version (for JMX output files) +# Must be updated if the file has been changed since the previous release +# Format is: +# Save service version=JMeter version at which change occurred +# 1.7 = 2.1.1 +# 1.8 = 2.1.2 +# (Some version updates were missed here...) +# 2.0 = 2.3.1 +# 2.1 = 2.3.2 +# (Some version updates were missed here...) +# 2.2 = 2.6 +# 2.3 = 2.7 +# 2.4 = 2.9 +# 2.5 = 2.10 +# 2.6 = 2.11 +# 2.7 = 2.12 +# 2.8 = 2.13 +# 2.9 = 2.14 +# 3.1 = 3.1 +# 3.2 = 3.2 +# 4.0 = 4.0 +# 5.0 = 5.0 +_version=5.0 +# +# +# Character set encoding used to read and write JMeter XML files and CSV results +# +_file_encoding=UTF-8 +# +#--------------------------------------------------------- +# +# The following properties are used to create aliases +# [Must all start with capital letter] +# +AccessLogSampler=org.apache.jmeter.protocol.http.sampler.AccessLogSampler +AjpSampler=org.apache.jmeter.protocol.http.sampler.AjpSampler +AjpSamplerGui=org.apache.jmeter.protocol.http.control.gui.AjpSamplerGui +AnchorModifier=org.apache.jmeter.protocol.http.modifier.AnchorModifier +AnchorModifierGui=org.apache.jmeter.protocol.http.modifier.gui.AnchorModifierGui +Argument=org.apache.jmeter.config.Argument +Arguments=org.apache.jmeter.config.Arguments +ArgumentsPanel=org.apache.jmeter.config.gui.ArgumentsPanel +AssertionGui=org.apache.jmeter.assertions.gui.AssertionGui +AssertionVisualizer=org.apache.jmeter.visualizers.AssertionVisualizer +AuthManager=org.apache.jmeter.protocol.http.control.AuthManager +Authorization=org.apache.jmeter.protocol.http.control.Authorization +AuthPanel=org.apache.jmeter.protocol.http.gui.AuthPanel +BackendListener=org.apache.jmeter.visualizers.backend.BackendListener +BackendListenerGui=org.apache.jmeter.visualizers.backend.BackendListenerGui +BeanShellAssertion=org.apache.jmeter.assertions.BeanShellAssertion +BeanShellAssertionGui=org.apache.jmeter.assertions.gui.BeanShellAssertionGui +BeanShellListener=org.apache.jmeter.visualizers.BeanShellListener +BeanShellPostProcessor=org.apache.jmeter.extractor.BeanShellPostProcessor +BeanShellPreProcessor=org.apache.jmeter.modifiers.BeanShellPreProcessor +BeanShellSampler=org.apache.jmeter.protocol.java.sampler.BeanShellSampler +BeanShellSamplerGui=org.apache.jmeter.protocol.java.control.gui.BeanShellSamplerGui +BeanShellTimer=org.apache.jmeter.timers.BeanShellTimer +BoundaryExtractor=org.apache.jmeter.extractor.BoundaryExtractor +BoundaryExtractorGui=org.apache.jmeter.extractor.gui.BoundaryExtractorGui +BSFAssertion=org.apache.jmeter.assertions.BSFAssertion +BSFListener=org.apache.jmeter.visualizers.BSFListener +BSFPreProcessor=org.apache.jmeter.modifiers.BSFPreProcessor +BSFPostProcessor=org.apache.jmeter.extractor.BSFPostProcessor +BSFSampler=org.apache.jmeter.protocol.java.sampler.BSFSampler +BSFSamplerGui=org.apache.jmeter.protocol.java.control.gui.BSFSamplerGui +BSFTimer=org.apache.jmeter.timers.BSFTimer +CacheManager=org.apache.jmeter.protocol.http.control.CacheManager +CacheManagerGui=org.apache.jmeter.protocol.http.gui.CacheManagerGui +CompareAssertion=org.apache.jmeter.assertions.CompareAssertion +ComparisonVisualizer=org.apache.jmeter.visualizers.ComparisonVisualizer +ConfigTestElement=org.apache.jmeter.config.ConfigTestElement +ConstantThroughputTimer=org.apache.jmeter.timers.ConstantThroughputTimer +ConstantTimer=org.apache.jmeter.timers.ConstantTimer +ConstantTimerGui=org.apache.jmeter.timers.gui.ConstantTimerGui +Cookie=org.apache.jmeter.protocol.http.control.Cookie +CookieManager=org.apache.jmeter.protocol.http.control.CookieManager +CookiePanel=org.apache.jmeter.protocol.http.gui.CookiePanel +CounterConfig=org.apache.jmeter.modifiers.CounterConfig +CriticalSectionController=org.apache.jmeter.control.CriticalSectionController +CriticalSectionControllerGui=org.apache.jmeter.control.gui.CriticalSectionControllerGui +CounterConfigGui=org.apache.jmeter.modifiers.gui.CounterConfigGui +CSVDataSet=org.apache.jmeter.config.CSVDataSet +DebugPostProcessor=org.apache.jmeter.extractor.DebugPostProcessor +DebugSampler=org.apache.jmeter.sampler.DebugSampler +# removed in 3.1, class was deleted in r1763837 +DistributionGraphVisualizer=org.apache.jmeter.visualizers.DistributionGraphVisualizer +DNSCacheManager=org.apache.jmeter.protocol.http.control.DNSCacheManager +DNSCachePanel=org.apache.jmeter.protocol.http.gui.DNSCachePanel +DubboSample=io.github.ningyu.jmeter.plugin.dubbo.sample.DubboSample +DubboSampleGui=io.github.ningyu.jmeter.plugin.dubbo.gui.DubboSampleGui +DubboDefaultConfigGui=io.github.ningyu.jmeter.plugin.dubbo.gui.DubboDefaultConfigGui +DurationAssertion=org.apache.jmeter.assertions.DurationAssertion +DurationAssertionGui=org.apache.jmeter.assertions.gui.DurationAssertionGui +PreciseThroughputTimer=org.apache.jmeter.timers.poissonarrivals.PreciseThroughputTimer +# Should really have been defined as floatProp to agree with other properties +# No point changing this now +FloatProperty=org.apache.jmeter.testelement.property.FloatProperty +ForeachController=org.apache.jmeter.control.ForeachController +ForeachControlPanel=org.apache.jmeter.control.gui.ForeachControlPanel +FtpConfigGui=org.apache.jmeter.protocol.ftp.config.gui.FtpConfigGui +FTPSampler=org.apache.jmeter.protocol.ftp.sampler.FTPSampler +FtpTestSamplerGui=org.apache.jmeter.protocol.ftp.control.gui.FtpTestSamplerGui +GaussianRandomTimer=org.apache.jmeter.timers.GaussianRandomTimer +GaussianRandomTimerGui=org.apache.jmeter.timers.gui.GaussianRandomTimerGui +GenericController=org.apache.jmeter.control.GenericController +GraphAccumVisualizer=org.apache.jmeter.visualizers.GraphAccumVisualizer +GraphVisualizer=org.apache.jmeter.visualizers.GraphVisualizer +Header=org.apache.jmeter.protocol.http.control.Header +HeaderManager=org.apache.jmeter.protocol.http.control.HeaderManager +HeaderPanel=org.apache.jmeter.protocol.http.gui.HeaderPanel +HTMLAssertion=org.apache.jmeter.assertions.HTMLAssertion +HTMLAssertionGui=org.apache.jmeter.assertions.gui.HTMLAssertionGui +HTTPArgument=org.apache.jmeter.protocol.http.util.HTTPArgument +HTTPArgumentsPanel=org.apache.jmeter.protocol.http.gui.HTTPArgumentsPanel +HTTPFileArg=org.apache.jmeter.protocol.http.util.HTTPFileArg +HTTPFileArgs=org.apache.jmeter.protocol.http.util.HTTPFileArgs +HttpDefaultsGui=org.apache.jmeter.protocol.http.config.gui.HttpDefaultsGui +HtmlExtractor=org.apache.jmeter.extractor.HtmlExtractor +HtmlExtractorGui=org.apache.jmeter.extractor.gui.HtmlExtractorGui +# removed in r1039684, probably not released. Not present in r322831 or since. +#HttpGenericSampler=org.apache.jmeter.protocol.http.sampler.HttpGenericSampler +# removed in r1039684, probably not released. Not present in r322831 or since. +#HttpGenericSamplerGui=org.apache.jmeter.protocol.http.control.gui.HttpGenericSamplerGui +HttpMirrorControl=org.apache.jmeter.protocol.http.control.HttpMirrorControl +HttpMirrorControlGui=org.apache.jmeter.protocol.http.control.gui.HttpMirrorControlGui +# r397955 - removed test class. Keep as commented entry for info only. +#HTTPNullSampler=org.apache.jmeter.protocol.http.sampler.HTTPNullSampler +# Merge previous 2 HTTP samplers into one +HTTPSampler_=org.apache.jmeter.protocol.http.sampler.HTTPSampler +HTTPSampler2_=org.apache.jmeter.protocol.http.sampler.HTTPSampler2 +HTTPSamplerProxy,HTTPSampler,HTTPSampler2=org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy +# Merge GUIs +HttpTestSampleGui,HttpTestSampleGui2=org.apache.jmeter.protocol.http.control.gui.HttpTestSampleGui +#HttpTestSampleGui2=org.apache.jmeter.protocol.http.control.gui.HttpTestSampleGui2 +IfController=org.apache.jmeter.control.IfController +IfControllerPanel=org.apache.jmeter.control.gui.IfControllerPanel +IncludeController=org.apache.jmeter.control.IncludeController +IncludeControllerGui=org.apache.jmeter.control.gui.IncludeControllerGui +InterleaveControl=org.apache.jmeter.control.InterleaveControl +InterleaveControlGui=org.apache.jmeter.control.gui.InterleaveControlGui +JavaConfig=org.apache.jmeter.protocol.java.config.JavaConfig +JavaConfigGui=org.apache.jmeter.protocol.java.config.gui.JavaConfigGui +JavaSampler=org.apache.jmeter.protocol.java.sampler.JavaSampler +JavaTest=org.apache.jmeter.protocol.java.test.JavaTest +JavaTestSamplerGui=org.apache.jmeter.protocol.java.control.gui.JavaTestSamplerGui +JDBCDataSource=org.apache.jmeter.protocol.jdbc.config.DataSourceElement +JDBCPostProcessor=org.apache.jmeter.protocol.jdbc.processor.JDBCPostProcessor +JDBCPreProcessor=org.apache.jmeter.protocol.jdbc.processor.JDBCPreProcessor +JDBCSampler=org.apache.jmeter.protocol.jdbc.sampler.JDBCSampler +JMESPathAssertion=org.apache.jmeter.assertions.jmespath.JMESPathAssertion +JMESPathAssertionGui=org.apache.jmeter.assertions.jmespath.gui.JMESPathAssertionGui +JMESPathExtractor=org.apache.jmeter.extractor.json.jmespath.JMESPathExtractor +JMESPathExtractorGui=org.apache.jmeter.extractor.json.jmespath.gui.JMESPathExtractorGui +# Renamed to JMSSamplerGui; keep original entry for backwards compatibility +JMSConfigGui=org.apache.jmeter.protocol.jms.control.gui.JMSConfigGui +JMSProperties=org.apache.jmeter.protocol.jms.sampler.JMSProperties +JMSProperty=org.apache.jmeter.protocol.jms.sampler.JMSProperty +JMSPublisherGui=org.apache.jmeter.protocol.jms.control.gui.JMSPublisherGui +JMSSampler=org.apache.jmeter.protocol.jms.sampler.JMSSampler +JMSSamplerGui=org.apache.jmeter.protocol.jms.control.gui.JMSSamplerGui +JMSSubscriberGui=org.apache.jmeter.protocol.jms.control.gui.JMSSubscriberGui +JSONPathAssertion=org.apache.jmeter.assertions.JSONPathAssertion +JSONPathAssertionGui=org.apache.jmeter.assertions.gui.JSONPathAssertionGui +JSONPostProcessor=org.apache.jmeter.extractor.json.jsonpath.JSONPostProcessor +JSONPostProcessorGui=org.apache.jmeter.extractor.json.jsonpath.gui.JSONPostProcessorGui +# Removed in r545311 as Jndi no longer present; keep for compat. +JndiDefaultsGui=org.apache.jmeter.protocol.jms.control.gui.JndiDefaultsGui +JSR223Assertion=org.apache.jmeter.assertions.JSR223Assertion +JSR223Listener=org.apache.jmeter.visualizers.JSR223Listener +JSR223PostProcessor=org.apache.jmeter.extractor.JSR223PostProcessor +JSR223PreProcessor=org.apache.jmeter.modifiers.JSR223PreProcessor +JSR223Sampler=org.apache.jmeter.protocol.java.sampler.JSR223Sampler +JSR223Timer=org.apache.jmeter.timers.JSR223Timer +JUnitSampler=org.apache.jmeter.protocol.java.sampler.JUnitSampler +JUnitTestSamplerGui=org.apache.jmeter.protocol.java.control.gui.JUnitTestSamplerGui +KeystoreConfig=org.apache.jmeter.config.KeystoreConfig +LDAPArgument=org.apache.jmeter.protocol.ldap.config.gui.LDAPArgument +LDAPArguments=org.apache.jmeter.protocol.ldap.config.gui.LDAPArguments +LDAPArgumentsPanel=org.apache.jmeter.protocol.ldap.config.gui.LDAPArgumentsPanel +LdapConfigGui=org.apache.jmeter.protocol.ldap.config.gui.LdapConfigGui +LdapExtConfigGui=org.apache.jmeter.protocol.ldap.config.gui.LdapExtConfigGui +LDAPExtSampler=org.apache.jmeter.protocol.ldap.sampler.LDAPExtSampler +LdapExtTestSamplerGui=org.apache.jmeter.protocol.ldap.control.gui.LdapExtTestSamplerGui +LDAPSampler=org.apache.jmeter.protocol.ldap.sampler.LDAPSampler +LdapTestSamplerGui=org.apache.jmeter.protocol.ldap.control.gui.LdapTestSamplerGui +LogicControllerGui=org.apache.jmeter.control.gui.LogicControllerGui +LoginConfig=org.apache.jmeter.config.LoginConfig +LoginConfigGui=org.apache.jmeter.config.gui.LoginConfigGui +LoopController=org.apache.jmeter.control.LoopController +LoopControlPanel=org.apache.jmeter.control.gui.LoopControlPanel +MailerModel=org.apache.jmeter.reporters.MailerModel +MailerResultCollector=org.apache.jmeter.reporters.MailerResultCollector +MailerVisualizer=org.apache.jmeter.visualizers.MailerVisualizer +MailReaderSampler=org.apache.jmeter.protocol.mail.sampler.MailReaderSampler +MailReaderSamplerGui=org.apache.jmeter.protocol.mail.sampler.gui.MailReaderSamplerGui +MD5HexAssertion=org.apache.jmeter.assertions.MD5HexAssertion +MD5HexAssertionGUI=org.apache.jmeter.assertions.gui.MD5HexAssertionGUI +ModuleController=org.apache.jmeter.control.ModuleController +ModuleControllerGui=org.apache.jmeter.control.gui.ModuleControllerGui +MongoScriptSampler=org.apache.jmeter.protocol.mongodb.sampler.MongoScriptSampler +MongoSourceElement=org.apache.jmeter.protocol.mongodb.config.MongoSourceElement +# removed in 3.2, class was deleted in r +MonitorHealthVisualizer=org.apache.jmeter.visualizers.MonitorHealthVisualizer +NamePanel=org.apache.jmeter.gui.NamePanel +BoltSampler=org.apache.jmeter.protocol.bolt.sampler.BoltSampler +BoltConnectionElement=org.apache.jmeter.protocol.bolt.config.BoltConnectionElement +ObsoleteGui=org.apache.jmeter.config.gui.ObsoleteGui +OnceOnlyController=org.apache.jmeter.control.OnceOnlyController +OnceOnlyControllerGui=org.apache.jmeter.control.gui.OnceOnlyControllerGui +# removed in 3.0, class was deleted in r1722962 +ParamMask=org.apache.jmeter.protocol.http.modifier.ParamMask +# removed in 3.0, class was deleted in r1722757 +ParamModifier=org.apache.jmeter.protocol.http.modifier.ParamModifier +# removed in 3.0, class was deleted in r1722757 +ParamModifierGui=org.apache.jmeter.protocol.http.modifier.gui.ParamModifierGui +PoissonRandomTimer=org.apache.jmeter.timers.PoissonRandomTimer +PoissonRandomTimerGui=org.apache.jmeter.timers.gui.PoissonRandomTimerGui +PropertyControlGui=org.apache.jmeter.visualizers.PropertyControlGui +ProxyControl=org.apache.jmeter.protocol.http.proxy.ProxyControl +ProxyControlGui=org.apache.jmeter.protocol.http.proxy.gui.ProxyControlGui +PublisherSampler=org.apache.jmeter.protocol.jms.sampler.PublisherSampler +RandomControlGui=org.apache.jmeter.control.gui.RandomControlGui +RandomController=org.apache.jmeter.control.RandomController +RandomOrderController=org.apache.jmeter.control.RandomOrderController +RandomOrderControllerGui=org.apache.jmeter.control.gui.RandomOrderControllerGui +RandomVariableConfig=org.apache.jmeter.config.RandomVariableConfig +RecordController=org.apache.jmeter.protocol.http.control.gui.RecordController +RecordingController=org.apache.jmeter.protocol.http.control.RecordingController +# removed in r1039684, class was deleted in r580452 +ReflectionThreadGroup=org.apache.jmeter.threads.ReflectionThreadGroup +RegexExtractor=org.apache.jmeter.extractor.RegexExtractor +RegexExtractorGui=org.apache.jmeter.extractor.gui.RegexExtractorGui +RegExUserParameters=org.apache.jmeter.protocol.http.modifier.RegExUserParameters +RegExUserParametersGui=org.apache.jmeter.protocol.http.modifier.gui.RegExUserParametersGui +RemoteListenerWrapper=org.apache.jmeter.samplers.RemoteListenerWrapper +RemoteSampleListenerWrapper=org.apache.jmeter.samplers.RemoteSampleListenerWrapper +RemoteTestListenerWrapper=org.apache.jmeter.samplers.RemoteTestListenerWrapper +RemoteThreadsListenerWrapper=org.apache.jmeter.threads.RemoteThreadsListenerWrapper +ResponseAssertion=org.apache.jmeter.assertions.ResponseAssertion +RespTimeGraphVisualizer=org.apache.jmeter.visualizers.RespTimeGraphVisualizer +ResultAction=org.apache.jmeter.reporters.ResultAction +ResultActionGui=org.apache.jmeter.reporters.gui.ResultActionGui +ResultCollector=org.apache.jmeter.reporters.ResultCollector +ResultSaver=org.apache.jmeter.reporters.ResultSaver +ResultSaverGui=org.apache.jmeter.reporters.gui.ResultSaverGui +RunTime=org.apache.jmeter.control.RunTime +RunTimeGui=org.apache.jmeter.control.gui.RunTimeGui +SampleSaveConfiguration=org.apache.jmeter.samplers.SampleSaveConfiguration +SampleTimeout=org.apache.jmeter.modifiers.SampleTimeout +SampleTimeoutGui=org.apache.jmeter.modifiers.gui.SampleTimeoutGui +SimpleConfigGui=org.apache.jmeter.config.gui.SimpleConfigGui +SimpleDataWriter=org.apache.jmeter.visualizers.SimpleDataWriter +SizeAssertion=org.apache.jmeter.assertions.SizeAssertion +SizeAssertionGui=org.apache.jmeter.assertions.gui.SizeAssertionGui +SMIMEAssertion=org.apache.jmeter.assertions.SMIMEAssertionTestElement +SMIMEAssertionGui=org.apache.jmeter.assertions.gui.SMIMEAssertionGui +SmtpSampler=org.apache.jmeter.protocol.smtp.sampler.SmtpSampler +SmtpSamplerGui=org.apache.jmeter.protocol.smtp.sampler.gui.SmtpSamplerGui +# removed in 3.2, class was deleted in r +SoapSampler=org.apache.jmeter.protocol.http.sampler.SoapSampler +# removed in 3.2, class was deleted in r +SoapSamplerGui=org.apache.jmeter.protocol.http.control.gui.SoapSamplerGui +# removed in 3.1, class was deleted in r1763837 +SplineVisualizer=org.apache.jmeter.visualizers.SplineVisualizer +# Originally deleted in r397955 as class is obsolete; needed for compat. +SqlConfigGui=org.apache.jmeter.protocol.jdbc.config.gui.SqlConfigGui +StaticHost=org.apache.jmeter.protocol.http.control.StaticHost +StatGraphVisualizer=org.apache.jmeter.visualizers.StatGraphVisualizer +StatVisualizer=org.apache.jmeter.visualizers.StatVisualizer +SubscriberSampler=org.apache.jmeter.protocol.jms.sampler.SubscriberSampler +SubstitutionElement=org.apache.jmeter.assertions.SubstitutionElement +Summariser=org.apache.jmeter.reporters.Summariser +SummariserGui=org.apache.jmeter.reporters.gui.SummariserGui +SummaryReport=org.apache.jmeter.visualizers.SummaryReport +SwitchController=org.apache.jmeter.control.SwitchController +SwitchControllerGui=org.apache.jmeter.control.gui.SwitchControllerGui +SyncTimer=org.apache.jmeter.timers.SyncTimer +SystemSampler=org.apache.jmeter.protocol.system.SystemSampler +SystemSamplerGui=org.apache.jmeter.protocol.system.gui.SystemSamplerGui +TableVisualizer=org.apache.jmeter.visualizers.TableVisualizer +TCPConfigGui=org.apache.jmeter.protocol.tcp.config.gui.TCPConfigGui +TCPSampler=org.apache.jmeter.protocol.tcp.sampler.TCPSampler +TCPSamplerGui=org.apache.jmeter.protocol.tcp.control.gui.TCPSamplerGui +TestAction=org.apache.jmeter.sampler.TestAction +TestActionGui=org.apache.jmeter.sampler.gui.TestActionGui +TestBeanGUI=org.apache.jmeter.testbeans.gui.TestBeanGUI +TestFragmentController=org.apache.jmeter.control.TestFragmentController +TestFragmentControllerGui=org.apache.jmeter.control.gui.TestFragmentControllerGui +TestPlan=org.apache.jmeter.testelement.TestPlan +TestPlanGui=org.apache.jmeter.control.gui.TestPlanGui +ThreadGroup=org.apache.jmeter.threads.ThreadGroup +ThreadGroupGui=org.apache.jmeter.threads.gui.ThreadGroupGui +PostThreadGroup=org.apache.jmeter.threads.PostThreadGroup +PostThreadGroupGui=org.apache.jmeter.threads.gui.PostThreadGroupGui +SetupThreadGroup=org.apache.jmeter.threads.SetupThreadGroup +SetupThreadGroupGui=org.apache.jmeter.threads.gui.SetupThreadGroupGui +ThroughputController=org.apache.jmeter.control.ThroughputController +ThroughputControllerGui=org.apache.jmeter.control.gui.ThroughputControllerGui +TransactionController=org.apache.jmeter.control.TransactionController +TransactionControllerGui=org.apache.jmeter.control.gui.TransactionControllerGui +TransactionSampler=org.apache.jmeter.control.TransactionSampler +UniformRandomTimer=org.apache.jmeter.timers.UniformRandomTimer +UniformRandomTimerGui=org.apache.jmeter.timers.gui.UniformRandomTimerGui +URLRewritingModifier=org.apache.jmeter.protocol.http.modifier.URLRewritingModifier +URLRewritingModifierGui=org.apache.jmeter.protocol.http.modifier.gui.URLRewritingModifierGui +UserParameterModifier=org.apache.jmeter.protocol.http.modifier.UserParameterModifier +UserParameterModifierGui=org.apache.jmeter.protocol.http.modifier.gui.UserParameterModifierGui +UserParameters=org.apache.jmeter.modifiers.UserParameters +UserParametersGui=org.apache.jmeter.modifiers.gui.UserParametersGui +ViewResultsFullVisualizer=org.apache.jmeter.visualizers.ViewResultsFullVisualizer +# removed in 3.0, class was deleted in r1722757 +WebServiceSampler=org.apache.jmeter.protocol.http.sampler.WebServiceSampler +# removed in 3.0, class was deleted in r1722757 +WebServiceSamplerGui=org.apache.jmeter.protocol.http.control.gui.WebServiceSamplerGui +WhileController=org.apache.jmeter.control.WhileController +WhileControllerGui=org.apache.jmeter.control.gui.WhileControllerGui +WorkBench=org.apache.jmeter.testelement.WorkBench +WorkBenchGui=org.apache.jmeter.control.gui.WorkBenchGui +XMLAssertion=org.apache.jmeter.assertions.XMLAssertion +XMLAssertionGui=org.apache.jmeter.assertions.gui.XMLAssertionGui +XMLSchemaAssertion=org.apache.jmeter.assertions.XMLSchemaAssertion +XMLSchemaAssertionGUI=org.apache.jmeter.assertions.gui.XMLSchemaAssertionGUI +XPathAssertion=org.apache.jmeter.assertions.XPathAssertion +XPathAssertionGui=org.apache.jmeter.assertions.gui.XPathAssertionGui +XPath2Assertion=org.apache.jmeter.assertions.XPath2Assertion +XPath2AssertionGui=org.apache.jmeter.assertions.gui.XPath2AssertionGui +XPathExtractor=org.apache.jmeter.extractor.XPathExtractor +XPathExtractorGui=org.apache.jmeter.extractor.gui.XPathExtractorGui +XPath2Extractor=org.apache.jmeter.extractor.XPath2Extractor +XPath2ExtractorGui=org.apache.jmeter.extractor.gui.XPath2ExtractorGui +# Properties - all start with lower case letter and end with Prop +# +boolProp=org.apache.jmeter.testelement.property.BooleanProperty +collectionProp=org.apache.jmeter.testelement.property.CollectionProperty +doubleProp=org.apache.jmeter.testelement.property.DoubleProperty +elementProp=org.apache.jmeter.testelement.property.TestElementProperty +# see above - already defined as FloatProperty +#floatProp=org.apache.jmeter.testelement.property.FloatProperty +intProp=org.apache.jmeter.testelement.property.IntegerProperty +longProp=org.apache.jmeter.testelement.property.LongProperty +mapProp=org.apache.jmeter.testelement.property.MapProperty +objProp=org.apache.jmeter.testelement.property.ObjectProperty +stringProp=org.apache.jmeter.testelement.property.StringProperty +# +# Other - must start with a lower case letter (and not end with Prop) +# (otherwise they could clash with the initial set of aliases) +# +hashTree=org.apache.jorphan.collections.ListedHashTree +jmeterTestPlan=org.apache.jmeter.save.ScriptWrapper +sample=org.apache.jmeter.samplers.SampleResult +httpSample=org.apache.jmeter.protocol.http.sampler.HTTPSampleResult +statSample=org.apache.jmeter.samplers.StatisticalSampleResult +testResults=org.apache.jmeter.save.TestResultWrapper +assertionResult=org.apache.jmeter.assertions.AssertionResult +# removed in 3.2, class was deleted in r +monitorStats=org.apache.jmeter.visualizers.MonitorStats +sampleEvent=org.apache.jmeter.samplers.SampleEvent +# +# Converters to register. Must start line with '_' +# If the converter is a collection of subitems, set equal to "collection" +# If the converter needs to know the class mappings but is not a collection of +# subitems, set it equal to "mapping" +_org.apache.jmeter.protocol.http.sampler.HTTPSamplerBaseConverter=collection +_org.apache.jmeter.protocol.http.util.HTTPResultConverter=collection +_org.apache.jmeter.save.converters.BooleanPropertyConverter= +_org.apache.jmeter.save.converters.IntegerPropertyConverter= +_org.apache.jmeter.save.converters.LongPropertyConverter= +_org.apache.jmeter.save.converters.MultiPropertyConverter=collection +_org.apache.jmeter.save.converters.SampleEventConverter= +_org.apache.jmeter.save.converters.SampleResultConverter=collection +_org.apache.jmeter.save.converters.SampleSaveConfigurationConverter=collection +_org.apache.jmeter.save.converters.StringPropertyConverter= +_org.apache.jmeter.save.converters.HashTreeConverter=collection +_org.apache.jmeter.save.converters.TestElementConverter=collection +_org.apache.jmeter.save.converters.TestElementPropertyConverter=collection +_org.apache.jmeter.save.converters.TestResultWrapperConverter=collection +_org.apache.jmeter.save.ScriptWrapperConverter=mapping +# +# Remember to update the _version entry +# \ No newline at end of file diff --git a/src/main/resources/jmeter/bin/upgrade.properties b/src/main/resources/jmeter/bin/upgrade.properties new file mode 100644 index 0000000..03742c0 --- /dev/null +++ b/src/main/resources/jmeter/bin/upgrade.properties @@ -0,0 +1,123 @@ +# Class, property and value upgrade equivalences. + +## Licensed to the Apache Software Foundation (ASF) under one or more +## contributor license agreements. See the NOTICE file distributed with +## this work for additional information regarding copyright ownership. +## The ASF licenses this file to You under the Apache License, Version 2.0 +## (the "License"); you may not use this file except in compliance with +## the License. You may obtain a copy of the License at +## +## http://www.apache.org/licenses/LICENSE-2.0 +## +## Unless required by applicable law or agreed to in writing, software +## distributed under the License is distributed on an "AS IS" BASIS, +## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +## See the License for the specific language governing permissions and +## limitations under the License. + +# +# Format is as follows -- +# for renamed test element & GUI classes: +# old.class.Name=new.class.Name +# old.class.Name|guiClassName=new.class.Name +# (e.g. for ConfigTestElement) +# +# for renamed / deleted properties: +# class.Name/Old.propertyName=newPropertyName +# if newPropertyName is omitted, then property is deleted +# +# for renamed values: +# old.class.Name.old.propertyName/oldValue=newValue +# + +org.apache.jmeter.protocol.http.config.gui.UrlConfigGui=org.apache.jmeter.protocol.http.config.gui.HttpDefaultsGui +org.apache.jmeter.assertions.Assertion=org.apache.jmeter.assertions.ResponseAssertion +org.apache.jmeter.protocol.http.sampler.HTTPSamplerFull=org.apache.jmeter.protocol.http.sampler.HTTPSampler +org.apache.jmeter.control.gui.RecordController=org.apache.jmeter.protocol.http.control.gui.RecordController + +org.apache.jmeter.timers.gui.ConstantThroughputTimerGui=org.apache.jmeter.testbeans.gui.TestBeanGUI +org.apache.jmeter.timers.ConstantThroughputTimer/ConstantThroughputTimer.throughput=throughput + +org.apache.jmeter.protocol.jdbc.control.gui.JdbcTestSampleGui=org.apache.jmeter.testbeans.gui.TestBeanGUI +org.apache.jmeter.protocol.jdbc.sampler.JDBCSampler/JDBCSampler.query=query +#org.apache.jmeter.protocol.jdbc.sampler.JDBCSampler.JDBCSampler.dataSource/NULL= + +# Convert DBconfig +org.apache.jmeter.protocol.jdbc.config.gui.DbConfigGui=org.apache.jmeter.testbeans.gui.TestBeanGUI +org.apache.jmeter.config.ConfigTestElement|org.apache.jmeter.protocol.jdbc.config.gui.DbConfigGui=org.apache.jmeter.protocol.jdbc.config.DataSourceElement +org.apache.jmeter.protocol.jdbc.config.DataSourceElement/JDBCSampler.url=dbUrl +org.apache.jmeter.protocol.jdbc.config.DataSourceElement/JDBCSampler.driver=driver +org.apache.jmeter.protocol.jdbc.config.DataSourceElement/JDBCSampler.query=query +org.apache.jmeter.protocol.jdbc.config.DataSourceElement/ConfigTestElement.username=username +org.apache.jmeter.protocol.jdbc.config.DataSourceElement/ConfigTestElement.password=password + +# Convert PoolConfig +org.apache.jmeter.protocol.jdbc.config.gui.PoolConfigGui=org.apache.jmeter.testbeans.gui.TestBeanGUI +org.apache.jmeter.config.ConfigTestElement|org.apache.jmeter.protocol.jdbc.config.gui.PoolConfigGui=org.apache.jmeter.protocol.jdbc.config.DataSourceElement +org.apache.jmeter.protocol.jdbc.config.DataSourceElement/JDBCSampler.connections= +org.apache.jmeter.protocol.jdbc.config.DataSourceElement/JDBCSampler.connPoolClass= +org.apache.jmeter.protocol.jdbc.config.DataSourceElement/JDBCSampler.maxuse=poolMax + +# SQL Config +org.apache.jmeter.config.ConfigTestElement/JDBCSampler.query=query +org.apache.jmeter.protocol.http.control.Header/TestElement.name=Header.name + +# Upgrade AccessLogSampler +org.apache.jmeter.protocol.http.control.gui.AccessLogSamplerGui=org.apache.jmeter.testbeans.gui.TestBeanGUI +org.apache.jmeter.protocol.http.sampler.AccessLogSampler/AccessLogSampler.log_file=logFile +org.apache.jmeter.protocol.http.sampler.AccessLogSampler/HTTPSampler.port=portString +#Is the following used now? +#org.apache.jmeter.protocol.http.sampler.AccessLogSampler/AccessLogSampler.generator_class_name= +#Looks to be a new field +#filterClassName +org.apache.jmeter.protocol.http.sampler.AccessLogSampler/HTTPSampler.domain=domain +org.apache.jmeter.protocol.http.sampler.AccessLogSampler/AccessLogSampler.parser_class_name=parserClassName +org.apache.jmeter.protocol.http.sampler.AccessLogSampler/HTTPSampler.image_parser=imageParsing + +# Renamed class +org.apache.jmeter.protocol.jms.control.gui.JMSConfigGui=org.apache.jmeter.protocol.jms.control.gui.JMSSamplerGui + +# These classes have been deleted; there's no defined replacement +org.apache.jmeter.protocol.jdbc.config.gui.SqlConfigGui=org.apache.jmeter.config.gui.ObsoleteGui +org.apache.jmeter.protocol.jms.control.gui.JndiDefaultsGui=org.apache.jmeter.config.gui.ObsoleteGui +# Should probably map to something other than ObsoleteGui... +org.apache.jmeter.threads.ReflectionThreadGroup=org.apache.jmeter.config.gui.ObsoleteGui + +# Convert BSFSamplerGui +org.apache.jmeter.protocol.java.control.gui.BSFSamplerGui=org.apache.jmeter.testbeans.gui.TestBeanGUI +org.apache.jmeter.protocol.java.sampler.BSFSampler/BSFSampler.filename=filename +org.apache.jmeter.protocol.java.sampler.BSFSampler/BSFSampler.language=scriptLanguage +org.apache.jmeter.protocol.java.sampler.BSFSampler/BSFSampler.parameters=parameters +org.apache.jmeter.protocol.java.sampler.BSFSampler/BSFSampler.query=script + +# Obsolete Http user Parameters modifier test element +# Note: ConfigTestElement is the test element associated with ObsoleteGui +org.apache.jmeter.protocol.http.modifier.UserParameterModifier=org.apache.jmeter.config.ConfigTestElement +org.apache.jmeter.protocol.http.modifier.gui.UserParameterModifierGui=org.apache.jmeter.config.gui.ObsoleteGui + +# Obsolete Graph Full Results listener +org.apache.jmeter.visualizers.GraphAccumVisualizer=org.apache.jmeter.config.gui.ObsoleteGui +# removed in 3.0, class was deleted in r1722757 +org.apache.jmeter.protocol.http.sampler.WebServiceSampler=org.apache.jmeter.config.ConfigTestElement +# removed in 3.0, class was deleted in r1722757 +org.apache.jmeter.protocol.http.control.gui.WebServiceSamplerGui=org.apache.jmeter.config.gui.ObsoleteGui +# removed in 3.0, class was deleted in r1722757 +org.apache.jmeter.protocol.http.modifier.ParamModifier=org.apache.jmeter.config.ConfigTestElement +# removed in 3.0, class was deleted in r1722962 +org.apache.jmeter.protocol.http.modifier.ParamMask=org.apache.jmeter.config.ConfigTestElement +# removed in 3.0, class was deleted in r1722757 +org.apache.jmeter.protocol.http.modifier.gui.ParamModifierGui=org.apache.jmeter.config.gui.ObsoleteGui + +# removed in 3.1, class was deleted in r1774947 +org.apache.jmeter.visualizers.SplineVisualizer=org.apache.jmeter.config.gui.ObsoleteGui +# removed in 3.1 class was deleted in r1763837 +org.apache.jmeter.visualizers.DistributionGraphVisualizer=org.apache.jmeter.config.gui.ObsoleteGui + +# removed in 3.2 class was deleted in r1771608 +org.apache.jmeter.visualizers.MonitorStats=org.apache.jmeter.config.ConfigTestElement +org.apache.jmeter.visualizers.MonitorHealthVisualizer=org.apache.jmeter.config.gui.ObsoleteGui + +# removed in 3.2 class was deleted in r1783280 +org.apache.jmeter.protocol.http.sampler.HTTPSampler2=org.apache.jmeter.config.ConfigTestElement +org.apache.jmeter.protocol.http.sampler.SoapSampler=org.apache.jmeter.config.ConfigTestElement +org.apache.jmeter.protocol.http.control.gui.SoapSamplerGui=org.apache.jmeter.config.gui.ObsoleteGui \ No newline at end of file diff --git a/src/main/resources/jmeter/bin/user.properties b/src/main/resources/jmeter/bin/user.properties new file mode 100644 index 0000000..d6a2625 --- /dev/null +++ b/src/main/resources/jmeter/bin/user.properties @@ -0,0 +1,147 @@ +# Sample user.properties file +# +## Licensed to the Apache Software Foundation (ASF) under one or more +## contributor license agreements. See the NOTICE file distributed with +## this work for additional information regarding copyright ownership. +## The ASF licenses this file to You under the Apache License, Version 2.0 +## (the "License"); you may not use this file except in compliance with +## the License. You may obtain a copy of the License at +## +## http://www.apache.org/licenses/LICENSE-2.0 +## +## Unless required by applicable law or agreed to in writing, software +## distributed under the License is distributed on an "AS IS" BASIS, +## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +## See the License for the specific language governing permissions and +## limitations under the License. + +#--------------------------------------------------------------------------- +# Classpath configuration +#--------------------------------------------------------------------------- +# +# List of paths (separated by ;) to search for additional JMeter plugin classes, +# for example new GUI elements and samplers. +# A path item can either be a jar file or a directory. +# Any jar file in such a directory will be automatically included, +# jar files in sub directories are ignored. +# The given value is in addition to any jars found in the lib/ext directory. +# Do not use this for utility or plugin dependency jars. +#search_paths=/app1/lib;/app2/lib + +# List of paths that JMeter will search for utility and plugin dependency classes. +# Use your platform path separator (java.io.File.pathSeparatorChar in Java) to separate multiple paths. +# A path item can either be a jar file or a directory. +# Any jar file in such a directory will be automatically included, +# jar files in sub directories are ignored. +# The given value is in addition to any jars found in the lib directory. +# All entries will be added to the class path of the system class loader +# and also to the path of the JMeter internal loader. +# Paths with spaces may cause problems for the JVM +#Example for windows (; separator) +#user.classpath=../classes;../lib;../app1/jar1.jar;../app2/jar2.jar +#Example for linux (:separator) +#user.classpath=../classes:../lib:../app1/jar1.jar:../app2/jar2.jar + +# List of paths (separated by ;) that JMeter will search for utility +# and plugin dependency classes. +# A path item can either be a jar file or a directory. +# Any jar file in such a directory will be automatically included, +# jar files in sub directories are ignored. +# The given value is in addition to any jars found in the lib directory +# or given by the user.classpath property. +# All entries will be added to the path of the JMeter internal loader only. +# For plugin dependencies using plugin_dependency_paths should be preferred over +# user.classpath. +#plugin_dependency_paths=../dependencies/lib;../app1/jar1.jar;../app2/jar2.jar + +#--------------------------------------------------------------------------- +# Reporting configuration +#--------------------------------------------------------------------------- + +# Configure this property to change the report title +#jmeter.reportgenerator.report_title=Apache JMeter Dashboard + +# Used to generate a report based on a date range if needed +# Default date format (from SimpleDateFormat Java API and Locale.ENGLISH) +#jmeter.reportgenerator.date_format=yyyyMMddHHmmss +# Date range start date using date_format property +#jmeter.reportgenerator.start_date= +# Date range end date using date_format property +#jmeter.reportgenerator.end_date= + +# Change this parameter if you want to change the granularity of over time graphs. +#jmeter.reportgenerator.overall_granularity=60000 + +# Change this parameter if you want to change the granularity of Response time distribution +# Set to 100 ms by default +#jmeter.reportgenerator.graph.responseTimeDistribution.property.set_granularity=100 + +# Change this parameter if you want to keep only some samples. +# Regular Expression which Indicates which samples to keep for graphs and statistics generation. +# Empty value means no filtering +#jmeter.reportgenerator.sample_filter= + +# Change this parameter if you want to override the APDEX satisfaction threshold. +#jmeter.reportgenerator.apdex_satisfied_threshold=500 + +# Change this parameter if you want to override the APDEX tolerance threshold. +#jmeter.reportgenerator.apdex_tolerated_threshold=1500 + +# Indicates which graph series are filtered (regular expression) +# In the below example we filter on Search and Order samples +# Note that the end of the pattern should always include (-success|-failure)?$ +# TransactionsPerSecondGraphConsumer suffixes transactions with "-success" or "-failure" depending +# on the result +#jmeter.reportgenerator.exporter.html.series_filter=^(Search|Order)(-success|-failure)?$ + +# Indicates whether only controller samples are displayed on graphs that support it. +#jmeter.reportgenerator.exporter.html.show_controllers_only=false + +# This property is used by menu item "Export transactions for report" +# It is used to select which transactions by default will be exported +#jmeter.reportgenerator.exported_transactions_pattern=[a-zA-Z0-9_\\-{}\\$\\.]*[-_][0-9]* + + +## Custom graph definition +#jmeter.reportgenerator.graph.custom_mm_hit.classname=org.apache.jmeter.report.processor.graph.impl.CustomGraphConsumer +#jmeter.reportgenerator.graph.custom_mm_hit.title=Graph Title +#jmeter.reportgenerator.graph.custom_mm_hit.property.set_Y_Axis=Response Time (ms) +#jmeter.reportgenerator.graph.custom_mm_hit.property.set_X_Axis=Over Time +#jmeter.reportgenerator.graph.custom_mm_hit.property.set_granularity=${jmeter.reportgenerator.overall_granularity} +#jmeter.reportgenerator.graph.custom_mm_hit.property.setSampleVariableName=VarName +#jmeter.reportgenerator.graph.custom_mm_hit.property.setContentMessage=Message for graph point label + +######################################################################## +################## DISTRIBUTED TESTING CONFIGURATION ################## +######################################################################## +# Type of keystore : JKS +# +#server.rmi.ssl.keystore.type=JKS +# +# Keystore file that contains private key +# +#server.rmi.ssl.keystore.file=rmi_keystore.jks +# +# Password of Keystore +# +#server.rmi.ssl.keystore.password=changeit +# +# Key alias +# +#server.rmi.ssl.keystore.alias=rmi +# +# Type of truststore : JKS +# +#server.rmi.ssl.truststore.type=JKS +# +# Keystore file that contains certificate +# +#server.rmi.ssl.truststore.file=rmi_keystore.jks +# +# Password of Trust store +# +#server.rmi.ssl.truststore.password=changeit +# +# Set this if you don't want to use SSL for RMI +# +#server.rmi.ssl.disable=false \ No newline at end of file From 2671372a9bb74a66b86f570e5c718c08adebd126 Mon Sep 17 00:00:00 2001 From: liuruibin Date: Fri, 16 Apr 2021 20:46:26 +0800 Subject: [PATCH 049/109] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9dockerfil?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index cea8bc7..a99da6c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,10 +4,12 @@ MAINTAINER FIT2CLOUD ARG MS_VERSION=dev -RUN mkdir -p /opt/apps +RUN mkdir -p /opt/apps && mkdir -p /opt/jmeter/lib/junit ADD target/node-controller-1.8.jar /opt/apps +COPY target/classes/jmeter/ /opt/jmeter/ + ENV JAVA_APP_JAR=/opt/apps/node-controller-1.8.jar ENV AB_OFF=true From 8edc50fe373e6651356b3d05f1fe3674d320c120 Mon Sep 17 00:00:00 2001 From: liuruibin Date: Fri, 16 Apr 2021 21:29:11 +0800 Subject: [PATCH 050/109] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9pom?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/pom.xml b/pom.xml index 127e7fa..88e6033 100644 --- a/pom.xml +++ b/pom.xml @@ -403,6 +403,44 @@ org.springframework.boot spring-boot-maven-plugin + + org.apache.maven.plugins + maven-dependency-plugin + + + copy + generate-resources + + copy + + + + + + + org.apache.jmeter + ApacheJMeter_functions + ${jmeter.version} + jar + true + src/main/resources/jmeter/lib/ext + ApacheJMeter_functions.jar + + + org.python + jython-standalone + 2.7.0 + jar + true + src/main/resources/jmeter/lib/ext + jython-standalone.jar + + + ${project.build.directory}/wars + false + true + + From c5b09600a4d8f0d3b0b7b91255c201571c8bd9fa Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Wed, 21 Apr 2021 14:30:45 +0800 Subject: [PATCH 051/109] refactor: ignore .jar --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 179b07f..b9d234f 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ target/ *.iml *.ipr src/test/ +*.jar \ No newline at end of file From 3832ddb8a4a879f58407f705cbb49ab2874abb58 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Wed, 21 Apr 2021 14:41:16 +0800 Subject: [PATCH 052/109] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9pom?= =?UTF-8?q?=E5=8E=BB=E6=8E=89dubbo=E4=B8=AD=E5=B8=A6=E6=9C=89=E7=9A=84spri?= =?UTF-8?q?ng?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pom.xml b/pom.xml index 88e6033..7190ae3 100644 --- a/pom.xml +++ b/pom.xml @@ -184,6 +184,12 @@ org.apache.dubbo dubbo ${dubbo.version} + + + spring-context + org.springframework + + org.apache.zookeeper From 48d3d19a919edfd314bcf111f17e29a9fdd6db08 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Wed, 21 Apr 2021 15:51:00 +0800 Subject: [PATCH 053/109] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9pom=20gro?= =?UTF-8?q?ovy=20=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 7190ae3..d61197e 100644 --- a/pom.xml +++ b/pom.xml @@ -259,7 +259,7 @@ org.codehaus.groovy groovy-jsr223 - 2.5.10 + 3.0.8 From 111559a96230e78398e1bc5ba6a0296b803859d0 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Wed, 21 Apr 2021 18:34:51 +0800 Subject: [PATCH 054/109] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9pom=20gro?= =?UTF-8?q?ovy=20=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d61197e..94c86e4 100644 --- a/pom.xml +++ b/pom.xml @@ -19,6 +19,7 @@ 5.4.1 1.1.3 2.7.8 + 3.0.8 @@ -259,7 +260,6 @@ org.codehaus.groovy groovy-jsr223 - 3.0.8 From e77f6ce00eb5484a979f349b958a0c18b5ef1cdd Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Thu, 22 Apr 2021 10:08:45 +0800 Subject: [PATCH 055/109] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9=20NewDri?= =?UTF-8?q?ver=20loader?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/org/apache/jmeter/NewDriver.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/apache/jmeter/NewDriver.java b/src/main/java/org/apache/jmeter/NewDriver.java index fa24fcd..9b30948 100644 --- a/src/main/java/org/apache/jmeter/NewDriver.java +++ b/src/main/java/org/apache/jmeter/NewDriver.java @@ -127,7 +127,7 @@ public static void loaderClass(String name) { System.setProperty(JAVA_CLASS_PATH, initiaClasspath + classpath.toString()); loader = AccessController.doPrivileged( (PrivilegedAction) () -> - new DynamicClassLoader(jars.toArray(new URL[jars.size()])) + new DynamicClassLoader(jars.toArray(new URL[jars.size()]), Thread.currentThread().getContextClassLoader()) ); } From 73b41b682bae93ea4491cac639eab8910f5e51bb Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Mon, 26 Apr 2021 18:59:43 +0800 Subject: [PATCH 056/109] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96):=20=E5=90=8C=E6=AD=A5=E5=8F=98=E6=9B=B4?= =?UTF-8?q?=E9=83=A8=E5=88=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/JmeterExecuteController.java | 8 +++++ .../api/jmeter/APIBackendListenerClient.java | 10 +++--- .../metersphere/api/jmeter/JMeterService.java | 2 +- .../io/metersphere/api/module/JvmInfo.java | 35 +++++++++++++++++++ .../{jmeter => }/module/RequestResult.java | 2 +- .../module/ResponseAssertionResult.java | 2 +- .../{jmeter => }/module/ResponseResult.java | 2 +- .../{jmeter => }/module/ScenarioResult.java | 2 +- .../api/{jmeter => }/module/TestResult.java | 10 +++--- .../metersphere/api/service/JvmService.java | 35 +++++++++++++++++++ 10 files changed, 93 insertions(+), 15 deletions(-) create mode 100644 src/main/java/io/metersphere/api/module/JvmInfo.java rename src/main/java/io/metersphere/api/{jmeter => }/module/RequestResult.java (94%) rename src/main/java/io/metersphere/api/{jmeter => }/module/ResponseAssertionResult.java (78%) rename src/main/java/io/metersphere/api/{jmeter => }/module/ResponseResult.java (91%) rename src/main/java/io/metersphere/api/{jmeter => }/module/ScenarioResult.java (95%) rename src/main/java/io/metersphere/api/{jmeter => }/module/TestResult.java (92%) create mode 100644 src/main/java/io/metersphere/api/service/JvmService.java diff --git a/src/main/java/io/metersphere/api/controller/JmeterExecuteController.java b/src/main/java/io/metersphere/api/controller/JmeterExecuteController.java index 03a9c1b..7d0a7e4 100644 --- a/src/main/java/io/metersphere/api/controller/JmeterExecuteController.java +++ b/src/main/java/io/metersphere/api/controller/JmeterExecuteController.java @@ -2,7 +2,9 @@ import com.alibaba.fastjson.JSON; import io.metersphere.api.controller.request.RunRequest; +import io.metersphere.api.module.JvmInfo; import io.metersphere.api.service.JmeterExecuteService; +import io.metersphere.api.service.JvmService; import io.metersphere.node.util.LogUtil; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; @@ -28,4 +30,10 @@ public String getStatus() { return "OK"; } + + @GetMapping("/getJvmInfo") + public JvmInfo getJvmInfo() { + return JvmService.jvmInfo(); + } + } diff --git a/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java b/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java index c78488a..262eee3 100644 --- a/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java +++ b/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java @@ -4,7 +4,7 @@ import io.metersphere.api.service.LoadTestProducer; import io.metersphere.api.jmeter.constants.ApiRunMode; import io.metersphere.api.jmeter.constants.RequestType; -import io.metersphere.api.jmeter.module.*; +import io.metersphere.api.module.*; import io.metersphere.api.jmeter.utils.CommonBeanFactory; import io.metersphere.node.util.LogUtil; import org.apache.commons.lang3.StringUtils; @@ -28,7 +28,7 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl public final static String TEST_ID = "ms.test.id"; - public final static String TEST_REPORT_NAME = "ms.test.report.name"; + public final static String TEST_REPORT_ID = "ms.test.report.name"; private final static String THREAD_SPLIT = " "; @@ -52,7 +52,7 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl /** * 只有合并报告是这个有值 */ - private String reportName; + private String setReportId; /** * 获得控制台内容 @@ -88,7 +88,7 @@ public void teardownTest(BackendListenerContext context) throws Exception { TestResult testResult = new TestResult(); testResult.setTestId(testId); testResult.setTotal(queue.size()); - testResult.setReportName(this.reportName); + testResult.setSetReportId(this.setReportId); testResult.setDebug(this.isDebug); testResult.setUserId(this.userId); loadTestProducer = CommonBeanFactory.getBean(LoadTestProducer.class); @@ -224,7 +224,7 @@ private String getMethod(SampleResult result) { private void setParam(BackendListenerContext context) { this.testId = context.getParameter(TEST_ID); - this.reportName = context.getParameter(TEST_REPORT_NAME); + this.setReportId = context.getParameter(TEST_REPORT_ID); this.runMode = context.getParameter("runMode"); this.isDebug = StringUtils.equals(context.getParameter("DEBUG"), "DEBUG") ? true : false; this.userId = context.getParameter("USER_ID"); diff --git a/src/main/java/io/metersphere/api/jmeter/JMeterService.java b/src/main/java/io/metersphere/api/jmeter/JMeterService.java index 670023d..152aedc 100644 --- a/src/main/java/io/metersphere/api/jmeter/JMeterService.java +++ b/src/main/java/io/metersphere/api/jmeter/JMeterService.java @@ -59,7 +59,7 @@ private void addBackendListener(HashTree testPlan, RunRequest request) { backendListener.setName(request.getTestId()); Arguments arguments = new Arguments(); if (request.getConfig() != null && request.getConfig().getMode().equals("serial") && request.getConfig().getReportType().equals("setReport")) { - arguments.addArgument(APIBackendListenerClient.TEST_REPORT_NAME, request.getConfig().getReportName()); + arguments.addArgument(APIBackendListenerClient.TEST_REPORT_ID, request.getConfig().getReportName()); } arguments.addArgument(APIBackendListenerClient.TEST_ID, request.getTestId()); if (StringUtils.isNotBlank(request.getRunMode())) { diff --git a/src/main/java/io/metersphere/api/module/JvmInfo.java b/src/main/java/io/metersphere/api/module/JvmInfo.java new file mode 100644 index 0000000..05c8dab --- /dev/null +++ b/src/main/java/io/metersphere/api/module/JvmInfo.java @@ -0,0 +1,35 @@ +package io.metersphere.api.module; + +import lombok.Data; + +@Data +public class JvmInfo { + /** + * JVM内存的空闲空间为 + */ + private long vmFree; + /** + * JVM内存已用的空间为 + */ + private long vmUse; + + private long vmTotal; + /** + * JVM总内存空间为 + */ + private long vmMax; + + private int totalThread; + + public JvmInfo() { + + } + + public JvmInfo(long vmTotal, long vmFree, long vmMax, long vmUse, int totalThread) { + this.vmFree = vmFree; + this.vmTotal = vmTotal; + this.vmMax = vmMax; + this.vmUse = vmUse; + this.totalThread = totalThread; + } +} diff --git a/src/main/java/io/metersphere/api/jmeter/module/RequestResult.java b/src/main/java/io/metersphere/api/module/RequestResult.java similarity index 94% rename from src/main/java/io/metersphere/api/jmeter/module/RequestResult.java rename to src/main/java/io/metersphere/api/module/RequestResult.java index d9cec5c..b650fff 100644 --- a/src/main/java/io/metersphere/api/jmeter/module/RequestResult.java +++ b/src/main/java/io/metersphere/api/module/RequestResult.java @@ -1,4 +1,4 @@ -package io.metersphere.api.jmeter.module; +package io.metersphere.api.module; import lombok.Data; diff --git a/src/main/java/io/metersphere/api/jmeter/module/ResponseAssertionResult.java b/src/main/java/io/metersphere/api/module/ResponseAssertionResult.java similarity index 78% rename from src/main/java/io/metersphere/api/jmeter/module/ResponseAssertionResult.java rename to src/main/java/io/metersphere/api/module/ResponseAssertionResult.java index 05c4512..f75904c 100644 --- a/src/main/java/io/metersphere/api/jmeter/module/ResponseAssertionResult.java +++ b/src/main/java/io/metersphere/api/module/ResponseAssertionResult.java @@ -1,4 +1,4 @@ -package io.metersphere.api.jmeter.module; +package io.metersphere.api.module; import lombok.Data; diff --git a/src/main/java/io/metersphere/api/jmeter/module/ResponseResult.java b/src/main/java/io/metersphere/api/module/ResponseResult.java similarity index 91% rename from src/main/java/io/metersphere/api/jmeter/module/ResponseResult.java rename to src/main/java/io/metersphere/api/module/ResponseResult.java index 7e4ad10..f1ecd30 100644 --- a/src/main/java/io/metersphere/api/jmeter/module/ResponseResult.java +++ b/src/main/java/io/metersphere/api/module/ResponseResult.java @@ -1,4 +1,4 @@ -package io.metersphere.api.jmeter.module; +package io.metersphere.api.module; import lombok.Data; diff --git a/src/main/java/io/metersphere/api/jmeter/module/ScenarioResult.java b/src/main/java/io/metersphere/api/module/ScenarioResult.java similarity index 95% rename from src/main/java/io/metersphere/api/jmeter/module/ScenarioResult.java rename to src/main/java/io/metersphere/api/module/ScenarioResult.java index 3be1644..d388e3f 100644 --- a/src/main/java/io/metersphere/api/jmeter/module/ScenarioResult.java +++ b/src/main/java/io/metersphere/api/module/ScenarioResult.java @@ -1,4 +1,4 @@ -package io.metersphere.api.jmeter.module; +package io.metersphere.api.module; import lombok.Data; diff --git a/src/main/java/io/metersphere/api/jmeter/module/TestResult.java b/src/main/java/io/metersphere/api/module/TestResult.java similarity index 92% rename from src/main/java/io/metersphere/api/jmeter/module/TestResult.java rename to src/main/java/io/metersphere/api/module/TestResult.java index 60ed9b0..9e818bc 100644 --- a/src/main/java/io/metersphere/api/jmeter/module/TestResult.java +++ b/src/main/java/io/metersphere/api/module/TestResult.java @@ -1,8 +1,8 @@ -package io.metersphere.api.jmeter.module; +package io.metersphere.api.module; import lombok.Data; +import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; -import org.apache.dubbo.common.utils.CollectionUtils; import java.util.ArrayList; import java.util.List; @@ -12,13 +12,13 @@ public class TestResult { private String testId; - private boolean isDebug; + private String setReportId; private String userId; - private String runMode; + private boolean isDebug; - private String reportName; + private String runMode; private int success = 0; diff --git a/src/main/java/io/metersphere/api/service/JvmService.java b/src/main/java/io/metersphere/api/service/JvmService.java new file mode 100644 index 0000000..2e470ea --- /dev/null +++ b/src/main/java/io/metersphere/api/service/JvmService.java @@ -0,0 +1,35 @@ +/** + * + */ +package io.metersphere.api.service; + +import io.metersphere.api.module.JvmInfo; + +/** + * @author mr.zhao + */ +public class JvmService { + + // 日志输出 + public static JvmInfo jvmInfo() { + // 虚拟机级内存情况查询 + long vmFree = 0; + long vmUse = 0; + long vmTotal = 0; + long vmMax = 0; + int byteToMb = 1024 * 1024; + Runtime rt = Runtime.getRuntime(); + vmTotal = rt.totalMemory() / byteToMb; + vmFree = rt.freeMemory() / byteToMb; + vmMax = rt.maxMemory() / byteToMb; + vmUse = vmTotal - vmFree; + // 获得线程总数 + ThreadGroup parentThread; + int totalThread = 0; + for (parentThread = Thread.currentThread().getThreadGroup(); parentThread + .getParent() != null; parentThread = parentThread.getParent()) { + totalThread = parentThread.activeCount(); + } + return new JvmInfo(vmTotal, vmFree, vmMax, vmUse, totalThread); + } +} From 242fcac78f027189c52954ba9b7332e54cc67a1a Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Thu, 29 Apr 2021 10:23:32 +0800 Subject: [PATCH 057/109] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96):=20=E4=BF=AE=E5=A4=8Dcsv=E5=B9=B6=E5=8F=91?= =?UTF-8?q?=E5=8A=A0=E8=BD=BD=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apache/jmeter/services/FileServer.java | 582 ++++++++++++++++++ 1 file changed, 582 insertions(+) create mode 100644 src/main/java/org/apache/jmeter/services/FileServer.java diff --git a/src/main/java/org/apache/jmeter/services/FileServer.java b/src/main/java/org/apache/jmeter/services/FileServer.java new file mode 100644 index 0000000..d51e9a8 --- /dev/null +++ b/src/main/java/org/apache/jmeter/services/FileServer.java @@ -0,0 +1,582 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jmeter.services; + +import org.apache.commons.io.input.BOMInputStream; +import org.apache.jmeter.gui.JMeterFileFilter; +import org.apache.jmeter.save.CSVSaveService; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.util.JOrphanUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.*; +import java.nio.file.Files; +import java.util.ArrayDeque; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ThreadLocalRandom; + +/** + * This class provides thread-safe access to files, and to + * provide some simplifying assumptions about where to find files and how to + * name them. For instance, putting supporting files in the same directory as + * the saved test plan file allows users to refer to the file with just it's + * name - this FileServer class will find the file without a problem. + * Eventually, I want all in-test file access to be done through here, with the + * goal of packaging up entire test plans as a directory structure that can be + * sent via rmi to remote servers (currently, one must make sure the remote + * server has all support files in a relative-same location) and to package up + * test plans to execute on unknown boxes that only have Java installed. + */ +public class FileServer { + + private static final Logger log = LoggerFactory.getLogger(FileServer.class); + + /** + * The default base used for resolving relative files, i.e.
+ * {@code System.getProperty("user.dir")} + */ + private static final String DEFAULT_BASE = System.getProperty("user.dir");// $NON-NLS-1$ + + /** Default base prefix: {@value} */ + private static final String BASE_PREFIX_DEFAULT = "~/"; // $NON-NLS-1$ + + private static final String BASE_PREFIX = + JMeterUtils.getPropDefault("jmeter.save.saveservice.base_prefix", // $NON-NLS-1$ + BASE_PREFIX_DEFAULT); + + private File base; + + private final Map files = new HashMap<>(); + + private static final FileServer server = new FileServer(); + + // volatile needed to ensure safe publication + private volatile String scriptName; + + // Cannot be instantiated + private FileServer() { + base = new File(DEFAULT_BASE); + log.info("Default base='{}'", DEFAULT_BASE); + } + + /** + * @return the singleton instance of the server. + */ + public static FileServer getFileServer() { + return server; + } + + /** + * Resets the current base to DEFAULT_BASE. + */ + public synchronized void resetBase() { + checkForOpenFiles(); + base = new File(DEFAULT_BASE); + log.info("Reset base to '{}'", base); + } + + /** + * Sets the current base directory for relative file names from the provided path. + * If the path does not refer to an existing directory, then its parent is used. + * Normally the provided path is a file, so using the parent directory is appropriate. + * + * @param basedir the path to set, or {@code null} if the GUI is being cleared + * @throws IllegalStateException if files are still open + */ + public synchronized void setBasedir(String basedir) { + checkForOpenFiles(); // TODO should this be called if basedir == null? + if (basedir != null) { + File newBase = new File(basedir); + if (!newBase.isDirectory()) { + newBase = newBase.getParentFile(); + } + base = newBase; + log.info("Set new base='{}'", base); + } + } + + /** + * Sets the current base directory for relative file names from the provided script file. + * The parameter is assumed to be the path to a JMX file, so the base directory is derived + * from its parent. + * + * @param scriptPath the path of the script file; must be not be {@code null} + * @throws IllegalStateException if files are still open + * @throws IllegalArgumentException if scriptPath parameter is null + */ + public synchronized void setBaseForScript(File scriptPath) { + if (scriptPath == null){ + throw new IllegalArgumentException("scriptPath must not be null"); + } + setScriptName(scriptPath.getName()); + // getParentFile() may not work on relative paths + setBase(scriptPath.getAbsoluteFile().getParentFile()); + } + + /** + * Sets the current base directory for relative file names. + * + * @param jmxBase the path of the script file base directory, cannot be null + * @throws IllegalStateException if files are still open + * @throws IllegalArgumentException if {@code basepath} is null + */ + public synchronized void setBase(File jmxBase) { + if (jmxBase == null) { + throw new IllegalArgumentException("jmxBase must not be null"); + } + checkForOpenFiles(); + base = jmxBase; + log.info("Set new base='{}'", base); + } + + /** + * Check if there are entries in use. + *

+ * Caller must ensure that access to the files map is single-threaded as + * there is a window between checking the files Map and clearing it. + * + * @throws IllegalStateException if there are any entries still in use + */ + private void checkForOpenFiles() throws IllegalStateException { + if (filesOpen()) { // checks for entries in use + throw new IllegalStateException("Files are still open, cannot change base directory"); + } + files.clear(); // tidy up any unused entries + } + + public synchronized String getBaseDir() { + return base.getAbsolutePath(); + } + + public static String getDefaultBase(){ + return DEFAULT_BASE; + } + + /** + * Calculates the relative path from DEFAULT_BASE to the current base, + * which must be the same as or a child of the default. + * + * @return the relative path, or {@code "."} if the path cannot be determined + */ + public synchronized File getBaseDirRelative() { + // Must first convert to absolute path names to ensure parents are available + File parent = new File(DEFAULT_BASE).getAbsoluteFile(); + File f = base.getAbsoluteFile(); + ArrayDeque l = new ArrayDeque<>(); + while (f != null) { + if (f.equals(parent)){ + if (l.isEmpty()){ + break; + } + File rel = new File(l.pop()); + while(!l.isEmpty()) { + rel = new File(rel, l.pop()); + } + return rel; + } + l.push(f.getName()); + f = f.getParentFile(); + } + return new File("."); + } + + /** + * Creates an association between a filename and a File inputOutputObject, + * and stores it for later use - unless it is already stored. + * + * @param filename - relative (to base) or absolute file name (must not be null) + */ + public void reserveFile(String filename) { + reserveFile(filename,null); + } + + /** + * Creates an association between a filename and a File inputOutputObject, + * and stores it for later use - unless it is already stored. + * + * @param filename - relative (to base) or absolute file name (must not be null) + * @param charsetName - the character set encoding to use for the file (may be null) + */ + public void reserveFile(String filename, String charsetName) { + reserveFile(filename, charsetName, filename, false); + } + + /** + * Creates an association between a filename and a File inputOutputObject, + * and stores it for later use - unless it is already stored. + * + * @param filename - relative (to base) or absolute file name (must not be null) + * @param charsetName - the character set encoding to use for the file (may be null) + * @param alias - the name to be used to access the object (must not be null) + */ + public void reserveFile(String filename, String charsetName, String alias) { + reserveFile(filename, charsetName, alias, false); + } + + /** + * Creates an association between a filename and a File inputOutputObject, + * and stores it for later use - unless it is already stored. + * + * @param filename - relative (to base) or absolute file name (must not be null or empty) + * @param charsetName - the character set encoding to use for the file (may be null) + * @param alias - the name to be used to access the object (must not be null) + * @param hasHeader true if the file has a header line describing the contents + * @return the header line; may be null + * @throws IllegalArgumentException if header could not be read or filename is null or empty + */ + public synchronized String reserveFile(String filename, String charsetName, String alias, boolean hasHeader) { + if (filename == null || filename.isEmpty()){ + throw new IllegalArgumentException("Filename must not be null or empty"); + } + if (alias == null){ + throw new IllegalArgumentException("Alias must not be null"); + } + FileEntry fileEntry = files.get(alias); + if (fileEntry == null) { + fileEntry = new FileEntry(resolveFileFromPath(filename), null, charsetName); + if (filename.equals(alias)){ + log.info("Stored: {}", filename); + } else { + log.info("Stored: {} Alias: {}", filename, alias); + } + files.put(alias, fileEntry); + if (hasHeader) { + try { + fileEntry.headerLine = readLine(alias, false); + if (fileEntry.headerLine == null) { + fileEntry.exception = new EOFException("File is empty: " + fileEntry.file); + } + } catch (IOException | IllegalArgumentException e) { + fileEntry.exception = e; + } + } + } + if (hasHeader && fileEntry.headerLine == null) { + throw new IllegalArgumentException("Could not read file header line for file " + filename, + fileEntry.exception); + } + return fileEntry.headerLine; + } + + /** + * Resolves file name into {@link File} instance. + * When filename is not absolute and not found from current working dir, + * it tries to find it under current base directory + * @param filename original file name + * @return {@link File} instance + */ + private File resolveFileFromPath(String filename) { + File f = new File(filename); + if (f.isAbsolute() || f.exists()) { + return f; + } else { + return new File(base, filename); + } + } + + /** + * Get the next line of the named file, recycle by default. + * + * @param filename the filename or alias that was used to reserve the file + * @return String containing the next line in the file + * @throws IOException when reading of the file fails, or the file was not reserved properly + */ + public String readLine(String filename) throws IOException { + return readLine(filename, true); + } + + /** + * Get the next line of the named file, first line is name to false + * + * @param filename the filename or alias that was used to reserve the file + * @param recycle - should file be restarted at EOF? + * @return String containing the next line in the file (null if EOF reached and not recycle) + * @throws IOException when reading of the file fails, or the file was not reserved properly + */ + public String readLine(String filename, boolean recycle) throws IOException { + return readLine(filename, recycle, false); + } + /** + * Get the next line of the named file + * + * @param filename the filename or alias that was used to reserve the file + * @param recycle - should file be restarted at EOF? + * @param ignoreFirstLine - Ignore first line + * @return String containing the next line in the file (null if EOF reached and not recycle) + * @throws IOException when reading of the file fails, or the file was not reserved properly + */ + public synchronized String readLine(String filename, boolean recycle, + boolean ignoreFirstLine) throws IOException { + FileEntry fileEntry = files.get(filename); + if(fileEntry == null ){ + this.reserveFile(filename); + fileEntry = files.get(filename); + } + if (fileEntry != null) { + if (fileEntry.inputOutputObject == null) { + fileEntry.inputOutputObject = createBufferedReader(fileEntry); + } else if (!(fileEntry.inputOutputObject instanceof Reader)) { + throw new IOException("File " + filename + " already in use"); + } + BufferedReader reader = (BufferedReader) fileEntry.inputOutputObject; + String line = reader.readLine(); + if (line == null && recycle) { + reader.close(); + reader = createBufferedReader(fileEntry); + fileEntry.inputOutputObject = reader; + if (ignoreFirstLine) { + // read first line and forget + reader.readLine();//NOSONAR + } + line = reader.readLine(); + } + log.debug("Read:{}", line); + return line; + } + throw new IOException("File never reserved: "+filename); + } + + /** + * + * @param alias the file name or alias + * @param recycle whether the file should be re-started on EOF + * @param ignoreFirstLine whether the file contains a file header which will be ignored + * @param delim the delimiter to use for parsing + * @return the parsed line, will be empty if the file is at EOF + * @throws IOException when reading of the aliased file fails, or the file was not reserved properly + */ + public synchronized String[] getParsedLine(String alias, boolean recycle, boolean ignoreFirstLine, char delim) throws IOException { + BufferedReader reader = getReader(alias, recycle, ignoreFirstLine); + return CSVSaveService.csvReadFile(reader, delim); + } + + /** + * Return BufferedReader handling close if EOF reached and recycle is true + * and ignoring first line if ignoreFirstLine is true + * + * @param alias String alias + * @param recycle Recycle at eof + * @param ignoreFirstLine Ignore first line + * @return {@link BufferedReader} + */ + private BufferedReader getReader(String alias, boolean recycle, boolean ignoreFirstLine) throws IOException { + FileEntry fileEntry = files.get(alias); + if (fileEntry != null) { + BufferedReader reader; + if (fileEntry.inputOutputObject == null) { + reader = createBufferedReader(fileEntry); + fileEntry.inputOutputObject = reader; + if (ignoreFirstLine) { + // read first line and forget + reader.readLine(); //NOSONAR + } + } else if (!(fileEntry.inputOutputObject instanceof Reader)) { + throw new IOException("File " + alias + " already in use"); + } else { + reader = (BufferedReader) fileEntry.inputOutputObject; + if (recycle) { // need to check if we are at EOF already + reader.mark(1); + int peek = reader.read(); + if (peek == -1) { // already at EOF + reader.close(); + reader = createBufferedReader(fileEntry); + fileEntry.inputOutputObject = reader; + if (ignoreFirstLine) { + // read first line and forget + reader.readLine(); //NOSONAR + } + } else { // OK, we still have some data, restore it + reader.reset(); + } + } + } + return reader; + } else { + throw new IOException("File never reserved: "+alias); + } + } + + private BufferedReader createBufferedReader(FileEntry fileEntry) throws IOException { + if (!fileEntry.file.canRead() || !fileEntry.file.isFile()) { + throw new IllegalArgumentException("File "+ fileEntry.file.getName()+ " must exist and be readable"); + } + BOMInputStream fis = new BOMInputStream(Files.newInputStream(fileEntry.file.toPath())); //NOSONAR + InputStreamReader isr = null; + // If file encoding is specified, read using that encoding, otherwise use default platform encoding + String charsetName = fileEntry.charSetEncoding; + if(!JOrphanUtils.isBlank(charsetName)) { + isr = new InputStreamReader(fis, charsetName); + } else if (fis.hasBOM()) { + isr = new InputStreamReader(fis, fis.getBOM().getCharsetName()); + } else { + @SuppressWarnings("DefaultCharset") + final InputStreamReader withPlatformEncoding = new InputStreamReader(fis); + isr = withPlatformEncoding; + } + return new BufferedReader(isr); + } + + public synchronized void write(String filename, String value) throws IOException { + FileEntry fileEntry = files.get(filename); + if (fileEntry != null) { + if (fileEntry.inputOutputObject == null) { + fileEntry.inputOutputObject = createBufferedWriter(fileEntry); + } else if (!(fileEntry.inputOutputObject instanceof Writer)) { + throw new IOException("File " + filename + " already in use"); + } + BufferedWriter writer = (BufferedWriter) fileEntry.inputOutputObject; + log.debug("Write:{}", value); + writer.write(value); + } else { + throw new IOException("File never reserved: "+filename); + } + } + + private BufferedWriter createBufferedWriter(FileEntry fileEntry) throws IOException { + OutputStream fos = Files.newOutputStream(fileEntry.file.toPath()); + OutputStreamWriter osw; + // If file encoding is specified, write using that encoding, otherwise use default platform encoding + String charsetName = fileEntry.charSetEncoding; + if(!JOrphanUtils.isBlank(charsetName)) { + osw = new OutputStreamWriter(fos, charsetName); + } else { + @SuppressWarnings("DefaultCharset") + final OutputStreamWriter withPlatformEncoding = new OutputStreamWriter(fos); + osw = withPlatformEncoding; + } + return new BufferedWriter(osw); + } + + public synchronized void closeFiles() throws IOException { + for (Map.Entry me : files.entrySet()) { + closeFile(me.getKey(),me.getValue() ); + } + files.clear(); + } + + /** + * @param name the name or alias of the file to be closed + * @throws IOException when closing of the aliased file fails + */ + public synchronized void closeFile(String name) throws IOException { + FileEntry fileEntry = files.get(name); + closeFile(name, fileEntry); + } + + private void closeFile(String name, FileEntry fileEntry) throws IOException { + if (fileEntry != null && fileEntry.inputOutputObject != null) { + log.info("Close: {}", name); + fileEntry.inputOutputObject.close(); + fileEntry.inputOutputObject = null; + } + } + + boolean filesOpen() { // package access for test code only + return files.values().stream() + .anyMatch(fileEntry -> fileEntry.inputOutputObject != null); + } + + /** + * Method will get a random file in a base directory + *

+ * TODO hey, not sure this method belongs here. + * FileServer is for thread safe File access relative to current test's base directory. + * + * @param basedir name of the directory in which the files can be found + * @param extensions array of allowed extensions, if null is given, + * any file be allowed + * @return a random File from the basedir that matches one of + * the extensions + */ + public File getRandomFile(String basedir, String[] extensions) { + File input = null; + if (basedir != null) { + File src = new File(basedir); + File[] lfiles = src.listFiles(new JMeterFileFilter(extensions)); + if (lfiles != null) { + // lfiles cannot be null as it has been checked before + int count = lfiles.length; + input = lfiles[ThreadLocalRandom.current().nextInt(count)]; + } + } + return input; + } + + /** + * Get {@link File} instance for provided file path, + * resolve file location relative to base dir or script dir when needed + * + * @param path original path to file, maybe relative + * @return {@link File} instance + */ + public File getResolvedFile(String path) { + reserveFile(path); + return files.get(path).file; + } + + private static class FileEntry{ + private String headerLine; + private Throwable exception; + private final File file; + private Closeable inputOutputObject; + private final String charSetEncoding; + + FileEntry(File f, Closeable o, String e) { + file = f; + inputOutputObject = o; + charSetEncoding = e; + } + } + + /** + * Resolve a file name that may be relative to the base directory. If the + * name begins with the value of the JMeter property + * "jmeter.save.saveservice.base_prefix" - default "~/" - then the name is + * assumed to be relative to the basename. + * + * @param relativeName + * filename that should be checked for + * jmeter.save.saveservice.base_prefix + * @return the updated filename + */ + public static String resolveBaseRelativeName(String relativeName) { + if (relativeName.startsWith(BASE_PREFIX)){ + String newName = relativeName.substring(BASE_PREFIX.length()); + return new File(getFileServer().getBaseDir(),newName).getAbsolutePath(); + } + return relativeName; + } + + /** + * @return JMX Script name + * @since 2.6 + */ + public String getScriptName() { + return scriptName; + } + + /** + * @param scriptName Script name + * @since 2.6 + */ + public void setScriptName(String scriptName) { + this.scriptName = scriptName; + } +} From cde85c1f231320b3fc7d3517349afbbe02da6131 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Fri, 30 Apr 2021 15:53:01 +0800 Subject: [PATCH 058/109] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96)=20=E4=BF=AE=E5=A4=8D=E5=B9=B6=E5=8F=91?= =?UTF-8?q?=E6=89=A7=E8=A1=8C=E6=97=B6CSV=E5=8F=96=E4=B8=8D=E5=88=B0?= =?UTF-8?q?=E5=80=BC=E9=97=AE=E9=A2=98=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/apache/jmeter/config/CSVDataSet.java | 346 +++++++++++ .../apache/jmeter/services/FileServer.java | 582 ------------------ 2 files changed, 346 insertions(+), 582 deletions(-) create mode 100644 src/main/java/org/apache/jmeter/config/CSVDataSet.java delete mode 100644 src/main/java/org/apache/jmeter/services/FileServer.java diff --git a/src/main/java/org/apache/jmeter/config/CSVDataSet.java b/src/main/java/org/apache/jmeter/config/CSVDataSet.java new file mode 100644 index 0000000..7fd554f --- /dev/null +++ b/src/main/java/org/apache/jmeter/config/CSVDataSet.java @@ -0,0 +1,346 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jmeter.config; + +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.engine.event.LoopIterationEvent; +import org.apache.jmeter.engine.event.LoopIterationListener; +import org.apache.jmeter.engine.util.NoConfigMerge; +import org.apache.jmeter.gui.GUIMenuSortOrder; +import org.apache.jmeter.gui.TestElementMetadata; +import org.apache.jmeter.save.CSVSaveService; +import org.apache.jmeter.services.FileServer; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.testbeans.gui.GenericTestBeanCustomizer; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.util.JMeterStopThreadException; +import org.apache.jorphan.util.JOrphanUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.io.IOException; +import java.util.ResourceBundle; + +/** + * Read lines from a file and split int variables. + *

+ * The iterationStart() method is used to set up each set of values. + *

+ * By default, the same file is shared between all threads + * (and other thread groups, if they use the same file name). + *

+ * The shareMode can be set to: + *

    + *
  • All threads - default, as described above
  • + *
  • Current thread group
  • + *
  • Current thread
  • + *
  • Identifier - all threads sharing the same identifier
  • + *
+ *

+ * The class uses the FileServer alias mechanism to provide the different share modes. + * For all threads, the file alias is set to the file name. + * Otherwise, a suffix is appended to the filename to make it unique within the required context. + * For current thread group, the thread group identityHashcode is used; + * for individual threads, the thread hashcode is used as the suffix. + * Or the user can provide their own suffix, in which case the file is shared between all + * threads with the same suffix. + */ +@GUIMenuSortOrder(1) +@TestElementMetadata(labelResource = "displayName") +public class CSVDataSet extends ConfigTestElement + implements TestBean, LoopIterationListener, NoConfigMerge { + private static final Logger log = LoggerFactory.getLogger(CSVDataSet.class); + + private static final long serialVersionUID = 233L; + + private static final String EOFVALUE = // value to return at EOF + JMeterUtils.getPropDefault("csvdataset.eofstring", ""); //$NON-NLS-1$ //$NON-NLS-2$ + + private transient String filename; + + private transient String fileEncoding; + + private transient String variableNames; + + private transient String delimiter; + + private transient boolean quoted; + + private transient boolean recycle = true; + + private transient boolean stopThread; + + private transient String[] vars; + + private transient String alias; + + private transient String shareMode; + + private boolean firstLineIsNames = false; + + private boolean ignoreFirstLine = false; + + private Object readResolve() { + recycle = true; + return this; + } + + /** + * Override the setProperty method in order to convert + * the original String shareMode property. + * This used the locale-dependent display value, so caused + * problems when the language was changed. + * If the "shareMode" value matches a resource value then it is converted + * into the resource key. + * To reduce the need to look up resources, we only attempt to + * convert values with spaces in them, as these are almost certainly + * not variables (and they are definitely not resource keys). + */ + @Override + public void setProperty(JMeterProperty property) { + if (!(property instanceof StringProperty)) { + super.setProperty(property); + return; + } + + final String propName = property.getName(); + if (!"shareMode".equals(propName)) { + super.setProperty(property); + return; + } + + final String propValue = property.getStringValue(); + if (propValue.contains(" ")) { // variables are unlikely to contain spaces, so most likely a translation + try { + final BeanInfo beanInfo = Introspector.getBeanInfo(this.getClass()); + final ResourceBundle rb = (ResourceBundle) beanInfo.getBeanDescriptor().getValue(GenericTestBeanCustomizer.RESOURCE_BUNDLE); + for (String resKey : CSVDataSetBeanInfo.getShareTags()) { + if (propValue.equals(rb.getString(resKey))) { + if (log.isDebugEnabled()) { + log.debug("Converted {}={} to {} using Locale: {}", propName, propValue, resKey, rb.getLocale()); + } + ((StringProperty) property).setValue(resKey); // reset the value + super.setProperty(property); + return; + } + } + // This could perhaps be a variable name + log.warn("Could not translate {}={} using Locale: {}", propName, propValue, rb.getLocale()); + } catch (IntrospectionException e) { + log.error("Could not find BeanInfo; cannot translate shareMode entries", e); + } + } + super.setProperty(property); + } + + @Override + public void iterationStart(LoopIterationEvent iterEvent) { + FileServer server = FileServer.getFileServer(); + final JMeterContext context = getThreadContext(); + String delim = getDelimiter(); + if ("\\t".equals(delim)) { // $NON-NLS-1$ + delim = "\t";// Make it easier to enter a Tab // $NON-NLS-1$ + } else if (delim.isEmpty()) { + log.debug("Empty delimiter, will use ','"); + delim = ","; + } + //if (vars == null) { + initVars(server, context, delim); + //} + + // TODO: fetch this once as per vars above? + JMeterVariables threadVars = context.getVariables(); + String[] lineValues = {}; + try { + if (getQuotedData()) { + lineValues = server.getParsedLine(alias, recycle, + firstLineIsNames || ignoreFirstLine, delim.charAt(0)); + } else { + String line = server.readLine(alias, recycle, + firstLineIsNames || ignoreFirstLine); + lineValues = JOrphanUtils.split(line, delim, false); + } + for (int a = 0; a < vars.length && a < lineValues.length; a++) { + threadVars.put(vars[a], lineValues[a]); + } + } catch (IOException e) { // treat the same as EOF + log.error(e.toString()); + } + if (lineValues.length == 0) {// i.e. EOF + if (getStopThread()) { + throw new JMeterStopThreadException("End of file:" + getFilename() + " detected for CSV DataSet:" + + getName() + " configured with stopThread:" + getStopThread() + ", recycle:" + getRecycle()); + } + for (String var : vars) { + threadVars.put(var, EOFVALUE); + } + } + } + + private void initVars(FileServer server, final JMeterContext context, String delim) { + String fileName = getFilename().trim(); + setAlias(context, fileName); + final String names = getVariableNames(); + if (StringUtils.isEmpty(names)) { + String header = server.reserveFile(fileName, getFileEncoding(), alias, true); + try { + vars = CSVSaveService.csvSplitString(header, delim.charAt(0)); + firstLineIsNames = true; + } catch (IOException e) { + throw new IllegalArgumentException("Could not split CSV header line from file:" + fileName, e); + } + } else { + server.reserveFile(fileName, getFileEncoding(), alias, ignoreFirstLine); + vars = JOrphanUtils.split(names, ","); // $NON-NLS-1$ + } + trimVarNames(vars); + } + + private void setAlias(final JMeterContext context, String alias) { + String mode = getShareMode(); + int modeInt = CSVDataSetBeanInfo.getShareModeAsInt(mode); + switch (modeInt) { + case CSVDataSetBeanInfo.SHARE_ALL: + this.alias = alias; + break; + case CSVDataSetBeanInfo.SHARE_GROUP: + this.alias = alias + "@" + System.identityHashCode(context.getThreadGroup()); + break; + case CSVDataSetBeanInfo.SHARE_THREAD: + this.alias = alias + "@" + System.identityHashCode(context.getThread()); + break; + default: + this.alias = alias + "@" + mode; // user-specified key + break; + } + } + + /** + * trim content of array varNames + * + * @param varsNames + */ + private void trimVarNames(String[] varsNames) { + for (int i = 0; i < varsNames.length; i++) { + varsNames[i] = varsNames[i].trim(); + } + } + + /** + * @return Returns the filename. + */ + public String getFilename() { + return filename; + } + + /** + * @param filename The filename to set. + */ + public void setFilename(String filename) { + this.filename = filename; + } + + /** + * @return Returns the file encoding. + */ + public String getFileEncoding() { + return fileEncoding; + } + + /** + * @param fileEncoding The fileEncoding to set. + */ + public void setFileEncoding(String fileEncoding) { + this.fileEncoding = fileEncoding; + } + + /** + * @return Returns the variableNames. + */ + public String getVariableNames() { + return variableNames; + } + + /** + * @param variableNames The variableNames to set. + */ + public void setVariableNames(String variableNames) { + this.variableNames = variableNames; + } + + public String getDelimiter() { + return delimiter; + } + + public void setDelimiter(String delimiter) { + this.delimiter = delimiter; + } + + public boolean getQuotedData() { + return quoted; + } + + public void setQuotedData(boolean quoted) { + this.quoted = quoted; + } + + public boolean getRecycle() { + return recycle; + } + + public void setRecycle(boolean recycle) { + this.recycle = recycle; + } + + public boolean getStopThread() { + return stopThread; + } + + public void setStopThread(boolean value) { + this.stopThread = value; + } + + public String getShareMode() { + return shareMode; + } + + public void setShareMode(String value) { + this.shareMode = value; + } + + /** + * @return the ignoreFirstLine + */ + public boolean isIgnoreFirstLine() { + return ignoreFirstLine; + } + + /** + * @param ignoreFirstLine the ignoreFirstLine to set + */ + public void setIgnoreFirstLine(boolean ignoreFirstLine) { + this.ignoreFirstLine = ignoreFirstLine; + } +} diff --git a/src/main/java/org/apache/jmeter/services/FileServer.java b/src/main/java/org/apache/jmeter/services/FileServer.java deleted file mode 100644 index d51e9a8..0000000 --- a/src/main/java/org/apache/jmeter/services/FileServer.java +++ /dev/null @@ -1,582 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.jmeter.services; - -import org.apache.commons.io.input.BOMInputStream; -import org.apache.jmeter.gui.JMeterFileFilter; -import org.apache.jmeter.save.CSVSaveService; -import org.apache.jmeter.util.JMeterUtils; -import org.apache.jorphan.util.JOrphanUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.*; -import java.nio.file.Files; -import java.util.ArrayDeque; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.ThreadLocalRandom; - -/** - * This class provides thread-safe access to files, and to - * provide some simplifying assumptions about where to find files and how to - * name them. For instance, putting supporting files in the same directory as - * the saved test plan file allows users to refer to the file with just it's - * name - this FileServer class will find the file without a problem. - * Eventually, I want all in-test file access to be done through here, with the - * goal of packaging up entire test plans as a directory structure that can be - * sent via rmi to remote servers (currently, one must make sure the remote - * server has all support files in a relative-same location) and to package up - * test plans to execute on unknown boxes that only have Java installed. - */ -public class FileServer { - - private static final Logger log = LoggerFactory.getLogger(FileServer.class); - - /** - * The default base used for resolving relative files, i.e.
- * {@code System.getProperty("user.dir")} - */ - private static final String DEFAULT_BASE = System.getProperty("user.dir");// $NON-NLS-1$ - - /** Default base prefix: {@value} */ - private static final String BASE_PREFIX_DEFAULT = "~/"; // $NON-NLS-1$ - - private static final String BASE_PREFIX = - JMeterUtils.getPropDefault("jmeter.save.saveservice.base_prefix", // $NON-NLS-1$ - BASE_PREFIX_DEFAULT); - - private File base; - - private final Map files = new HashMap<>(); - - private static final FileServer server = new FileServer(); - - // volatile needed to ensure safe publication - private volatile String scriptName; - - // Cannot be instantiated - private FileServer() { - base = new File(DEFAULT_BASE); - log.info("Default base='{}'", DEFAULT_BASE); - } - - /** - * @return the singleton instance of the server. - */ - public static FileServer getFileServer() { - return server; - } - - /** - * Resets the current base to DEFAULT_BASE. - */ - public synchronized void resetBase() { - checkForOpenFiles(); - base = new File(DEFAULT_BASE); - log.info("Reset base to '{}'", base); - } - - /** - * Sets the current base directory for relative file names from the provided path. - * If the path does not refer to an existing directory, then its parent is used. - * Normally the provided path is a file, so using the parent directory is appropriate. - * - * @param basedir the path to set, or {@code null} if the GUI is being cleared - * @throws IllegalStateException if files are still open - */ - public synchronized void setBasedir(String basedir) { - checkForOpenFiles(); // TODO should this be called if basedir == null? - if (basedir != null) { - File newBase = new File(basedir); - if (!newBase.isDirectory()) { - newBase = newBase.getParentFile(); - } - base = newBase; - log.info("Set new base='{}'", base); - } - } - - /** - * Sets the current base directory for relative file names from the provided script file. - * The parameter is assumed to be the path to a JMX file, so the base directory is derived - * from its parent. - * - * @param scriptPath the path of the script file; must be not be {@code null} - * @throws IllegalStateException if files are still open - * @throws IllegalArgumentException if scriptPath parameter is null - */ - public synchronized void setBaseForScript(File scriptPath) { - if (scriptPath == null){ - throw new IllegalArgumentException("scriptPath must not be null"); - } - setScriptName(scriptPath.getName()); - // getParentFile() may not work on relative paths - setBase(scriptPath.getAbsoluteFile().getParentFile()); - } - - /** - * Sets the current base directory for relative file names. - * - * @param jmxBase the path of the script file base directory, cannot be null - * @throws IllegalStateException if files are still open - * @throws IllegalArgumentException if {@code basepath} is null - */ - public synchronized void setBase(File jmxBase) { - if (jmxBase == null) { - throw new IllegalArgumentException("jmxBase must not be null"); - } - checkForOpenFiles(); - base = jmxBase; - log.info("Set new base='{}'", base); - } - - /** - * Check if there are entries in use. - *

- * Caller must ensure that access to the files map is single-threaded as - * there is a window between checking the files Map and clearing it. - * - * @throws IllegalStateException if there are any entries still in use - */ - private void checkForOpenFiles() throws IllegalStateException { - if (filesOpen()) { // checks for entries in use - throw new IllegalStateException("Files are still open, cannot change base directory"); - } - files.clear(); // tidy up any unused entries - } - - public synchronized String getBaseDir() { - return base.getAbsolutePath(); - } - - public static String getDefaultBase(){ - return DEFAULT_BASE; - } - - /** - * Calculates the relative path from DEFAULT_BASE to the current base, - * which must be the same as or a child of the default. - * - * @return the relative path, or {@code "."} if the path cannot be determined - */ - public synchronized File getBaseDirRelative() { - // Must first convert to absolute path names to ensure parents are available - File parent = new File(DEFAULT_BASE).getAbsoluteFile(); - File f = base.getAbsoluteFile(); - ArrayDeque l = new ArrayDeque<>(); - while (f != null) { - if (f.equals(parent)){ - if (l.isEmpty()){ - break; - } - File rel = new File(l.pop()); - while(!l.isEmpty()) { - rel = new File(rel, l.pop()); - } - return rel; - } - l.push(f.getName()); - f = f.getParentFile(); - } - return new File("."); - } - - /** - * Creates an association between a filename and a File inputOutputObject, - * and stores it for later use - unless it is already stored. - * - * @param filename - relative (to base) or absolute file name (must not be null) - */ - public void reserveFile(String filename) { - reserveFile(filename,null); - } - - /** - * Creates an association between a filename and a File inputOutputObject, - * and stores it for later use - unless it is already stored. - * - * @param filename - relative (to base) or absolute file name (must not be null) - * @param charsetName - the character set encoding to use for the file (may be null) - */ - public void reserveFile(String filename, String charsetName) { - reserveFile(filename, charsetName, filename, false); - } - - /** - * Creates an association between a filename and a File inputOutputObject, - * and stores it for later use - unless it is already stored. - * - * @param filename - relative (to base) or absolute file name (must not be null) - * @param charsetName - the character set encoding to use for the file (may be null) - * @param alias - the name to be used to access the object (must not be null) - */ - public void reserveFile(String filename, String charsetName, String alias) { - reserveFile(filename, charsetName, alias, false); - } - - /** - * Creates an association between a filename and a File inputOutputObject, - * and stores it for later use - unless it is already stored. - * - * @param filename - relative (to base) or absolute file name (must not be null or empty) - * @param charsetName - the character set encoding to use for the file (may be null) - * @param alias - the name to be used to access the object (must not be null) - * @param hasHeader true if the file has a header line describing the contents - * @return the header line; may be null - * @throws IllegalArgumentException if header could not be read or filename is null or empty - */ - public synchronized String reserveFile(String filename, String charsetName, String alias, boolean hasHeader) { - if (filename == null || filename.isEmpty()){ - throw new IllegalArgumentException("Filename must not be null or empty"); - } - if (alias == null){ - throw new IllegalArgumentException("Alias must not be null"); - } - FileEntry fileEntry = files.get(alias); - if (fileEntry == null) { - fileEntry = new FileEntry(resolveFileFromPath(filename), null, charsetName); - if (filename.equals(alias)){ - log.info("Stored: {}", filename); - } else { - log.info("Stored: {} Alias: {}", filename, alias); - } - files.put(alias, fileEntry); - if (hasHeader) { - try { - fileEntry.headerLine = readLine(alias, false); - if (fileEntry.headerLine == null) { - fileEntry.exception = new EOFException("File is empty: " + fileEntry.file); - } - } catch (IOException | IllegalArgumentException e) { - fileEntry.exception = e; - } - } - } - if (hasHeader && fileEntry.headerLine == null) { - throw new IllegalArgumentException("Could not read file header line for file " + filename, - fileEntry.exception); - } - return fileEntry.headerLine; - } - - /** - * Resolves file name into {@link File} instance. - * When filename is not absolute and not found from current working dir, - * it tries to find it under current base directory - * @param filename original file name - * @return {@link File} instance - */ - private File resolveFileFromPath(String filename) { - File f = new File(filename); - if (f.isAbsolute() || f.exists()) { - return f; - } else { - return new File(base, filename); - } - } - - /** - * Get the next line of the named file, recycle by default. - * - * @param filename the filename or alias that was used to reserve the file - * @return String containing the next line in the file - * @throws IOException when reading of the file fails, or the file was not reserved properly - */ - public String readLine(String filename) throws IOException { - return readLine(filename, true); - } - - /** - * Get the next line of the named file, first line is name to false - * - * @param filename the filename or alias that was used to reserve the file - * @param recycle - should file be restarted at EOF? - * @return String containing the next line in the file (null if EOF reached and not recycle) - * @throws IOException when reading of the file fails, or the file was not reserved properly - */ - public String readLine(String filename, boolean recycle) throws IOException { - return readLine(filename, recycle, false); - } - /** - * Get the next line of the named file - * - * @param filename the filename or alias that was used to reserve the file - * @param recycle - should file be restarted at EOF? - * @param ignoreFirstLine - Ignore first line - * @return String containing the next line in the file (null if EOF reached and not recycle) - * @throws IOException when reading of the file fails, or the file was not reserved properly - */ - public synchronized String readLine(String filename, boolean recycle, - boolean ignoreFirstLine) throws IOException { - FileEntry fileEntry = files.get(filename); - if(fileEntry == null ){ - this.reserveFile(filename); - fileEntry = files.get(filename); - } - if (fileEntry != null) { - if (fileEntry.inputOutputObject == null) { - fileEntry.inputOutputObject = createBufferedReader(fileEntry); - } else if (!(fileEntry.inputOutputObject instanceof Reader)) { - throw new IOException("File " + filename + " already in use"); - } - BufferedReader reader = (BufferedReader) fileEntry.inputOutputObject; - String line = reader.readLine(); - if (line == null && recycle) { - reader.close(); - reader = createBufferedReader(fileEntry); - fileEntry.inputOutputObject = reader; - if (ignoreFirstLine) { - // read first line and forget - reader.readLine();//NOSONAR - } - line = reader.readLine(); - } - log.debug("Read:{}", line); - return line; - } - throw new IOException("File never reserved: "+filename); - } - - /** - * - * @param alias the file name or alias - * @param recycle whether the file should be re-started on EOF - * @param ignoreFirstLine whether the file contains a file header which will be ignored - * @param delim the delimiter to use for parsing - * @return the parsed line, will be empty if the file is at EOF - * @throws IOException when reading of the aliased file fails, or the file was not reserved properly - */ - public synchronized String[] getParsedLine(String alias, boolean recycle, boolean ignoreFirstLine, char delim) throws IOException { - BufferedReader reader = getReader(alias, recycle, ignoreFirstLine); - return CSVSaveService.csvReadFile(reader, delim); - } - - /** - * Return BufferedReader handling close if EOF reached and recycle is true - * and ignoring first line if ignoreFirstLine is true - * - * @param alias String alias - * @param recycle Recycle at eof - * @param ignoreFirstLine Ignore first line - * @return {@link BufferedReader} - */ - private BufferedReader getReader(String alias, boolean recycle, boolean ignoreFirstLine) throws IOException { - FileEntry fileEntry = files.get(alias); - if (fileEntry != null) { - BufferedReader reader; - if (fileEntry.inputOutputObject == null) { - reader = createBufferedReader(fileEntry); - fileEntry.inputOutputObject = reader; - if (ignoreFirstLine) { - // read first line and forget - reader.readLine(); //NOSONAR - } - } else if (!(fileEntry.inputOutputObject instanceof Reader)) { - throw new IOException("File " + alias + " already in use"); - } else { - reader = (BufferedReader) fileEntry.inputOutputObject; - if (recycle) { // need to check if we are at EOF already - reader.mark(1); - int peek = reader.read(); - if (peek == -1) { // already at EOF - reader.close(); - reader = createBufferedReader(fileEntry); - fileEntry.inputOutputObject = reader; - if (ignoreFirstLine) { - // read first line and forget - reader.readLine(); //NOSONAR - } - } else { // OK, we still have some data, restore it - reader.reset(); - } - } - } - return reader; - } else { - throw new IOException("File never reserved: "+alias); - } - } - - private BufferedReader createBufferedReader(FileEntry fileEntry) throws IOException { - if (!fileEntry.file.canRead() || !fileEntry.file.isFile()) { - throw new IllegalArgumentException("File "+ fileEntry.file.getName()+ " must exist and be readable"); - } - BOMInputStream fis = new BOMInputStream(Files.newInputStream(fileEntry.file.toPath())); //NOSONAR - InputStreamReader isr = null; - // If file encoding is specified, read using that encoding, otherwise use default platform encoding - String charsetName = fileEntry.charSetEncoding; - if(!JOrphanUtils.isBlank(charsetName)) { - isr = new InputStreamReader(fis, charsetName); - } else if (fis.hasBOM()) { - isr = new InputStreamReader(fis, fis.getBOM().getCharsetName()); - } else { - @SuppressWarnings("DefaultCharset") - final InputStreamReader withPlatformEncoding = new InputStreamReader(fis); - isr = withPlatformEncoding; - } - return new BufferedReader(isr); - } - - public synchronized void write(String filename, String value) throws IOException { - FileEntry fileEntry = files.get(filename); - if (fileEntry != null) { - if (fileEntry.inputOutputObject == null) { - fileEntry.inputOutputObject = createBufferedWriter(fileEntry); - } else if (!(fileEntry.inputOutputObject instanceof Writer)) { - throw new IOException("File " + filename + " already in use"); - } - BufferedWriter writer = (BufferedWriter) fileEntry.inputOutputObject; - log.debug("Write:{}", value); - writer.write(value); - } else { - throw new IOException("File never reserved: "+filename); - } - } - - private BufferedWriter createBufferedWriter(FileEntry fileEntry) throws IOException { - OutputStream fos = Files.newOutputStream(fileEntry.file.toPath()); - OutputStreamWriter osw; - // If file encoding is specified, write using that encoding, otherwise use default platform encoding - String charsetName = fileEntry.charSetEncoding; - if(!JOrphanUtils.isBlank(charsetName)) { - osw = new OutputStreamWriter(fos, charsetName); - } else { - @SuppressWarnings("DefaultCharset") - final OutputStreamWriter withPlatformEncoding = new OutputStreamWriter(fos); - osw = withPlatformEncoding; - } - return new BufferedWriter(osw); - } - - public synchronized void closeFiles() throws IOException { - for (Map.Entry me : files.entrySet()) { - closeFile(me.getKey(),me.getValue() ); - } - files.clear(); - } - - /** - * @param name the name or alias of the file to be closed - * @throws IOException when closing of the aliased file fails - */ - public synchronized void closeFile(String name) throws IOException { - FileEntry fileEntry = files.get(name); - closeFile(name, fileEntry); - } - - private void closeFile(String name, FileEntry fileEntry) throws IOException { - if (fileEntry != null && fileEntry.inputOutputObject != null) { - log.info("Close: {}", name); - fileEntry.inputOutputObject.close(); - fileEntry.inputOutputObject = null; - } - } - - boolean filesOpen() { // package access for test code only - return files.values().stream() - .anyMatch(fileEntry -> fileEntry.inputOutputObject != null); - } - - /** - * Method will get a random file in a base directory - *

- * TODO hey, not sure this method belongs here. - * FileServer is for thread safe File access relative to current test's base directory. - * - * @param basedir name of the directory in which the files can be found - * @param extensions array of allowed extensions, if null is given, - * any file be allowed - * @return a random File from the basedir that matches one of - * the extensions - */ - public File getRandomFile(String basedir, String[] extensions) { - File input = null; - if (basedir != null) { - File src = new File(basedir); - File[] lfiles = src.listFiles(new JMeterFileFilter(extensions)); - if (lfiles != null) { - // lfiles cannot be null as it has been checked before - int count = lfiles.length; - input = lfiles[ThreadLocalRandom.current().nextInt(count)]; - } - } - return input; - } - - /** - * Get {@link File} instance for provided file path, - * resolve file location relative to base dir or script dir when needed - * - * @param path original path to file, maybe relative - * @return {@link File} instance - */ - public File getResolvedFile(String path) { - reserveFile(path); - return files.get(path).file; - } - - private static class FileEntry{ - private String headerLine; - private Throwable exception; - private final File file; - private Closeable inputOutputObject; - private final String charSetEncoding; - - FileEntry(File f, Closeable o, String e) { - file = f; - inputOutputObject = o; - charSetEncoding = e; - } - } - - /** - * Resolve a file name that may be relative to the base directory. If the - * name begins with the value of the JMeter property - * "jmeter.save.saveservice.base_prefix" - default "~/" - then the name is - * assumed to be relative to the basename. - * - * @param relativeName - * filename that should be checked for - * jmeter.save.saveservice.base_prefix - * @return the updated filename - */ - public static String resolveBaseRelativeName(String relativeName) { - if (relativeName.startsWith(BASE_PREFIX)){ - String newName = relativeName.substring(BASE_PREFIX.length()); - return new File(getFileServer().getBaseDir(),newName).getAbsolutePath(); - } - return relativeName; - } - - /** - * @return JMX Script name - * @since 2.6 - */ - public String getScriptName() { - return scriptName; - } - - /** - * @param scriptName Script name - * @since 2.6 - */ - public void setScriptName(String scriptName) { - this.scriptName = scriptName; - } -} From c3191ced1981c541ac02ff03ac1507e4d6242f87 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Thu, 6 May 2021 17:47:02 +0800 Subject: [PATCH 059/109] =?UTF-8?q?feat(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96):=20=E6=8A=A5=E5=91=8A=E7=BB=9F=E8=AE=A1?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=9C=BA=E6=99=AF=E6=AD=A5=E9=AA=A4=E7=BB=9F?= =?UTF-8?q?=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/jmeter/APIBackendListenerClient.java | 1 + .../metersphere/api/module/RequestResult.java | 2 ++ .../io/metersphere/api/module/TestResult.java | 20 ++++++------------- .../apache/jmeter/samplers/SampleResult.java | 16 +++++++++++++++ 4 files changed, 25 insertions(+), 14 deletions(-) diff --git a/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java b/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java index 262eee3..3b0a798 100644 --- a/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java +++ b/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java @@ -155,6 +155,7 @@ private RequestResult getRequestResult(SampleResult result) { requestResult.setTotalAssertions(result.getAssertionResults().length); requestResult.setSuccess(result.isSuccessful()); requestResult.setError(result.getErrorCount()); + requestResult.setScenario(result.getScenario()); if (result instanceof HTTPSampleResult) { HTTPSampleResult res = (HTTPSampleResult) result; requestResult.setCookies(res.getCookies()); diff --git a/src/main/java/io/metersphere/api/module/RequestResult.java b/src/main/java/io/metersphere/api/module/RequestResult.java index b650fff..d3ac1c2 100644 --- a/src/main/java/io/metersphere/api/module/RequestResult.java +++ b/src/main/java/io/metersphere/api/module/RequestResult.java @@ -16,6 +16,8 @@ public class RequestResult { private String method; + private String scenario; + private long requestSize; private long startTime; diff --git a/src/main/java/io/metersphere/api/module/TestResult.java b/src/main/java/io/metersphere/api/module/TestResult.java index 9e818bc..45b80f4 100644 --- a/src/main/java/io/metersphere/api/module/TestResult.java +++ b/src/main/java/io/metersphere/api/module/TestResult.java @@ -14,6 +14,12 @@ public class TestResult { private String setReportId; + private int scenarioTotal; + + private int scenarioSuccess; + + private int scenarioError; + private String userId; private boolean isDebug; @@ -50,18 +56,4 @@ public void addPassAssertions(int count) { private static final String SEPARATOR = "<->"; - public void addScenario(ScenarioResult result) { - if (result != null && CollectionUtils.isNotEmpty(result.getRequestResults())) { - result.getRequestResults().forEach(item -> { - if (StringUtils.isNotEmpty(item.getName()) && item.getName().indexOf(SEPARATOR) != -1) { - String array[] = item.getName().split(SEPARATOR); - item.setName(array[1] + array[0]); - item.getSubRequestResults().forEach(subItem -> { - subItem.setName(array[0]); - }); - } - }); - scenarios.add(result); - } - } } diff --git a/src/main/java/org/apache/jmeter/samplers/SampleResult.java b/src/main/java/org/apache/jmeter/samplers/SampleResult.java index ee44117..a10b2a2 100644 --- a/src/main/java/org/apache/jmeter/samplers/SampleResult.java +++ b/src/main/java/org/apache/jmeter/samplers/SampleResult.java @@ -163,12 +163,27 @@ public class SampleResult implements Serializable, Cloneable, Searchable { } } + + /** + * 定制自定义添加 ================= + */ private String samplerId; public String getSamplerId() { return this.samplerId; } + // 数据格式 List 多层父级按照同级统计 + private String scenario; + + public String getScenario() { + return this.scenario; + } + + /** + * 定制自定义添加 ================= + */ + private SampleSaveConfiguration saveConfig; private SampleResult parent; @@ -335,6 +350,7 @@ public SampleResult() { Sampler sampler = JMeterContextService.getContext().getCurrentSampler(); if (sampler != null) { this.samplerId = sampler.getPropertyAsString("MS-ID"); + this.scenario = sampler.getPropertyAsString("MS-SCENARIO"); } } From dd8ccc24245a2afcf55b64a83a133fe148dc0fac Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Mon, 17 May 2021 16:23:18 +0800 Subject: [PATCH 060/109] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0SSL=E8=AF=81?= =?UTF-8?q?=E4=B9=A6=E8=AE=A4=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apache/jmeter/config/KeystoreConfig.java | 174 ++++++++ .../org/apache/jmeter/util/SSLManager.java | 378 ++++++++++++++++++ 2 files changed, 552 insertions(+) create mode 100644 src/main/java/org/apache/jmeter/config/KeystoreConfig.java create mode 100644 src/main/java/org/apache/jmeter/util/SSLManager.java diff --git a/src/main/java/org/apache/jmeter/config/KeystoreConfig.java b/src/main/java/org/apache/jmeter/config/KeystoreConfig.java new file mode 100644 index 0000000..16e4fcf --- /dev/null +++ b/src/main/java/org/apache/jmeter/config/KeystoreConfig.java @@ -0,0 +1,174 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jmeter.config; + +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.gui.TestElementMetadata; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.testelement.TestStateListener; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.util.SSLManager; +import org.apache.jorphan.util.JMeterStopTestException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * Configure Keystore + */ +@TestElementMetadata(labelResource = "displayName") +public class KeystoreConfig extends ConfigTestElement implements TestBean, TestStateListener { + + private static final long serialVersionUID = 1L; + private static final Logger log = LoggerFactory.getLogger(KeystoreConfig.class); + + private static final String KEY_STORE_START_INDEX = "https.keyStoreStartIndex"; // $NON-NLS-1$ + private static final String KEY_STORE_END_INDEX = "https.keyStoreEndIndex"; // $NON-NLS-1$ + + private String startIndex; + private String endIndex; + private String preload; + private String clientCertAliasVarName; + + public KeystoreConfig() { + super(); + } + + @Override + public void testEnded() { + testEnded(null); + } + + @Override + public void testEnded(String host) { + log.info("Destroying Keystore"); + SSLManager.getInstance().destroyKeystore(); + } + + @Override + public void testStarted() { + testStarted(null); + } + + @Override + public void testStarted(String host) { + String reuseSSLContext = JMeterUtils.getProperty("https.use.cached.ssl.context"); + if (StringUtils.isEmpty(reuseSSLContext) || "true".equals(reuseSSLContext)) { + log.warn("https.use.cached.ssl.context property must be set to false to ensure Multiple Certificates are used"); + } + int startIndexAsInt = JMeterUtils.getPropDefault(KEY_STORE_START_INDEX, 0); + int endIndexAsInt = JMeterUtils.getPropDefault(KEY_STORE_END_INDEX, -1); + + if (!StringUtils.isEmpty(this.startIndex)) { + try { + startIndexAsInt = Integer.parseInt(this.startIndex); + } catch (NumberFormatException e) { + log.warn("Failed parsing startIndex: {}, will default to: {}, error message: {}", this.startIndex, + startIndexAsInt, e, e); + } + } + + if (!StringUtils.isEmpty(this.endIndex)) { + try { + endIndexAsInt = Integer.parseInt(this.endIndex); + } catch (NumberFormatException e) { + log.warn("Failed parsing endIndex: {}, will default to: {}, error message: {}", this.endIndex, + endIndexAsInt, e, e); + } + } + if (endIndexAsInt != -1 && startIndexAsInt > endIndexAsInt) { + throw new JMeterStopTestException("Keystore Config error : Alias start index must be lower than Alias end index"); + } + log.info( + "Configuring Keystore with (preload: '{}', startIndex: {}, endIndex: {}, clientCertAliasVarName: '{}')", + preload, startIndexAsInt, endIndexAsInt, clientCertAliasVarName); + // 加载认证文件 + String path = this.getPropertyAsString("MS-KEYSTORE-FILE-PATH"); + String password = this.getPropertyAsString("MS-KEYSTORE-FILE-PASSWORD"); + InputStream in = null; + try { + in = new FileInputStream(new File(path)); + } catch (IOException e) { + log.error(e.getMessage()); + } + SSLManager.getInstance().configureKeystore(Boolean.parseBoolean(preload), + startIndexAsInt, + endIndexAsInt, + clientCertAliasVarName, in, password); + } + + /** + * @return the endIndex + */ + public String getEndIndex() { + return endIndex; + } + + /** + * @param endIndex the endIndex to set + */ + public void setEndIndex(String endIndex) { + this.endIndex = endIndex; + } + + /** + * @return the startIndex + */ + public String getStartIndex() { + return startIndex; + } + + /** + * @param startIndex the startIndex to set + */ + public void setStartIndex(String startIndex) { + this.startIndex = startIndex; + } + + /** + * @return the preload + */ + public String getPreload() { + return preload; + } + + /** + * @param preload the preload to set + */ + public void setPreload(String preload) { + this.preload = preload; + } + + /** + * @return the clientCertAliasVarName + */ + public String getClientCertAliasVarName() { + return clientCertAliasVarName; + } + + /** + * @param clientCertAliasVarName the clientCertAliasVarName to set + */ + public void setClientCertAliasVarName(String clientCertAliasVarName) { + this.clientCertAliasVarName = clientCertAliasVarName; + } +} diff --git a/src/main/java/org/apache/jmeter/util/SSLManager.java b/src/main/java/org/apache/jmeter/util/SSLManager.java new file mode 100644 index 0000000..93363ed --- /dev/null +++ b/src/main/java/org/apache/jmeter/util/SSLManager.java @@ -0,0 +1,378 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jmeter.util; + +import org.apache.commons.lang3.Validate; +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.util.keystore.JmeterKeyStore; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.security.*; +import java.security.cert.CertificateException; +import java.util.Locale; + +/** + * The SSLManager handles the KeyStore information for JMeter. Basically, it + * handles all the logic for loading and initializing all the JSSE parameters + * and selecting the alias to authenticate against if it is available. + * SSLManager will try to automatically select the client certificate for you, + * but if it can't make a decision, it will pop open a dialog asking you for + * more information. + *

+ * TODO? - N.B. does not currently allow the selection of a client certificate. + */ +public abstract class SSLManager { + private static final Logger log = LoggerFactory.getLogger(SSLManager.class); + + private static final String SSL_TRUST_STORE = "javax.net.ssl.trustStore";// $NON-NLS-1$ + + private static final String KEY_STORE_PASSWORD = "javax.net.ssl.keyStorePassword"; // $NON-NLS-1$ NOSONAR no hard coded password + + public static final String JAVAX_NET_SSL_KEY_STORE = "javax.net.ssl.keyStore"; // $NON-NLS-1$ + + private static final String JAVAX_NET_SSL_KEY_STORE_TYPE = "javax.net.ssl.keyStoreType"; // $NON-NLS-1$ + + private static final String PKCS12 = "pkcs12"; // $NON-NLS-1$ + + /** + * Singleton instance of the manager + */ + private static SSLManager manager; + + private static final boolean IS_SSL_SUPPORTED = true; + + /** + * Cache the KeyStore instance + */ + private JmeterKeyStore keyStore; + + /** + * Cache the TrustStore instance - null if no truststore name was provided + */ + private KeyStore trustStore = null; + // Have we yet tried to load the truststore? + private volatile boolean truststoreLoaded = false; + + /** + * Have the password available + */ + protected volatile String defaultpw = System.getProperty(KEY_STORE_PASSWORD); + + private int keystoreAliasStartIndex; + + private int keystoreAliasEndIndex; + + private String clientCertAliasVarName; + + /** + * Resets the SSLManager so that we can create a new one with a new keystore + */ + public static synchronized void reset() { + SSLManager.manager = null; + } + + public abstract void setContext(HttpURLConnection conn); + + /** + * Default implementation of setting the Provider + * + * @param provider the provider to use + */ + protected void setProvider(Provider provider) { + if (null != provider) { + Security.addProvider(provider); + } + } + + protected synchronized JmeterKeyStore getKeyStore() { + if (null == this.keyStore) { + String fileName = System.getProperty(JAVAX_NET_SSL_KEY_STORE, ""); // empty if not provided + String fileType = System.getProperty(JAVAX_NET_SSL_KEY_STORE_TYPE, // use the system property to determine the type + fileName.toLowerCase(Locale.ENGLISH).endsWith(".p12") ? PKCS12 : "JKS"); // otherwise use the name + log.info("JmeterKeyStore Location: {} type {}", fileName, fileType); + try { + this.keyStore = JmeterKeyStore.getInstance(fileType, keystoreAliasStartIndex, keystoreAliasEndIndex, clientCertAliasVarName); + log.info("KeyStore created OK"); + } catch (Exception e) { + this.keyStore = null; + throw new IllegalArgumentException("Could not create keystore: " + e.getMessage(), e); + } + + try { + + // The string 'NONE' is used for the keystore location when using PKCS11 + // https://docs.oracle.com/javase/8/docs/technotes/guides/security/p11guide.html#JSSE + if ("NONE".equalsIgnoreCase(fileName)) { + retryLoadKeys(null, false); + log.info("Total of {} aliases loaded OK from PKCS11", keyStore.getAliasCount()); + } else { + File initStore = new File(fileName); + if (fileName.length() > 0 && initStore.exists()) { + retryLoadKeys(initStore, true); + if (log.isInfoEnabled()) { + log.info("Total of {} aliases loaded OK from keystore {}", + keyStore.getAliasCount(), fileName); + } + } else { + log.warn("Keystore file not found, loading empty keystore"); + this.defaultpw = ""; // Ensure not null + this.keyStore.load(null, ""); + } + } + } catch (Exception e) { + log.error("Problem loading keystore: {}", e.getMessage(), e); + } + + if (log.isDebugEnabled()) { + log.debug("JmeterKeyStore type: {}", this.keyStore.getClass()); + } + } + + return this.keyStore; + } + + /** + * Opens and initializes the KeyStore. If the password for the KeyStore is + * not set, this method will prompt you to enter it. Unfortunately, there is + * no PasswordEntryField available from JOptionPane. + * + * @return the configured {@link JmeterKeyStore} + */ + protected synchronized JmeterKeyStore getKeyStore(InputStream is, String password) { + if (null == this.keyStore) { + String fileName = System.getProperty(JAVAX_NET_SSL_KEY_STORE, ""); // empty if not provided + String fileType = System.getProperty(JAVAX_NET_SSL_KEY_STORE_TYPE, // use the system property to determine the type + fileName.toLowerCase(Locale.ENGLISH).endsWith(".p12") ? PKCS12 : "JKS"); // otherwise use the name + log.info("JmeterKeyStore Location: {} type {}", fileName, fileType); + try { + this.keyStore = JmeterKeyStore.getInstance(fileType, keystoreAliasStartIndex, keystoreAliasEndIndex, clientCertAliasVarName); + log.info("KeyStore created OK"); + } catch (Exception e) { + this.keyStore = null; + throw new IllegalArgumentException("Could not create keystore: " + e.getMessage(), e); + } + + try { + + // The string 'NONE' is used for the keystore location when using PKCS11 + // https://docs.oracle.com/javase/8/docs/technotes/guides/security/p11guide.html#JSSE + if ("NONE".equalsIgnoreCase(fileName)) { + retryLoadKeys(null, false); + log.info("Total of {} aliases loaded OK from PKCS11", keyStore.getAliasCount()); + } else { + File initStore = new File(fileName); + if (fileName.length() > 0 && initStore.exists()) { + retryLoadKeys(initStore, true); + if (log.isInfoEnabled()) { + log.info("Total of {} aliases loaded OK from keystore {}", + keyStore.getAliasCount(), fileName); + } + } else { + log.warn("Keystore file not found, loading empty keystore"); + this.defaultpw = ""; // Ensure not null + this.keyStore.load(is, password); + } + } + } catch (Exception e) { + log.error("Problem loading keystore: {}", e.getMessage(), e); + } + + if (log.isDebugEnabled()) { + log.debug("JmeterKeyStore type: {}", this.keyStore.getClass()); + } + } + + return this.keyStore; + } + + private void retryLoadKeys(File initStore, boolean allowEmptyPassword) throws NoSuchAlgorithmException, + CertificateException, IOException, KeyStoreException, UnrecoverableKeyException { + for (int i = 0; i < 3; i++) { + String password = getPassword(); + if (!allowEmptyPassword) { + Validate.notNull(password, "Password for keystore must not be null"); + } + try { + if (initStore == null) { + this.keyStore.load(null, password); + } else { + try (InputStream fis = new FileInputStream(initStore)) { + this.keyStore.load(fis, password); + } + } + return; + } catch (IOException e) { + log.debug("Could not load keystore. Wrong password for keystore?", e); + } + this.defaultpw = null; + } + } + + /* + * The password can be defined as a property; this dialogue is provided to allow it + * to be entered at run-time. + */ + private String getPassword() { + String password = this.defaultpw; + if (null == password) { + final GuiPackage guiInstance = GuiPackage.getInstance(); +// if (guiInstance != null) { +// JPanel panel = new JPanel(new MigLayout("fillx, wrap 2", "[][fill, grow]")); +// JLabel passwordLabel = new JLabel("Password: "); +// JPasswordField pwf = new JPasswordField(64); +// pwf.setEchoChar('*'); +// passwordLabel.setLabelFor(pwf); +// panel.add(passwordLabel); +// panel.add(pwf); +// int choice = JOptionPane.showConfirmDialog(guiInstance.getMainFrame(), panel, +// JMeterUtils.getResString("ssl_pass_prompt"), JOptionPane.OK_CANCEL_OPTION, +// JOptionPane.PLAIN_MESSAGE); +// if (choice == JOptionPane.OK_OPTION) { +// char[] pwchars = pwf.getPassword(); +// this.defaultpw = new String(pwchars); +// Arrays.fill(pwchars, '*'); +// } +// System.setProperty(KEY_STORE_PASSWORD, this.defaultpw); +// password = this.defaultpw; +// } + } else { + log.warn("No password provided, and no GUI present so cannot prompt"); + } + return password; + } + + /** + * Opens and initializes the TrustStore. + *

+ * There are 3 possibilities: + *

    + *
  • no truststore name provided, in which case the default Java truststore + * should be used
  • + *
  • truststore name is provided, and loads OK
  • + *
  • truststore name is provided, but is not found or does not load OK, in + * which case an empty + * truststore is created
  • + *
+ * If the KeyStore object cannot be created, then this is currently treated the + * same as if no truststore name was provided. + * + * @return {@code null} when Java truststore should be used. + * Otherwise the truststore, which may be empty if the file could not be + * loaded. + */ + protected KeyStore getTrustStore() { + if (!truststoreLoaded) { + + truststoreLoaded = true;// we've tried ... + + String fileName = System.getProperty(SSL_TRUST_STORE); + if (fileName == null) { + return null; + } + log.info("TrustStore Location: {}", fileName); + + try { + this.trustStore = KeyStore.getInstance("JKS"); + log.info("TrustStore created OK, Type: JKS"); + } catch (Exception e) { + this.trustStore = null; + throw new RuntimeException("Problem creating truststore: " + e.getMessage(), e); + } + + try { + File initStore = new File(fileName); + + if (initStore.exists()) { + try (InputStream fis = new FileInputStream(initStore)) { + this.trustStore.load(fis, null); + log.info("Truststore loaded OK from file"); + } + } else { + log.warn("Truststore file not found, loading empty truststore"); + this.trustStore.load(null, null); + } + } catch (Exception e) { + throw new RuntimeException("Can't load TrustStore: " + e.getMessage(), e); + } + } + + return this.trustStore; + } + + /** + * Protected Constructor to remove the possibility of directly instantiating + * this object. Create the SSLContext, and wrap all the X509KeyManagers with + * our X509KeyManager so that we can choose our alias. + */ + protected SSLManager() { + } + + /** + * Static accessor for the SSLManager object. The SSLManager is a singleton. + * + * @return the singleton {@link SSLManager} + */ + public static synchronized SSLManager getInstance() { + if (null == SSLManager.manager) { + SSLManager.manager = new JsseSSLManager(null); + } + + return SSLManager.manager; + } + + /** + * Test whether SSL is supported or not. + * + * @return flag whether SSL is supported + */ + public static boolean isSSLSupported() { + return SSLManager.IS_SSL_SUPPORTED; + } + + /** + * Configure Keystore + * + * @param preload flag whether the keystore should be opened within this method, + * or the opening should be delayed + * @param startIndex first index to consider for a key + * @param endIndex last index to consider for a key + * @param clientCertAliasVarName name of the default key, if empty the first key will be used + * as default key + */ + public synchronized void configureKeystore(boolean preload, int startIndex, int endIndex, String clientCertAliasVarName, InputStream is, String password) { + this.keystoreAliasStartIndex = startIndex; + this.keystoreAliasEndIndex = endIndex; + this.clientCertAliasVarName = clientCertAliasVarName; + if (preload) { + keyStore = getKeyStore(is, password); + } + } + + /** + * Destroy Keystore + */ + public synchronized void destroyKeystore() { + keyStore = null; + } +} From 03cc69ac328e55f34f70f42521beb8db5a9b8498 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Sat, 22 May 2021 20:54:49 +0800 Subject: [PATCH 061/109] =?UTF-8?q?refactor:=20=E5=AE=B9=E5=99=A8=E9=80=80?= =?UTF-8?q?=E5=87=BA=E5=89=8D=E5=8F=91=E9=80=81=E7=BB=93=E6=9D=9F=E6=B6=88?= =?UTF-8?q?=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../node/service/JmeterOperateService.java | 9 +++++++++ .../metersphere/node/service/KafkaProducer.java | 17 +++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 src/main/java/io/metersphere/node/service/KafkaProducer.java diff --git a/src/main/java/io/metersphere/node/service/JmeterOperateService.java b/src/main/java/io/metersphere/node/service/JmeterOperateService.java index b3e6668..a64fd22 100644 --- a/src/main/java/io/metersphere/node/service/JmeterOperateService.java +++ b/src/main/java/io/metersphere/node/service/JmeterOperateService.java @@ -14,6 +14,7 @@ import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; +import javax.annotation.Resource; import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.Socket; @@ -26,6 +27,8 @@ @Service public class JmeterOperateService { + @Resource + private KafkaProducer kafkaProducer; public void startContainer(TestRequest testRequest) { Map env = testRequest.getEnv(); @@ -65,6 +68,12 @@ public void onComplete() { if (DockerClientService.existContainer(dockerClient, containerId) > 0) { DockerClientService.removeContainer(dockerClient, containerId); } + // 上传结束消息,取保正常结束 + String topic = testRequest.getEnv().getOrDefault("LOG_TOPIC", "JMETER_LOGS"); + String reportId = testRequest.getEnv().get("REPORT_ID"); + String[] contents = new String[]{reportId, "none", "0", "Notifying test listeners of end of test"}; + String log = StringUtils.join(contents, " "); + kafkaProducer.sendMessage(topic, log); LogUtil.info("Remove container completed: " + containerId); } catch (Exception e) { LogUtil.error("Remove container error: ", e); diff --git a/src/main/java/io/metersphere/node/service/KafkaProducer.java b/src/main/java/io/metersphere/node/service/KafkaProducer.java new file mode 100644 index 0000000..b05ecd3 --- /dev/null +++ b/src/main/java/io/metersphere/node/service/KafkaProducer.java @@ -0,0 +1,17 @@ +package io.metersphere.node.service; + +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; + +@Service +public class KafkaProducer { + + @Resource + private KafkaTemplate kafkaTemplate; + + public void sendMessage(String topic, String report) { + this.kafkaTemplate.send(topic, report); + } +} From 1a3efa0b71d96fa407646a085e83f25ff58dd65e Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Tue, 25 May 2021 17:19:31 +0800 Subject: [PATCH 062/109] =?UTF-8?q?refactor:=20=E5=AE=B9=E5=99=A8=E9=80=80?= =?UTF-8?q?=E5=87=BA=E5=89=8D=E5=8F=91=E9=80=81=E7=BB=93=E6=9D=9F=E6=B6=88?= =?UTF-8?q?=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/metersphere/node/service/JmeterOperateService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/metersphere/node/service/JmeterOperateService.java b/src/main/java/io/metersphere/node/service/JmeterOperateService.java index a64fd22..fd43d91 100644 --- a/src/main/java/io/metersphere/node/service/JmeterOperateService.java +++ b/src/main/java/io/metersphere/node/service/JmeterOperateService.java @@ -71,7 +71,7 @@ public void onComplete() { // 上传结束消息,取保正常结束 String topic = testRequest.getEnv().getOrDefault("LOG_TOPIC", "JMETER_LOGS"); String reportId = testRequest.getEnv().get("REPORT_ID"); - String[] contents = new String[]{reportId, "none", "0", "Notifying test listeners of end of test"}; + String[] contents = new String[]{reportId, "none", "0", "Remove container completed"}; String log = StringUtils.join(contents, " "); kafkaProducer.sendMessage(topic, log); LogUtil.info("Remove container completed: " + containerId); From af9053b37b59ab0930ce7051fa1491a1deeb26b8 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Mon, 31 May 2021 14:54:50 +0800 Subject: [PATCH 063/109] refactor: spring boot version 2.5.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 94c86e4..6ece8b1 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 2.2.6.RELEASE + 2.5.0 io.metersphere From c206a554a48248684d26af32e71addea9f183840 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Mon, 31 May 2021 14:54:50 +0800 Subject: [PATCH 064/109] refactor: spring boot version 2.5.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 94c86e4..6ece8b1 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 2.2.6.RELEASE + 2.5.0 io.metersphere From b18e949bc6783443580c5eec98584ee05c19e14f Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Mon, 31 May 2021 18:44:56 +0800 Subject: [PATCH 065/109] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96):=20=E4=BF=AE=E5=A4=8DJSONPath=E6=96=AD?= =?UTF-8?q?=E8=A8=80=E6=8F=90=E7=A4=BA=E9=94=99=E8=AF=AF=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../metersphere/api/jmeter/JMeterService.java | 2 +- .../api/jmeter/utils/FileUtils.java | 2 +- .../api/service/JmeterExecuteService.java | 6 +++--- .../jmeter/assertions/JSONPathAssertion.java | 18 ++++++++++++++++-- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/main/java/io/metersphere/api/jmeter/JMeterService.java b/src/main/java/io/metersphere/api/jmeter/JMeterService.java index 152aedc..8c37fdb 100644 --- a/src/main/java/io/metersphere/api/jmeter/JMeterService.java +++ b/src/main/java/io/metersphere/api/jmeter/JMeterService.java @@ -72,7 +72,7 @@ private void addBackendListener(HashTree testPlan, RunRequest request) { testPlan.add(testPlan.getArray()[0], backendListener); } - public void runSerial(RunRequest request, HashTree testPlan) { + public void run(RunRequest request, HashTree testPlan) { try { init(); addBackendListener(testPlan, request); diff --git a/src/main/java/io/metersphere/api/jmeter/utils/FileUtils.java b/src/main/java/io/metersphere/api/jmeter/utils/FileUtils.java index 6a47e83..57fcc34 100644 --- a/src/main/java/io/metersphere/api/jmeter/utils/FileUtils.java +++ b/src/main/java/io/metersphere/api/jmeter/utils/FileUtils.java @@ -10,7 +10,7 @@ public class FileUtils { public static final String BODY_FILE_DIR = "/opt/metersphere/data/body"; public static final String JAR_FILE_DIR = "/opt/metersphere/data/node/jar"; - public static void createBodyFiles(MultipartFile[] bodyFiles,String path) { + public static void createFiles(MultipartFile[] bodyFiles, String path) { if (bodyFiles != null && bodyFiles.length > 0) { File testDir = new File(path); if (!testDir.exists()) { diff --git a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java index f34f576..8c55378 100644 --- a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java +++ b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java @@ -54,8 +54,8 @@ public String run(RunRequest request, MultipartFile[] bodyFiles, MultipartFile[] // 检查KAFKA loadTestProducer.checkKafka(); // 生成附件/JAR文件 - FileUtils.createBodyFiles(bodyFiles, FileUtils.BODY_FILE_DIR); - FileUtils.createBodyFiles(jarFiles, FileUtils.JAR_FILE_DIR); + FileUtils.createFiles(bodyFiles, FileUtils.BODY_FILE_DIR); + FileUtils.createFiles(jarFiles, FileUtils.JAR_FILE_DIR); try { this.loadJar(FileUtils.JAR_FILE_DIR); // 生成执行脚本 @@ -63,7 +63,7 @@ public String run(RunRequest request, MultipartFile[] bodyFiles, MultipartFile[] Object scriptWrapper = SaveService.loadElement(inputSource); HashTree testPlan = JMeterService.getHashTree(scriptWrapper); // 开始执行 - jMeterService.runSerial(request, testPlan); + jMeterService.run(request, testPlan); } catch (Exception e) { LogUtil.error(e.getMessage()); return e.getMessage(); diff --git a/src/main/java/org/apache/jmeter/assertions/JSONPathAssertion.java b/src/main/java/org/apache/jmeter/assertions/JSONPathAssertion.java index 4b832e4..c6cdcc0 100644 --- a/src/main/java/org/apache/jmeter/assertions/JSONPathAssertion.java +++ b/src/main/java/org/apache/jmeter/assertions/JSONPathAssertion.java @@ -109,13 +109,27 @@ private void doAssert(String jsonString) { if (this.isExpectNull()) { throw new IllegalStateException(String.format("Value expected to be null, but found '%s'", value)); } else { - String msg; + String msg = ""; if (this.isUseRegex()) { msg = "Value expected to match regexp '%s', but it did not match: '%s'"; + } else if (StringUtils.isNotEmpty(getOption()) && !this.isEquals(value)) { + switch (getOption()) { + case "CONTAINS": + msg = "Value contains to be '%s', but found '%s'"; + break; + case "NOT_CONTAINS": + msg = "Value not contains to be '%s', but found '%s'"; + break; + case "EQUALS": + msg = "Value equals to be '%s', but found '%s'"; + break; + case "NOT_EQUALS": + msg = "Value not equals to be '%s', but found '%s'"; + break; + } } else { msg = "Value expected to be '%s', but found '%s'"; } - throw new IllegalStateException(String.format(msg, this.getExpectedValue(), objectToString(value))); } } From dfb58eb98d598b8213f0b5394af76e0944f2e738 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Wed, 2 Jun 2021 16:04:22 +0800 Subject: [PATCH 066/109] =?UTF-8?q?refactor:=20=E5=A4=84=E7=90=86oom?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../node/service/JmeterOperateService.java | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/main/java/io/metersphere/node/service/JmeterOperateService.java b/src/main/java/io/metersphere/node/service/JmeterOperateService.java index fd43d91..18bad59 100644 --- a/src/main/java/io/metersphere/node/service/JmeterOperateService.java +++ b/src/main/java/io/metersphere/node/service/JmeterOperateService.java @@ -59,6 +59,10 @@ private void startContainer(TestRequest testRequest, DockerClient dockerClient, DockerClientService.startContainer(dockerClient, containerId); LogUtil.info("Container create started containerId: " + containerId); + + String topic = testRequest.getEnv().getOrDefault("LOG_TOPIC", "JMETER_LOGS"); + String reportId = testRequest.getEnv().get("REPORT_ID"); + dockerClient.waitContainerCmd(containerId) .exec(new WaitContainerResultCallback() { @Override @@ -68,9 +72,7 @@ public void onComplete() { if (DockerClientService.existContainer(dockerClient, containerId) > 0) { DockerClientService.removeContainer(dockerClient, containerId); } - // 上传结束消息,取保正常结束 - String topic = testRequest.getEnv().getOrDefault("LOG_TOPIC", "JMETER_LOGS"); - String reportId = testRequest.getEnv().get("REPORT_ID"); + // 上传结束消息 String[] contents = new String[]{reportId, "none", "0", "Remove container completed"}; String log = StringUtils.join(contents, " "); kafkaProducer.sendMessage(topic, log); @@ -90,7 +92,15 @@ public void onComplete() { .exec(new InvocationBuilder.AsyncResultCallback() { @Override public void onNext(Frame item) { - LogUtil.info(new String(item.getPayload()).trim()); + String log = new String(item.getPayload()).trim(); + String oomMessage = "There is insufficient memory for the Java Runtime Environment to continue."; + if (StringUtils.contains(log, oomMessage)) { + // oom 退出 + String[] contents = new String[]{reportId, "none", "0", oomMessage}; + String message = StringUtils.join(contents, " "); + kafkaProducer.sendMessage(topic, message); + } + LogUtil.info(log); } }); } From fd6988d538aea4582681609437364230c0f25133 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Sun, 6 Jun 2021 17:02:48 +0800 Subject: [PATCH 067/109] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9pom?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 6ece8b1..e845d8f 100644 --- a/pom.xml +++ b/pom.xml @@ -130,7 +130,7 @@ com.thoughtworks.xstream xstream - 1.4.16 + 1.4.17 @@ -318,7 +318,6 @@ org.apache.httpcomponents httpclient - 4.5.6 From 3fe4d0e0b8c2c9fd734b5cf7ca74431dd08cf312 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Sun, 6 Jun 2021 17:02:48 +0800 Subject: [PATCH 068/109] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9pom?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 6ece8b1..e845d8f 100644 --- a/pom.xml +++ b/pom.xml @@ -130,7 +130,7 @@ com.thoughtworks.xstream xstream - 1.4.16 + 1.4.17 @@ -318,7 +318,6 @@ org.apache.httpcomponents httpclient - 4.5.6 From 429e22b493887aca3b3d7caa3ce84146b2195091 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Mon, 7 Jun 2021 10:55:06 +0800 Subject: [PATCH 069/109] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96):=20=E5=90=8C=E6=AD=A5=E4=BC=98=E5=8C=96cons?= =?UTF-8?q?ole=E6=95=B0=E6=8D=AE=E5=AD=98=E5=82=A8=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/io/metersphere/api/module/TestResult.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/io/metersphere/api/module/TestResult.java b/src/main/java/io/metersphere/api/module/TestResult.java index 45b80f4..8780d81 100644 --- a/src/main/java/io/metersphere/api/module/TestResult.java +++ b/src/main/java/io/metersphere/api/module/TestResult.java @@ -36,6 +36,8 @@ public class TestResult { private int passAssertions = 0; + private String console; + private List scenarios = new ArrayList<>(); public void addError(int count) { From ba729566eec164218518241bba127c9d83a9680b Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Fri, 11 Jun 2021 15:21:01 +0800 Subject: [PATCH 070/109] =?UTF-8?q?refactor:=20=E5=88=86=E5=B1=82=E6=9E=84?= =?UTF-8?q?=E5=BB=BA=E9=95=9C=E5=83=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/Dockerfile b/Dockerfile index a99da6c..ab75490 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,21 +1,28 @@ +FROM openjdk:8-jdk-alpine as build +WORKDIR /workspace/app + +COPY target/node-controller-1.8.jar . + +RUN mkdir -p dependency && (cd dependency; jar -xf ../*.jar) + FROM metersphere/fabric8-java-alpine-openjdk8-jre MAINTAINER FIT2CLOUD ARG MS_VERSION=dev +ARG DEPENDENCY=/workspace/app/dependency -RUN mkdir -p /opt/apps && mkdir -p /opt/jmeter/lib/junit - -ADD target/node-controller-1.8.jar /opt/apps +COPY --from=build ${DEPENDENCY}/BOOT-INF/lib /app/lib +COPY --from=build ${DEPENDENCY}/META-INF /app/META-INF +COPY --from=build ${DEPENDENCY}/BOOT-INF/classes /app +RUN mkdir -p /opt/jmeter/lib/junit COPY target/classes/jmeter/ /opt/jmeter/ -ENV JAVA_APP_JAR=/opt/apps/node-controller-1.8.jar - +ENV JAVA_CLASSPATH=/app:/app/lib/* +ENV JAVA_MAIN_CLASS=io.metersphere.Application ENV AB_OFF=true - ENV MS_VERSION=${MS_VERSION} - ENV JAVA_OPTIONS=-Dfile.encoding=utf-8 CMD ["/deployments/run-java.sh"] From c67eb3dade374c3dcc3e568996e1c8ba5893927c Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Fri, 11 Jun 2021 18:09:08 +0800 Subject: [PATCH 071/109] =?UTF-8?q?feat(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96):=20=E4=BF=AE=E6=94=B9=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E6=8E=A5=E6=94=B6=E6=96=B9=E5=BC=8F=EF=BC=8C=E7=94=B1=E6=8E=A5?= =?UTF-8?q?=E6=94=B6=E6=96=B9=E6=94=B9=E4=B8=BA=E6=8B=89=E5=8F=96=E6=96=B9?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/JmeterExecuteController.java | 6 + .../api/controller/request/RunRequest.java | 1 + .../api/service/JmeterExecuteService.java | 34 ++++ .../io/metersphere/api/service/ZipSpider.java | 187 ++++++++++++++++++ 4 files changed, 228 insertions(+) create mode 100644 src/main/java/io/metersphere/api/service/ZipSpider.java diff --git a/src/main/java/io/metersphere/api/controller/JmeterExecuteController.java b/src/main/java/io/metersphere/api/controller/JmeterExecuteController.java index 7d0a7e4..d5c283d 100644 --- a/src/main/java/io/metersphere/api/controller/JmeterExecuteController.java +++ b/src/main/java/io/metersphere/api/controller/JmeterExecuteController.java @@ -25,6 +25,12 @@ public String apiRun(@RequestParam(value = "files") MultipartFile[] bodyFiles, @ return jmeterExecuteService.run(runRequest, bodyFiles, jarFiles); } + @PostMapping(value = "/api/start") + public String apiStartRun(@RequestBody RunRequest runRequest) { + LogUtil.info("接收到测试请求 start "); + return jmeterExecuteService.runStart(runRequest); + } + @GetMapping("/status") public String getStatus() { return "OK"; diff --git a/src/main/java/io/metersphere/api/controller/request/RunRequest.java b/src/main/java/io/metersphere/api/controller/request/RunRequest.java index f0e6408..5e97072 100644 --- a/src/main/java/io/metersphere/api/controller/request/RunRequest.java +++ b/src/main/java/io/metersphere/api/controller/request/RunRequest.java @@ -6,6 +6,7 @@ @Data public class RunRequest { private String testId; + private String url; private String userId; private boolean isDebug; private String runMode; diff --git a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java index 8c55378..6e8ebd0 100644 --- a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java +++ b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java @@ -2,6 +2,7 @@ import io.metersphere.api.controller.request.RunRequest; import io.metersphere.api.jmeter.JMeterService; +import io.metersphere.api.jmeter.LocalRunner; import io.metersphere.api.jmeter.utils.FileUtils; import io.metersphere.api.jmeter.utils.MSException; import io.metersphere.node.util.LogUtil; @@ -14,8 +15,10 @@ import javax.annotation.Resource; import java.io.ByteArrayInputStream; +import java.io.File; import java.io.InputStream; import java.net.MalformedURLException; +import java.net.URL; @Service public class JmeterExecuteService { @@ -70,4 +73,35 @@ public String run(RunRequest request, MultipartFile[] bodyFiles, MultipartFile[] } return "SUCCESS"; } + + public String runStart(RunRequest runRequest) { + // 检查KAFKA + loadTestProducer.checkKafka(); + try { + // 生成附件/JAR文件 + URL urlObject = new URL(runRequest.getUrl()); + String jarUrl = urlObject.getProtocol() + "://" + urlObject.getHost() + (urlObject.getPort() > 0 ? ":" + urlObject.getPort() : "") + "/api/jmeter/download/jar"; + LogUtil.info("开始同步上传的第三方JAR:" + jarUrl); + + File file = ZipSpider.downloadFile(jarUrl, FileUtils.JAR_FILE_DIR); + if (file != null) { + ZipSpider.unzip(file.getPath(), FileUtils.JAR_FILE_DIR); + this.loadJar(FileUtils.JAR_FILE_DIR); + } + LogUtil.info("开始拉取脚本和脚本附件:" + runRequest.getUrl()); + + File bodyFile = ZipSpider.downloadFile(runRequest.getUrl(), FileUtils.BODY_FILE_DIR); + ZipSpider.unzip(bodyFile.getPath(), FileUtils.BODY_FILE_DIR); + File jmxFile = new File(FileUtils.BODY_FILE_DIR + "/" + runRequest.getTestId() + ".jmx"); + // 生成执行脚本 + HashTree testPlan = SaveService.loadTree(jmxFile); + // 开始执行 + jMeterService.run(runRequest, testPlan); + } catch (Exception e) { + LogUtil.error(e.getMessage()); + return e.getMessage(); + } + return "SUCCESS"; + } + } diff --git a/src/main/java/io/metersphere/api/service/ZipSpider.java b/src/main/java/io/metersphere/api/service/ZipSpider.java new file mode 100644 index 0000000..baa9c8a --- /dev/null +++ b/src/main/java/io/metersphere/api/service/ZipSpider.java @@ -0,0 +1,187 @@ +package io.metersphere.api.service; + +import java.io.*; +import java.net.*; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +import io.metersphere.node.util.LogUtil; + +public class ZipSpider { + + //根据网址返回网页源代码 + public static String getHtmlFromUrl(String url, String encoding) { + StringBuffer html = new StringBuffer(); + InputStreamReader isr = null; + BufferedReader buf = null; + String str = null; + try { + URL urlObj = new URL(url); + URLConnection con = urlObj.openConnection(); + isr = new InputStreamReader(con.getInputStream(), encoding); + buf = new BufferedReader(isr); + while ((str = buf.readLine()) != null) { + html.append(str + "\n"); + } + //sop(html.toString()); + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (isr != null) { + try { + buf.close(); + isr.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + return html.toString(); + } + + //根据网址下载网络文件到硬盘,包括图片,Gif图,以及压缩包 + public static void download(String url, String path) { + File file = null; + FileOutputStream fos = null; + String downloadName = url.substring(url.lastIndexOf("/") + 1); + HttpURLConnection httpCon = null; + URLConnection con = null; + URL urlObj = null; + InputStream in = null; + byte[] size = new byte[1024]; + int num = 0; + try { + file = new File(path + downloadName); + fos = new FileOutputStream(file); + if (url.startsWith("http")) { + urlObj = new URL(url); + con = urlObj.openConnection(); + httpCon = (HttpURLConnection) con; + in = httpCon.getInputStream(); + while ((num = in.read(size)) != -1) { + for (int i = 0; i < num; i++) + fos.write(size[i]); + } + } + + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + in.close(); + fos.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + //解压本地文件至目的文件路径 + public static void unzip(String fromFile, String toFile) { + try { + ZipInputStream Zin = new ZipInputStream(new FileInputStream(fromFile)); + BufferedInputStream Bin = new BufferedInputStream(Zin); + String Parent = toFile; + File Fout = null; + ZipEntry entry; + try { + while ((entry = Zin.getNextEntry()) != null && !entry.isDirectory()) { + Fout = new File(Parent, entry.getName()); + if (!Fout.exists()) { + (new File(Fout.getParent())).mkdirs(); + } + FileOutputStream out = new FileOutputStream(Fout); + BufferedOutputStream Bout = new BufferedOutputStream(out); + int b; + while ((b = Bin.read()) != -1) { + Bout.write(b); + } + Bout.close(); + out.close(); + LogUtil.info(Fout + "解压成功"); + } + Bin.close(); + Zin.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + } + + //从总目录下解压文件里所有的压缩包至目的文件路径 + public static void unzipFromLoc(String filePath) throws Exception { + File file = new File(filePath); + File[] list = file.listFiles(); + String from = ""; + String to = "E:\\myDownload\\unzipFileFromWeb\\"; + for (File f : list) { + boolean bool = f.isFile(); + if (bool) { + from = f.getAbsolutePath(); + from = from.replace("\\", "\\\\"); + sop(from); + unzip(from, to); + } + } + } + + public static void sop(Object obj) { + LogUtil.info(obj); + } + + public static void seperate(char c) { + for (int x = 0; x < 100; x++) { + System.out.print(c); + } + sop(""); + } + + @SuppressWarnings("finally") + public static File downloadFile(String urlPath, String downloadDir) { + File file = null; + try { + URL url = new URL(urlPath); + URLConnection urlConnection = url.openConnection(); + HttpURLConnection httpURLConnection = (HttpURLConnection) urlConnection;// http的连接类 + //String contentType = httpURLConnection.getContentType();//请求类型,可用来过滤请求, + httpURLConnection.setConnectTimeout(1000 * 5);//设置超时 + httpURLConnection.setRequestMethod("GET");//设置请求方式,默认是GET + httpURLConnection.setRequestProperty("Charset", "UTF-8");// 设置字符编码 + httpURLConnection.connect();// 打开连接 + + BufferedInputStream bin = new BufferedInputStream(httpURLConnection.getInputStream()); + String fileName = httpURLConnection.getHeaderField("Content-Disposition"); + fileName = URLDecoder.decode(fileName.substring(fileName.indexOf("filename") + 10, fileName.length() - 1), "UTF-8"); + String path = downloadDir + File.separatorChar + fileName;// 指定存放位置 + file = new File(path); + // 校验文件夹目录是否存在,不存在就创建一个目录 + if (!file.getParentFile().exists()) { + file.getParentFile().mkdirs(); + } + + OutputStream out = new FileOutputStream(file); + int size = 0; + + byte[] b = new byte[2048]; + //把输入流的文件读取到字节数据b中,然后输出到指定目录的文件 + while ((size = bin.read(b)) != -1) { + out.write(b, 0, size); + } + // 关闭资源 + bin.close(); + out.close(); + LogUtil.info("文件下载成功!"); + } catch (MalformedURLException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + LogUtil.info("文件下载失败!"); + } finally { + return file; + } + } +} From 59024f0fa333834a971d2a5642fd9686a47b07eb Mon Sep 17 00:00:00 2001 From: liuruibin Date: Sun, 13 Jun 2021 13:40:47 +0800 Subject: [PATCH 072/109] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9dockerfil?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index ab75490..d9c0345 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ FROM openjdk:8-jdk-alpine as build WORKDIR /workspace/app -COPY target/node-controller-1.8.jar . +COPY target/*.jar . RUN mkdir -p dependency && (cd dependency; jar -xf ../*.jar) From 84cd44a91f1ce5f86d86ea92762118ee3f829f2f Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Tue, 15 Jun 2021 16:21:44 +0800 Subject: [PATCH 073/109] =?UTF-8?q?feat(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96):=20=E4=BF=AE=E5=A4=8D=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E6=89=B9=E9=87=8F=E6=89=A7=E8=A1=8C=E6=96=B9=E6=B3=95=EF=BC=8C?= =?UTF-8?q?=E4=B8=BB=E5=8A=A8=E4=B8=8B=E8=BD=BD=E6=89=A7=E8=A1=8C=E6=96=87?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/controller/request/RunRequest.java | 2 + .../api/jmeter/APIBackendListenerClient.java | 65 ++++++++++--------- .../metersphere/api/jmeter/JMeterService.java | 13 +++- .../io/metersphere/api/module/TestResult.java | 2 + 4 files changed, 50 insertions(+), 32 deletions(-) diff --git a/src/main/java/io/metersphere/api/controller/request/RunRequest.java b/src/main/java/io/metersphere/api/controller/request/RunRequest.java index 5e97072..180ec4f 100644 --- a/src/main/java/io/metersphere/api/controller/request/RunRequest.java +++ b/src/main/java/io/metersphere/api/controller/request/RunRequest.java @@ -6,6 +6,8 @@ @Data public class RunRequest { private String testId; + // api / case 或有这个属性值 + private String reportId; private String url; private String userId; private boolean isDebug; diff --git a/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java b/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java index 3b0a798..f9a8957 100644 --- a/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java +++ b/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java @@ -96,41 +96,46 @@ public void teardownTest(BackendListenerContext context) throws Exception { final Map scenarios = new LinkedHashMap<>(); queue.forEach(result -> { // 线程名称: <场景名> <场景Index>-<请求Index>, 例如:Scenario 2-1 - String scenarioName = StringUtils.substringBeforeLast(result.getThreadName(), THREAD_SPLIT); - String index = StringUtils.substringAfterLast(result.getThreadName(), THREAD_SPLIT); - String scenarioId = StringUtils.substringBefore(index, ID_SPLIT); - ScenarioResult scenarioResult; - if (!scenarios.containsKey(scenarioId)) { - scenarioResult = new ScenarioResult(); - try { - scenarioResult.setId(Integer.parseInt(scenarioId)); - } catch (Exception e) { - scenarioResult.setId(0); - LogUtil.error("场景ID转换异常: " + e.getMessage()); + if (StringUtils.equals(result.getSampleLabel(), "RunningDebugSampler")) { + String evnStr = result.getResponseDataAsString(); + testResult.setRunningDebugSampler(result.getResponseDataAsString()); + } else { + String scenarioName = StringUtils.substringBeforeLast(result.getThreadName(), THREAD_SPLIT); + String index = StringUtils.substringAfterLast(result.getThreadName(), THREAD_SPLIT); + String scenarioId = StringUtils.substringBefore(index, ID_SPLIT); + ScenarioResult scenarioResult; + if (!scenarios.containsKey(scenarioId)) { + scenarioResult = new ScenarioResult(); + try { + scenarioResult.setId(Integer.parseInt(scenarioId)); + } catch (Exception e) { + scenarioResult.setId(0); + LogUtil.error("场景ID转换异常: " + e.getMessage()); + } + scenarioResult.setName(scenarioName); + scenarios.put(scenarioId, scenarioResult); + } else { + scenarioResult = scenarios.get(scenarioId); } - scenarioResult.setName(scenarioName); - scenarios.put(scenarioId, scenarioResult); - } else { - scenarioResult = scenarios.get(scenarioId); - } - if (result.isSuccessful()) { - scenarioResult.addSuccess(); - testResult.addSuccess(); - } else { - scenarioResult.addError(result.getErrorCount()); - testResult.addError(result.getErrorCount()); - } + if (result.isSuccessful()) { + scenarioResult.addSuccess(); + testResult.addSuccess(); + } else { + scenarioResult.addError(result.getErrorCount()); + testResult.addError(result.getErrorCount()); + } - RequestResult requestResult = getRequestResult(result); - scenarioResult.getRequestResults().add(requestResult); - scenarioResult.addResponseTime(result.getTime()); + RequestResult requestResult = getRequestResult(result); + scenarioResult.getRequestResults().add(requestResult); + scenarioResult.addResponseTime(result.getTime()); - testResult.addPassAssertions(requestResult.getPassAssertions()); - testResult.addTotalAssertions(requestResult.getTotalAssertions()); + testResult.addPassAssertions(requestResult.getPassAssertions()); + testResult.addTotalAssertions(requestResult.getTotalAssertions()); - scenarioResult.addPassAssertions(requestResult.getPassAssertions()); - scenarioResult.addTotalAssertions(requestResult.getTotalAssertions()); + scenarioResult.addPassAssertions(requestResult.getPassAssertions()); + scenarioResult.addTotalAssertions(requestResult.getTotalAssertions()); + } }); testResult.getScenarios().addAll(scenarios.values()); testResult.getScenarios().sort(Comparator.comparing(ScenarioResult::getId)); diff --git a/src/main/java/io/metersphere/api/jmeter/JMeterService.java b/src/main/java/io/metersphere/api/jmeter/JMeterService.java index 8c37fdb..1a3baf6 100644 --- a/src/main/java/io/metersphere/api/jmeter/JMeterService.java +++ b/src/main/java/io/metersphere/api/jmeter/JMeterService.java @@ -9,6 +9,7 @@ import org.apache.jmeter.util.JMeterUtils; import org.apache.jmeter.visualizers.backend.BackendListener; import org.apache.jorphan.collections.HashTree; +import org.python.antlr.ast.arguments; import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.stereotype.Service; @@ -56,12 +57,20 @@ public static HashTree getHashTree(Object scriptWrapper) throws Exception { private void addBackendListener(HashTree testPlan, RunRequest request) { BackendListener backendListener = new BackendListener(); - backendListener.setName(request.getTestId()); + if (StringUtils.isNotEmpty(request.getReportId())) { + backendListener.setName(request.getReportId()); + } else { + backendListener.setName(request.getTestId()); + } Arguments arguments = new Arguments(); if (request.getConfig() != null && request.getConfig().getMode().equals("serial") && request.getConfig().getReportType().equals("setReport")) { arguments.addArgument(APIBackendListenerClient.TEST_REPORT_ID, request.getConfig().getReportName()); } - arguments.addArgument(APIBackendListenerClient.TEST_ID, request.getTestId()); + if (StringUtils.isNotEmpty(request.getReportId())) { + arguments.addArgument(APIBackendListenerClient.TEST_ID, request.getReportId()); + } else { + arguments.addArgument(APIBackendListenerClient.TEST_ID, request.getTestId()); + } if (StringUtils.isNotBlank(request.getRunMode())) { arguments.addArgument("runMode", request.getRunMode()); } diff --git a/src/main/java/io/metersphere/api/module/TestResult.java b/src/main/java/io/metersphere/api/module/TestResult.java index 8780d81..c0d48e4 100644 --- a/src/main/java/io/metersphere/api/module/TestResult.java +++ b/src/main/java/io/metersphere/api/module/TestResult.java @@ -38,6 +38,8 @@ public class TestResult { private String console; + private String runningDebugSampler; + private List scenarios = new ArrayList<>(); public void addError(int count) { From 4453a1633fb056d69eccea6fe3adbdb04c36af3c Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Wed, 16 Jun 2021 10:44:31 +0800 Subject: [PATCH 074/109] =?UTF-8?q?feat(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96):=20=E4=BF=AE=E5=A4=8D=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E6=89=B9=E9=87=8F=E6=89=A7=E8=A1=8C=E6=96=B9=E6=B3=95=EF=BC=8C?= =?UTF-8?q?=E4=B8=BB=E5=8A=A8=E4=B8=8B=E8=BD=BD=E6=89=A7=E8=A1=8C=E6=96=87?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/io/metersphere/api/config/AppStartListener.java | 2 +- .../io/metersphere/api/controller/JmeterExecuteController.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/metersphere/api/config/AppStartListener.java b/src/main/java/io/metersphere/api/config/AppStartListener.java index fd4b171..6d2e6db 100644 --- a/src/main/java/io/metersphere/api/config/AppStartListener.java +++ b/src/main/java/io/metersphere/api/config/AppStartListener.java @@ -26,7 +26,7 @@ public class AppStartListener implements ApplicationListener Date: Thu, 17 Jun 2021 14:17:05 +0800 Subject: [PATCH 075/109] =?UTF-8?q?feat(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96):=20=E6=94=AF=E6=8C=81K8s=E8=B0=83=E5=BA=A6?= =?UTF-8?q?=E6=89=A7=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/io/metersphere/api/jmeter/JMeterService.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/metersphere/api/jmeter/JMeterService.java b/src/main/java/io/metersphere/api/jmeter/JMeterService.java index 1a3baf6..1eb36c0 100644 --- a/src/main/java/io/metersphere/api/jmeter/JMeterService.java +++ b/src/main/java/io/metersphere/api/jmeter/JMeterService.java @@ -1,6 +1,7 @@ package io.metersphere.api.jmeter; import io.metersphere.api.controller.request.RunRequest; +import io.metersphere.api.jmeter.constants.ApiRunMode; import io.metersphere.api.jmeter.utils.JmeterProperties; import io.metersphere.api.jmeter.utils.MSException; import io.metersphere.node.util.LogUtil; @@ -66,7 +67,7 @@ private void addBackendListener(HashTree testPlan, RunRequest request) { if (request.getConfig() != null && request.getConfig().getMode().equals("serial") && request.getConfig().getReportType().equals("setReport")) { arguments.addArgument(APIBackendListenerClient.TEST_REPORT_ID, request.getConfig().getReportName()); } - if (StringUtils.isNotEmpty(request.getReportId())) { + if (StringUtils.isNotEmpty(request.getReportId()) && ApiRunMode.API_PLAN.name().equals(request.getRunMode())) { arguments.addArgument(APIBackendListenerClient.TEST_ID, request.getReportId()); } else { arguments.addArgument(APIBackendListenerClient.TEST_ID, request.getTestId()); From 0cb5544af7bd25582076efae71cad5f122d5dd44 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Thu, 17 Jun 2021 15:45:36 +0800 Subject: [PATCH 076/109] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96):=20=E7=BB=9F=E4=B8=80topic=20=E5=85=A5?= =?UTF-8?q?=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/io/metersphere/api/config/KafkaConfig.java | 2 +- .../java/io/metersphere/api/service/LoadTestProducer.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/metersphere/api/config/KafkaConfig.java b/src/main/java/io/metersphere/api/config/KafkaConfig.java index 80ce8a6..940317f 100644 --- a/src/main/java/io/metersphere/api/config/KafkaConfig.java +++ b/src/main/java/io/metersphere/api/config/KafkaConfig.java @@ -7,7 +7,7 @@ @Configuration public class KafkaConfig { - private final String testTopic = "ms-api-exec-topic"; + public final static String testTopic = "ms-api-exec-topic"; @Bean public NewTopic apiExecTopic() { diff --git a/src/main/java/io/metersphere/api/service/LoadTestProducer.java b/src/main/java/io/metersphere/api/service/LoadTestProducer.java index 0b323c3..00ec653 100644 --- a/src/main/java/io/metersphere/api/service/LoadTestProducer.java +++ b/src/main/java/io/metersphere/api/service/LoadTestProducer.java @@ -1,5 +1,6 @@ package io.metersphere.api.service; +import io.metersphere.api.config.KafkaConfig; import io.metersphere.api.config.KafkaProperties; import io.metersphere.api.jmeter.utils.CommonBeanFactory; import io.metersphere.api.jmeter.utils.MSException; @@ -15,7 +16,6 @@ @Service public class LoadTestProducer { - private static final String topic = "ms-api-exec-topic"; public void checkKafka() { KafkaProperties kafkaProperties = CommonBeanFactory.getBean(KafkaProperties.class); @@ -48,6 +48,6 @@ public void checkKafka() { private KafkaTemplate kafkaTemplate; public void sendMessage(String report) { - this.kafkaTemplate.send(topic, report); + this.kafkaTemplate.send(KafkaConfig.testTopic, report); } } From 17ecb1ec0e3e52cd4b28930bfe022ebdcedad233 Mon Sep 17 00:00:00 2001 From: liuruibin Date: Sat, 19 Jun 2021 11:18:29 +0800 Subject: [PATCH 077/109] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9dockerfil?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index d9c0345..897d08e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,7 +7,7 @@ RUN mkdir -p dependency && (cd dependency; jar -xf ../*.jar) FROM metersphere/fabric8-java-alpine-openjdk8-jre -MAINTAINER FIT2CLOUD +LABEL maintainer="FIT2CLOUD " ARG MS_VERSION=dev ARG DEPENDENCY=/workspace/app/dependency @@ -18,6 +18,7 @@ COPY --from=build ${DEPENDENCY}/BOOT-INF/classes /app RUN mkdir -p /opt/jmeter/lib/junit COPY target/classes/jmeter/ /opt/jmeter/ +COPY backend/target/classes/jmeter/ /app/jmeter/ ENV JAVA_CLASSPATH=/app:/app/lib/* ENV JAVA_MAIN_CLASS=io.metersphere.Application From dc9118d6ae3bc27dec1ff24e6879d996ffcab6e8 Mon Sep 17 00:00:00 2001 From: liuruibin Date: Sat, 19 Jun 2021 11:19:17 +0800 Subject: [PATCH 078/109] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9dockerfil?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 897d08e..a6ba570 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,7 +18,7 @@ COPY --from=build ${DEPENDENCY}/BOOT-INF/classes /app RUN mkdir -p /opt/jmeter/lib/junit COPY target/classes/jmeter/ /opt/jmeter/ -COPY backend/target/classes/jmeter/ /app/jmeter/ +COPY target/classes/jmeter/ /app/jmeter/ ENV JAVA_CLASSPATH=/app:/app/lib/* ENV JAVA_MAIN_CLASS=io.metersphere.Application From 240e904ab09f51b2b77ef7362c2f3e01b88b9227 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Sun, 20 Jun 2021 09:44:13 +0800 Subject: [PATCH 079/109] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9pom?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/pom.xml b/pom.xml index e845d8f..627a933 100644 --- a/pom.xml +++ b/pom.xml @@ -20,6 +20,7 @@ 1.1.3 2.7.8 3.0.8 + 1.2 @@ -377,6 +378,12 @@ ApacheJMeter_native ${jmeter.version} + + io.metersphere + metersphere-jmeter-functions + ${metersphere-jmeter-functions.version} + + com.jayway.jsonpath @@ -431,6 +438,15 @@ src/main/resources/jmeter/lib/ext ApacheJMeter_functions.jar + + io.metersphere + metersphere-jmeter-functions + ${metersphere-jmeter-functions.version} + jar + true + src/main/resources/jmeter/lib/ext + metersphere-jmeter-functions.jar + org.python jython-standalone From 7e7b6a7229378d6255292449bcdd049db91001ba Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Sat, 26 Jun 2021 18:27:19 +0800 Subject: [PATCH 080/109] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9pom?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 627a933..dc08624 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ 1.1.3 2.7.8 3.0.8 - 1.2 + 1.4 From 02f52108f35a83e91b1b91fce00d370589bb18c7 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Wed, 30 Jun 2021 18:12:39 +0800 Subject: [PATCH 081/109] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9dockerfil?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index a6ba570..cd15d07 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,10 +15,10 @@ ARG DEPENDENCY=/workspace/app/dependency COPY --from=build ${DEPENDENCY}/BOOT-INF/lib /app/lib COPY --from=build ${DEPENDENCY}/META-INF /app/META-INF COPY --from=build ${DEPENDENCY}/BOOT-INF/classes /app +COPY target/classes/jmeter/ /opt/jmeter/ RUN mkdir -p /opt/jmeter/lib/junit -COPY target/classes/jmeter/ /opt/jmeter/ -COPY target/classes/jmeter/ /app/jmeter/ +RUN cp -rf /opt/jmeter/ /app/jmeter/ ENV JAVA_CLASSPATH=/app:/app/lib/* ENV JAVA_MAIN_CLASS=io.metersphere.Application From 7120fda7cd5405007e5a64a002b9486e09f64a2c Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Wed, 30 Jun 2021 18:34:31 +0800 Subject: [PATCH 082/109] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9dockerfil?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index cd15d07..74c5d55 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,7 +18,7 @@ COPY --from=build ${DEPENDENCY}/BOOT-INF/classes /app COPY target/classes/jmeter/ /opt/jmeter/ RUN mkdir -p /opt/jmeter/lib/junit -RUN cp -rf /opt/jmeter/ /app/jmeter/ +RUN cp -rf /opt/jmeter /app/ ENV JAVA_CLASSPATH=/app:/app/lib/* ENV JAVA_MAIN_CLASS=io.metersphere.Application From 06e2495a15e57553814e422be8cb07e3736b1e97 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Thu, 1 Jul 2021 12:26:34 +0800 Subject: [PATCH 083/109] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9dockerfil?= =?UTF-8?q?e,=20=E9=81=BF=E5=85=8D=E5=A4=8D=E5=88=B6=E5=90=8C=E6=A0=B7?= =?UTF-8?q?=E7=9A=84=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 74c5d55..6b0280c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,10 +15,9 @@ ARG DEPENDENCY=/workspace/app/dependency COPY --from=build ${DEPENDENCY}/BOOT-INF/lib /app/lib COPY --from=build ${DEPENDENCY}/META-INF /app/META-INF COPY --from=build ${DEPENDENCY}/BOOT-INF/classes /app -COPY target/classes/jmeter/ /opt/jmeter/ +RUN cp -rf /app/jmeter /opt/ RUN mkdir -p /opt/jmeter/lib/junit -RUN cp -rf /opt/jmeter /app/ ENV JAVA_CLASSPATH=/app:/app/lib/* ENV JAVA_MAIN_CLASS=io.metersphere.Application From a007981d6327ec85cb830a281fed141c3d672a7c Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Thu, 1 Jul 2021 12:41:42 +0800 Subject: [PATCH 084/109] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9dockerfil?= =?UTF-8?q?e,=20=E9=81=BF=E5=85=8D=E5=A4=8D=E5=88=B6=E5=90=8C=E6=A0=B7?= =?UTF-8?q?=E7=9A=84=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 6b0280c..2a8637e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,7 +16,7 @@ COPY --from=build ${DEPENDENCY}/BOOT-INF/lib /app/lib COPY --from=build ${DEPENDENCY}/META-INF /app/META-INF COPY --from=build ${DEPENDENCY}/BOOT-INF/classes /app -RUN cp -rf /app/jmeter /opt/ +RUN mv -rf /app/jmeter /opt/ RUN mkdir -p /opt/jmeter/lib/junit ENV JAVA_CLASSPATH=/app:/app/lib/* From d7711359630dd8f9696ed3db964520f02286030d Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Thu, 1 Jul 2021 12:44:20 +0800 Subject: [PATCH 085/109] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9dockerfil?= =?UTF-8?q?e,=20=E9=81=BF=E5=85=8D=E5=A4=8D=E5=88=B6=E5=90=8C=E6=A0=B7?= =?UTF-8?q?=E7=9A=84=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 2a8637e..4abaf45 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,7 +16,7 @@ COPY --from=build ${DEPENDENCY}/BOOT-INF/lib /app/lib COPY --from=build ${DEPENDENCY}/META-INF /app/META-INF COPY --from=build ${DEPENDENCY}/BOOT-INF/classes /app -RUN mv -rf /app/jmeter /opt/ +RUN mv /app/jmeter /opt/ RUN mkdir -p /opt/jmeter/lib/junit ENV JAVA_CLASSPATH=/app:/app/lib/* From bbd086f5e5db606e2c5c84b5136a47a0d0922f3d Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Tue, 13 Jul 2021 15:41:56 +0800 Subject: [PATCH 086/109] =?UTF-8?q?build:=20=E6=8C=87=E5=AE=9A=20kafka=20p?= =?UTF-8?q?roducer=20=E7=9A=84=E5=8D=8F=E8=AE=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.properties | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 70b2f6a..f241ab2 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -5,6 +5,7 @@ server.port=8082 #kafka spring.kafka.bootstrap-servers=${kafka.bootstrap-servers} spring.kafka.consumer.group-id=metersphere_group_id +spring.kafka.producer.properties.security.protocol=PLAINTEXT kafka.fields= kafka.timestamp=yyyy-MM-dd'T'HH:mm:ss.SSSZZ kafka.sample-filter= From 9a73386037ac8a2ff9ad1414b962a4755ddc1937 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Tue, 13 Jul 2021 16:58:13 +0800 Subject: [PATCH 087/109] =?UTF-8?q?chore:=20=E4=BF=AE=E6=94=B9=20check=20k?= =?UTF-8?q?afka=20=E7=9A=84=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/service/LoadTestProducer.java | 16 +++++----------- .../node/service/JmeterOperateService.java | 15 +++++---------- src/main/resources/application.properties | 1 - 3 files changed, 10 insertions(+), 22 deletions(-) diff --git a/src/main/java/io/metersphere/api/service/LoadTestProducer.java b/src/main/java/io/metersphere/api/service/LoadTestProducer.java index 00ec653..42948c6 100644 --- a/src/main/java/io/metersphere/api/service/LoadTestProducer.java +++ b/src/main/java/io/metersphere/api/service/LoadTestProducer.java @@ -10,7 +10,6 @@ import org.springframework.stereotype.Service; import javax.annotation.Resource; -import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.Socket; @@ -26,16 +25,11 @@ public void checkKafka() { //1,建立tcp String ip = ipAndPort[0]; int port = Integer.parseInt(ipAndPort[1]); - Socket soc = new Socket(); - // 1s timeout - soc.connect(new InetSocketAddress(ip, port), 1000); - //2.输入内容 - String content = "1010"; - byte[] bs = content.getBytes(); - OutputStream os = soc.getOutputStream(); - os.write(bs); - //3.关闭 - soc.close(); + try ( + Socket soc = new Socket() + ) { + soc.connect(new InetSocketAddress(ip, port), 1000); + } } } catch (Exception e) { LogUtil.error(e); diff --git a/src/main/java/io/metersphere/node/service/JmeterOperateService.java b/src/main/java/io/metersphere/node/service/JmeterOperateService.java index 18bad59..3487dfa 100644 --- a/src/main/java/io/metersphere/node/service/JmeterOperateService.java +++ b/src/main/java/io/metersphere/node/service/JmeterOperateService.java @@ -15,7 +15,6 @@ import org.springframework.util.CollectionUtils; import javax.annotation.Resource; -import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.Socket; import java.util.Arrays; @@ -124,15 +123,11 @@ private void checkKafka(String bootstrapServers) { //1,建立tcp String ip = ipAndPort[0]; int port = Integer.parseInt(ipAndPort[1]); - Socket soc = new Socket(); - soc.connect(new InetSocketAddress(ip, port), 1000); // 1s timeout - //2.输入内容 - String content = "1010"; - byte[] bs = content.getBytes(); - OutputStream os = soc.getOutputStream(); - os.write(bs); - //3.关闭 - soc.close(); + try ( + Socket soc = new Socket() + ) { + soc.connect(new InetSocketAddress(ip, port), 1000); // 1s timeout + } } } catch (Exception e) { LogUtil.error(e); diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index f241ab2..70b2f6a 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -5,7 +5,6 @@ server.port=8082 #kafka spring.kafka.bootstrap-servers=${kafka.bootstrap-servers} spring.kafka.consumer.group-id=metersphere_group_id -spring.kafka.producer.properties.security.protocol=PLAINTEXT kafka.fields= kafka.timestamp=yyyy-MM-dd'T'HH:mm:ss.SSSZZ kafka.sample-filter= From 2b28108867bb6c23caf7e73d42199378abaa5280 Mon Sep 17 00:00:00 2001 From: Cong Date: Wed, 14 Jul 2021 21:25:06 +0800 Subject: [PATCH 088/109] =?UTF-8?q?=E4=B8=8D=E7=AD=89=E4=BA=8E=E6=96=AD?= =?UTF-8?q?=E8=A8=80bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 包含情况断言失败 (cherry picked from commit c534bdba59ca1ef3392780b518d6d45d2067aa14) --- .../java/org/apache/jmeter/assertions/JSONPathAssertion.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/apache/jmeter/assertions/JSONPathAssertion.java b/src/main/java/org/apache/jmeter/assertions/JSONPathAssertion.java index c6cdcc0..a20750a 100644 --- a/src/main/java/org/apache/jmeter/assertions/JSONPathAssertion.java +++ b/src/main/java/org/apache/jmeter/assertions/JSONPathAssertion.java @@ -172,7 +172,7 @@ private boolean isEquals(Object subj) { refFlag = str.equals(getExpectedValue()); break; case "NOT_EQUALS": - refFlag = !str.contains(getExpectedValue()); + refFlag = !str.equals(getExpectedValue()); break; } return refFlag; From 0773b068bf16a5818737e3f4cdf55341e59843bd Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Mon, 19 Jul 2021 13:17:26 +0800 Subject: [PATCH 089/109] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96):=20=E5=90=8C=E6=AD=A5=E6=89=A7=E8=A1=8C?= =?UTF-8?q?=E5=8F=82=E6=95=B0=EF=BC=8C=E8=8E=B7=E5=8F=96=E5=94=AF=E4=B8=80?= =?UTF-8?q?=E6=AD=A5=E9=AA=A4=E6=A0=87=E8=AF=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/jmeter/APIBackendListenerClient.java | 1 + .../metersphere/api/module/RequestResult.java | 2 ++ .../api/service/JmeterExecuteService.java | 17 ++++++++++------- .../apache/jmeter/samplers/SampleResult.java | 13 +++++++++---- 4 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java b/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java index f9a8957..f09e88e 100644 --- a/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java +++ b/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java @@ -149,6 +149,7 @@ public void teardownTest(BackendListenerContext context) throws Exception { private RequestResult getRequestResult(SampleResult result) { RequestResult requestResult = new RequestResult(); requestResult.setId(result.getSamplerId()); + requestResult.setResourceId(result.getResourceId()); requestResult.setName(result.getSampleLabel()); requestResult.setUrl(result.getUrlAsString()); requestResult.setMethod(getMethod(result)); diff --git a/src/main/java/io/metersphere/api/module/RequestResult.java b/src/main/java/io/metersphere/api/module/RequestResult.java index d3ac1c2..3732394 100644 --- a/src/main/java/io/metersphere/api/module/RequestResult.java +++ b/src/main/java/io/metersphere/api/module/RequestResult.java @@ -10,6 +10,8 @@ public class RequestResult { // 请求ID private String id; + private String resourceId; + private String name; private String url; diff --git a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java index 6e8ebd0..3851a3a 100644 --- a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java +++ b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java @@ -2,7 +2,6 @@ import io.metersphere.api.controller.request.RunRequest; import io.metersphere.api.jmeter.JMeterService; -import io.metersphere.api.jmeter.LocalRunner; import io.metersphere.api.jmeter.utils.FileUtils; import io.metersphere.api.jmeter.utils.MSException; import io.metersphere.node.util.LogUtil; @@ -91,12 +90,16 @@ public String runStart(RunRequest runRequest) { LogUtil.info("开始拉取脚本和脚本附件:" + runRequest.getUrl()); File bodyFile = ZipSpider.downloadFile(runRequest.getUrl(), FileUtils.BODY_FILE_DIR); - ZipSpider.unzip(bodyFile.getPath(), FileUtils.BODY_FILE_DIR); - File jmxFile = new File(FileUtils.BODY_FILE_DIR + "/" + runRequest.getTestId() + ".jmx"); - // 生成执行脚本 - HashTree testPlan = SaveService.loadTree(jmxFile); - // 开始执行 - jMeterService.run(runRequest, testPlan); + if (bodyFile != null) { + ZipSpider.unzip(bodyFile.getPath(), FileUtils.BODY_FILE_DIR); + File jmxFile = new File(FileUtils.BODY_FILE_DIR + "/" + runRequest.getTestId() + ".jmx"); + // 生成执行脚本 + HashTree testPlan = SaveService.loadTree(jmxFile); + // 开始执行 + jMeterService.run(runRequest, testPlan); + } else { + MSException.throwException("未找到执行的JMX文件"); + } } catch (Exception e) { LogUtil.error(e.getMessage()); return e.getMessage(); diff --git a/src/main/java/org/apache/jmeter/samplers/SampleResult.java b/src/main/java/org/apache/jmeter/samplers/SampleResult.java index a10b2a2..529ab10 100644 --- a/src/main/java/org/apache/jmeter/samplers/SampleResult.java +++ b/src/main/java/org/apache/jmeter/samplers/SampleResult.java @@ -97,7 +97,7 @@ public class SampleResult implements Serializable, Cloneable, Searchable { * @see #getDataType * @see #setDataType(String) */ - public static final String BINARY = "jmeter.bin"; // $NON-NLS-1$ + public static final String BINARY = "bin"; // $NON-NLS-1$ private static final boolean DISABLE_SUBRESULTS_RENAMING = JMeterUtils.getPropDefault("subresults.disable_renaming", false); @@ -163,16 +163,21 @@ public class SampleResult implements Serializable, Cloneable, Searchable { } } - /** * 定制自定义添加 ================= */ private String samplerId; + private String resourceId; + public String getSamplerId() { return this.samplerId; } + public String getResourceId() { + return this.resourceId; + } + // 数据格式 List 多层父级按照同级统计 private String scenario; @@ -183,7 +188,6 @@ public String getScenario() { /** * 定制自定义添加 ================= */ - private SampleSaveConfiguration saveConfig; private SampleResult parent; @@ -247,7 +251,7 @@ public String getScenario() { /** * Files that this sample has been saved in. - * In Non GUI mode and when best utils is used, size never exceeds 1, + * In Non GUI mode and when best config is used, size never exceeds 1, * but as a compromise set it to 2 */ private final Set files = ConcurrentHashMap.newKeySet(2); @@ -350,6 +354,7 @@ public SampleResult() { Sampler sampler = JMeterContextService.getContext().getCurrentSampler(); if (sampler != null) { this.samplerId = sampler.getPropertyAsString("MS-ID"); + this.resourceId = sampler.getPropertyAsString("MS-RESOURCE-ID"); this.scenario = sampler.getPropertyAsString("MS-SCENARIO"); } From e4a55221385a1601fe5572aded4678681f0e9409 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Sun, 1 Aug 2021 22:06:49 +0800 Subject: [PATCH 090/109] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../node/service/JmeterOperateService.java | 53 ++++++ .../metersphere/node/util/CompressUtils.java | 153 ++++++++++++++++++ 2 files changed, 206 insertions(+) create mode 100644 src/main/java/io/metersphere/node/util/CompressUtils.java diff --git a/src/main/java/io/metersphere/node/service/JmeterOperateService.java b/src/main/java/io/metersphere/node/service/JmeterOperateService.java index 3487dfa..1e1dcb3 100644 --- a/src/main/java/io/metersphere/node/service/JmeterOperateService.java +++ b/src/main/java/io/metersphere/node/service/JmeterOperateService.java @@ -8,13 +8,19 @@ import com.github.dockerjava.api.model.Image; import com.github.dockerjava.core.InvocationBuilder; import io.metersphere.node.controller.request.TestRequest; +import io.metersphere.node.util.CompressUtils; import io.metersphere.node.util.DockerClientService; import io.metersphere.node.util.LogUtil; +import org.apache.commons.io.FileUtils; import org.apache.commons.lang.StringUtils; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; import javax.annotation.Resource; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; import java.net.InetSocketAddress; import java.net.Socket; import java.util.Arrays; @@ -23,6 +29,7 @@ import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; +import java.util.zip.ZipOutputStream; @Service public class JmeterOperateService { @@ -61,6 +68,7 @@ private void startContainer(TestRequest testRequest, DockerClient dockerClient, String topic = testRequest.getEnv().getOrDefault("LOG_TOPIC", "JMETER_LOGS"); String reportId = testRequest.getEnv().get("REPORT_ID"); + String resourceIndex = testRequest.getEnv().get("RESOURCE_INDEX"); dockerClient.waitContainerCmd(containerId) .exec(new WaitContainerResultCallback() { @@ -69,6 +77,9 @@ public void onComplete() { // 清理文件夹 try { if (DockerClientService.existContainer(dockerClient, containerId) > 0) { + + copyTestResources(dockerClient, containerId, reportId, resourceIndex); + DockerClientService.removeContainer(dockerClient, containerId); } // 上传结束消息 @@ -104,6 +115,48 @@ public void onNext(Frame item) { }); } + private void copyTestResources(DockerClient dockerClient, String containerId, String reportId, String resourceIndex) throws IOException { + InputStream testIn = dockerClient + .copyArchiveFromContainerCmd(containerId, "/test/") + .exec(); + testIn.available(); + String pathname = reportId + "_" + resourceIndex; + File dir = new File(pathname); + + FileUtils.forceMkdir(dir); + File testDir = new File(pathname + "/test.tar"); + FileUtils.copyInputStreamToFile(testIn, testDir); + + + InputStream jtlIn = dockerClient + .copyArchiveFromContainerCmd(containerId, "/jmeter-log/" + reportId + ".jtl") + .exec(); + jtlIn.available(); + File jtl = new File(pathname + "/report.jtl.tar"); + FileUtils.copyInputStreamToFile(jtlIn, jtl); + + InputStream logIn = dockerClient + .copyArchiveFromContainerCmd(containerId, "/jmeter-log/jmeter.log") + .exec(); + logIn.available(); + File log = new File(pathname + "/jmeter.log.tar"); + FileUtils.copyInputStreamToFile(logIn, log); + + InputStream generateLogIn = dockerClient + .copyArchiveFromContainerCmd(containerId, "/jmeter-log/generate-report.log") + .exec(); + generateLogIn.available(); + File generateLog = new File(pathname + "/generate-report.log.tar"); + FileUtils.copyInputStreamToFile(generateLogIn, generateLog); + + try ( + ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream(reportId + "_" + resourceIndex + ".zip")) + ) { + CompressUtils.zipDirectory(dir, zipOutputStream, ""); + FileUtils.forceDelete(dir); + } + } + private void checkContainerExists(DockerClient dockerClient, String testId) { List list = dockerClient.listContainersCmd() .withShowAll(true) diff --git a/src/main/java/io/metersphere/node/util/CompressUtils.java b/src/main/java/io/metersphere/node/util/CompressUtils.java new file mode 100644 index 0000000..04bcd95 --- /dev/null +++ b/src/main/java/io/metersphere/node/util/CompressUtils.java @@ -0,0 +1,153 @@ +package io.metersphere.node.util; + +import java.io.*; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; + +public class CompressUtils { + + /*** + * 压缩Zip + * + * @param data + * @return + */ + public static Object zip(Object data) { + if (!(data instanceof byte[])) { + return data; + } + + byte[] temp = (byte[]) data; + byte[] b = (byte[]) data; + try { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ZipOutputStream zip = new ZipOutputStream(bos); + ZipEntry entry = new ZipEntry("zip"); + entry.setSize(temp.length); + zip.putNextEntry(entry); + zip.write(temp); + zip.closeEntry(); + zip.close(); + b = bos.toByteArray(); + bos.close(); + } catch (Exception ex) { + LogUtil.error(ex); + } + + return b; + } + + /*** + * 解压Zip + * + * @param data + * @return + */ + public static Object unzip(Object data) { + if (!(data instanceof byte[])) { + return data; + } + byte[] temp = (byte[]) data; + byte[] b = (byte[]) data; + try { + ByteArrayInputStream bis = new ByteArrayInputStream(temp); + ZipInputStream zip = new ZipInputStream(bis); + while (zip.getNextEntry() != null) { + byte[] buf = new byte[1024]; + int num; + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + while ((num = zip.read(buf, 0, buf.length)) != -1) { + baos.write(buf, 0, num); + } + b = baos.toByteArray(); + baos.flush(); + baos.close(); + } + zip.close(); + bis.close(); + } catch (Exception ex) { + LogUtil.error(ex); + } + return b; + } + + + public static void zipFiles(File srcfile, File targetFile) { + + ZipOutputStream out = null; + try { + out = new ZipOutputStream(new FileOutputStream(targetFile)); + + if (srcfile.isFile()) { + zipFile(srcfile, out, ""); + } else { + File[] list = srcfile.listFiles(); + for (int i = 0; i < list.length; i++) { + compress(list[i], out, ""); + } + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + if (out != null) + out.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + private static void compress(File file, ZipOutputStream out, String basedir) { + /* 判断是目录还是文件 */ + if (file.isDirectory()) { + zipDirectory(file, out, basedir); + } else { + zipFile(file, out, basedir); + } + } + + public static void zipDirectory(File dir, ZipOutputStream out, String basedir) { + if (!dir.exists()) + return; + + File[] files = dir.listFiles(); + for (int i = 0; i < files.length; i++) { + /* 递归 */ + compress(files[i], out, basedir + dir.getName() + "/"); + } + } + + + public static void zipFile(File srcfile, ZipOutputStream out, String basedir) { + if (!srcfile.exists()) + return; + + byte[] buf = new byte[8192]; + FileInputStream in = null; + + try { + int len; + in = new FileInputStream(srcfile); + out.putNextEntry(new ZipEntry(basedir + srcfile.getName())); + + while ((len = in.read(buf)) > 0) { + out.write(buf, 0, len); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + if (out != null) + out.closeEntry(); + if (in != null) + in.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + +} From ae245f60855ee4f37a4abac16431fd0617160ad6 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Wed, 4 Aug 2021 12:17:31 +0800 Subject: [PATCH 091/109] =?UTF-8?q?feat(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96)=E3=80=90=E3=80=90github#5188=E3=80=91[FEATU?= =?UTF-8?q?RE]JSONpath=20=E6=96=AD=E8=A8=80=E6=8F=90=E4=BE=9B=E5=A4=A7?= =?UTF-8?q?=E4=BA=8E=E5=92=8C=E5=B0=8F=E4=BA=8E=E5=8A=9F=E8=83=BD=20#5188?= =?UTF-8?q?=E3=80=91=20https://www.tapd.cn/55049933/prong/stories/view/115?= =?UTF-8?q?5049933001002278?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jmeter/assertions/JSONPathAssertion.java | 33 +++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/apache/jmeter/assertions/JSONPathAssertion.java b/src/main/java/org/apache/jmeter/assertions/JSONPathAssertion.java index a20750a..ef459e7 100644 --- a/src/main/java/org/apache/jmeter/assertions/JSONPathAssertion.java +++ b/src/main/java/org/apache/jmeter/assertions/JSONPathAssertion.java @@ -5,10 +5,10 @@ package org.apache.jmeter.assertions; +import com.google.gson.Gson; import com.jayway.jsonpath.JsonPath; import com.jayway.jsonpath.Predicate; import net.minidev.json.JSONArray; -import net.minidev.json.JSONObject; import org.apache.commons.lang3.StringUtils; import org.apache.jmeter.samplers.SampleResult; import org.apache.jmeter.testelement.AbstractTestElement; @@ -126,6 +126,12 @@ private void doAssert(String jsonString) { case "NOT_EQUALS": msg = "Value not equals to be '%s', but found '%s'"; break; + case "GT": + msg = "Value > '%s', but found '%s'"; + break; + case "LT": + msg = "Value < '%s', but found '%s'"; + break; } } else { msg = "Value expected to be '%s', but found '%s'"; @@ -153,6 +159,22 @@ private boolean arrayMatched(JSONArray value) { } } + private boolean isGt(String v1, String v2) { + try { + return Long.parseLong(v1) > Long.parseLong(v2); + } catch (Exception e) { + return false; + } + } + + private boolean isLt(String v1, String v2) { + try { + return Long.parseLong(v1) < Long.parseLong(v2); + } catch (Exception e) { + return false; + } + } + private boolean isEquals(Object subj) { String str = objectToString(subj); if (this.isUseRegex()) { @@ -174,6 +196,13 @@ private boolean isEquals(Object subj) { case "NOT_EQUALS": refFlag = !str.equals(getExpectedValue()); break; + case "GT": + refFlag = isGt(str, getExpectedValue()); + break; + case "LT": + refFlag = isLt(str, getExpectedValue()); + break; + } return refFlag; } @@ -224,7 +253,7 @@ public static String objectToString(Object subj) { if (subj == null) { str = "null"; } else if (subj instanceof Map) { - str = (new JSONObject((Map) subj)).toJSONString(); + str = new Gson().toJson(subj); } else if (!(subj instanceof Double) && !(subj instanceof Float)) { str = subj.toString(); } else { From 5448e3d0712dc0be53f1d1b631f0eb6cb06565d3 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Wed, 4 Aug 2021 19:18:41 +0800 Subject: [PATCH 092/109] =?UTF-8?q?refactor:=20=E5=90=AF=E5=8A=A8=E6=97=A5?= =?UTF-8?q?=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/metersphere/node/service/JmeterOperateService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/metersphere/node/service/JmeterOperateService.java b/src/main/java/io/metersphere/node/service/JmeterOperateService.java index 1e1dcb3..a7d8fdc 100644 --- a/src/main/java/io/metersphere/node/service/JmeterOperateService.java +++ b/src/main/java/io/metersphere/node/service/JmeterOperateService.java @@ -129,7 +129,7 @@ private void copyTestResources(DockerClient dockerClient, String containerId, St InputStream jtlIn = dockerClient - .copyArchiveFromContainerCmd(containerId, "/jmeter-log/" + reportId + ".jtl") + .copyArchiveFromContainerCmd(containerId, "/jmeter-log/" + reportId + "_" + resourceIndex + ".jtl") .exec(); jtlIn.available(); File jtl = new File(pathname + "/report.jtl.tar"); From 02ef2d5e5746dcacaa2c3bfd752af277f6d7aaa7 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Tue, 24 Aug 2021 10:33:18 +0800 Subject: [PATCH 093/109] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96)=20=E4=BF=AE=E6=94=B9jsonpath=E7=89=88?= =?UTF-8?q?=E6=9C=AC=EF=BC=8C=E5=85=BC=E5=AE=B9=E5=8E=86=E5=8F=B2=E7=89=88?= =?UTF-8?q?=E6=96=AD=E8=A8=80=E6=8F=90=E5=8F=96$..a=20=E7=9A=84=E5=86=99?= =?UTF-8?q?=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index dc08624..f5644b9 100644 --- a/pom.xml +++ b/pom.xml @@ -388,7 +388,7 @@ com.jayway.jsonpath json-path - 2.5.0 + 2.4.0 org.aspectj From de26f03be3d364ffca33d7e9d2d4774b11461c1f Mon Sep 17 00:00:00 2001 From: BugKing Date: Wed, 25 Aug 2021 13:43:28 +0800 Subject: [PATCH 094/109] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index dc08624..277b7dc 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.metersphere node-controller - 1.8 + 1.12 node-controller node-controller From 70ab55caa0f64e0de5520432c84d53ba40bd0124 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Fri, 27 Aug 2021 12:16:10 +0800 Subject: [PATCH 095/109] build: spring version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f5644b9..d398445 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 2.5.0 + 2.5.4 io.metersphere From 586ebe0536f4e569ca4db91059b67df17e93cddf Mon Sep 17 00:00:00 2001 From: zhaoyong Date: Sun, 29 Aug 2021 17:43:12 +0800 Subject: [PATCH 096/109] =?UTF-8?q?=E9=87=8D=E6=9E=84=E5=B9=B6=E5=8F=91?= =?UTF-8?q?=E6=89=A7=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/metersphere/api/config/WebConfig.java | 27 +++++ .../api/service/JmeterExecuteService.java | 73 ++++++++++--- .../api/service/utils/BodyFile.java | 10 ++ .../api/service/{ => utils}/ZipSpider.java | 100 +++++++++++++++++- 4 files changed, 190 insertions(+), 20 deletions(-) create mode 100644 src/main/java/io/metersphere/api/config/WebConfig.java create mode 100644 src/main/java/io/metersphere/api/service/utils/BodyFile.java rename src/main/java/io/metersphere/api/service/{ => utils}/ZipSpider.java (59%) diff --git a/src/main/java/io/metersphere/api/config/WebConfig.java b/src/main/java/io/metersphere/api/config/WebConfig.java new file mode 100644 index 0000000..b4a2469 --- /dev/null +++ b/src/main/java/io/metersphere/api/config/WebConfig.java @@ -0,0 +1,27 @@ +package io.metersphere.api.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebConfig implements WebMvcConfigurer { + + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } + + @Bean + public RestTemplate restTemplateWithTimeOut() { + RestTemplate restTemplate = new RestTemplate(); + HttpComponentsClientHttpRequestFactory httpRequestFactory = new HttpComponentsClientHttpRequestFactory(); + httpRequestFactory.setConnectionRequestTimeout(4000); + httpRequestFactory.setConnectTimeout(4000); + httpRequestFactory.setReadTimeout(10 * 1000); + restTemplate.setRequestFactory(httpRequestFactory); + return restTemplate; + } +} diff --git a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java index 3851a3a..8005167 100644 --- a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java +++ b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java @@ -4,17 +4,18 @@ import io.metersphere.api.jmeter.JMeterService; import io.metersphere.api.jmeter.utils.FileUtils; import io.metersphere.api.jmeter.utils.MSException; +import io.metersphere.api.service.utils.ZipSpider; import io.metersphere.node.util.LogUtil; import org.apache.commons.lang3.StringUtils; import org.apache.jmeter.NewDriver; import org.apache.jmeter.save.SaveService; import org.apache.jorphan.collections.HashTree; import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; import java.io.ByteArrayInputStream; -import java.io.File; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; @@ -25,6 +26,8 @@ public class JmeterExecuteService { private JMeterService jMeterService; @Resource LoadTestProducer loadTestProducer; + @Resource + private RestTemplate restTemplate; private static InputStream getStrToStream(String sInputString) { if (StringUtils.isNotEmpty(sInputString)) { @@ -39,6 +42,21 @@ private static InputStream getStrToStream(String sInputString) { return null; } + private HashTree getHashTree(byte[] hashTree) { + if (hashTree.length > 0) { + try { + ByteArrayInputStream tInputStringStream = new ByteArrayInputStream(hashTree); + Object scriptWrapper = SaveService.loadElement(tInputStringStream); + HashTree testPlan = JMeterService.getHashTree(scriptWrapper); + return testPlan; + } catch (Exception ex) { + ex.printStackTrace(); + MSException.throwException("生成脚本异常"); + } + } + return null; + } + private void loadJar(String path) { try { NewDriver.addPath(path); @@ -73,6 +91,40 @@ public String run(RunRequest request, MultipartFile[] bodyFiles, MultipartFile[] return "SUCCESS"; } +// public String runStart(RunRequest runRequest) { +// // 检查KAFKA +// loadTestProducer.checkKafka(); +// try { +// // 生成附件/JAR文件 +// URL urlObject = new URL(runRequest.getUrl()); +// String jarUrl = urlObject.getProtocol() + "://" + urlObject.getHost() + (urlObject.getPort() > 0 ? ":" + urlObject.getPort() : "") + "/api/jmeter/download/jar"; +// LogUtil.info("开始同步上传的第三方JAR:" + jarUrl); +// +// File file = ZipSpider.downloadFile(jarUrl, FileUtils.JAR_FILE_DIR); +// if (file != null) { +// ZipSpider.unzip(file.getPath(), FileUtils.JAR_FILE_DIR); +// this.loadJar(FileUtils.JAR_FILE_DIR); +// } +// LogUtil.info("开始拉取脚本和脚本附件:" + runRequest.getUrl()); +// +// File bodyFile = ZipSpider.downloadFile(runRequest.getUrl(), FileUtils.BODY_FILE_DIR); +// if (bodyFile != null) { +// ZipSpider.unzip(bodyFile.getPath(), FileUtils.BODY_FILE_DIR); +// File jmxFile = new File(FileUtils.BODY_FILE_DIR + "/" + runRequest.getTestId() + ".jmx"); +// // 生成执行脚本 +// HashTree testPlan = SaveService.loadTree(jmxFile); +// // 开始执行 +// jMeterService.run(runRequest, testPlan); +// } else { +// MSException.throwException("未找到执行的JMX文件"); +// } +// } catch (Exception e) { +// LogUtil.error(e.getMessage()); +// return e.getMessage(); +// } +// return "SUCCESS"; +// } + public String runStart(RunRequest runRequest) { // 检查KAFKA loadTestProducer.checkKafka(); @@ -80,21 +132,13 @@ public String runStart(RunRequest runRequest) { // 生成附件/JAR文件 URL urlObject = new URL(runRequest.getUrl()); String jarUrl = urlObject.getProtocol() + "://" + urlObject.getHost() + (urlObject.getPort() > 0 ? ":" + urlObject.getPort() : "") + "/api/jmeter/download/jar"; - LogUtil.info("开始同步上传的第三方JAR:" + jarUrl); - - File file = ZipSpider.downloadFile(jarUrl, FileUtils.JAR_FILE_DIR); - if (file != null) { - ZipSpider.unzip(file.getPath(), FileUtils.JAR_FILE_DIR); - this.loadJar(FileUtils.JAR_FILE_DIR); - } LogUtil.info("开始拉取脚本和脚本附件:" + runRequest.getUrl()); - - File bodyFile = ZipSpider.downloadFile(runRequest.getUrl(), FileUtils.BODY_FILE_DIR); - if (bodyFile != null) { - ZipSpider.unzip(bodyFile.getPath(), FileUtils.BODY_FILE_DIR); - File jmxFile = new File(FileUtils.BODY_FILE_DIR + "/" + runRequest.getTestId() + ".jmx"); + byte[] jmx = ZipSpider.get(runRequest.getUrl(), restTemplate); + if (jmx != null) { // 生成执行脚本 - HashTree testPlan = SaveService.loadTree(jmxFile); + HashTree testPlan = this.getHashTree(jmx); + //加载附件 + ZipSpider.downloadFiles(runRequest.getUrl(), testPlan); // 开始执行 jMeterService.run(runRequest, testPlan); } else { @@ -106,5 +150,4 @@ public String runStart(RunRequest runRequest) { } return "SUCCESS"; } - } diff --git a/src/main/java/io/metersphere/api/service/utils/BodyFile.java b/src/main/java/io/metersphere/api/service/utils/BodyFile.java new file mode 100644 index 0000000..f73d457 --- /dev/null +++ b/src/main/java/io/metersphere/api/service/utils/BodyFile.java @@ -0,0 +1,10 @@ +package io.metersphere.api.service.utils; + +import lombok.Data; + +@Data +public class BodyFile { + + private String id; + private String name; +} diff --git a/src/main/java/io/metersphere/api/service/ZipSpider.java b/src/main/java/io/metersphere/api/service/utils/ZipSpider.java similarity index 59% rename from src/main/java/io/metersphere/api/service/ZipSpider.java rename to src/main/java/io/metersphere/api/service/utils/ZipSpider.java index baa9c8a..fe49244 100644 --- a/src/main/java/io/metersphere/api/service/ZipSpider.java +++ b/src/main/java/io/metersphere/api/service/utils/ZipSpider.java @@ -1,11 +1,25 @@ -package io.metersphere.api.service; +package io.metersphere.api.service.utils; import java.io.*; import java.net.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; +import com.alibaba.fastjson.JSON; +import io.metersphere.api.jmeter.utils.FileUtils; import io.metersphere.node.util.LogUtil; +import org.apache.dubbo.common.utils.CollectionUtils; +import org.apache.jmeter.config.CSVDataSet; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy; +import org.apache.jmeter.protocol.http.util.HTTPFileArg; +import org.apache.jorphan.collections.HashTree; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.client.RestTemplate; public class ZipSpider { @@ -139,19 +153,35 @@ public static void seperate(char c) { } @SuppressWarnings("finally") - public static File downloadFile(String urlPath, String downloadDir) { + public static File downloadFile(String urlPath, String downloadDir,String json) { File file = null; + BufferedInputStream bin = null; + OutputStream out = null; try { URL url = new URL(urlPath); URLConnection urlConnection = url.openConnection(); HttpURLConnection httpURLConnection = (HttpURLConnection) urlConnection;// http的连接类 //String contentType = httpURLConnection.getContentType();//请求类型,可用来过滤请求, + httpURLConnection.setUseCaches(false); + httpURLConnection.setDoOutput(true); + httpURLConnection.setDoInput(true); httpURLConnection.setConnectTimeout(1000 * 5);//设置超时 - httpURLConnection.setRequestMethod("GET");//设置请求方式,默认是GET + httpURLConnection.setRequestMethod("POST");//设置请求方式,默认是GET httpURLConnection.setRequestProperty("Charset", "UTF-8");// 设置字符编码 + httpURLConnection.setRequestProperty("Connection", "Keep-Alive"); + httpURLConnection.setRequestProperty("Content-Type", "application/json; charset=UTF-8"); + httpURLConnection.setInstanceFollowRedirects(false); + byte[] writebytes = json.getBytes(); + // 设置文件长度 + httpURLConnection.setRequestProperty("Content-Length", String.valueOf(writebytes.length)); + OutputStream outwritestream = httpURLConnection.getOutputStream(); + outwritestream.write(json.getBytes()); + outwritestream.flush(); + outwritestream.close(); + httpURLConnection.connect();// 打开连接 + bin = new BufferedInputStream(httpURLConnection.getInputStream()); - BufferedInputStream bin = new BufferedInputStream(httpURLConnection.getInputStream()); String fileName = httpURLConnection.getHeaderField("Content-Disposition"); fileName = URLDecoder.decode(fileName.substring(fileName.indexOf("filename") + 10, fileName.length() - 1), "UTF-8"); String path = downloadDir + File.separatorChar + fileName;// 指定存放位置 @@ -161,7 +191,7 @@ public static File downloadFile(String urlPath, String downloadDir) { file.getParentFile().mkdirs(); } - OutputStream out = new FileOutputStream(file); + out = new FileOutputStream(file); int size = 0; byte[] b = new byte[2048]; @@ -181,7 +211,67 @@ public static File downloadFile(String urlPath, String downloadDir) { e.printStackTrace(); LogUtil.info("文件下载失败!"); } finally { + try { + bin.close(); + out.close(); + } catch (Exception e) { + + } return file; } } + + public static void getFiles(HashTree tree, List files) { + for (Object key : tree.keySet()) { + HashTree node = tree.get(key); + if (key instanceof HTTPSamplerProxy) { + HTTPSamplerProxy source = (HTTPSamplerProxy) key; + if (source != null && source.getHTTPFiles().length > 0) { + for (HTTPFileArg arg : source.getHTTPFiles()) { + BodyFile file = new BodyFile(); + file.setId(arg.getParamName()); + file.setName(arg.getPath()); + files.add(file); + } + } + } else if (key instanceof CSVDataSet) { + CSVDataSet source = (CSVDataSet) key; + if (source != null && source.getFilename() != null) { + BodyFile file = new BodyFile(); + file.setId(source.getFilename()); + file.setName(source.getFilename()); + files.add(file); + } + } + if (node != null) { + getFiles(node, files); + } + } + } + + public static void downloadFiles(String uri, HashTree hashTree) { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.MULTIPART_FORM_DATA); + headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN)); + List files = new ArrayList<>(); + ZipSpider.getFiles(hashTree, files); + if (CollectionUtils.isNotEmpty(files)) { + try { + URL urlObject = new URL(uri); + String url = urlObject.getProtocol() + "://" + urlObject.getHost() + (urlObject.getPort() > 0 ? ":" + urlObject.getPort() : "") + "/api/jmeter/download/files"; + LogUtil.info("开始同步下载附件:" + url); + ZipSpider.downloadFile(url, FileUtils.BODY_FILE_DIR, JSON.toJSONString(files)); + }catch (Exception e){ + e.printStackTrace(); + } + } + } + + public static byte[] get(String uri, RestTemplate restTemplate) { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.MULTIPART_FORM_DATA); + headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN)); + ResponseEntity result = restTemplate.getForEntity(uri, byte[].class); + return result.getBody(); + } } From da755a7b369c5904d932bd5e3c16607cb5dca5b9 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Mon, 30 Aug 2021 09:28:15 +0800 Subject: [PATCH 097/109] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96)=20=E5=AE=9A=E6=97=B6=E5=90=8C=E6=AD=A5jar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/metersphere/api/config/FixedTask.java | 38 ++++++++ .../api/service/JmeterExecuteService.java | 16 +++- .../api/service/utils/ZipSpider.java | 93 ++++++++++++++----- 3 files changed, 119 insertions(+), 28 deletions(-) create mode 100644 src/main/java/io/metersphere/api/config/FixedTask.java diff --git a/src/main/java/io/metersphere/api/config/FixedTask.java b/src/main/java/io/metersphere/api/config/FixedTask.java new file mode 100644 index 0000000..3407fb1 --- /dev/null +++ b/src/main/java/io/metersphere/api/config/FixedTask.java @@ -0,0 +1,38 @@ +package io.metersphere.api.config; + +import io.metersphere.api.jmeter.utils.FileUtils; +import io.metersphere.api.jmeter.utils.MSException; +import io.metersphere.api.service.utils.ZipSpider; +import io.metersphere.node.util.LogUtil; +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.NewDriver; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.io.File; +import java.net.MalformedURLException; + +@Component +public class FixedTask { + public static String url = null; + + private void loadJar(String path) { + try { + NewDriver.addPath(path); + } catch (MalformedURLException e) { + LogUtil.error(e.getMessage(), e); + MSException.throwException(e.getMessage()); + } + } + + @Scheduled(cron = "0 0/5 * * * ?") + public void execute() { + if (StringUtils.isNotEmpty(FixedTask.url)) { + File file = ZipSpider.downloadFile(FixedTask.url, FileUtils.JAR_FILE_DIR); + if (file != null) { + ZipSpider.unzip(file.getPath(), FileUtils.JAR_FILE_DIR); + this.loadJar(FileUtils.JAR_FILE_DIR); + } + } + } +} diff --git a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java index 8005167..cee60b3 100644 --- a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java +++ b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java @@ -1,5 +1,6 @@ package io.metersphere.api.service; +import io.metersphere.api.config.FixedTask; import io.metersphere.api.controller.request.RunRequest; import io.metersphere.api.jmeter.JMeterService; import io.metersphere.api.jmeter.utils.FileUtils; @@ -16,6 +17,7 @@ import javax.annotation.Resource; import java.io.ByteArrayInputStream; +import java.io.File; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; @@ -126,13 +128,21 @@ public String run(RunRequest request, MultipartFile[] bodyFiles, MultipartFile[] // } public String runStart(RunRequest runRequest) { - // 检查KAFKA - loadTestProducer.checkKafka(); try { + LogUtil.info("开始拉取脚本和脚本附件:" + runRequest.getUrl()); + // 检查KAFKA + loadTestProducer.checkKafka(); // 生成附件/JAR文件 URL urlObject = new URL(runRequest.getUrl()); String jarUrl = urlObject.getProtocol() + "://" + urlObject.getHost() + (urlObject.getPort() > 0 ? ":" + urlObject.getPort() : "") + "/api/jmeter/download/jar"; - LogUtil.info("开始拉取脚本和脚本附件:" + runRequest.getUrl()); + if (StringUtils.isEmpty(FixedTask.url)) { + File file = ZipSpider.downloadFile(FixedTask.url, FileUtils.JAR_FILE_DIR); + if (file != null) { + ZipSpider.unzip(file.getPath(), FileUtils.JAR_FILE_DIR); + this.loadJar(FileUtils.JAR_FILE_DIR); + } + } + FixedTask.url = jarUrl; byte[] jmx = ZipSpider.get(runRequest.getUrl(), restTemplate); if (jmx != null) { // 生成执行脚本 diff --git a/src/main/java/io/metersphere/api/service/utils/ZipSpider.java b/src/main/java/io/metersphere/api/service/utils/ZipSpider.java index fe49244..4b7de92 100644 --- a/src/main/java/io/metersphere/api/service/utils/ZipSpider.java +++ b/src/main/java/io/metersphere/api/service/utils/ZipSpider.java @@ -92,35 +92,31 @@ public static void download(String url, String path) { //解压本地文件至目的文件路径 public static void unzip(String fromFile, String toFile) { - try { - ZipInputStream Zin = new ZipInputStream(new FileInputStream(fromFile)); - BufferedInputStream Bin = new BufferedInputStream(Zin); + try (ZipInputStream zin = new ZipInputStream(new FileInputStream(fromFile)); BufferedInputStream bin = new BufferedInputStream(zin);) { String Parent = toFile; - File Fout = null; + File fout = null; ZipEntry entry; - try { - while ((entry = Zin.getNextEntry()) != null && !entry.isDirectory()) { - Fout = new File(Parent, entry.getName()); - if (!Fout.exists()) { - (new File(Fout.getParent())).mkdirs(); - } - FileOutputStream out = new FileOutputStream(Fout); - BufferedOutputStream Bout = new BufferedOutputStream(out); - int b; - while ((b = Bin.read()) != -1) { - Bout.write(b); - } - Bout.close(); - out.close(); - LogUtil.info(Fout + "解压成功"); + while ((entry = zin.getNextEntry()) != null && !entry.isDirectory()) { + fout = new File(Parent, entry.getName()); + if (!fout.exists()) { + (new File(fout.getParent())).mkdirs(); } - Bin.close(); - Zin.close(); - } catch (IOException e) { - e.printStackTrace(); + FileOutputStream out = new FileOutputStream(fout); + BufferedOutputStream Bout = new BufferedOutputStream(out); + int b; + while ((b = bin.read()) != -1) { + Bout.write(b); + } + Bout.close(); + out.close(); + LogUtil.info(fout + "解压成功"); } + bin.close(); + zin.close(); } catch (FileNotFoundException e) { e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); } } @@ -153,7 +149,7 @@ public static void seperate(char c) { } @SuppressWarnings("finally") - public static File downloadFile(String urlPath, String downloadDir,String json) { + public static File downloadFile(String urlPath, String downloadDir, String json) { File file = null; BufferedInputStream bin = null; OutputStream out = null; @@ -261,7 +257,7 @@ public static void downloadFiles(String uri, HashTree hashTree) { String url = urlObject.getProtocol() + "://" + urlObject.getHost() + (urlObject.getPort() > 0 ? ":" + urlObject.getPort() : "") + "/api/jmeter/download/files"; LogUtil.info("开始同步下载附件:" + url); ZipSpider.downloadFile(url, FileUtils.BODY_FILE_DIR, JSON.toJSONString(files)); - }catch (Exception e){ + } catch (Exception e) { e.printStackTrace(); } } @@ -274,4 +270,51 @@ public static byte[] get(String uri, RestTemplate restTemplate) { ResponseEntity result = restTemplate.getForEntity(uri, byte[].class); return result.getBody(); } + + @SuppressWarnings("finally") + public static File downloadFile(String urlPath, String downloadDir) { + File file = null; + try { + URL url = new URL(urlPath); + URLConnection urlConnection = url.openConnection(); + HttpURLConnection httpURLConnection = (HttpURLConnection) urlConnection;// http的连接类 + //String contentType = httpURLConnection.getContentType();//请求类型,可用来过滤请求, + httpURLConnection.setConnectTimeout(1000 * 5);//设置超时 + httpURLConnection.setRequestMethod("GET");//设置请求方式,默认是GET + httpURLConnection.setRequestProperty("Charset", "UTF-8");// 设置字符编码 + httpURLConnection.connect();// 打开连接 + + BufferedInputStream bin = new BufferedInputStream(httpURLConnection.getInputStream()); + String fileName = httpURLConnection.getHeaderField("Content-Disposition"); + fileName = URLDecoder.decode(fileName.substring(fileName.indexOf("filename") + 10, fileName.length() - 1), "UTF-8"); + String path = downloadDir + File.separatorChar + fileName;// 指定存放位置 + file = new File(path); + // 校验文件夹目录是否存在,不存在就创建一个目录 + if (!file.getParentFile().exists()) { + file.getParentFile().mkdirs(); + } + + OutputStream out = new FileOutputStream(file); + int size = 0; + + byte[] b = new byte[2048]; + //把输入流的文件读取到字节数据b中,然后输出到指定目录的文件 + while ((size = bin.read(b)) != -1) { + out.write(b, 0, size); + } + // 关闭资源 + bin.close(); + out.close(); + LogUtil.info("文件下载成功!"); + } catch (MalformedURLException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + LogUtil.info("文件下载失败!"); + } finally { + return file; + } + } } From c539f406fe90c50fe10527b03d7671653c7ca5f0 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Mon, 30 Aug 2021 10:31:06 +0800 Subject: [PATCH 098/109] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96)=20=E5=AE=9A=E6=97=B6=E5=90=8C=E6=AD=A5jar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/metersphere/api/service/JmeterExecuteService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java index cee60b3..90d7713 100644 --- a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java +++ b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java @@ -136,6 +136,7 @@ public String runStart(RunRequest runRequest) { URL urlObject = new URL(runRequest.getUrl()); String jarUrl = urlObject.getProtocol() + "://" + urlObject.getHost() + (urlObject.getPort() > 0 ? ":" + urlObject.getPort() : "") + "/api/jmeter/download/jar"; if (StringUtils.isEmpty(FixedTask.url)) { + FixedTask.url = jarUrl; File file = ZipSpider.downloadFile(FixedTask.url, FileUtils.JAR_FILE_DIR); if (file != null) { ZipSpider.unzip(file.getPath(), FileUtils.JAR_FILE_DIR); From 49586ed6a3f3281d788875f3213b48390d4da61c Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Mon, 30 Aug 2021 17:29:49 +0800 Subject: [PATCH 099/109] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96)=20=E5=B9=B6=E5=8F=91=E6=89=A7=E8=A1=8C?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../metersphere/api/config/KafkaConfig.java | 8 ++ .../api/service/JmeterExecuteService.java | 12 +-- .../api/service/MsKafkaListener.java | 86 +++++++++++++++++++ .../apache/jmeter/util/JSR223TestElement.java | 6 +- src/main/resources/application.properties | 3 +- 5 files changed, 106 insertions(+), 9 deletions(-) create mode 100644 src/main/java/io/metersphere/api/service/MsKafkaListener.java diff --git a/src/main/java/io/metersphere/api/config/KafkaConfig.java b/src/main/java/io/metersphere/api/config/KafkaConfig.java index 940317f..d0c6616 100644 --- a/src/main/java/io/metersphere/api/config/KafkaConfig.java +++ b/src/main/java/io/metersphere/api/config/KafkaConfig.java @@ -14,4 +14,12 @@ public NewTopic apiExecTopic() { return TopicBuilder.name(testTopic) .build(); } + + public final static String execTopic = "ms-automation-exec-topic"; + + @Bean + public NewTopic automationTopic() { + return TopicBuilder.name(execTopic) + .build(); + } } diff --git a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java index 90d7713..a5781d5 100644 --- a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java +++ b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java @@ -136,12 +136,12 @@ public String runStart(RunRequest runRequest) { URL urlObject = new URL(runRequest.getUrl()); String jarUrl = urlObject.getProtocol() + "://" + urlObject.getHost() + (urlObject.getPort() > 0 ? ":" + urlObject.getPort() : "") + "/api/jmeter/download/jar"; if (StringUtils.isEmpty(FixedTask.url)) { - FixedTask.url = jarUrl; - File file = ZipSpider.downloadFile(FixedTask.url, FileUtils.JAR_FILE_DIR); - if (file != null) { - ZipSpider.unzip(file.getPath(), FileUtils.JAR_FILE_DIR); - this.loadJar(FileUtils.JAR_FILE_DIR); - } +// FixedTask.url = jarUrl; +// File file = ZipSpider.downloadFile(FixedTask.url, FileUtils.JAR_FILE_DIR); +// if (file != null) { +// ZipSpider.unzip(file.getPath(), FileUtils.JAR_FILE_DIR); +// this.loadJar(FileUtils.JAR_FILE_DIR); +// } } FixedTask.url = jarUrl; byte[] jmx = ZipSpider.get(runRequest.getUrl(), restTemplate); diff --git a/src/main/java/io/metersphere/api/service/MsKafkaListener.java b/src/main/java/io/metersphere/api/service/MsKafkaListener.java new file mode 100644 index 0000000..8174f89 --- /dev/null +++ b/src/main/java/io/metersphere/api/service/MsKafkaListener.java @@ -0,0 +1,86 @@ +package io.metersphere.api.service; + +import com.alibaba.fastjson.JSON; +import io.metersphere.api.config.FixedTask; +import io.metersphere.api.controller.request.RunRequest; +import io.metersphere.api.jmeter.JMeterService; +import io.metersphere.api.jmeter.utils.FileUtils; +import io.metersphere.api.jmeter.utils.MSException; +import io.metersphere.api.service.utils.ZipSpider; +import io.metersphere.node.util.LogUtil; +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.NewDriver; +import org.apache.jmeter.save.SaveService; +import org.apache.jorphan.collections.HashTree; +import org.apache.kafka.clients.consumer.ConsumerRecord; +import org.springframework.kafka.annotation.KafkaListener; +import org.springframework.kafka.support.Acknowledgment; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.InputStream; +import java.net.MalformedURLException; + +@Service +public class MsKafkaListener { + public final static String EXEC_TOPIC = "ms-automation-exec-topic"; + + public static final String CONSUME_ID = "ms-api-automation-consume"; + @Resource + private JMeterService jMeterService; + + private static InputStream getStrToStream(String sInputString) { + if (StringUtils.isNotEmpty(sInputString)) { + try { + ByteArrayInputStream tInputStringStream = new ByteArrayInputStream(sInputString.getBytes()); + return tInputStringStream; + } catch (Exception ex) { + ex.printStackTrace(); + MSException.throwException("生成脚本异常"); + } + } + return null; + } + + private void loadJar(String path) { + try { + NewDriver.addPath(path); + } catch (MalformedURLException e) { + LogUtil.error(e.getMessage(), e); + MSException.throwException(e.getMessage()); + } + } + + @KafkaListener(id = CONSUME_ID, topics = EXEC_TOPIC, groupId = "${spring.kafka.consumer.group-id}") + public void consume(ConsumerRecord record, Acknowledgment ack) { + LogUtil.info("接收到执行执行请求开始处理"); + try { + if (record.value() != null) { + RunRequest request = JSON.parseObject(record.value(), RunRequest.class); + // 生成执行脚本 + InputStream inputSource = getStrToStream(request.getJmx()); + Object scriptWrapper = SaveService.loadElement(inputSource); + HashTree testPlan = JMeterService.getHashTree(scriptWrapper); + //加载附件 + ZipSpider.downloadFiles(request.getUrl(), testPlan); + if (StringUtils.isNotEmpty(FixedTask.url)) { + File file = ZipSpider.downloadFile(FixedTask.url, FileUtils.JAR_FILE_DIR); + if (file != null) { + ZipSpider.unzip(file.getPath(), FileUtils.JAR_FILE_DIR); + this.loadJar(FileUtils.JAR_FILE_DIR); + } + } + // 开始执行 + jMeterService.run(request, testPlan); + } + } catch (Exception e) { + LogUtil.error(e.getMessage()); + } finally { + ack.acknowledge(); + } + LogUtil.info("执行执行请求处理结束"); + } + +} diff --git a/src/main/java/org/apache/jmeter/util/JSR223TestElement.java b/src/main/java/org/apache/jmeter/util/JSR223TestElement.java index d7cd59c..2dd7645 100644 --- a/src/main/java/org/apache/jmeter/util/JSR223TestElement.java +++ b/src/main/java/org/apache/jmeter/util/JSR223TestElement.java @@ -272,8 +272,10 @@ public static void loadGroovyJar(ScriptEngine scriptEngine) { groovyScriptEngine.getClassLoader().addURL(file.toURI().toURL()); } else { File[] files = file.listFiles(); - for (File f : files) { - groovyScriptEngine.getClassLoader().addURL(f.toURI().toURL()); + if(files!= null && files.length > 0){ + for (File f : files) { + groovyScriptEngine.getClassLoader().addURL(f.toURI().toURL()); + } } } } catch (Exception e) { diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 70b2f6a..1e3ee1e 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -5,6 +5,7 @@ server.port=8082 #kafka spring.kafka.bootstrap-servers=${kafka.bootstrap-servers} spring.kafka.consumer.group-id=metersphere_group_id +spring.kafka.listener.ack-mode=MANUAL_IMMEDIATE kafka.fields= kafka.timestamp=yyyy-MM-dd'T'HH:mm:ss.SSSZZ kafka.sample-filter= @@ -26,6 +27,6 @@ kafka.ssl.keystore-type=JKS kafka.ssl.protocol=TLS kafka.ssl.provider= kafka.ssl.truststore-type= - +spring.kafka.producer.properties.max.request.size=32428800 # jmeter jmeter.home=/opt/jmeter From b971be6aad61876615593ffe66ac93ec257b76ef Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Mon, 30 Aug 2021 19:12:25 +0800 Subject: [PATCH 100/109] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96)=20=E5=B9=B6=E5=8F=91=E6=89=A7=E8=A1=8C?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/metersphere/api/config/WebConfig.java | 27 ------ .../api/jmeter/APIBackendListenerClient.java | 8 +- .../api/service/JmeterExecuteService.java | 89 +++++-------------- .../api/service/LoadTestProducer.java | 47 ---------- .../api/service/MsKafkaListener.java | 3 + 5 files changed, 27 insertions(+), 147 deletions(-) delete mode 100644 src/main/java/io/metersphere/api/config/WebConfig.java delete mode 100644 src/main/java/io/metersphere/api/service/LoadTestProducer.java diff --git a/src/main/java/io/metersphere/api/config/WebConfig.java b/src/main/java/io/metersphere/api/config/WebConfig.java deleted file mode 100644 index b4a2469..0000000 --- a/src/main/java/io/metersphere/api/config/WebConfig.java +++ /dev/null @@ -1,27 +0,0 @@ -package io.metersphere.api.config; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; -import org.springframework.web.client.RestTemplate; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - -@Configuration -public class WebConfig implements WebMvcConfigurer { - - @Bean - public RestTemplate restTemplate() { - return new RestTemplate(); - } - - @Bean - public RestTemplate restTemplateWithTimeOut() { - RestTemplate restTemplate = new RestTemplate(); - HttpComponentsClientHttpRequestFactory httpRequestFactory = new HttpComponentsClientHttpRequestFactory(); - httpRequestFactory.setConnectionRequestTimeout(4000); - httpRequestFactory.setConnectTimeout(4000); - httpRequestFactory.setReadTimeout(10 * 1000); - restTemplate.setRequestFactory(httpRequestFactory); - return restTemplate; - } -} diff --git a/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java b/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java index f09e88e..b6dfeea 100644 --- a/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java +++ b/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java @@ -1,11 +1,11 @@ package io.metersphere.api.jmeter; import com.alibaba.fastjson.JSON; -import io.metersphere.api.service.LoadTestProducer; import io.metersphere.api.jmeter.constants.ApiRunMode; import io.metersphere.api.jmeter.constants.RequestType; import io.metersphere.api.module.*; import io.metersphere.api.jmeter.utils.CommonBeanFactory; +import io.metersphere.api.service.JmeterExecuteService; import io.metersphere.node.util.LogUtil; import org.apache.commons.lang3.StringUtils; import org.apache.dubbo.common.utils.CollectionUtils; @@ -42,7 +42,7 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl private boolean isDebug; - private LoadTestProducer loadTestProducer; + private JmeterExecuteService jmeterExecuteService; /** * 测试ID @@ -91,7 +91,7 @@ public void teardownTest(BackendListenerContext context) throws Exception { testResult.setSetReportId(this.setReportId); testResult.setDebug(this.isDebug); testResult.setUserId(this.userId); - loadTestProducer = CommonBeanFactory.getBean(LoadTestProducer.class); + jmeterExecuteService = CommonBeanFactory.getBean(JmeterExecuteService.class); // 一个脚本里可能包含多个场景(ThreadGroup),所以要区分开,key: 场景Id final Map scenarios = new LinkedHashMap<>(); queue.forEach(result -> { @@ -141,7 +141,7 @@ public void teardownTest(BackendListenerContext context) throws Exception { testResult.getScenarios().sort(Comparator.comparing(ScenarioResult::getId)); testResult.setRunMode(this.runMode); // 推送执行结果 - loadTestProducer.sendMessage(JSON.toJSONString(testResult)); + jmeterExecuteService.sendMessage(JSON.toJSONString(testResult)); queue.clear(); super.teardownTest(context); } diff --git a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java index a5781d5..a834420 100644 --- a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java +++ b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java @@ -1,5 +1,6 @@ package io.metersphere.api.service; +import com.alibaba.fastjson.JSON; import io.metersphere.api.config.FixedTask; import io.metersphere.api.controller.request.RunRequest; import io.metersphere.api.jmeter.JMeterService; @@ -11,6 +12,7 @@ import org.apache.jmeter.NewDriver; import org.apache.jmeter.save.SaveService; import org.apache.jorphan.collections.HashTree; +import org.springframework.kafka.core.KafkaTemplate; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; import org.springframework.web.multipart.MultipartFile; @@ -27,9 +29,7 @@ public class JmeterExecuteService { @Resource private JMeterService jMeterService; @Resource - LoadTestProducer loadTestProducer; - @Resource - private RestTemplate restTemplate; + private KafkaTemplate kafkaTemplate; private static InputStream getStrToStream(String sInputString) { if (StringUtils.isNotEmpty(sInputString)) { @@ -44,20 +44,6 @@ private static InputStream getStrToStream(String sInputString) { return null; } - private HashTree getHashTree(byte[] hashTree) { - if (hashTree.length > 0) { - try { - ByteArrayInputStream tInputStringStream = new ByteArrayInputStream(hashTree); - Object scriptWrapper = SaveService.loadElement(tInputStringStream); - HashTree testPlan = JMeterService.getHashTree(scriptWrapper); - return testPlan; - } catch (Exception ex) { - ex.printStackTrace(); - MSException.throwException("生成脚本异常"); - } - } - return null; - } private void loadJar(String path) { try { @@ -73,8 +59,6 @@ public String run(RunRequest request, MultipartFile[] bodyFiles, MultipartFile[] return "执行文件为空,无法执行!"; } LogUtil.info(request.getJmx()); - // 检查KAFKA - loadTestProducer.checkKafka(); // 生成附件/JAR文件 FileUtils.createFiles(bodyFiles, FileUtils.BODY_FILE_DIR); FileUtils.createFiles(jarFiles, FileUtils.JAR_FILE_DIR); @@ -93,63 +77,26 @@ public String run(RunRequest request, MultipartFile[] bodyFiles, MultipartFile[] return "SUCCESS"; } -// public String runStart(RunRequest runRequest) { -// // 检查KAFKA -// loadTestProducer.checkKafka(); -// try { -// // 生成附件/JAR文件 -// URL urlObject = new URL(runRequest.getUrl()); -// String jarUrl = urlObject.getProtocol() + "://" + urlObject.getHost() + (urlObject.getPort() > 0 ? ":" + urlObject.getPort() : "") + "/api/jmeter/download/jar"; -// LogUtil.info("开始同步上传的第三方JAR:" + jarUrl); -// -// File file = ZipSpider.downloadFile(jarUrl, FileUtils.JAR_FILE_DIR); -// if (file != null) { -// ZipSpider.unzip(file.getPath(), FileUtils.JAR_FILE_DIR); -// this.loadJar(FileUtils.JAR_FILE_DIR); -// } -// LogUtil.info("开始拉取脚本和脚本附件:" + runRequest.getUrl()); -// -// File bodyFile = ZipSpider.downloadFile(runRequest.getUrl(), FileUtils.BODY_FILE_DIR); -// if (bodyFile != null) { -// ZipSpider.unzip(bodyFile.getPath(), FileUtils.BODY_FILE_DIR); -// File jmxFile = new File(FileUtils.BODY_FILE_DIR + "/" + runRequest.getTestId() + ".jmx"); -// // 生成执行脚本 -// HashTree testPlan = SaveService.loadTree(jmxFile); -// // 开始执行 -// jMeterService.run(runRequest, testPlan); -// } else { -// MSException.throwException("未找到执行的JMX文件"); -// } -// } catch (Exception e) { -// LogUtil.error(e.getMessage()); -// return e.getMessage(); -// } -// return "SUCCESS"; -// } - public String runStart(RunRequest runRequest) { try { - LogUtil.info("开始拉取脚本和脚本附件:" + runRequest.getUrl()); - // 检查KAFKA - loadTestProducer.checkKafka(); // 生成附件/JAR文件 URL urlObject = new URL(runRequest.getUrl()); String jarUrl = urlObject.getProtocol() + "://" + urlObject.getHost() + (urlObject.getPort() > 0 ? ":" + urlObject.getPort() : "") + "/api/jmeter/download/jar"; - if (StringUtils.isEmpty(FixedTask.url)) { -// FixedTask.url = jarUrl; -// File file = ZipSpider.downloadFile(FixedTask.url, FileUtils.JAR_FILE_DIR); -// if (file != null) { -// ZipSpider.unzip(file.getPath(), FileUtils.JAR_FILE_DIR); -// this.loadJar(FileUtils.JAR_FILE_DIR); -// } + LogUtil.info("开始同步上传的第三方JAR:" + jarUrl); + + File file = ZipSpider.downloadFile(jarUrl, FileUtils.JAR_FILE_DIR); + if (file != null) { + ZipSpider.unzip(file.getPath(), FileUtils.JAR_FILE_DIR); + this.loadJar(FileUtils.JAR_FILE_DIR); } - FixedTask.url = jarUrl; - byte[] jmx = ZipSpider.get(runRequest.getUrl(), restTemplate); - if (jmx != null) { + LogUtil.info("开始拉取脚本和脚本附件:" + runRequest.getUrl()); + + File bodyFile = ZipSpider.downloadFile(runRequest.getUrl(), FileUtils.BODY_FILE_DIR); + if (bodyFile != null) { + ZipSpider.unzip(bodyFile.getPath(), FileUtils.BODY_FILE_DIR); + File jmxFile = new File(FileUtils.BODY_FILE_DIR + "/" + runRequest.getTestId() + ".jmx"); // 生成执行脚本 - HashTree testPlan = this.getHashTree(jmx); - //加载附件 - ZipSpider.downloadFiles(runRequest.getUrl(), testPlan); + HashTree testPlan = SaveService.loadTree(jmxFile); // 开始执行 jMeterService.run(runRequest, testPlan); } else { @@ -161,4 +108,8 @@ public String runStart(RunRequest runRequest) { } return "SUCCESS"; } + + public void sendMessage(String message) { + kafkaTemplate.send(MsKafkaListener.TOPICS, message); + } } diff --git a/src/main/java/io/metersphere/api/service/LoadTestProducer.java b/src/main/java/io/metersphere/api/service/LoadTestProducer.java deleted file mode 100644 index 42948c6..0000000 --- a/src/main/java/io/metersphere/api/service/LoadTestProducer.java +++ /dev/null @@ -1,47 +0,0 @@ -package io.metersphere.api.service; - -import io.metersphere.api.config.KafkaConfig; -import io.metersphere.api.config.KafkaProperties; -import io.metersphere.api.jmeter.utils.CommonBeanFactory; -import io.metersphere.api.jmeter.utils.MSException; -import io.metersphere.node.util.LogUtil; -import org.apache.commons.lang.StringUtils; -import org.springframework.kafka.core.KafkaTemplate; -import org.springframework.stereotype.Service; - -import javax.annotation.Resource; -import java.net.InetSocketAddress; -import java.net.Socket; - -@Service -public class LoadTestProducer { - - public void checkKafka() { - KafkaProperties kafkaProperties = CommonBeanFactory.getBean(KafkaProperties.class); - String[] servers = StringUtils.split(kafkaProperties.getBootstrapServers(), ","); - try { - for (String s : servers) { - String[] ipAndPort = s.split(":"); - //1,建立tcp - String ip = ipAndPort[0]; - int port = Integer.parseInt(ipAndPort[1]); - try ( - Socket soc = new Socket() - ) { - soc.connect(new InetSocketAddress(ip, port), 1000); - } - } - } catch (Exception e) { - LogUtil.error(e); - MSException.throwException("Failed to connect to Kafka"); - } - } - - - @Resource - private KafkaTemplate kafkaTemplate; - - public void sendMessage(String report) { - this.kafkaTemplate.send(KafkaConfig.testTopic, report); - } -} diff --git a/src/main/java/io/metersphere/api/service/MsKafkaListener.java b/src/main/java/io/metersphere/api/service/MsKafkaListener.java index 8174f89..e3a24fd 100644 --- a/src/main/java/io/metersphere/api/service/MsKafkaListener.java +++ b/src/main/java/io/metersphere/api/service/MsKafkaListener.java @@ -25,7 +25,10 @@ @Service public class MsKafkaListener { + // 执行内容监听 public final static String EXEC_TOPIC = "ms-automation-exec-topic"; + //执行结果回传 + public static final String TOPICS = "ms-api-exec-topic"; public static final String CONSUME_ID = "ms-api-automation-consume"; @Resource From cff17598db3ed9a18fc746776afe269240cc086d Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Mon, 30 Aug 2021 19:28:48 +0800 Subject: [PATCH 101/109] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96)=20=E5=B9=B6=E5=8F=91=E6=89=A7=E8=A1=8C?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=89=8B=E5=8A=A8=20flush=E6=B6=88=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/metersphere/api/service/JmeterExecuteService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java index a834420..8f2e952 100644 --- a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java +++ b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java @@ -111,5 +111,6 @@ public String runStart(RunRequest runRequest) { public void sendMessage(String message) { kafkaTemplate.send(MsKafkaListener.TOPICS, message); + kafkaTemplate.flush(); } } From 189f487f0bd4553113e50221d7b84f3afeb9aaf1 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Tue, 31 Aug 2021 18:24:00 +0800 Subject: [PATCH 102/109] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96)=20=E5=B9=B6=E5=8F=91=E6=89=A7=E8=A1=8C?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=A2=9E=E5=8A=A0=E5=AE=88=E6=8A=A4=E7=BA=BF?= =?UTF-8?q?=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/metersphere/api/config/FixedTask.java | 38 ----- .../metersphere/api/config/KafkaConfig.java | 11 +- .../controller/JmeterExecuteController.java | 5 + .../api/controller/request/RunRequest.java | 2 + .../api/jmeter/APIBackendListenerClient.java | 95 ++++++------ .../metersphere/api/jmeter/JMeterService.java | 3 + .../api/service/JmeterExecuteService.java | 62 ++++++-- .../api/service/MsKafkaListener.java | 62 +------- .../api/service/utils/ZipSpider.java | 143 ++++-------------- 9 files changed, 158 insertions(+), 263 deletions(-) delete mode 100644 src/main/java/io/metersphere/api/config/FixedTask.java diff --git a/src/main/java/io/metersphere/api/config/FixedTask.java b/src/main/java/io/metersphere/api/config/FixedTask.java deleted file mode 100644 index 3407fb1..0000000 --- a/src/main/java/io/metersphere/api/config/FixedTask.java +++ /dev/null @@ -1,38 +0,0 @@ -package io.metersphere.api.config; - -import io.metersphere.api.jmeter.utils.FileUtils; -import io.metersphere.api.jmeter.utils.MSException; -import io.metersphere.api.service.utils.ZipSpider; -import io.metersphere.node.util.LogUtil; -import org.apache.commons.lang3.StringUtils; -import org.apache.jmeter.NewDriver; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Component; - -import java.io.File; -import java.net.MalformedURLException; - -@Component -public class FixedTask { - public static String url = null; - - private void loadJar(String path) { - try { - NewDriver.addPath(path); - } catch (MalformedURLException e) { - LogUtil.error(e.getMessage(), e); - MSException.throwException(e.getMessage()); - } - } - - @Scheduled(cron = "0 0/5 * * * ?") - public void execute() { - if (StringUtils.isNotEmpty(FixedTask.url)) { - File file = ZipSpider.downloadFile(FixedTask.url, FileUtils.JAR_FILE_DIR); - if (file != null) { - ZipSpider.unzip(file.getPath(), FileUtils.JAR_FILE_DIR); - this.loadJar(FileUtils.JAR_FILE_DIR); - } - } - } -} diff --git a/src/main/java/io/metersphere/api/config/KafkaConfig.java b/src/main/java/io/metersphere/api/config/KafkaConfig.java index d0c6616..4249b6b 100644 --- a/src/main/java/io/metersphere/api/config/KafkaConfig.java +++ b/src/main/java/io/metersphere/api/config/KafkaConfig.java @@ -7,19 +7,20 @@ @Configuration public class KafkaConfig { - public final static String testTopic = "ms-api-exec-topic"; + // 执行内容监听 + public final static String EXEC_TOPIC = "ms-automation-exec-topic121"; + //执行结果回传 + public static final String TOPICS = "ms-api-exec-topic1212"; @Bean public NewTopic apiExecTopic() { - return TopicBuilder.name(testTopic) + return TopicBuilder.name(TOPICS) .build(); } - public final static String execTopic = "ms-automation-exec-topic"; - @Bean public NewTopic automationTopic() { - return TopicBuilder.name(execTopic) + return TopicBuilder.name(EXEC_TOPIC) .build(); } } diff --git a/src/main/java/io/metersphere/api/controller/JmeterExecuteController.java b/src/main/java/io/metersphere/api/controller/JmeterExecuteController.java index c497d66..16a4c5e 100644 --- a/src/main/java/io/metersphere/api/controller/JmeterExecuteController.java +++ b/src/main/java/io/metersphere/api/controller/JmeterExecuteController.java @@ -42,4 +42,9 @@ public JvmInfo getJvmInfo() { return JvmService.jvmInfo(); } + @GetMapping("/getRunning/{key}") + public Integer getRunning(@PathVariable String key) { + return jmeterExecuteService.getRunningTasks(key); + } + } diff --git a/src/main/java/io/metersphere/api/controller/request/RunRequest.java b/src/main/java/io/metersphere/api/controller/request/RunRequest.java index 180ec4f..6efb04c 100644 --- a/src/main/java/io/metersphere/api/controller/request/RunRequest.java +++ b/src/main/java/io/metersphere/api/controller/request/RunRequest.java @@ -13,5 +13,7 @@ public class RunRequest { private boolean isDebug; private String runMode; private String jmx; + // 集成报告ID + private String amassReport; private RunModeConfig config; } diff --git a/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java b/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java index b6dfeea..ca731f2 100644 --- a/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java +++ b/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java @@ -30,6 +30,8 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl public final static String TEST_REPORT_ID = "ms.test.report.name"; + public final static String AMASS_REPORT = "ms.test.amass.report.id"; + private final static String THREAD_SPLIT = " "; private final static String ID_SPLIT = "-"; @@ -54,6 +56,7 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl */ private String setReportId; + private String amassReport; /** * 获得控制台内容 */ @@ -92,56 +95,61 @@ public void teardownTest(BackendListenerContext context) throws Exception { testResult.setDebug(this.isDebug); testResult.setUserId(this.userId); jmeterExecuteService = CommonBeanFactory.getBean(JmeterExecuteService.class); - // 一个脚本里可能包含多个场景(ThreadGroup),所以要区分开,key: 场景Id - final Map scenarios = new LinkedHashMap<>(); - queue.forEach(result -> { - // 线程名称: <场景名> <场景Index>-<请求Index>, 例如:Scenario 2-1 - if (StringUtils.equals(result.getSampleLabel(), "RunningDebugSampler")) { - String evnStr = result.getResponseDataAsString(); - testResult.setRunningDebugSampler(result.getResponseDataAsString()); - } else { - String scenarioName = StringUtils.substringBeforeLast(result.getThreadName(), THREAD_SPLIT); - String index = StringUtils.substringAfterLast(result.getThreadName(), THREAD_SPLIT); - String scenarioId = StringUtils.substringBefore(index, ID_SPLIT); - ScenarioResult scenarioResult; - if (!scenarios.containsKey(scenarioId)) { - scenarioResult = new ScenarioResult(); - try { - scenarioResult.setId(Integer.parseInt(scenarioId)); - } catch (Exception e) { - scenarioResult.setId(0); - LogUtil.error("场景ID转换异常: " + e.getMessage()); - } - scenarioResult.setName(scenarioName); - scenarios.put(scenarioId, scenarioResult); + try { + + // 一个脚本里可能包含多个场景(ThreadGroup),所以要区分开,key: 场景Id + final Map scenarios = new LinkedHashMap<>(); + queue.forEach(result -> { + // 线程名称: <场景名> <场景Index>-<请求Index>, 例如:Scenario 2-1 + if (StringUtils.equals(result.getSampleLabel(), "RunningDebugSampler")) { + testResult.setRunningDebugSampler(result.getResponseDataAsString()); } else { - scenarioResult = scenarios.get(scenarioId); - } + String scenarioName = StringUtils.substringBeforeLast(result.getThreadName(), THREAD_SPLIT); + String index = StringUtils.substringAfterLast(result.getThreadName(), THREAD_SPLIT); + String scenarioId = StringUtils.substringBefore(index, ID_SPLIT); + ScenarioResult scenarioResult; + if (!scenarios.containsKey(scenarioId)) { + scenarioResult = new ScenarioResult(); + try { + scenarioResult.setId(Integer.parseInt(scenarioId)); + } catch (Exception e) { + scenarioResult.setId(0); + LogUtil.error("场景ID转换异常: " + e.getMessage()); + } + scenarioResult.setName(scenarioName); + scenarios.put(scenarioId, scenarioResult); + } else { + scenarioResult = scenarios.get(scenarioId); + } - if (result.isSuccessful()) { - scenarioResult.addSuccess(); - testResult.addSuccess(); - } else { - scenarioResult.addError(result.getErrorCount()); - testResult.addError(result.getErrorCount()); - } + if (result.isSuccessful()) { + scenarioResult.addSuccess(); + testResult.addSuccess(); + } else { + scenarioResult.addError(result.getErrorCount()); + testResult.addError(result.getErrorCount()); + } - RequestResult requestResult = getRequestResult(result); - scenarioResult.getRequestResults().add(requestResult); - scenarioResult.addResponseTime(result.getTime()); + RequestResult requestResult = getRequestResult(result); + scenarioResult.getRequestResults().add(requestResult); + scenarioResult.addResponseTime(result.getTime()); - testResult.addPassAssertions(requestResult.getPassAssertions()); - testResult.addTotalAssertions(requestResult.getTotalAssertions()); + testResult.addPassAssertions(requestResult.getPassAssertions()); + testResult.addTotalAssertions(requestResult.getTotalAssertions()); - scenarioResult.addPassAssertions(requestResult.getPassAssertions()); - scenarioResult.addTotalAssertions(requestResult.getTotalAssertions()); - } - }); - testResult.getScenarios().addAll(scenarios.values()); - testResult.getScenarios().sort(Comparator.comparing(ScenarioResult::getId)); - testResult.setRunMode(this.runMode); + scenarioResult.addPassAssertions(requestResult.getPassAssertions()); + scenarioResult.addTotalAssertions(requestResult.getTotalAssertions()); + } + }); + testResult.getScenarios().addAll(scenarios.values()); + testResult.getScenarios().sort(Comparator.comparing(ScenarioResult::getId)); + testResult.setRunMode(this.runMode); + } catch (Exception e) { + LogUtil.error("处理执行数据异常:" + e.getMessage()); + } // 推送执行结果 jmeterExecuteService.sendMessage(JSON.toJSONString(testResult)); + jmeterExecuteService.remove(amassReport,testId); queue.clear(); super.teardownTest(context); } @@ -232,6 +240,7 @@ private String getMethod(SampleResult result) { private void setParam(BackendListenerContext context) { this.testId = context.getParameter(TEST_ID); this.setReportId = context.getParameter(TEST_REPORT_ID); + this.amassReport = context.getParameter(AMASS_REPORT); this.runMode = context.getParameter("runMode"); this.isDebug = StringUtils.equals(context.getParameter("DEBUG"), "DEBUG") ? true : false; this.userId = context.getParameter("USER_ID"); diff --git a/src/main/java/io/metersphere/api/jmeter/JMeterService.java b/src/main/java/io/metersphere/api/jmeter/JMeterService.java index 1eb36c0..f2eb9bc 100644 --- a/src/main/java/io/metersphere/api/jmeter/JMeterService.java +++ b/src/main/java/io/metersphere/api/jmeter/JMeterService.java @@ -64,6 +64,9 @@ private void addBackendListener(HashTree testPlan, RunRequest request) { backendListener.setName(request.getTestId()); } Arguments arguments = new Arguments(); + if (StringUtils.isNotEmpty(request.getAmassReport())) { + arguments.addArgument(APIBackendListenerClient.AMASS_REPORT, request.getAmassReport()); + } if (request.getConfig() != null && request.getConfig().getMode().equals("serial") && request.getConfig().getReportType().equals("setReport")) { arguments.addArgument(APIBackendListenerClient.TEST_REPORT_ID, request.getConfig().getReportName()); } diff --git a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java index 8f2e952..2335096 100644 --- a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java +++ b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java @@ -1,7 +1,6 @@ package io.metersphere.api.service; -import com.alibaba.fastjson.JSON; -import io.metersphere.api.config.FixedTask; +import io.metersphere.api.config.KafkaConfig; import io.metersphere.api.controller.request.RunRequest; import io.metersphere.api.jmeter.JMeterService; import io.metersphere.api.jmeter.utils.FileUtils; @@ -13,8 +12,8 @@ import org.apache.jmeter.save.SaveService; import org.apache.jorphan.collections.HashTree; import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; -import org.springframework.web.client.RestTemplate; import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; @@ -23,6 +22,10 @@ import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; @Service public class JmeterExecuteService { @@ -31,6 +34,10 @@ public class JmeterExecuteService { @Resource private KafkaTemplate kafkaTemplate; + private static String url = null; + // 记录所以执行中的请求/场景 + private Map> runningTasks = new HashMap<>(); + private static InputStream getStrToStream(String sInputString) { if (StringUtils.isNotEmpty(sInputString)) { try { @@ -44,7 +51,6 @@ private static InputStream getStrToStream(String sInputString) { return null; } - private void loadJar(String path) { try { NewDriver.addPath(path); @@ -82,13 +88,16 @@ public String runStart(RunRequest runRequest) { // 生成附件/JAR文件 URL urlObject = new URL(runRequest.getUrl()); String jarUrl = urlObject.getProtocol() + "://" + urlObject.getHost() + (urlObject.getPort() > 0 ? ":" + urlObject.getPort() : "") + "/api/jmeter/download/jar"; - LogUtil.info("开始同步上传的第三方JAR:" + jarUrl); - File file = ZipSpider.downloadFile(jarUrl, FileUtils.JAR_FILE_DIR); - if (file != null) { - ZipSpider.unzip(file.getPath(), FileUtils.JAR_FILE_DIR); - this.loadJar(FileUtils.JAR_FILE_DIR); + if (StringUtils.isEmpty(url)) { + LogUtil.info("开始同步上传的第三方JAR:" + jarUrl); + File file = ZipSpider.downloadFile(jarUrl, FileUtils.JAR_FILE_DIR); + if (file != null) { + ZipSpider.unzip(file.getPath(), FileUtils.JAR_FILE_DIR); + this.loadJar(FileUtils.JAR_FILE_DIR); + } } + url = jarUrl; LogUtil.info("开始拉取脚本和脚本附件:" + runRequest.getUrl()); File bodyFile = ZipSpider.downloadFile(runRequest.getUrl(), FileUtils.BODY_FILE_DIR); @@ -110,7 +119,40 @@ public String runStart(RunRequest runRequest) { } public void sendMessage(String message) { - kafkaTemplate.send(MsKafkaListener.TOPICS, message); + kafkaTemplate.send(KafkaConfig.TOPICS, message); kafkaTemplate.flush(); } + + public void putRunningTasks(String key, String value) { + List list = new ArrayList<>(); + if (this.runningTasks.containsKey(key)) { + list = this.runningTasks.get(key); + } + list.add(value); + this.runningTasks.put(key, list); + } + + public int getRunningTasks(String key) { + if (this.runningTasks.containsKey(key)) { + return this.runningTasks.get(key).size(); + } + return 0; + } + + public void remove(String key, String value) { + if (this.runningTasks.containsKey(key)) { + this.runningTasks.get(key).remove(value); + } + } + + @Scheduled(cron = "0 0/5 * * * ?") + public void execute() { + if (StringUtils.isNotEmpty(url)) { + File file = ZipSpider.downloadFile(url, FileUtils.JAR_FILE_DIR); + if (file != null) { + ZipSpider.unzip(file.getPath(), FileUtils.JAR_FILE_DIR); + this.loadJar(FileUtils.JAR_FILE_DIR); + } + } + } } diff --git a/src/main/java/io/metersphere/api/service/MsKafkaListener.java b/src/main/java/io/metersphere/api/service/MsKafkaListener.java index e3a24fd..52d0bcb 100644 --- a/src/main/java/io/metersphere/api/service/MsKafkaListener.java +++ b/src/main/java/io/metersphere/api/service/MsKafkaListener.java @@ -1,82 +1,35 @@ package io.metersphere.api.service; import com.alibaba.fastjson.JSON; -import io.metersphere.api.config.FixedTask; +import io.metersphere.api.config.KafkaConfig; import io.metersphere.api.controller.request.RunRequest; -import io.metersphere.api.jmeter.JMeterService; -import io.metersphere.api.jmeter.utils.FileUtils; -import io.metersphere.api.jmeter.utils.MSException; -import io.metersphere.api.service.utils.ZipSpider; import io.metersphere.node.util.LogUtil; import org.apache.commons.lang3.StringUtils; -import org.apache.jmeter.NewDriver; -import org.apache.jmeter.save.SaveService; -import org.apache.jorphan.collections.HashTree; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.springframework.kafka.annotation.KafkaListener; import org.springframework.kafka.support.Acknowledgment; import org.springframework.stereotype.Service; import javax.annotation.Resource; -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.InputStream; -import java.net.MalformedURLException; @Service public class MsKafkaListener { - // 执行内容监听 - public final static String EXEC_TOPIC = "ms-automation-exec-topic"; - //执行结果回传 - public static final String TOPICS = "ms-api-exec-topic"; public static final String CONSUME_ID = "ms-api-automation-consume"; - @Resource - private JMeterService jMeterService; - - private static InputStream getStrToStream(String sInputString) { - if (StringUtils.isNotEmpty(sInputString)) { - try { - ByteArrayInputStream tInputStringStream = new ByteArrayInputStream(sInputString.getBytes()); - return tInputStringStream; - } catch (Exception ex) { - ex.printStackTrace(); - MSException.throwException("生成脚本异常"); - } - } - return null; - } - private void loadJar(String path) { - try { - NewDriver.addPath(path); - } catch (MalformedURLException e) { - LogUtil.error(e.getMessage(), e); - MSException.throwException(e.getMessage()); - } - } + @Resource + private JmeterExecuteService jmeterExecuteService; - @KafkaListener(id = CONSUME_ID, topics = EXEC_TOPIC, groupId = "${spring.kafka.consumer.group-id}") + @KafkaListener(id = CONSUME_ID, topics = KafkaConfig.EXEC_TOPIC, groupId = "${spring.kafka.consumer.group-id}") public void consume(ConsumerRecord record, Acknowledgment ack) { LogUtil.info("接收到执行执行请求开始处理"); try { if (record.value() != null) { RunRequest request = JSON.parseObject(record.value(), RunRequest.class); - // 生成执行脚本 - InputStream inputSource = getStrToStream(request.getJmx()); - Object scriptWrapper = SaveService.loadElement(inputSource); - HashTree testPlan = JMeterService.getHashTree(scriptWrapper); - //加载附件 - ZipSpider.downloadFiles(request.getUrl(), testPlan); - if (StringUtils.isNotEmpty(FixedTask.url)) { - File file = ZipSpider.downloadFile(FixedTask.url, FileUtils.JAR_FILE_DIR); - if (file != null) { - ZipSpider.unzip(file.getPath(), FileUtils.JAR_FILE_DIR); - this.loadJar(FileUtils.JAR_FILE_DIR); - } + if (StringUtils.isNotEmpty(request.getAmassReport())) { + jmeterExecuteService.putRunningTasks(request.getAmassReport(), request.getTestId()); } - // 开始执行 - jMeterService.run(request, testPlan); + jmeterExecuteService.runStart(request); } } catch (Exception e) { LogUtil.error(e.getMessage()); @@ -85,5 +38,4 @@ public void consume(ConsumerRecord record, Acknowledgment ack) { } LogUtil.info("执行执行请求处理结束"); } - } diff --git a/src/main/java/io/metersphere/api/service/utils/ZipSpider.java b/src/main/java/io/metersphere/api/service/utils/ZipSpider.java index 4b7de92..89b749f 100644 --- a/src/main/java/io/metersphere/api/service/utils/ZipSpider.java +++ b/src/main/java/io/metersphere/api/service/utils/ZipSpider.java @@ -94,22 +94,25 @@ public static void download(String url, String path) { public static void unzip(String fromFile, String toFile) { try (ZipInputStream zin = new ZipInputStream(new FileInputStream(fromFile)); BufferedInputStream bin = new BufferedInputStream(zin);) { String Parent = toFile; - File fout = null; ZipEntry entry; while ((entry = zin.getNextEntry()) != null && !entry.isDirectory()) { - fout = new File(Parent, entry.getName()); + File fout = new File(Parent, entry.getName()); if (!fout.exists()) { (new File(fout.getParent())).mkdirs(); } - FileOutputStream out = new FileOutputStream(fout); - BufferedOutputStream Bout = new BufferedOutputStream(out); - int b; - while ((b = bin.read()) != -1) { - Bout.write(b); + try (FileOutputStream out = new FileOutputStream(fout); + BufferedOutputStream bout = new BufferedOutputStream(out);) { + int b; + while ((b = bin.read()) != -1) { + bout.write(b); + } + bout.close(); + out.close(); + LogUtil.info(fout + "解压成功"); + } catch (Exception e) { + e.printStackTrace(); } - Bout.close(); - out.close(); - LogUtil.info(fout + "解压成功"); + } bin.close(); zin.close(); @@ -148,75 +151,6 @@ public static void seperate(char c) { sop(""); } - @SuppressWarnings("finally") - public static File downloadFile(String urlPath, String downloadDir, String json) { - File file = null; - BufferedInputStream bin = null; - OutputStream out = null; - try { - URL url = new URL(urlPath); - URLConnection urlConnection = url.openConnection(); - HttpURLConnection httpURLConnection = (HttpURLConnection) urlConnection;// http的连接类 - //String contentType = httpURLConnection.getContentType();//请求类型,可用来过滤请求, - httpURLConnection.setUseCaches(false); - httpURLConnection.setDoOutput(true); - httpURLConnection.setDoInput(true); - httpURLConnection.setConnectTimeout(1000 * 5);//设置超时 - httpURLConnection.setRequestMethod("POST");//设置请求方式,默认是GET - httpURLConnection.setRequestProperty("Charset", "UTF-8");// 设置字符编码 - httpURLConnection.setRequestProperty("Connection", "Keep-Alive"); - httpURLConnection.setRequestProperty("Content-Type", "application/json; charset=UTF-8"); - httpURLConnection.setInstanceFollowRedirects(false); - byte[] writebytes = json.getBytes(); - // 设置文件长度 - httpURLConnection.setRequestProperty("Content-Length", String.valueOf(writebytes.length)); - OutputStream outwritestream = httpURLConnection.getOutputStream(); - outwritestream.write(json.getBytes()); - outwritestream.flush(); - outwritestream.close(); - - httpURLConnection.connect();// 打开连接 - bin = new BufferedInputStream(httpURLConnection.getInputStream()); - - String fileName = httpURLConnection.getHeaderField("Content-Disposition"); - fileName = URLDecoder.decode(fileName.substring(fileName.indexOf("filename") + 10, fileName.length() - 1), "UTF-8"); - String path = downloadDir + File.separatorChar + fileName;// 指定存放位置 - file = new File(path); - // 校验文件夹目录是否存在,不存在就创建一个目录 - if (!file.getParentFile().exists()) { - file.getParentFile().mkdirs(); - } - - out = new FileOutputStream(file); - int size = 0; - - byte[] b = new byte[2048]; - //把输入流的文件读取到字节数据b中,然后输出到指定目录的文件 - while ((size = bin.read(b)) != -1) { - out.write(b, 0, size); - } - // 关闭资源 - bin.close(); - out.close(); - LogUtil.info("文件下载成功!"); - } catch (MalformedURLException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - LogUtil.info("文件下载失败!"); - } finally { - try { - bin.close(); - out.close(); - } catch (Exception e) { - - } - return file; - } - } - public static void getFiles(HashTree tree, List files) { for (Object key : tree.keySet()) { HashTree node = tree.get(key); @@ -245,56 +179,30 @@ public static void getFiles(HashTree tree, List files) { } } - public static void downloadFiles(String uri, HashTree hashTree) { - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.MULTIPART_FORM_DATA); - headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN)); - List files = new ArrayList<>(); - ZipSpider.getFiles(hashTree, files); - if (CollectionUtils.isNotEmpty(files)) { - try { - URL urlObject = new URL(uri); - String url = urlObject.getProtocol() + "://" + urlObject.getHost() + (urlObject.getPort() > 0 ? ":" + urlObject.getPort() : "") + "/api/jmeter/download/files"; - LogUtil.info("开始同步下载附件:" + url); - ZipSpider.downloadFile(url, FileUtils.BODY_FILE_DIR, JSON.toJSONString(files)); - } catch (Exception e) { - e.printStackTrace(); - } - } - } - - public static byte[] get(String uri, RestTemplate restTemplate) { - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.MULTIPART_FORM_DATA); - headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN)); - ResponseEntity result = restTemplate.getForEntity(uri, byte[].class); - return result.getBody(); - } - @SuppressWarnings("finally") public static File downloadFile(String urlPath, String downloadDir) { - File file = null; + OutputStream out = null; + BufferedInputStream bin = null; try { URL url = new URL(urlPath); URLConnection urlConnection = url.openConnection(); HttpURLConnection httpURLConnection = (HttpURLConnection) urlConnection;// http的连接类 - //String contentType = httpURLConnection.getContentType();//请求类型,可用来过滤请求, httpURLConnection.setConnectTimeout(1000 * 5);//设置超时 httpURLConnection.setRequestMethod("GET");//设置请求方式,默认是GET httpURLConnection.setRequestProperty("Charset", "UTF-8");// 设置字符编码 httpURLConnection.connect();// 打开连接 - BufferedInputStream bin = new BufferedInputStream(httpURLConnection.getInputStream()); + bin = new BufferedInputStream(httpURLConnection.getInputStream()); String fileName = httpURLConnection.getHeaderField("Content-Disposition"); fileName = URLDecoder.decode(fileName.substring(fileName.indexOf("filename") + 10, fileName.length() - 1), "UTF-8"); String path = downloadDir + File.separatorChar + fileName;// 指定存放位置 - file = new File(path); + File file = new File(path); // 校验文件夹目录是否存在,不存在就创建一个目录 - if (!file.getParentFile().exists()) { + if (file.getParentFile() != null && !file.getParentFile().exists()) { file.getParentFile().mkdirs(); } - OutputStream out = new FileOutputStream(file); + out = new FileOutputStream(file); int size = 0; byte[] b = new byte[2048]; @@ -306,6 +214,7 @@ public static File downloadFile(String urlPath, String downloadDir) { bin.close(); out.close(); LogUtil.info("文件下载成功!"); + return file; } catch (MalformedURLException e) { // TODO Auto-generated catch block e.printStackTrace(); @@ -314,7 +223,17 @@ public static File downloadFile(String urlPath, String downloadDir) { e.printStackTrace(); LogUtil.info("文件下载失败!"); } finally { - return file; + try { + if (bin != null) { + bin.close(); + } + if (out != null) { + out.close(); + } + } catch (Exception e) { + + } } + return null; } } From 459ed0b347ba41b894cc2e5a406086bddafd1065 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Tue, 31 Aug 2021 18:24:56 +0800 Subject: [PATCH 103/109] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96)=20topic=20=E5=90=8D=E7=A7=B0=E6=81=A2?= =?UTF-8?q?=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/io/metersphere/api/config/KafkaConfig.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/metersphere/api/config/KafkaConfig.java b/src/main/java/io/metersphere/api/config/KafkaConfig.java index 4249b6b..2b9f3ce 100644 --- a/src/main/java/io/metersphere/api/config/KafkaConfig.java +++ b/src/main/java/io/metersphere/api/config/KafkaConfig.java @@ -8,9 +8,9 @@ @Configuration public class KafkaConfig { // 执行内容监听 - public final static String EXEC_TOPIC = "ms-automation-exec-topic121"; + public final static String EXEC_TOPIC = "ms-automation-exec-topic"; //执行结果回传 - public static final String TOPICS = "ms-api-exec-topic1212"; + public static final String TOPICS = "ms-api-exec-topic"; @Bean public NewTopic apiExecTopic() { From 4bb6739adb60c93671d4e0c6f41408d047ab4962 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Wed, 1 Sep 2021 16:28:56 +0800 Subject: [PATCH 104/109] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96)=20=E5=8A=A8=E6=80=81=E5=88=9B=E5=BB=BAKafka?= =?UTF-8?q?=20=E4=BB=A3=E7=90=86=EF=BC=9B=E7=BB=9F=E4=B8=80=E6=8E=A5?= =?UTF-8?q?=E6=94=B6=E4=B8=BB=E6=9C=8D=E5=8A=A1=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/io/metersphere/Application.java | 3 - .../metersphere/api/config/KafkaConfig.java | 18 ----- .../api/config/KafkaProperties.java | 51 ------------ .../api/controller/ConsumerController.java | 77 +++++++++++++++++++ .../api/controller/ProducerController.java | 20 +++++ .../api/jmeter/APIBackendListenerClient.java | 8 +- .../api/jmeter/utils/FileUtils.java | 6 ++ .../api/service/JmeterExecuteService.java | 8 +- .../api/service/MsKafkaListener.java | 10 +-- .../api/service/ProducerService.java | 38 +++++++++ src/main/resources/application.properties | 27 +------ 11 files changed, 153 insertions(+), 113 deletions(-) delete mode 100644 src/main/java/io/metersphere/api/config/KafkaProperties.java create mode 100644 src/main/java/io/metersphere/api/controller/ConsumerController.java create mode 100644 src/main/java/io/metersphere/api/controller/ProducerController.java create mode 100644 src/main/java/io/metersphere/api/service/ProducerService.java diff --git a/src/main/java/io/metersphere/Application.java b/src/main/java/io/metersphere/Application.java index d304b57..5212b82 100644 --- a/src/main/java/io/metersphere/Application.java +++ b/src/main/java/io/metersphere/Application.java @@ -1,6 +1,4 @@ package io.metersphere; - -import io.metersphere.api.config.KafkaProperties; import io.metersphere.api.jmeter.utils.CommonBeanFactory; import io.metersphere.api.jmeter.utils.JmeterProperties; import org.springframework.boot.SpringApplication; @@ -13,7 +11,6 @@ @ServletComponentScan @EnableConfigurationProperties({ - KafkaProperties.class, JmeterProperties.class, }) @PropertySource(value = {"file:/opt/metersphere/conf/metersphere.properties"}, encoding = "UTF-8", ignoreResourceNotFound = true) diff --git a/src/main/java/io/metersphere/api/config/KafkaConfig.java b/src/main/java/io/metersphere/api/config/KafkaConfig.java index 2b9f3ce..3dac81f 100644 --- a/src/main/java/io/metersphere/api/config/KafkaConfig.java +++ b/src/main/java/io/metersphere/api/config/KafkaConfig.java @@ -1,26 +1,8 @@ package io.metersphere.api.config; - -import org.apache.kafka.clients.admin.NewTopic; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.kafka.config.TopicBuilder; - -@Configuration public class KafkaConfig { // 执行内容监听 public final static String EXEC_TOPIC = "ms-automation-exec-topic"; //执行结果回传 public static final String TOPICS = "ms-api-exec-topic"; - @Bean - public NewTopic apiExecTopic() { - return TopicBuilder.name(TOPICS) - .build(); - } - - @Bean - public NewTopic automationTopic() { - return TopicBuilder.name(EXEC_TOPIC) - .build(); - } } diff --git a/src/main/java/io/metersphere/api/config/KafkaProperties.java b/src/main/java/io/metersphere/api/config/KafkaProperties.java deleted file mode 100644 index e52b95a..0000000 --- a/src/main/java/io/metersphere/api/config/KafkaProperties.java +++ /dev/null @@ -1,51 +0,0 @@ -package io.metersphere.api.config; - -import lombok.Getter; -import lombok.Setter; -import org.springframework.boot.context.properties.ConfigurationProperties; - -@ConfigurationProperties(prefix = KafkaProperties.KAFKA_PREFIX) -@Getter -@Setter -public class KafkaProperties { - public static final String KAFKA_PREFIX = "kafka"; - - private String acks = "0"; // 不要设置all - private String expectedDelayEndTime = "30000"; // 30s - private String topic; - private String fields; - private String timestamp; - private String bootstrapServers; - private String sampleFilter; - private String testMode; - private String parseAllReqHeaders; - private String parseAllResHeaders; - private String compressionType; - private String batchSize; - private String clientId; - private String connectionsMaxIdleMs; - private KafkaProperties.Ssl ssl = new KafkaProperties.Ssl(); - private KafkaProperties.Log log = new KafkaProperties.Log(); - - @Getter - @Setter - public static class Ssl { - private String enabled = "false"; - private String keyPassword; - private String keystoreLocation; - private String keystorePassword; - private String keystoreType; - private String truststoreLocation; - private String truststorePassword; - private String truststoreType; - private String protocol; - private String enabledProtocols; - private String provider; - } - - @Getter - @Setter - public static class Log { - private String topic; - } -} diff --git a/src/main/java/io/metersphere/api/controller/ConsumerController.java b/src/main/java/io/metersphere/api/controller/ConsumerController.java new file mode 100644 index 0000000..6cf2981 --- /dev/null +++ b/src/main/java/io/metersphere/api/controller/ConsumerController.java @@ -0,0 +1,77 @@ +package io.metersphere.api.controller; + +import io.metersphere.api.service.MsKafkaListener; +import org.apache.kafka.clients.consumer.ConsumerConfig; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Scope; +import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory; +import org.springframework.kafka.config.KafkaListenerEndpointRegistry; +import org.springframework.kafka.core.DefaultKafkaConsumerFactory; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.Map; + +@RestController +@RequestMapping("consumer") +public class ConsumerController { + /** + * 应用程序上行文 + */ + @Resource + ApplicationContext context; + /** + * 监听器容器工厂 + */ + @Resource + ConcurrentKafkaListenerContainerFactory containerFactory; + /** + * 所有@KafkaListener这个注解所标注的方法都会被注册在这里面中 + */ + @Resource + KafkaListenerEndpointRegistry registry; + + private MsKafkaListener listener; + + /** + * 创建消费者分组 + */ + @PostMapping("/create") + public void create(@RequestBody Map consumerProps) { + try { + if (consumerProps != null && consumerProps.size() > 0 && listener == null) { + consumerProps.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false); + consumerProps.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, + "org.apache.kafka.common.serialization.StringDeserializer"); + consumerProps.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, + "org.apache.kafka.common.serialization.StringDeserializer"); + // 设置监听器容器工厂 + containerFactory.setConsumerFactory(new DefaultKafkaConsumerFactory<>(consumerProps)); + // 获取监听类实例 + listener = context.getBean(MsKafkaListener.class); + } + } catch (Exception e) { + } + } + + /** + * 停止所有消费监听 + */ + @GetMapping("/stop") + public void stop() { + registry.getListenerContainers().forEach(container -> { + container.stop(); + }); + } + + /** + * 获取监听类实例 + */ + @Bean + @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) + public MsKafkaListener listener() { + return new MsKafkaListener(); + } +} diff --git a/src/main/java/io/metersphere/api/controller/ProducerController.java b/src/main/java/io/metersphere/api/controller/ProducerController.java new file mode 100644 index 0000000..3d88387 --- /dev/null +++ b/src/main/java/io/metersphere/api/controller/ProducerController.java @@ -0,0 +1,20 @@ +package io.metersphere.api.controller; + +import io.metersphere.api.service.ProducerService; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.Map; + +@RestController +@RequestMapping("producer") +public class ProducerController { + @Resource + private ProducerService producerService; + + @PostMapping("/create") + public String create(@RequestBody Map producerProps) { + return producerService.init(producerProps); + } +} + diff --git a/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java b/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java index ca731f2..40c7785 100644 --- a/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java +++ b/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java @@ -6,6 +6,7 @@ import io.metersphere.api.module.*; import io.metersphere.api.jmeter.utils.CommonBeanFactory; import io.metersphere.api.service.JmeterExecuteService; +import io.metersphere.api.service.ProducerService; import io.metersphere.node.util.LogUtil; import org.apache.commons.lang3.StringUtils; import org.apache.dubbo.common.utils.CollectionUtils; @@ -45,7 +46,7 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl private boolean isDebug; private JmeterExecuteService jmeterExecuteService; - + private ProducerService producerServer; /** * 测试ID */ @@ -95,6 +96,7 @@ public void teardownTest(BackendListenerContext context) throws Exception { testResult.setDebug(this.isDebug); testResult.setUserId(this.userId); jmeterExecuteService = CommonBeanFactory.getBean(JmeterExecuteService.class); + producerServer = CommonBeanFactory.getBean(ProducerService.class); try { // 一个脚本里可能包含多个场景(ThreadGroup),所以要区分开,key: 场景Id @@ -148,8 +150,8 @@ public void teardownTest(BackendListenerContext context) throws Exception { LogUtil.error("处理执行数据异常:" + e.getMessage()); } // 推送执行结果 - jmeterExecuteService.sendMessage(JSON.toJSONString(testResult)); - jmeterExecuteService.remove(amassReport,testId); + producerServer.send(JSON.toJSONString(testResult)); + jmeterExecuteService.remove(amassReport, testId); queue.clear(); super.teardownTest(context); } diff --git a/src/main/java/io/metersphere/api/jmeter/utils/FileUtils.java b/src/main/java/io/metersphere/api/jmeter/utils/FileUtils.java index 57fcc34..de94ce0 100644 --- a/src/main/java/io/metersphere/api/jmeter/utils/FileUtils.java +++ b/src/main/java/io/metersphere/api/jmeter/utils/FileUtils.java @@ -29,4 +29,10 @@ public static void createFiles(MultipartFile[] bodyFiles, String path) { } } } + public static void deleteFile(String path) { + File file = new File(path); + if (file.exists()) { + file.delete(); + } + } } diff --git a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java index 2335096..8ba4dd4 100644 --- a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java +++ b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java @@ -1,6 +1,5 @@ package io.metersphere.api.service; -import io.metersphere.api.config.KafkaConfig; import io.metersphere.api.controller.request.RunRequest; import io.metersphere.api.jmeter.JMeterService; import io.metersphere.api.jmeter.utils.FileUtils; @@ -108,6 +107,7 @@ public String runStart(RunRequest runRequest) { HashTree testPlan = SaveService.loadTree(jmxFile); // 开始执行 jMeterService.run(runRequest, testPlan); + FileUtils.deleteFile(bodyFile.getPath()); } else { MSException.throwException("未找到执行的JMX文件"); } @@ -118,11 +118,6 @@ public String runStart(RunRequest runRequest) { return "SUCCESS"; } - public void sendMessage(String message) { - kafkaTemplate.send(KafkaConfig.TOPICS, message); - kafkaTemplate.flush(); - } - public void putRunningTasks(String key, String value) { List list = new ArrayList<>(); if (this.runningTasks.containsKey(key)) { @@ -152,6 +147,7 @@ public void execute() { if (file != null) { ZipSpider.unzip(file.getPath(), FileUtils.JAR_FILE_DIR); this.loadJar(FileUtils.JAR_FILE_DIR); + FileUtils.deleteFile(file.getPath()); } } } diff --git a/src/main/java/io/metersphere/api/service/MsKafkaListener.java b/src/main/java/io/metersphere/api/service/MsKafkaListener.java index 52d0bcb..a945d1c 100644 --- a/src/main/java/io/metersphere/api/service/MsKafkaListener.java +++ b/src/main/java/io/metersphere/api/service/MsKafkaListener.java @@ -1,28 +1,26 @@ package io.metersphere.api.service; - import com.alibaba.fastjson.JSON; import io.metersphere.api.config.KafkaConfig; import io.metersphere.api.controller.request.RunRequest; +import io.metersphere.api.jmeter.utils.CommonBeanFactory; import io.metersphere.node.util.LogUtil; import org.apache.commons.lang3.StringUtils; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.springframework.kafka.annotation.KafkaListener; import org.springframework.kafka.support.Acknowledgment; -import org.springframework.stereotype.Service; - -import javax.annotation.Resource; -@Service public class MsKafkaListener { public static final String CONSUME_ID = "ms-api-automation-consume"; - @Resource private JmeterExecuteService jmeterExecuteService; @KafkaListener(id = CONSUME_ID, topics = KafkaConfig.EXEC_TOPIC, groupId = "${spring.kafka.consumer.group-id}") public void consume(ConsumerRecord record, Acknowledgment ack) { LogUtil.info("接收到执行执行请求开始处理"); + if (jmeterExecuteService == null) { + jmeterExecuteService = CommonBeanFactory.getBean(JmeterExecuteService.class); + } try { if (record.value() != null) { RunRequest request = JSON.parseObject(record.value(), RunRequest.class); diff --git a/src/main/java/io/metersphere/api/service/ProducerService.java b/src/main/java/io/metersphere/api/service/ProducerService.java new file mode 100644 index 0000000..c19894e --- /dev/null +++ b/src/main/java/io/metersphere/api/service/ProducerService.java @@ -0,0 +1,38 @@ +package io.metersphere.api.service; + +import io.metersphere.api.config.KafkaConfig; +import org.apache.kafka.clients.producer.ProducerConfig; +import org.springframework.kafka.core.DefaultKafkaProducerFactory; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.stereotype.Service; + +import java.util.Map; + +@Service +public class ProducerService { + private KafkaTemplate kafkaTemplate; + + public String init(Map producerProps) { + try { + if (kafkaTemplate == null) { + producerProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, + "org.apache.kafka.common.serialization.StringSerializer"); + producerProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, + "org.apache.kafka.common.serialization.StringSerializer"); + DefaultKafkaProducerFactory pf = new DefaultKafkaProducerFactory<>(producerProps); + KafkaTemplate kafkaTemplate = new KafkaTemplate(pf, true); + this.kafkaTemplate = kafkaTemplate; + } + return "SUCCESS"; + } catch (Exception e) { + return e.getMessage(); + } + } + + public void send(String message) { + if (this.kafkaTemplate != null) { + this.kafkaTemplate.send(KafkaConfig.TOPICS, message); + this.kafkaTemplate.flush(); + } + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 1e3ee1e..21c0c57 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,32 +1,7 @@ spring.application.name=node-controller logging.file.path=/opt/metersphere/logs/${spring.application.name} server.port=8082 - -#kafka -spring.kafka.bootstrap-servers=${kafka.bootstrap-servers} spring.kafka.consumer.group-id=metersphere_group_id spring.kafka.listener.ack-mode=MANUAL_IMMEDIATE -kafka.fields= -kafka.timestamp=yyyy-MM-dd'T'HH:mm:ss.SSSZZ -kafka.sample-filter= -kafka.test-mode=info -kafka.parse-all-req-headers=false -kafka.parse-all-res-headers=false -kafka.compression-type= -kafka.batch-size=16384 -kafka.client-id=JMeterKafkaBackendListener -kafka.connections-max-idle-ms=180000 -kafka.ssl.enabled=false -kafka.ssl.key-password= -kafka.ssl.keystore-location= -kafka.ssl.keystore-password= -kafka.ssl.truststore-location= -kafka.ssl.truststore-password= -kafka.ssl.enabled-protocols=TLSv1.2,TLSv1.1,TLSv1 -kafka.ssl.keystore-type=JKS -kafka.ssl.protocol=TLS -kafka.ssl.provider= -kafka.ssl.truststore-type= -spring.kafka.producer.properties.max.request.size=32428800 # jmeter -jmeter.home=/opt/jmeter +jmeter.home=/opt/jmeter \ No newline at end of file From 778beaa87dc033e426f2478cf72557cf34518aa9 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Wed, 15 Sep 2021 11:31:20 +0800 Subject: [PATCH 105/109] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96)=20=E5=A2=9E=E5=8A=A0=E5=81=9C=E6=AD=A2?= =?UTF-8?q?=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/JmeterExecuteController.java | 7 ++++ .../api/jmeter/APIBackendListenerClient.java | 3 ++ .../metersphere/api/jmeter/JMeterService.java | 3 +- .../metersphere/api/jmeter/LocalRunner.java | 34 ++++++++++++++++--- .../api/jmeter/utils/MessageCache.java | 10 ++++++ 5 files changed, 50 insertions(+), 7 deletions(-) create mode 100644 src/main/java/io/metersphere/api/jmeter/utils/MessageCache.java diff --git a/src/main/java/io/metersphere/api/controller/JmeterExecuteController.java b/src/main/java/io/metersphere/api/controller/JmeterExecuteController.java index 16a4c5e..ffcdab3 100644 --- a/src/main/java/io/metersphere/api/controller/JmeterExecuteController.java +++ b/src/main/java/io/metersphere/api/controller/JmeterExecuteController.java @@ -2,6 +2,7 @@ import com.alibaba.fastjson.JSON; import io.metersphere.api.controller.request.RunRequest; +import io.metersphere.api.jmeter.LocalRunner; import io.metersphere.api.module.JvmInfo; import io.metersphere.api.service.JmeterExecuteService; import io.metersphere.api.service.JvmService; @@ -10,6 +11,7 @@ import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; +import java.util.List; @RestController @RequestMapping("/jmeter") @@ -47,4 +49,9 @@ public Integer getRunning(@PathVariable String key) { return jmeterExecuteService.getRunningTasks(key); } + @GetMapping("/stop") + public void stop(@RequestBody List keys) { + new LocalRunner().stop(keys); + } + } diff --git a/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java b/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java index 40c7785..3263780 100644 --- a/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java +++ b/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java @@ -3,6 +3,7 @@ import com.alibaba.fastjson.JSON; import io.metersphere.api.jmeter.constants.ApiRunMode; import io.metersphere.api.jmeter.constants.RequestType; +import io.metersphere.api.jmeter.utils.MessageCache; import io.metersphere.api.module.*; import io.metersphere.api.jmeter.utils.CommonBeanFactory; import io.metersphere.api.service.JmeterExecuteService; @@ -97,6 +98,8 @@ public void teardownTest(BackendListenerContext context) throws Exception { testResult.setUserId(this.userId); jmeterExecuteService = CommonBeanFactory.getBean(JmeterExecuteService.class); producerServer = CommonBeanFactory.getBean(ProducerService.class); + MessageCache.runningEngine.remove(testId); + MessageCache.runningEngine.remove(setReportId); try { // 一个脚本里可能包含多个场景(ThreadGroup),所以要区分开,key: 场景Id diff --git a/src/main/java/io/metersphere/api/jmeter/JMeterService.java b/src/main/java/io/metersphere/api/jmeter/JMeterService.java index f2eb9bc..72d376b 100644 --- a/src/main/java/io/metersphere/api/jmeter/JMeterService.java +++ b/src/main/java/io/metersphere/api/jmeter/JMeterService.java @@ -10,7 +10,6 @@ import org.apache.jmeter.util.JMeterUtils; import org.apache.jmeter.visualizers.backend.BackendListener; import org.apache.jorphan.collections.HashTree; -import org.python.antlr.ast.arguments; import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.stereotype.Service; @@ -90,7 +89,7 @@ public void run(RunRequest request, HashTree testPlan) { init(); addBackendListener(testPlan, request); LocalRunner runner = new LocalRunner(testPlan); - runner.run(); + runner.run(request.getReportId()); } catch (Exception e) { LogUtil.error(e.getMessage(), e); MSException.throwException("读取脚本失败"); diff --git a/src/main/java/io/metersphere/api/jmeter/LocalRunner.java b/src/main/java/io/metersphere/api/jmeter/LocalRunner.java index 3db1bb7..60ab90a 100644 --- a/src/main/java/io/metersphere/api/jmeter/LocalRunner.java +++ b/src/main/java/io/metersphere/api/jmeter/LocalRunner.java @@ -1,24 +1,48 @@ package io.metersphere.api.jmeter; -import org.apache.jmeter.engine.JMeterEngine; +import io.metersphere.api.jmeter.utils.MessageCache; +import io.metersphere.node.util.LogUtil; +import org.apache.commons.collections.CollectionUtils; import org.apache.jmeter.engine.JMeterEngineException; import org.apache.jmeter.engine.StandardJMeterEngine; import org.apache.jorphan.collections.HashTree; +import java.util.List; + public class LocalRunner { - private final HashTree jmxTree; + private HashTree jmxTree; public LocalRunner(HashTree jmxTree) { this.jmxTree = jmxTree; } - public void run() { - JMeterEngine engine = new StandardJMeterEngine(); + public LocalRunner() { + } + + public void run(String report) { + StandardJMeterEngine engine = new StandardJMeterEngine(); engine.configure(jmxTree); try { engine.runTest(); + MessageCache.runningEngine.put(report, engine); } catch (JMeterEngineException e) { engine.stopTest(true); } } -} + + public void stop(List reports) { + try { + if (CollectionUtils.isNotEmpty(reports)) { + for (String report : reports) { + StandardJMeterEngine engine = MessageCache.runningEngine.get(report); + if (engine != null) { + engine.stopTest(); + MessageCache.runningEngine.remove(report); + } + } + } + } catch (Exception e) { + LogUtil.error(e); + } + } +} \ No newline at end of file diff --git a/src/main/java/io/metersphere/api/jmeter/utils/MessageCache.java b/src/main/java/io/metersphere/api/jmeter/utils/MessageCache.java new file mode 100644 index 0000000..ee0cd9d --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/utils/MessageCache.java @@ -0,0 +1,10 @@ +package io.metersphere.api.jmeter.utils; + +import org.apache.jmeter.engine.StandardJMeterEngine; + +import java.util.concurrent.ConcurrentHashMap; + +public class MessageCache { + public static ConcurrentHashMap runningEngine = new ConcurrentHashMap<>(); + +} From bad2eac95737f765ee1dfb525706a80f01039eaf Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Wed, 15 Sep 2021 14:29:33 +0800 Subject: [PATCH 106/109] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96)=20=E4=BF=AE=E5=A4=8Dconsole=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E4=B8=A2=E5=A4=B1=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/metersphere/api/jmeter/APIBackendListenerClient.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java b/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java index 40c7785..7d3ac99 100644 --- a/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java +++ b/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java @@ -95,6 +95,7 @@ public void teardownTest(BackendListenerContext context) throws Exception { testResult.setSetReportId(this.setReportId); testResult.setDebug(this.isDebug); testResult.setUserId(this.userId); + testResult.setConsole(getConsole()); jmeterExecuteService = CommonBeanFactory.getBean(JmeterExecuteService.class); producerServer = CommonBeanFactory.getBean(ProducerService.class); try { @@ -209,8 +210,6 @@ private RequestResult getRequestResult(SampleResult result) { responseResult.getAssertions().add(responseAssertionResult); } } - responseResult.setConsole(getConsole()); - return requestResult; } From 5d6e3a87146074b5fac1cecd29228c53aca023d0 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Wed, 22 Sep 2021 17:05:05 +0800 Subject: [PATCH 107/109] =?UTF-8?q?refactor:=20=E9=81=BF=E5=85=8D=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E8=BF=87=E5=A4=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/metersphere/node/service/JmeterOperateService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/metersphere/node/service/JmeterOperateService.java b/src/main/java/io/metersphere/node/service/JmeterOperateService.java index a7d8fdc..b545025 100644 --- a/src/main/java/io/metersphere/node/service/JmeterOperateService.java +++ b/src/main/java/io/metersphere/node/service/JmeterOperateService.java @@ -78,7 +78,7 @@ public void onComplete() { try { if (DockerClientService.existContainer(dockerClient, containerId) > 0) { - copyTestResources(dockerClient, containerId, reportId, resourceIndex); +// copyTestResources(dockerClient, containerId, reportId, resourceIndex); DockerClientService.removeContainer(dockerClient, containerId); } From b85447e2910ba801c406c96bf6d2eab6908bf194 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Wed, 22 Sep 2021 18:55:59 +0800 Subject: [PATCH 108/109] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96)=20=E4=BF=AE=E5=A4=8D=E6=89=A7=E8=A1=8C?= =?UTF-8?q?=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/metersphere/api/config/KafkaConfig.java | 4 ++-- .../api/jmeter/APIBackendListenerClient.java | 11 +++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/main/java/io/metersphere/api/config/KafkaConfig.java b/src/main/java/io/metersphere/api/config/KafkaConfig.java index 3dac81f..f69fa6a 100644 --- a/src/main/java/io/metersphere/api/config/KafkaConfig.java +++ b/src/main/java/io/metersphere/api/config/KafkaConfig.java @@ -1,8 +1,8 @@ package io.metersphere.api.config; public class KafkaConfig { // 执行内容监听 - public final static String EXEC_TOPIC = "ms-automation-exec-topic"; + public final static String EXEC_TOPIC = "ms-automation-exec-topic99"; //执行结果回传 - public static final String TOPICS = "ms-api-exec-topic"; + public static final String TOPICS = "ms-api-exec-topic99"; } diff --git a/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java b/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java index fd32a8a..46d400b 100644 --- a/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java +++ b/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java @@ -3,9 +3,9 @@ import com.alibaba.fastjson.JSON; import io.metersphere.api.jmeter.constants.ApiRunMode; import io.metersphere.api.jmeter.constants.RequestType; +import io.metersphere.api.jmeter.utils.CommonBeanFactory; import io.metersphere.api.jmeter.utils.MessageCache; import io.metersphere.api.module.*; -import io.metersphere.api.jmeter.utils.CommonBeanFactory; import io.metersphere.api.service.JmeterExecuteService; import io.metersphere.api.service.ProducerService; import io.metersphere.node.util.LogUtil; @@ -99,10 +99,13 @@ public void teardownTest(BackendListenerContext context) throws Exception { testResult.setConsole(getConsole()); jmeterExecuteService = CommonBeanFactory.getBean(JmeterExecuteService.class); producerServer = CommonBeanFactory.getBean(ProducerService.class); - MessageCache.runningEngine.remove(testId); - MessageCache.runningEngine.remove(setReportId); try { - + if (StringUtils.isNotEmpty(testId) && !MessageCache.runningEngine.isEmpty()) { + MessageCache.runningEngine.remove(testId); + } + if (StringUtils.isNotEmpty(setReportId) && !MessageCache.runningEngine.isEmpty()) { + MessageCache.runningEngine.remove(setReportId); + } // 一个脚本里可能包含多个场景(ThreadGroup),所以要区分开,key: 场景Id final Map scenarios = new LinkedHashMap<>(); queue.forEach(result -> { From 769ba01b966e80399023f76e2a34d5f02e8c118e Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Wed, 22 Sep 2021 19:00:52 +0800 Subject: [PATCH 109/109] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96)=20=E4=BF=AE=E5=A4=8D=E6=89=A7=E8=A1=8C?= =?UTF-8?q?=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/io/metersphere/api/config/KafkaConfig.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/metersphere/api/config/KafkaConfig.java b/src/main/java/io/metersphere/api/config/KafkaConfig.java index f69fa6a..3dac81f 100644 --- a/src/main/java/io/metersphere/api/config/KafkaConfig.java +++ b/src/main/java/io/metersphere/api/config/KafkaConfig.java @@ -1,8 +1,8 @@ package io.metersphere.api.config; public class KafkaConfig { // 执行内容监听 - public final static String EXEC_TOPIC = "ms-automation-exec-topic99"; + public final static String EXEC_TOPIC = "ms-automation-exec-topic"; //执行结果回传 - public static final String TOPICS = "ms-api-exec-topic99"; + public static final String TOPICS = "ms-api-exec-topic"; }