chore: import upstream snapshot with attribution
MCP Bash Server CI / Test MCP Bash Server (dev) (push) Has been cancelled
MCP Bash Server CI / Test MCP Bash Server (release) (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:11:39 +08:00
commit c8cebdfeee
4654 changed files with 626756 additions and 0 deletions
@@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.hertzbeat</groupId>
<artifactId>hertzbeat-e2e</artifactId>
<version>2.0-SNAPSHOT</version>
</parent>
<artifactId>hertzbeat-collector-basic-e2e</artifactId>
<properties>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<postgresql.version>42.5.5</postgresql.version>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.hertzbeat</groupId>
<artifactId>hertzbeat-collector-common-e2e</artifactId>
<version>${hertzbeat.version}</version>
<scope>test</scope>
<type>test-jar</type>
</dependency>
<dependency>
<groupId>org.apache.hertzbeat</groupId>
<artifactId>hertzbeat-collector-basic</artifactId>
<version>${hertzbeat.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.hertzbeat</groupId>
<artifactId>hertzbeat-collector-common</artifactId>
<version>${hertzbeat.version}</version>
<scope>test</scope>
</dependency>
<!-- Testcontainers Jupiter / JUnit 5 -->
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<!-- Testcontainers JDBC Support -->
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-jdbc</artifactId>
<scope>test</scope>
</dependency>
<!-- Testcontainers Database Modules -->
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-mysql</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-postgresql</artifactId>
<scope>test</scope>
</dependency>
<!-- JDBC Drivers -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>test</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>${postgresql.version}</version>
<scope>test</scope>
<optional>true</optional>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,72 @@
/*
* 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.hertzbeat.collector.collect.basic.database;
/**
* Database type (image mame) and image tag enumeration class
*/
public enum DatabaseImagesEnum {
MYSQL("mysql", "8.0.36"),
POSTGRESQL("postgresql", "15");
private final String imageName;
private final String defaultTag;
DatabaseImagesEnum(String imageName, String defaultTag) {
this.imageName = imageName;
this.defaultTag = defaultTag;
}
public String getImageName() {
return imageName;
}
public String getDefaultTag() {
return defaultTag;
}
public String getFullImageName() {
return imageName + ":" + defaultTag;
}
public static DatabaseImagesEnum fromImageName(String imageName) {
for (DatabaseImagesEnum value : values()) {
if (value.getImageName().equalsIgnoreCase(imageName)) {
return value;
}
}
throw new IllegalArgumentException("Unknown database image name: " + imageName);
}
public static DatabaseImagesEnum fromFullImageName(String fullImageName) {
for (DatabaseImagesEnum value : values()) {
if (value.getFullImageName().equalsIgnoreCase(fullImageName)) {
return value;
}
}
throw new IllegalArgumentException("Unknown full database image name: " + fullImageName);
}
@Override
public String toString() {
return "DatabaseImagesEnum{"
+ "imageName='" + imageName + '\''
+ ", defaultTag='" + defaultTag + '\''
+ '}';
}
}
@@ -0,0 +1,99 @@
/*
* 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.hertzbeat.collector.collect.basic.database;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.collector.collect.AbstractCollectE2eTest;
import org.apache.hertzbeat.collector.collect.database.JdbcCommonCollect;
import org.apache.hertzbeat.collector.util.CollectUtil;
import org.apache.hertzbeat.common.entity.job.Configmap;
import org.apache.hertzbeat.common.entity.job.Job;
import org.apache.hertzbeat.common.entity.job.Metrics;
import org.apache.hertzbeat.common.entity.job.protocol.JdbcProtocol;
import org.apache.hertzbeat.common.entity.job.protocol.Protocol;
import org.apache.hertzbeat.common.entity.message.CollectRep;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/**
* E2e test monitored by Jdbc Common
*/
@Slf4j
@ExtendWith(MockitoExtension.class)
public class JdbcCommonCollectE2eTest extends AbstractCollectE2eTest {
private static final DatabaseImagesEnum databaseImage = DatabaseImagesEnum.fromImageName("postgresql");
private static final String DATABASE_IMAGE_NAME = databaseImage.getImageName();
private static final String DATABASE_IMAGE_TAG = databaseImage.getDefaultTag();
@BeforeEach
public void setUp() throws Exception {
super.setUp();
collect = new JdbcCommonCollect();
metrics = new Metrics();
}
@Override
protected CollectRep.MetricsData.Builder collectMetrics(Metrics metricsDef) {
JdbcProtocol jdbcProtocol = (JdbcProtocol) buildProtocol(metricsDef);
metrics.setJdbc(jdbcProtocol);
CollectRep.MetricsData.Builder metricsData = CollectRep.MetricsData.newBuilder();
metricsData.setApp(DATABASE_IMAGE_NAME);
metrics.setAliasFields(metricsDef.getAliasFields());
return collectMetricsData(metrics, metricsDef, metricsData);
}
@Override
protected Protocol buildProtocol(Metrics metricsDef) {
JdbcProtocol jdbcProtocol = metricsDef.getJdbc();
jdbcProtocol.setHost(DATABASE_IMAGE_NAME);
jdbcProtocol.setPort(DATABASE_IMAGE_TAG);
jdbcProtocol.setDatabase("test");
jdbcProtocol.setPlatform("testcontainers");
return jdbcProtocol;
}
@Test
public void testWithJdbcTcUrl() {
Job dockerJob = appService.getAppDefine(DATABASE_IMAGE_NAME);
List<Map<String, Configmap>> configmapFromPreCollectData = new LinkedList<>();
for (Metrics metricsDef : dockerJob.getMetrics()) {
metricsDef = CollectUtil.replaceCryPlaceholderToMetrics(metricsDef, configmapFromPreCollectData.size() > 0 ? configmapFromPreCollectData.get(0) : new HashMap<>());
String metricName = metricsDef.getName();
if ("slow_sql".equals(metricName)) {
Metrics finalMetricsDef = metricsDef;
assertDoesNotThrow(() -> collectMetrics(finalMetricsDef),
String.format("%s failed to collect metrics)", metricName));
log.info("{} metrics validation passed", metricName);
continue; // skip slow_sql, extra extensions
}
CollectRep.MetricsData metricsData = validateMetricsCollection(metricsDef, metricName, true);
CollectUtil.getConfigmapFromPreCollectData(metricsData);
}
}
}
@@ -0,0 +1,144 @@
/*
* 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.hertzbeat.collector.collect.basic.http;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpServer;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.collector.collect.AbstractCollectE2eTest;
import org.apache.hertzbeat.collector.collect.http.HttpCollectImpl;
import org.apache.hertzbeat.collector.util.CollectUtil;
import org.apache.hertzbeat.common.entity.job.Configmap;
import org.apache.hertzbeat.common.entity.job.Job;
import org.apache.hertzbeat.common.entity.job.Metrics;
import org.apache.hertzbeat.common.entity.job.protocol.HttpProtocol;
import org.apache.hertzbeat.common.entity.job.protocol.Protocol;
import org.apache.hertzbeat.common.entity.message.CollectRep;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.util.ResourceUtils;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* Integration test for Docker monitoring functionality
*/
@Slf4j
@ExtendWith(MockitoExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class DockerMonitorE2eTest extends AbstractCollectE2eTest {
private static final int MOCK_SERVER_PORT = 52375;
private static final String LOCALHOST = "127.0.0.1";
private static HttpServer mockServer;
@AfterAll
public static void tearDown() {
if (mockServer != null) {
mockServer.stop(0);
}
}
@BeforeEach
public void setUp() throws Exception {
super.setUp();
// Setup collect instance
collect = new HttpCollectImpl();
// Setup mock server and endpoints
mockServer = HttpServer.create(new InetSocketAddress(MOCK_SERVER_PORT), 0);
mockServer.setExecutor(null);
mockServer.start();
// Setup Docker API endpoints
String containerResponse = loadResponseFromFile("classpath:http/docker/containers_result.txt");
String infoResponse = loadResponseFromFile("classpath:http/docker/system_result.txt");
String containerStatsResponse = loadResponseFromFile("classpath:http/docker/containers_stats.txt");
mockServer.createContext("/containers/json", exchange -> {
String query = exchange.getRequestURI().getQuery();
if (query != null && query.contains("all=true")) {
sendJsonResponse(exchange, containerResponse);
} else {
exchange.sendResponseHeaders(404, 0);
exchange.close();
}
});
mockServer.createContext("/info", exchange -> sendJsonResponse(exchange, infoResponse));
mockServer.createContext("/containers/34174a918eb2e38cdb097c910f74af845e7383b04765d26ad52f940f86342a64/stats", exchange -> sendJsonResponse(exchange, containerStatsResponse));
}
private String loadResponseFromFile(String resourcePath) throws Exception {
return new String(Files.readAllBytes(ResourceUtils.getFile(resourcePath).toPath()));
}
private void sendJsonResponse(HttpExchange exchange, String response) throws IOException {
exchange.getResponseHeaders().set("Content-Type", "application/json");
final byte[] array = response.getBytes(StandardCharsets.UTF_8);
exchange.sendResponseHeaders(200, array.length);
try (OutputStream os = exchange.getResponseBody()) {
os.write(array);
}
}
@Test
public void testDockerMonitor() {
Job dockerJob = appService.getAppDefine("docker");
List<Map<String, Configmap>> configmapFromPreCollectData = new LinkedList<>();
for (Metrics metricsDef : dockerJob.getMetrics()) {
metricsDef = CollectUtil.replaceCryPlaceholderToMetrics(metricsDef, configmapFromPreCollectData.size() > 0 ? configmapFromPreCollectData.get(0) : new HashMap<>());
CollectRep.MetricsData metricsData = validateMetricsCollection(metricsDef, metricsDef.getName());
configmapFromPreCollectData = CollectUtil.getConfigmapFromPreCollectData(metricsData);
}
}
@Override
protected Protocol buildProtocol(Metrics metricsDef) {
// Setup HTTP protocol
HttpProtocol protocol = new HttpProtocol();
protocol.setHost(LOCALHOST);
protocol.setPort(String.valueOf(MOCK_SERVER_PORT));
protocol.setMethod(metricsDef.getHttp().getMethod());
protocol.setParseType(metricsDef.getHttp().getParseType());
protocol.setParseScript(metricsDef.getHttp().getParseScript());
protocol.setUrl(metricsDef.getHttp().getUrl());
return protocol;
}
@Override
protected CollectRep.MetricsData.Builder collectMetrics(Metrics metricsDef) {
HttpProtocol protocol = (HttpProtocol) buildProtocol(metricsDef);
metrics.setHttp(protocol);
return collectMetricsData(metrics, metricsDef);
}
}
@@ -0,0 +1,129 @@
/*
* 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.hertzbeat.collector.collect.basic.http;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpServer;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.collector.collect.AbstractCollectE2eTest;
import org.apache.hertzbeat.collector.collect.http.HttpCollectImpl;
import org.apache.hertzbeat.collector.util.CollectUtil;
import org.apache.hertzbeat.common.entity.job.Configmap;
import org.apache.hertzbeat.common.entity.job.Job;
import org.apache.hertzbeat.common.entity.job.Metrics;
import org.apache.hertzbeat.common.entity.job.protocol.HttpProtocol;
import org.apache.hertzbeat.common.entity.job.protocol.Protocol;
import org.apache.hertzbeat.common.entity.message.CollectRep;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.boot.test.context.SpringBootTest;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* E2E test for HTTP monitor
*/
@Slf4j
@ExtendWith(MockitoExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class HttpMonitorE2eTest extends AbstractCollectE2eTest {
private static final int MOCK_SERVER_PORT = 52376;
private static final String LOCALHOST = "127.0.0.1";
private static final String RELATIVE_PATH = "/";
private static final List<String> ALLOW_EMPTY_WHITE_LIST = List.of("header");
private static HttpServer mockServer;
@AfterAll
public static void tearDown() {
if (mockServer != null) {
mockServer.stop(0);
}
}
@BeforeEach
public void setUp() throws Exception {
super.setUp();
// Setup collect instance
collect = new HttpCollectImpl();
// Setup mock server and endpoints
mockServer = HttpServer.create(new InetSocketAddress(MOCK_SERVER_PORT), 0);
mockServer.setExecutor(null);
mockServer.start();
mockServer.createContext(RELATIVE_PATH, exchange -> sendJsonResponse(exchange, ""));
}
private void sendJsonResponse(HttpExchange exchange, String response) throws IOException {
exchange.getResponseHeaders().set("Content-Type", "application/json");
final byte[] array = response.getBytes(StandardCharsets.UTF_8);
exchange.sendResponseHeaders(200, array.length);
try (OutputStream os = exchange.getResponseBody()) {
os.write(array);
}
}
@Test
public void testHttpMonitor() {
Job dockerJob = appService.getAppDefine("api");
List<Map<String, Configmap>> configmapFromPreCollectData = new LinkedList<>();
for (Metrics metricsDef : dockerJob.getMetrics()) {
metricsDef = CollectUtil.replaceCryPlaceholderToMetrics(metricsDef, configmapFromPreCollectData.size() > 0 ? configmapFromPreCollectData.get(0) : new HashMap<>());
CollectRep.MetricsData metricsData;
if (ALLOW_EMPTY_WHITE_LIST.contains(metricsDef.getName())) {
metricsData = validateMetricsCollection(metricsDef, metricsDef.getName(), true);
} else {
metricsData = validateMetricsCollection(metricsDef, metricsDef.getName());
}
configmapFromPreCollectData = CollectUtil.getConfigmapFromPreCollectData(metricsData);
}
}
@Override
protected Protocol buildProtocol(Metrics metricsDef) {
// Setup HTTP protocol
HttpProtocol protocol = new HttpProtocol();
protocol.setHost(LOCALHOST);
protocol.setUrl(RELATIVE_PATH);
protocol.setMethod("GET");
protocol.setPort(String.valueOf(MOCK_SERVER_PORT));
protocol.setParseType(metricsDef.getHttp().getParseType());
protocol.setParseScript(metricsDef.getHttp().getParseScript());
return protocol;
}
@Override
protected CollectRep.MetricsData.Builder collectMetrics(Metrics metricsDef) {
HttpProtocol protocol = (HttpProtocol) buildProtocol(metricsDef);
metrics.setHttp(protocol);
return collectMetricsData(metrics, metricsDef);
}
}
@@ -0,0 +1,126 @@
/*
* 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.hertzbeat.collector.collect.basic.redis;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.collector.collect.AbstractCollectE2eTest;
import org.apache.hertzbeat.collector.collect.redis.RedisCommonCollectImpl;
import org.apache.hertzbeat.common.entity.job.Job;
import org.apache.hertzbeat.common.entity.job.Metrics;
import org.apache.hertzbeat.common.entity.job.protocol.Protocol;
import org.apache.hertzbeat.common.entity.job.protocol.RedisProtocol;
import org.apache.hertzbeat.common.entity.message.CollectRep;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.Network;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.lifecycle.Startables;
import org.testcontainers.utility.DockerImageName;
import java.time.Duration;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import java.util.stream.Stream;
/**
* End-to-end test class for redis collector
*/
@Slf4j
@ExtendWith(MockitoExtension.class)
public class RedisCommonCollectE2eTest extends AbstractCollectE2eTest {
private static final String REDIS_IMAGE = "redis:7.4.2";
private static final String HOST = "127.0.0.1";
private static final int REDIS_PORT = 6379;
private static final String REDIS_PATTERN = "1";
private static final List<String> ALLOW_EMPTY_WHITE_LIST = Arrays.asList("server", "errorstats", "commandstats", "keyspace");
private static GenericContainer<?> redisContainer;
private Integer mappedPort;
@AfterAll
public static void tearDown() throws InterruptedException {
if (redisContainer != null) {
redisContainer.stop();
}
}
@BeforeEach
public void setUp() throws Exception {
super.setUp();
collect = new RedisCommonCollectImpl();
// Set up and start container
setupAndStartContainer();
mappedPort = redisContainer.getMappedPort(REDIS_PORT);
log.info("Container started successfully with mapped port: {}", mappedPort);
}
@Test
public void testRedisCollect() throws ExecutionException, InterruptedException, TimeoutException {
Assertions.assertTrue(redisContainer.isRunning(), "redis container should be running");
Job redisJob = appService.getAppDefine("redis");
redisJob.getMetrics().forEach(metricsDef -> {
if (ALLOW_EMPTY_WHITE_LIST.contains(metricsDef.getName())) {
validateMetricsCollection(metricsDef, metricsDef.getName(), true);
} else {
validateMetricsCollection(metricsDef, metricsDef.getName());
}
});
}
@Override
protected CollectRep.MetricsData.Builder collectMetrics(Metrics metricsDef) {
//Build redis protocol configuration
RedisProtocol redisProtocol = (RedisProtocol) buildProtocol(metricsDef);
metrics.setRedis(redisProtocol);
metrics.setName(metricsDef.getName());
metrics.setFields(metricsDef.getFields());
return collectMetricsData(metrics, metricsDef);
}
@Override
protected Protocol buildProtocol(Metrics metricsDef) {
RedisProtocol redisProtocol = new RedisProtocol();
redisProtocol.setHost(HOST);
redisProtocol.setPort(mappedPort.toString());
redisProtocol.setPattern(REDIS_PATTERN);
return redisProtocol;
}
public void setupAndStartContainer() {
Network network = Network.builder().build();
redisContainer = new GenericContainer<>(DockerImageName.parse(REDIS_IMAGE))
.withExposedPorts(REDIS_PORT)
.withNetwork(network)
.withNetworkAliases("redis")
.waitingFor(Wait.forListeningPort())
.withStartupTimeout(Duration.ofSeconds(30));
Startables.deepStart(Stream.of(redisContainer)).join();
}
}
@@ -0,0 +1,150 @@
/*
* 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.hertzbeat.collector.collect.basic.ssh;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.collector.collect.AbstractCollectE2eTest;
import org.apache.hertzbeat.collector.collect.ssh.SshCollectImpl;
import org.apache.hertzbeat.common.entity.job.Job;
import org.apache.hertzbeat.common.entity.job.Metrics;
import org.apache.hertzbeat.common.entity.job.protocol.Protocol;
import org.apache.hertzbeat.common.entity.job.protocol.SshProtocol;
import org.apache.hertzbeat.common.entity.message.CollectRep;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.Network;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.lifecycle.Startables;
import org.testcontainers.utility.DockerImageName;
import java.time.Duration;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import java.util.stream.Stream;
/**
* End-to-end test class for SSH collector
*/
@Slf4j
@ExtendWith(MockitoExtension.class)
public class SshCollectE2eTest extends AbstractCollectE2eTest {
private static final String UBUNTU_IMAGE = "rastasheep/ubuntu-sshd:18.04";
private static final String HOST = "127.0.0.1";
private static final String ROOT_USER = "root";
private static final int SSH_PORT = 22;
private static final int PASSWORD_LENGTH = 12;
private static final List<String> ALLOW_EMPTY_WHITE_LIST = Arrays.asList("top_mem_process", "top_cpu_process");
private static GenericContainer<?> linuxContainer;
private Integer mappedPort;
private String password;
@AfterAll
public static void tearDown() {
if (linuxContainer != null) {
linuxContainer.stop();
}
}
@BeforeEach
public void setUp() throws Exception {
super.setUp();
collect = new SshCollectImpl();
password = generateRandomPassword();
// Set up and start container
setupAndStartContainer();
// Set root password
setContainerRootPassword();
mappedPort = linuxContainer.getMappedPort(SSH_PORT);
log.info("Container started successfully with mapped port: {}", mappedPort);
}
@Test
public void testSshCollect() throws ExecutionException, InterruptedException, TimeoutException {
Assertions.assertTrue(linuxContainer.isRunning(), "Ubuntu container should be running");
Job ubuntuJob = appService.getAppDefine("ubuntu");
ubuntuJob.getMetrics().forEach(metricsDef -> {
if (ALLOW_EMPTY_WHITE_LIST.contains(metricsDef.getName())) {
validateMetricsCollection(metricsDef, metricsDef.getName(), true);
} else {
validateMetricsCollection(metricsDef, metricsDef.getName());
}
});
}
@Override
protected CollectRep.MetricsData.Builder collectMetrics(Metrics metricsDef) {
// Build SSH protocol configuration
SshProtocol sshProtocol = (SshProtocol) buildProtocol(metricsDef);
metrics.setSsh(sshProtocol);
return collectMetricsData(metrics, metricsDef);
}
@Override
protected Protocol buildProtocol(Metrics metricsDef) {
SshProtocol sshProtocol = new SshProtocol();
sshProtocol.setHost(HOST);
sshProtocol.setPort(mappedPort.toString());
sshProtocol.setUsername(ROOT_USER);
sshProtocol.setPassword(password);
sshProtocol.setScript(metricsDef.getSsh().getScript());
sshProtocol.setParseType(metricsDef.getSsh().getParseType());
return sshProtocol;
}
private void setupAndStartContainer() {
Network network = Network.builder().build();
linuxContainer = new GenericContainer<>(DockerImageName.parse(UBUNTU_IMAGE))
.withExposedPorts(SSH_PORT)
.withNetwork(network)
.withNetworkAliases("ubuntu")
.waitingFor(Wait.forListeningPort())
.withCommand("/usr/sbin/sshd", "-D")
.withStartupTimeout(Duration.ofSeconds(30));
Startables.deepStart(Stream.of(linuxContainer)).join();
}
private void setContainerRootPassword() throws Exception {
linuxContainer.execInContainer("bash", "-c",
String.format("echo 'root:%s' | chpasswd", password));
}
private String generateRandomPassword() {
String characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
StringBuilder password = new StringBuilder();
Random random = new Random();
for (int i = 0; i < PASSWORD_LENGTH; i++) {
password.append(characters.charAt(random.nextInt(characters.length())));
}
return password.toString();
}
}
@@ -0,0 +1,128 @@
/*
* 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.hertzbeat.collector.collect.basic.telnet;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.collector.collect.AbstractCollectE2eTest;
import org.apache.hertzbeat.collector.collect.telnet.TelnetCollectImpl;
import org.apache.hertzbeat.collector.util.CollectUtil;
import org.apache.hertzbeat.common.entity.job.Configmap;
import org.apache.hertzbeat.common.entity.job.Job;
import org.apache.hertzbeat.common.entity.job.Metrics;
import org.apache.hertzbeat.common.entity.job.protocol.Protocol;
import org.apache.hertzbeat.common.entity.job.protocol.TelnetProtocol;
import org.apache.hertzbeat.common.entity.message.CollectRep;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.utility.DockerImageName;
import java.time.Duration;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* Integration test for Zookeeper monitoring functionality
*/
@Slf4j
@ExtendWith(MockitoExtension.class)
public class ZookeeperMonitorE2eTest extends AbstractCollectE2eTest {
private static final String ZOOKEEPER_IMAGE_NAME = "zookeeper:3.8.4";
private static final String ZOOKEEPER_NAME = "zookeeper";
private static final Integer ZOOKEEPER_PORT = 2181;
private static GenericContainer<?> zookeeperContainer;
@AfterAll
public static void tearDown() {
if (zookeeperContainer != null) {
zookeeperContainer.stop();
}
}
@BeforeEach
public void setUp() throws Exception {
super.setUp();
collect = new TelnetCollectImpl();
metrics = new Metrics();
try {
// Start Zookeeper container with custom configuration
zookeeperContainer = new GenericContainer<>(DockerImageName.parse(ZOOKEEPER_IMAGE_NAME))
.withExposedPorts(ZOOKEEPER_PORT)
.withEnv("ZOO_4LW_COMMANDS_WHITELIST", "*")
.withNetworkAliases(ZOOKEEPER_NAME)
.waitingFor(
Wait.forLogMessage(".*Started AdminServer on address.*\\n", 1)
.withStartupTimeout(Duration.ofSeconds(60))
)
.withLogConsumer(outputFrame -> {
log.info(outputFrame.getUtf8String());
});
zookeeperContainer.start();
log.info("Zookeeper container started at {}:{}",
zookeeperContainer.getHost(),
zookeeperContainer.getMappedPort(ZOOKEEPER_PORT));
} catch (Exception e) {
e.printStackTrace();
log.error("Failed to start Zookeeper container", e);
throw e;
}
Thread.sleep(30000);
}
@Override
protected CollectRep.MetricsData.Builder collectMetrics(Metrics metricsDef) {
TelnetProtocol telnetProtocol = (TelnetProtocol) buildProtocol(metricsDef);
metrics.setTelnet(telnetProtocol);
CollectRep.MetricsData.Builder metricsData = CollectRep.MetricsData.newBuilder();
metricsData.setApp(ZOOKEEPER_NAME);
metrics.setAliasFields(metricsDef.getAliasFields());
return collectMetricsData(metrics, metricsDef, metricsData);
}
@Override
protected Protocol buildProtocol(Metrics metricsDef) {
TelnetProtocol protocol = new TelnetProtocol();
protocol.setHost(zookeeperContainer.getHost());
protocol.setPort(String.valueOf(zookeeperContainer.getMappedPort(ZOOKEEPER_PORT)));
protocol.setCmd(metricsDef.getTelnet().getCmd());
return protocol;
}
@Test
public void testZookeeperMonitor() {
Assertions.assertTrue(zookeeperContainer.isRunning(), "Zookeeper container should be running");
Job dockerJob = appService.getAppDefine("zookeeper");
List<Map<String, Configmap>> configmapFromPreCollectData = new LinkedList<>();
for (Metrics metricsDef : dockerJob.getMetrics()) {
metricsDef = CollectUtil.replaceCryPlaceholderToMetrics(metricsDef, configmapFromPreCollectData.size() > 0 ? configmapFromPreCollectData.get(0) : new HashMap<>());
CollectRep.MetricsData metricsData = validateMetricsCollection(metricsDef, metricsDef.getName());
configmapFromPreCollectData = CollectUtil.getConfigmapFromPreCollectData(metricsData);
}
}
}
@@ -0,0 +1 @@
[{"Id":"34174a918eb2e38cdb097c910f74af845e7383b04765d26ad52f940f86342a64","Names":["/mysql"],"Image":"mysql:8.0","ImageID":"sha256:6c55ddbef96911f9f36d1330ffe3f7557c019d49434e738cafabd1a3dd6b4bac","Command":"docker-entrypoint.sh mysqld","Created":1735286602,"Ports":[{"IP":"0.0.0.0","PrivatePort":3306,"PublicPort":3306,"Type":"tcp"},{"IP":"::","PrivatePort":3306,"PublicPort":3306,"Type":"tcp"},{"PrivatePort":33060,"Type":"tcp"}],"Labels":{},"State":"running","Status":"Up 42 hours","HostConfig":{"NetworkMode":"bridge"},"NetworkSettings":{"Networks":{"bridge":{"IPAMConfig":null,"Links":null,"Aliases":null,"MacAddress":"02:42:ac:11:00:04","NetworkID":"305ba4c311d55570760456274cf1653e22bf5383873498e6a159d76baaba0e4e","EndpointID":"6925c9ece99d84ed57c4b08a41c38d009cd8becc9cc2e941eb2b572478a22747","Gateway":"172.17.0.1","IPAddress":"172.17.0.4","IPPrefixLen":16,"IPv6Gateway":"","GlobalIPv6Address":"","GlobalIPv6PrefixLen":0,"DriverOpts":null,"DNSNames":null}}},"Mounts":[{"Type":"bind","Source":"/docker/mysql/conf/my.cnf","Destination":"/etc/mysql/my.cnf","Mode":"","RW":true,"Propagation":"rprivate"},{"Type":"bind","Source":"/docker/mysql/data","Destination":"/var/lib/mysql","Mode":"","RW":true,"Propagation":"rprivate"}]},{"Id":"d1f070438d28de7059113df7e79423f5ba1fdc4968cee4914b661cec222ddb06","Names":["/dbgate-instance"],"Image":"dbgate:latest","ImageID":"sha256:107efa35665b98c97cb9698cae2daf3f80aa88854ac90d81bba24b8de02c830d","Command":"/home/dbgate-docker/entrypoint.sh","Created":1734751223,"Ports":[{"IP":"0.0.0.0","PrivatePort":3000,"PublicPort":3000,"Type":"tcp"},{"IP":"::","PrivatePort":3000,"PublicPort":3000,"Type":"tcp"}],"Labels":{"org.opencontainers.image.ref.name":"ubuntu","org.opencontainers.image.version":"22.04"},"State":"running","Status":"Up 7 days","HostConfig":{"NetworkMode":"bridge"},"NetworkSettings":{"Networks":{"bridge":{"IPAMConfig":null,"Links":null,"Aliases":null,"MacAddress":"02:42:ac:11:00:03","NetworkID":"305ba4c311d55570760456274cf1653e22bf5383873498e6a159d76baaba0e4e","EndpointID":"a8f31d5d2ec0100137dced7550b3d662b0804bf8f767a457cbaebda863fb4504","Gateway":"172.17.0.1","IPAddress":"172.17.0.3","IPPrefixLen":16,"IPv6Gateway":"","GlobalIPv6Address":"","GlobalIPv6PrefixLen":0,"DriverOpts":null,"DNSNames":null}}},"Mounts":[{"Type":"volume","Name":"d0d9b8e8edce3dc092b2b50a1f9c6bb754fd556cffe95be8ae00e016daaaa3fd","Source":"","Destination":"/root/.dbgate","Driver":"local","Mode":"","RW":true,"Propagation":""}]}]
@@ -0,0 +1 @@
{"read":"2000-01-01T00:00:15.864368408Z","preread":"2000-01-01T00:00:14.86209639Z","pids_stats":{"current":41},"blkio_stats":{"io_service_bytes_recursive":[{"major":8,"minor":0,"op":"Read","value":118784},{"major":8,"minor":0,"op":"Write","value":267395072},{"major":8,"minor":0,"op":"Sync","value":267395072},{"major":8,"minor":0,"op":"Async","value":118784},{"major":8,"minor":0,"op":"Total","value":267513856},{"major":253,"minor":0,"op":"Read","value":118784},{"major":253,"minor":0,"op":"Write","value":267395072},{"major":253,"minor":0,"op":"Sync","value":267395072},{"major":253,"minor":0,"op":"Async","value":118784},{"major":253,"minor":0,"op":"Total","value":267513856}],"io_serviced_recursive":[{"major":8,"minor":0,"op":"Read","value":8},{"major":8,"minor":0,"op":"Write","value":7892},{"major":8,"minor":0,"op":"Sync","value":7892},{"major":8,"minor":0,"op":"Async","value":8},{"major":8,"minor":0,"op":"Total","value":7900},{"major":253,"minor":0,"op":"Read","value":8},{"major":253,"minor":0,"op":"Write","value":7892},{"major":253,"minor":0,"op":"Sync","value":7892},{"major":253,"minor":0,"op":"Async","value":8},{"major":253,"minor":0,"op":"Total","value":7900}],"io_queue_recursive":[],"io_service_time_recursive":[],"io_wait_time_recursive":[],"io_merged_recursive":[],"io_time_recursive":[],"sectors_recursive":[]},"num_procs":0,"storage_stats":{},"cpu_stats":{"cpu_usage":{"total_usage":4810174080874,"percpu_usage":[733251885463,588352569755,649761861064,692942680516,618939038384,513206148155,431772846470,581947051067],"usage_in_kernelmode":1191390000000,"usage_in_usermode":1463160000000},"system_cpu_usage":21196078922904483,"online_cpus":8,"throttling_data":{"periods":0,"throttled_periods":0,"throttled_time":0}},"precpu_stats":{"cpu_usage":{"total_usage":4810165603627,"percpu_usage":[733250739914,588352569755,649761643572,692942567027,618937059553,513205711609,431769258762,581946053435],"usage_in_kernelmode":1191390000000,"usage_in_usermode":1463160000000},"system_cpu_usage":21196070922904483,"online_cpus":8,"throttling_data":{"periods":0,"throttled_periods":0,"throttled_time":0}},"memory_stats":{"usage":505090048,"max_usage":519450624,"stats":{"active_anon":396529664,"active_file":52400128,"cache":108560384,"dirty":0,"hierarchical_memory_limit":9223372036854771712,"hierarchical_memsw_limit":9223372036854771712,"inactive_anon":0,"inactive_file":56160256,"mapped_file":40960,"pgfault":409148,"pgmajfault":0,"pgpgin":343733,"pgpgout":220420,"rss":396529664,"rss_huge":0,"total_active_anon":396529664,"total_active_file":52400128,"total_cache":108560384,"total_dirty":0,"total_inactive_anon":0,"total_inactive_file":56160256,"total_mapped_file":40960,"total_pgfault":0,"total_pgmajfault":0,"total_pgpgin":0,"total_pgpgout":0,"total_rss":396529664,"total_rss_huge":0,"total_unevictable":0,"total_writeback":0,"unevictable":0,"writeback":0},"limit":33566269440},"name":"/mysql","id":"34174a918eb2e38cdb097c910f74af845e7383b04765d26ad52f940f86342a64","networks":{"eth0":{"rx_bytes":68182,"rx_packets":533,"rx_errors":0,"rx_dropped":0,"tx_bytes":211850,"tx_packets":361,"tx_errors":0,"tx_dropped":0}}}
File diff suppressed because one or more lines are too long