chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
<?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">
|
||||
<parent>
|
||||
<groupId>org.apache.hertzbeat</groupId>
|
||||
<artifactId>hertzbeat-e2e</artifactId>
|
||||
<version>2.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>hertzbeat-log-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>
|
||||
<maven.resources.plugin.version>3.3.1</maven.resources.plugin.version>
|
||||
<maven.dependency.plugin.version>3.6.1</maven.dependency.plugin.version>
|
||||
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!-- HertzBeat modules -->
|
||||
<dependency>
|
||||
<groupId>org.apache.hertzbeat</groupId>
|
||||
<artifactId>hertzbeat-startup</artifactId>
|
||||
<version>${hertzbeat.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.hertzbeat</groupId>
|
||||
<artifactId>hertzbeat-manager</artifactId>
|
||||
<version>${hertzbeat.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.hertzbeat</groupId>
|
||||
<artifactId>hertzbeat-log</artifactId>
|
||||
<version>${hertzbeat.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.hertzbeat</groupId>
|
||||
<artifactId>hertzbeat-alerter</artifactId>
|
||||
<version>${hertzbeat.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.hertzbeat</groupId>
|
||||
<artifactId>hertzbeat-warehouse</artifactId>
|
||||
<version>${hertzbeat.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- TestContainers -->
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>testcontainers-junit-jupiter</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Boot Test -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-test-autoconfigure</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- JUnit 5 -->
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- AssertJ -->
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Awaitility -->
|
||||
<dependency>
|
||||
<groupId>org.awaitility</groupId>
|
||||
<artifactId>awaitility</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
+242
@@ -0,0 +1,242 @@
|
||||
/*
|
||||
* 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.log.alert;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.alert.calculate.periodic.PeriodicAlertRuleScheduler;
|
||||
import org.apache.hertzbeat.alert.reduce.AlarmCommonReduce;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.alerter.AlertDefine;
|
||||
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestInstance;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.web.server.LocalServerPort;
|
||||
import org.springframework.test.context.bean.override.mockito.MockitoSpyBean;
|
||||
import org.springframework.test.context.DynamicPropertyRegistry;
|
||||
import org.springframework.test.context.DynamicPropertySource;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
import org.testcontainers.containers.Network;
|
||||
import org.testcontainers.containers.wait.strategy.Wait;
|
||||
import org.testcontainers.Testcontainers;
|
||||
import org.testcontainers.utility.DockerImageName;
|
||||
import org.testcontainers.utility.MountableFile;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.awaitility.Awaitility.await;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyList;
|
||||
import static org.mockito.ArgumentMatchers.anyMap;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
|
||||
/**
|
||||
* E2E tests for periodic log alert processing.
|
||||
*/
|
||||
@SpringBootTest(classes = org.apache.hertzbeat.startup.HertzBeatApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
@TestPropertySource(properties = {
|
||||
"warehouse.store.duckdb.enabled=false",
|
||||
"warehouse.store.greptime.enabled=true"
|
||||
})
|
||||
@Slf4j
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
public class LogPeriodicAlertE2eTest {
|
||||
|
||||
private static final String VECTOR_IMAGE = "timberio/vector:latest-alpine";
|
||||
private static final int VECTOR_PORT = 8686;
|
||||
private static final String VECTOR_CONFIG_PATH = "/etc/vector/vector.yml";
|
||||
private static final String ENV_HERTZBEAT_PORT = "HERTZBEAT_PORT";
|
||||
private static final String GREPTIME_IMAGE = "greptime/greptimedb:latest";
|
||||
private static final int GREPTIME_HTTP_PORT = 4000;
|
||||
private static final int GREPTIME_GRPC_PORT = 4001;
|
||||
private static final Duration CONTAINER_STARTUP_TIMEOUT = Duration.ofSeconds(120);
|
||||
|
||||
@LocalServerPort
|
||||
private int port;
|
||||
|
||||
@Autowired
|
||||
PeriodicAlertRuleScheduler periodicAlertRuleScheduler;
|
||||
|
||||
AlertDefine errorCountAlertByGroup;
|
||||
AlertDefine errorCountAlertByIndividual;
|
||||
|
||||
@MockitoSpyBean
|
||||
private AlarmCommonReduce alarmCommonReduce;
|
||||
|
||||
static GenericContainer<?> vector;
|
||||
|
||||
static GenericContainer<?> greptimedb;
|
||||
|
||||
static {
|
||||
greptimedb = new GenericContainer<>(DockerImageName.parse(GREPTIME_IMAGE))
|
||||
.withExposedPorts(GREPTIME_HTTP_PORT, GREPTIME_GRPC_PORT)
|
||||
.withCommand("standalone", "start",
|
||||
"--http-addr", "0.0.0.0:" + GREPTIME_HTTP_PORT,
|
||||
"--rpc-bind-addr", "0.0.0.0:" + GREPTIME_GRPC_PORT)
|
||||
.waitingFor(Wait.forListeningPorts(GREPTIME_HTTP_PORT, GREPTIME_GRPC_PORT))
|
||||
.withStartupTimeout(CONTAINER_STARTUP_TIMEOUT);
|
||||
greptimedb.start();
|
||||
}
|
||||
|
||||
@DynamicPropertySource
|
||||
static void greptimeProps(DynamicPropertyRegistry r) {
|
||||
// Configure GreptimeDB storage endpoints (dynamic ports)
|
||||
r.add("warehouse.store.greptime.http-endpoint", () -> "http://localhost:" + greptimedb.getMappedPort(GREPTIME_HTTP_PORT));
|
||||
r.add("warehouse.store.greptime.grpc-endpoints", () -> "localhost:" + greptimedb.getMappedPort(GREPTIME_GRPC_PORT));
|
||||
r.add("warehouse.store.greptime.username", () -> "");
|
||||
r.add("warehouse.store.greptime.password", () -> "");
|
||||
}
|
||||
|
||||
@BeforeAll
|
||||
void setUpAll() throws InterruptedException {
|
||||
// Setup test alert definitions
|
||||
setupTestAlertDefines();
|
||||
Testcontainers.exposeHostPorts(port);
|
||||
|
||||
// Wait for HertzBeat to be fully ready before starting Vector
|
||||
log.info("Waiting for HertzBeat to be fully ready on port {}...", port);
|
||||
Thread.sleep(5000); // Give HertzBeat time to fully initialize
|
||||
|
||||
vector = new GenericContainer<>(DockerImageName.parse(VECTOR_IMAGE))
|
||||
.withExposedPorts(VECTOR_PORT)
|
||||
.withCopyFileToContainer(MountableFile.forClasspathResource("vector.yml"), VECTOR_CONFIG_PATH)
|
||||
.withCommand("--config", "/etc/vector/vector.yml", "--verbose")
|
||||
.withLogConsumer(outputFrame -> log.info("Vector: {}", outputFrame.getUtf8String()))
|
||||
.withNetwork(Network.newNetwork())
|
||||
.withEnv(ENV_HERTZBEAT_PORT, String.valueOf(port))
|
||||
.waitingFor(Wait.forListeningPort())
|
||||
.withStartupTimeout(CONTAINER_STARTUP_TIMEOUT);
|
||||
vector.start();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPeriodicLogAlertWithIndividualAlert() {
|
||||
|
||||
List<SingleAlert> capturedAlerts = new ArrayList<>();
|
||||
|
||||
doAnswer(invocation -> {
|
||||
SingleAlert alert = invocation.getArgument(0);
|
||||
capturedAlerts.add(alert);
|
||||
return null;
|
||||
}).when(alarmCommonReduce).reduceAndSendAlarm(any(SingleAlert.class));
|
||||
|
||||
periodicAlertRuleScheduler.updateSchedule(errorCountAlertByIndividual);
|
||||
|
||||
// Wait for periodic individual alert to be generated through AlarmCommonReduce
|
||||
await().atMost(Duration.ofSeconds(60))
|
||||
.pollInterval(Duration.ofSeconds(3))
|
||||
.untilAsserted(() -> assertFalse(capturedAlerts.isEmpty(),
|
||||
"Should have generated at least one periodic individual alert"));
|
||||
|
||||
// Verify alert properties
|
||||
SingleAlert firstAlert = capturedAlerts.get(0);
|
||||
assertNotNull(firstAlert, "First periodic alert should not be null");
|
||||
assertEquals(CommonConstants.ALERT_STATUS_FIRING, firstAlert.getStatus(), "Alert should be in firing status");
|
||||
assertNotNull(firstAlert.getLabels(), "Alert should have labels");
|
||||
assertTrue(firstAlert.getLabels().containsKey(CommonConstants.LABEL_ALERT_SEVERITY), "Alert should have severity label");
|
||||
assertEquals(CommonConstants.ALERT_SEVERITY_CRITICAL, firstAlert.getLabels().get(CommonConstants.LABEL_ALERT_SEVERITY), "Alert should have critical severity");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPeriodicLogAlertWithGroupAlert() {
|
||||
|
||||
List<List<SingleAlert>> capturedGroupAlerts = new ArrayList<>();
|
||||
|
||||
doAnswer(invocation -> {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<SingleAlert> alerts = invocation.getArgument(1);
|
||||
capturedGroupAlerts.add(alerts);
|
||||
return null;
|
||||
}).when(alarmCommonReduce).reduceAndSendAlarmGroup(anyMap(), anyList());
|
||||
|
||||
periodicAlertRuleScheduler.updateSchedule(errorCountAlertByGroup);
|
||||
|
||||
await().atMost(Duration.ofSeconds(60))
|
||||
.pollInterval(Duration.ofSeconds(3))
|
||||
.untilAsserted(() -> {
|
||||
Optional<SingleAlert> matchedAlert = capturedGroupAlerts.stream()
|
||||
.flatMap(List::stream)
|
||||
.filter(alert -> alert.getLabels() != null)
|
||||
.filter(alert -> String.valueOf(errorCountAlertByGroup.getId())
|
||||
.equals(alert.getLabels().get(CommonConstants.LABEL_DEFINE_ID)))
|
||||
.findFirst();
|
||||
|
||||
assertTrue(matchedAlert.isPresent(), "Should have captured group alert from target alert define");
|
||||
SingleAlert anyAlert = matchedAlert.get();
|
||||
assertEquals(CommonConstants.ALERT_STATUS_FIRING, anyAlert.getStatus(), "Alert should be in firing status");
|
||||
assertEquals(CommonConstants.ALERT_SEVERITY_CRITICAL,
|
||||
anyAlert.getLabels().get(CommonConstants.LABEL_ALERT_SEVERITY),
|
||||
"Alert should have critical severity");
|
||||
assertTrue(anyAlert.getTriggerTimes() >= 1, "Alert should indicate aggregated trigger times");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup test alert definitions for periodic log processing
|
||||
*/
|
||||
private void setupTestAlertDefines() {
|
||||
// group
|
||||
errorCountAlertByGroup = AlertDefine.builder()
|
||||
.id(10L)
|
||||
.name("periodic_error_count_alert_group")
|
||||
.type(CommonConstants.LOG_ALERT_THRESHOLD_TYPE_PERIODIC)
|
||||
.expr("SELECT COUNT(*) as error_count FROM hertzbeat_logs WHERE time_unix_nano > NOW() - INTERVAL '10 minute'")
|
||||
.period(10) // Faster schedule for tests
|
||||
.template("High error count detected: {{ error_count }} errors in last period")
|
||||
.datasource("sql")
|
||||
.enable(true)
|
||||
.build();
|
||||
|
||||
Map<String, String> errorLabelsByGroup = new HashMap<>();
|
||||
errorLabelsByGroup.put(CommonConstants.LABEL_ALERT_SEVERITY, CommonConstants.ALERT_SEVERITY_CRITICAL);
|
||||
errorLabelsByGroup.put(CommonConstants.ALERT_MODE_LABEL, CommonConstants.ALERT_MODE_GROUP);
|
||||
errorLabelsByGroup.put("type", "error_count");
|
||||
errorCountAlertByGroup.setLabels(errorLabelsByGroup);
|
||||
|
||||
// individual
|
||||
errorCountAlertByIndividual = AlertDefine.builder()
|
||||
.id(11L)
|
||||
.name("periodic_error_count_alert_individual")
|
||||
.type(CommonConstants.LOG_ALERT_THRESHOLD_TYPE_PERIODIC)
|
||||
.expr("SELECT COUNT(*) as error_count, severity_text FROM hertzbeat_logs "
|
||||
+ "WHERE severity_text = 'ERROR' AND time_unix_nano > NOW() - INTERVAL '5 minute' GROUP BY severity_text HAVING COUNT(*) > 2")
|
||||
.period(10) // Faster schedule for tests
|
||||
.template("High error count detected: {{ error_count }} errors in last period")
|
||||
.datasource("sql")
|
||||
.enable(true)
|
||||
.build();
|
||||
|
||||
Map<String, String> errorLabelsByIndividual = new HashMap<>();
|
||||
errorLabelsByIndividual.put(CommonConstants.LABEL_ALERT_SEVERITY, CommonConstants.ALERT_SEVERITY_CRITICAL);
|
||||
errorLabelsByIndividual.put(CommonConstants.ALERT_MODE_LABEL, CommonConstants.ALERT_MODE_INDIVIDUAL);
|
||||
errorLabelsByIndividual.put("type", "error_count");
|
||||
errorCountAlertByIndividual.setLabels(errorLabelsByIndividual);
|
||||
}
|
||||
}
|
||||
+207
@@ -0,0 +1,207 @@
|
||||
/*
|
||||
* 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.log.alert;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.alert.reduce.AlarmCommonReduce;
|
||||
import org.apache.hertzbeat.common.cache.CacheFactory;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.alerter.AlertDefine;
|
||||
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestInstance;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.web.server.LocalServerPort;
|
||||
import org.springframework.test.context.bean.override.mockito.MockitoSpyBean;
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
import org.testcontainers.containers.Network;
|
||||
import org.testcontainers.containers.wait.strategy.Wait;
|
||||
import org.testcontainers.Testcontainers;
|
||||
import org.testcontainers.utility.DockerImageName;
|
||||
import org.testcontainers.utility.MountableFile;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.awaitility.Awaitility.await;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyList;
|
||||
import static org.mockito.ArgumentMatchers.anyMap;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
|
||||
/**
|
||||
* E2E tests for real-time log alert processing.
|
||||
*/
|
||||
@SpringBootTest(classes = org.apache.hertzbeat.startup.HertzBeatApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
@Slf4j
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
public class LogRealTimeAlertE2eTest {
|
||||
|
||||
private static final String VECTOR_IMAGE = "timberio/vector:latest-alpine";
|
||||
private static final int VECTOR_PORT = 8686;
|
||||
private static final String VECTOR_CONFIG_PATH = "/etc/vector/vector.yml";
|
||||
private static final String ENV_HERTZBEAT_PORT = "HERTZBEAT_PORT";
|
||||
private static final Duration CONTAINER_STARTUP_TIMEOUT = Duration.ofSeconds(180);
|
||||
private static final Duration TEST_WAIT_TIMEOUT = Duration.ofSeconds(120);
|
||||
|
||||
@LocalServerPort
|
||||
private int port;
|
||||
|
||||
private final List<SingleAlert> capturedAlerts = new ArrayList<>();
|
||||
private final ArrayList<List<SingleAlert>> capturedGroupAlerts = new ArrayList<>();
|
||||
|
||||
@MockitoSpyBean
|
||||
private AlarmCommonReduce alarmCommonReduce;
|
||||
|
||||
static GenericContainer<?> vector;
|
||||
|
||||
@BeforeAll
|
||||
void setUpAll() throws InterruptedException {
|
||||
// Setup test alert definitions
|
||||
setupTestAlertDefines();
|
||||
|
||||
// Expose host ports for testcontainers
|
||||
Testcontainers.exposeHostPorts(port);
|
||||
|
||||
// Wait for HertzBeat to be fully ready before starting Vector
|
||||
log.info("Waiting for HertzBeat to be fully ready on port {}...", port);
|
||||
Thread.sleep(5000); // Give HertzBeat time to fully initialize
|
||||
|
||||
vector = new GenericContainer<>(DockerImageName.parse(VECTOR_IMAGE))
|
||||
.withExposedPorts(VECTOR_PORT)
|
||||
.withCopyFileToContainer(MountableFile.forClasspathResource("vector.yml"), VECTOR_CONFIG_PATH)
|
||||
.withCommand("--config", "/etc/vector/vector.yml", "--verbose")
|
||||
.withLogConsumer(outputFrame -> log.info("Vector: {}", outputFrame.getUtf8String()))
|
||||
.withNetwork(Network.newNetwork())
|
||||
.withEnv(ENV_HERTZBEAT_PORT, String.valueOf(port))
|
||||
.waitingFor(Wait.forListeningPort())
|
||||
.withStartupTimeout(CONTAINER_STARTUP_TIMEOUT);
|
||||
vector.start();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRealTimeLogAlertWithIndividualAlert() {
|
||||
doAnswer(invocation -> {
|
||||
SingleAlert alert = invocation.getArgument(0);
|
||||
capturedAlerts.add(alert);
|
||||
return null;
|
||||
}).when(alarmCommonReduce).reduceAndSendAlarm(any(SingleAlert.class));
|
||||
// Clear any existing captured alerts
|
||||
capturedAlerts.clear();
|
||||
|
||||
// Wait for real alert to be generated through AlarmCommonReduce
|
||||
await().atMost(TEST_WAIT_TIMEOUT)
|
||||
.pollInterval(Duration.ofSeconds(3))
|
||||
.untilAsserted(() -> assertFalse(capturedAlerts.isEmpty(),
|
||||
"Should have generated at least one alert for error logs"));
|
||||
|
||||
// Verify alert properties
|
||||
SingleAlert firstAlert = capturedAlerts.get(0);
|
||||
assertNotNull(firstAlert, "First alert should not be null");
|
||||
assertEquals(CommonConstants.ALERT_STATUS_FIRING, firstAlert.getStatus(), "Alert should be in firing status");
|
||||
assertNotNull(firstAlert.getLabels(), "Alert should have labels");
|
||||
assertTrue(firstAlert.getLabels().containsKey(CommonConstants.LABEL_ALERT_SEVERITY), "Alert should have severity label");
|
||||
assertEquals(CommonConstants.ALERT_SEVERITY_CRITICAL, firstAlert.getLabels().get(CommonConstants.LABEL_ALERT_SEVERITY), "Alert should have critical severity");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRealTimeLogAlertWithGroupAlert() {
|
||||
doAnswer(invocation -> {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<SingleAlert> alerts = invocation.getArgument(1);
|
||||
capturedGroupAlerts.add(alerts);
|
||||
return null;
|
||||
}).when(alarmCommonReduce).reduceAndSendAlarmGroup(anyMap(), anyList());
|
||||
|
||||
// Clear any existing captured alerts
|
||||
capturedGroupAlerts.clear();
|
||||
|
||||
// Wait for group alert to be generated via AlarmCommonReduce
|
||||
await().atMost(TEST_WAIT_TIMEOUT)
|
||||
.pollInterval(Duration.ofSeconds(3))
|
||||
.untilAsserted(() -> assertFalse(capturedGroupAlerts.isEmpty(),
|
||||
"Should have generated high frequency warning group alert"));
|
||||
|
||||
List<SingleAlert> groupAlerts = capturedGroupAlerts.get(0);
|
||||
assertNotNull(groupAlerts, "Group alerts should not be null");
|
||||
assertTrue(groupAlerts.size() >= 2, "Group should contain at least 5 alerts");
|
||||
|
||||
SingleAlert anyAlert = groupAlerts.get(0);
|
||||
assertEquals(CommonConstants.ALERT_STATUS_FIRING, anyAlert.getStatus(), "Alert should be in firing status");
|
||||
assertNotNull(anyAlert.getLabels(), "Alert should have labels");
|
||||
assertEquals(CommonConstants.ALERT_SEVERITY_WARNING, anyAlert.getLabels().get(CommonConstants.LABEL_ALERT_SEVERITY), "Alert should have warning severity");
|
||||
assertTrue(anyAlert.getTriggerTimes() >= 2, "Alert should indicate high frequency trigger");
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup test alert definitions for real-time log processing
|
||||
*/
|
||||
private void setupTestAlertDefines() {
|
||||
List<AlertDefine> alertDefines = new ArrayList<>();
|
||||
|
||||
// Create error log alert definition
|
||||
AlertDefine errorLogAlert = AlertDefine.builder()
|
||||
.id(1L)
|
||||
.name("error_log_alert")
|
||||
.type(CommonConstants.LOG_ALERT_THRESHOLD_TYPE_REALTIME)
|
||||
.expr("log.severityText == 'ERROR'")
|
||||
.period(10) // 10 seconds window
|
||||
.times(1) // Trigger on first occurrence
|
||||
.template("Error detected: {{ body }}")
|
||||
.enable(true)
|
||||
.build();
|
||||
|
||||
Map<String, String> errorLabels = new HashMap<>();
|
||||
errorLabels.put(CommonConstants.LABEL_ALERT_SEVERITY, CommonConstants.ALERT_SEVERITY_CRITICAL);
|
||||
errorLabels.put(CommonConstants.ALERT_MODE_LABEL, CommonConstants.ALERT_MODE_INDIVIDUAL);
|
||||
errorLogAlert.setLabels(errorLabels);
|
||||
|
||||
alertDefines.add(errorLogAlert);
|
||||
|
||||
// Create high frequency warning alert definition
|
||||
AlertDefine highFrequencyWarning = AlertDefine.builder()
|
||||
.id(2L)
|
||||
.name("high_frequency_warning")
|
||||
.type(CommonConstants.LOG_ALERT_THRESHOLD_TYPE_REALTIME)
|
||||
.expr("log.severityText == 'ERROR'")
|
||||
.period(10)
|
||||
.times(2)
|
||||
.template("High frequency warnings detected: {{ __rows__ }} warnings in 30 seconds")
|
||||
.enable(true)
|
||||
.build();
|
||||
|
||||
Map<String, String> warningLabels = new HashMap<>();
|
||||
warningLabels.put(CommonConstants.LABEL_ALERT_SEVERITY, CommonConstants.ALERT_SEVERITY_WARNING);
|
||||
warningLabels.put(CommonConstants.ALERT_MODE_LABEL, CommonConstants.ALERT_MODE_GROUP);
|
||||
highFrequencyWarning.setLabels(warningLabels);
|
||||
|
||||
alertDefines.add(highFrequencyWarning);
|
||||
|
||||
// Set alert definitions in cache
|
||||
CacheFactory.setLogAlertDefineCache(alertDefines);
|
||||
}
|
||||
|
||||
}
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* 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.log.ingestion;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.common.entity.log.LogEntry;
|
||||
import org.apache.hertzbeat.common.queue.CommonDataQueue;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestInstance;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.web.server.LocalServerPort;
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
import org.testcontainers.containers.Network;
|
||||
import org.testcontainers.containers.wait.strategy.Wait;
|
||||
import org.testcontainers.Testcontainers;
|
||||
import org.testcontainers.utility.DockerImageName;
|
||||
import org.testcontainers.utility.MountableFile;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.awaitility.Awaitility.await;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
/**
|
||||
* E2E tests for log ingestion.
|
||||
*/
|
||||
@SpringBootTest(classes = org.apache.hertzbeat.startup.HertzBeatApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
@Slf4j
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
public class LogIngestionE2eTest {
|
||||
|
||||
private static final String VECTOR_IMAGE = "timberio/vector:latest-alpine";
|
||||
private static final int VECTOR_PORT = 8686;
|
||||
private static final String VECTOR_CONFIG_PATH = "/etc/vector/vector.yml";
|
||||
private static final String ENV_HERTZBEAT_PORT = "HERTZBEAT_PORT";
|
||||
private static final Duration CONTAINER_STARTUP_TIMEOUT = Duration.ofSeconds(120);
|
||||
|
||||
@LocalServerPort
|
||||
private int port;
|
||||
|
||||
@Autowired
|
||||
private CommonDataQueue commonDataQueue;
|
||||
|
||||
static GenericContainer<?> vector;
|
||||
|
||||
@BeforeAll
|
||||
void setUpAll() throws InterruptedException {
|
||||
Testcontainers.exposeHostPorts(port);
|
||||
|
||||
// Wait for HertzBeat to be fully ready before starting Vector
|
||||
log.info("Waiting for HertzBeat to be fully ready on port {}...", port);
|
||||
Thread.sleep(5000); // Give HertzBeat time to fully initialize
|
||||
|
||||
vector = new GenericContainer<>(DockerImageName.parse(VECTOR_IMAGE))
|
||||
.withExposedPorts(VECTOR_PORT)
|
||||
.withCopyFileToContainer(MountableFile.forClasspathResource("vector.yml"), VECTOR_CONFIG_PATH)
|
||||
.withCommand("--config", "/etc/vector/vector.yml", "--verbose")
|
||||
.withLogConsumer(outputFrame -> log.info("Vector: {}", outputFrame.getUtf8String()))
|
||||
.withNetwork(Network.newNetwork())
|
||||
.withEnv(ENV_HERTZBEAT_PORT, String.valueOf(port))
|
||||
.waitingFor(Wait.forListeningPort())
|
||||
.withStartupTimeout(CONTAINER_STARTUP_TIMEOUT);
|
||||
vector.start();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLogIngestion() {
|
||||
|
||||
// Start polling for log entries from the queue
|
||||
List<LogEntry> capturedLogs = new ArrayList<>();
|
||||
|
||||
// Wait for Vector to generate and send demo logs to HertzBeat
|
||||
await().atMost(Duration.ofSeconds(60))
|
||||
.pollInterval(Duration.ofSeconds(3))
|
||||
.untilAsserted(() -> {
|
||||
try {
|
||||
LogEntry logEntry = commonDataQueue.pollLogEntry();
|
||||
if (logEntry != null) {
|
||||
capturedLogs.add(logEntry);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new RuntimeException("Test interrupted", e);
|
||||
}
|
||||
});
|
||||
|
||||
// Verify the captured logs
|
||||
assertFalse(capturedLogs.isEmpty(), "No logs were captured from Vector");
|
||||
LogEntry firstLog = capturedLogs.get(0);
|
||||
assertNotNull(firstLog, "First log should not be null");
|
||||
assertNotNull(firstLog.getBody(), "Log body should not be null");
|
||||
assertNotNull(firstLog.getSeverityText(), "Severity text should not be null");
|
||||
}
|
||||
}
|
||||
+187
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
* 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.log.storage;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.common.entity.log.LogEntry;
|
||||
import org.apache.hertzbeat.common.queue.CommonDataQueue;
|
||||
import org.apache.hertzbeat.warehouse.store.history.tsdb.greptime.GreptimeDbDataStorage;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestInstance;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.web.server.LocalServerPort;
|
||||
import org.springframework.test.context.DynamicPropertyRegistry;
|
||||
import org.springframework.test.context.DynamicPropertySource;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
import org.testcontainers.containers.Network;
|
||||
import org.testcontainers.containers.wait.strategy.Wait;
|
||||
import org.testcontainers.Testcontainers;
|
||||
import org.testcontainers.utility.DockerImageName;
|
||||
import org.testcontainers.utility.MountableFile;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import static org.awaitility.Awaitility.await;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
/**
|
||||
* E2E tests for GreptimeDB log storage.
|
||||
*/
|
||||
@SpringBootTest(classes = org.apache.hertzbeat.startup.HertzBeatApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
@TestPropertySource(properties = {
|
||||
"warehouse.store.duckdb.enabled=false",
|
||||
"warehouse.store.greptime.enabled=true"
|
||||
})
|
||||
@Slf4j
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
public class GreptimeLogStorageE2eTest {
|
||||
|
||||
private static final String VECTOR_IMAGE = "timberio/vector:latest-alpine";
|
||||
private static final int VECTOR_PORT = 8686;
|
||||
private static final String VECTOR_CONFIG_PATH = "/etc/vector/vector.yml";
|
||||
private static final String ENV_HERTZBEAT_PORT = "HERTZBEAT_PORT";
|
||||
private static final String GREPTIME_IMAGE = "greptime/greptimedb:latest";
|
||||
private static final int GREPTIME_HTTP_PORT = 4000;
|
||||
private static final int GREPTIME_GRPC_PORT = 4001;
|
||||
private static final Duration CONTAINER_STARTUP_TIMEOUT = Duration.ofSeconds(120);
|
||||
|
||||
@LocalServerPort
|
||||
private int port;
|
||||
|
||||
@Autowired
|
||||
private CommonDataQueue commonDataQueue;
|
||||
|
||||
@Autowired
|
||||
private GreptimeDbDataStorage greptimeDbDataStorage;
|
||||
|
||||
static GenericContainer<?> vector;
|
||||
static GenericContainer<?> greptimedb;
|
||||
|
||||
static {
|
||||
greptimedb = new GenericContainer<>(DockerImageName.parse(GREPTIME_IMAGE))
|
||||
.withExposedPorts(GREPTIME_HTTP_PORT, GREPTIME_GRPC_PORT)
|
||||
.withCommand("standalone", "start",
|
||||
"--http-addr", "0.0.0.0:" + GREPTIME_HTTP_PORT,
|
||||
"--rpc-bind-addr", "0.0.0.0:" + GREPTIME_GRPC_PORT)
|
||||
.waitingFor(Wait.forListeningPorts(GREPTIME_HTTP_PORT, GREPTIME_GRPC_PORT))
|
||||
.withStartupTimeout(CONTAINER_STARTUP_TIMEOUT);
|
||||
greptimedb.start();
|
||||
}
|
||||
|
||||
@DynamicPropertySource
|
||||
static void greptimeProps(DynamicPropertyRegistry r) {
|
||||
r.add("warehouse.store.greptime.http-endpoint", () -> "http://localhost:" + greptimedb.getMappedPort(GREPTIME_HTTP_PORT));
|
||||
r.add("warehouse.store.greptime.grpc-endpoints", () -> "localhost:" + greptimedb.getMappedPort(GREPTIME_GRPC_PORT));
|
||||
r.add("warehouse.store.greptime.username", () -> "");
|
||||
r.add("warehouse.store.greptime.password", () -> "");
|
||||
}
|
||||
|
||||
|
||||
@BeforeAll
|
||||
void setUpAll() throws InterruptedException {
|
||||
// Expose host ports for testcontainers
|
||||
Testcontainers.exposeHostPorts(port);
|
||||
|
||||
// Wait for HertzBeat to be fully ready before starting Vector
|
||||
log.info("Waiting for HertzBeat to be fully ready on port {}...", port);
|
||||
Thread.sleep(5000); // Give HertzBeat time to fully initialize
|
||||
|
||||
vector = new GenericContainer<>(DockerImageName.parse(VECTOR_IMAGE))
|
||||
.withExposedPorts(VECTOR_PORT)
|
||||
.withCopyFileToContainer(MountableFile.forClasspathResource("vector.yml"), VECTOR_CONFIG_PATH)
|
||||
.withCommand("--config", "/etc/vector/vector.yml", "--verbose")
|
||||
.withLogConsumer(outputFrame -> log.info("Vector: {}", outputFrame.getUtf8String()))
|
||||
.withNetwork(Network.newNetwork())
|
||||
.withEnv(ENV_HERTZBEAT_PORT, String.valueOf(port))
|
||||
.waitingFor(Wait.forListeningPort())
|
||||
.withStartupTimeout(CONTAINER_STARTUP_TIMEOUT);
|
||||
vector.start();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLogStorageToGreptimeDb() {
|
||||
log.info("GreptimeDbDataStorage serverAvailable: {}", greptimeDbDataStorage.isServerAvailable());
|
||||
|
||||
List<LogEntry> capturedLogs = new ArrayList<>();
|
||||
|
||||
// Wait for Vector to generate and send logs to HertzBeat
|
||||
await().atMost(Duration.ofSeconds(30))
|
||||
.pollInterval(Duration.ofSeconds(3))
|
||||
.untilAsserted(() -> {
|
||||
// Poll log entries from the queue (non-blocking)
|
||||
try {
|
||||
LogEntry logEntry = commonDataQueue.pollLogEntry();
|
||||
if (logEntry != null) {
|
||||
capturedLogs.add(logEntry);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new RuntimeException("Test interrupted", e);
|
||||
}
|
||||
|
||||
// Assert that we have captured at least some logs
|
||||
assertFalse(capturedLogs.isEmpty(), "Should have captured at least one log entry");
|
||||
});
|
||||
|
||||
// Verify the captured logs
|
||||
assertFalse(capturedLogs.isEmpty(), "No logs were captured from Vector");
|
||||
LogEntry firstLog = capturedLogs.get(0);
|
||||
assertNotNull(firstLog, "First log should not be null");
|
||||
assertNotNull(firstLog.getBody(), "Log body should not be null");
|
||||
assertNotNull(firstLog.getSeverityText(), "Severity text should not be null");
|
||||
|
||||
// Directly write logs to GreptimeDB to test storage functionality
|
||||
log.info("Directly writing {} captured logs to GreptimeDB", capturedLogs.size());
|
||||
greptimeDbDataStorage.saveLogDataBatch(capturedLogs);
|
||||
|
||||
// Give some time for the write to complete
|
||||
try {
|
||||
Thread.sleep(2000);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
|
||||
// Additional wait to ensure logs are persisted to GreptimeDB
|
||||
await().atMost(Duration.ofSeconds(30))
|
||||
.pollInterval(Duration.ofSeconds(2))
|
||||
.untilAsserted(() -> {
|
||||
// Query GreptimeDB directly to verify data persistence
|
||||
List<LogEntry> storedLogs = queryStoredLogs();
|
||||
log.info("Queried {} logs from GreptimeDB", storedLogs.size());
|
||||
assertFalse(storedLogs.isEmpty(), "Should have logs stored in GreptimeDB");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to query stored logs directly from GreptimeDB
|
||||
*/
|
||||
private List<LogEntry> queryStoredLogs() {
|
||||
// Query without time condition to verify data exists
|
||||
List<LogEntry> result = greptimeDbDataStorage.queryLogsByMultipleConditions(
|
||||
null, null, null, null, null, null, null);
|
||||
log.info("queryLogsByMultipleConditions returned {} entries", result.size());
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
# 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.
|
||||
|
||||
## -- sureness.yml account source -- ##
|
||||
|
||||
# config the resource restful api that need auth protection, base rbac
|
||||
# rule: api===method===role
|
||||
# eg: /api/v1/source1===get===[admin] means /api/v2/host===post support role[admin] access.
|
||||
# eg: /api/v1/source2===get===[] means /api/v1/source2===get can not access by any role.
|
||||
resourceRole:
|
||||
- /api/account/auth/refresh===post===[admin,user,guest]
|
||||
- /api/apps/**===get===[admin,user,guest]
|
||||
- /api/monitor/**===get===[admin,user,guest]
|
||||
- /api/monitor/**===post===[admin,user]
|
||||
- /api/monitor/**===put===[admin,user]
|
||||
- /api/monitor/**===delete==[admin]
|
||||
- /api/monitors/**===get===[admin,user,guest]
|
||||
- /api/monitors/**===post===[admin,user]
|
||||
- /api/monitors/**===put===[admin,user]
|
||||
- /api/monitors/**===delete===[admin]
|
||||
- /api/alert/**===get===[admin,user,guest]
|
||||
- /api/alert/**===post===[admin,user]
|
||||
- /api/alert/**===put===[admin,user]
|
||||
- /api/alert/**===delete===[admin]
|
||||
- /api/alerts/**===get===[admin,user,guest]
|
||||
- /api/alerts/**===post===[admin,user]
|
||||
- /api/alerts/**===put===[admin,user]
|
||||
- /api/alerts/**===delete===[admin]
|
||||
- /api/notice/**===get===[admin,user,guest]
|
||||
- /api/notice/**===post===[admin,user]
|
||||
- /api/notice/**===put===[admin,user]
|
||||
- /api/notice/**===delete===[admin]
|
||||
- /api/tag/**===get===[admin,user,guest]
|
||||
- /api/tag/**===post===[admin,user]
|
||||
- /api/tag/**===put===[admin,user]
|
||||
- /api/tag/**===delete===[admin]
|
||||
- /api/summary/**===get===[admin,user,guest]
|
||||
- /api/summary/**===post===[admin,user]
|
||||
- /api/summary/**===put===[admin,user]
|
||||
- /api/summary/**===delete===[admin]
|
||||
- /api/collector/**===get===[admin,user,guest]
|
||||
- /api/collector/**===post===[admin,user]
|
||||
- /api/collector/**===put===[admin,user]
|
||||
- /api/collector/**===delete===[admin]
|
||||
- /api/status/page/**===get===[admin,user,guest]
|
||||
- /api/status/page/**===post===[admin,user]
|
||||
- /api/status/page/**===put===[admin,user]
|
||||
- /api/status/page/**===delete===[admin]
|
||||
- /api/grafana/**===get===[admin,user,guest]
|
||||
- /api/grafana/**===post===[admin,user]
|
||||
- /api/grafana/**===put===[admin,user]
|
||||
- /api/grafana/**===delete===[admin]
|
||||
- /api/bulletin/**===get===[admin,user,guest]
|
||||
- /api/bulletin/**===post===[admin,user]
|
||||
- /api/bulletin/**===put===[admin,user]
|
||||
- /api/bulletin/**===delete===[admin]
|
||||
- /api/sse/**===get===[admin,user]
|
||||
- /api/sse/**===post===[admin,user]
|
||||
- /api/chat/**===get===[admin,user]
|
||||
- /api/chat/**===post===[admin,user]
|
||||
- /api/logs/ingest/**===post===[admin,user]
|
||||
|
||||
# config the resource restful api that need bypass auth protection
|
||||
# rule: api===method
|
||||
# eg: /api/v1/source3===get means /api/v1/source3===get can be access by anyone, no need auth.
|
||||
excludedResource:
|
||||
- /api/alert/sse/**===*
|
||||
- /api/logs/sse/**===*
|
||||
- /api/account/auth/**===*
|
||||
- /api/i18n/**===get
|
||||
- /api/apps/hierarchy===get
|
||||
- /api/push/**===*
|
||||
- /api/status/page/public/**===*
|
||||
- /api/manager/sse/**===*
|
||||
# web ui resource
|
||||
- /===get
|
||||
- /assets/**===get
|
||||
- /dashboard/**===get
|
||||
- /monitors/**===get
|
||||
- /alert/**===get
|
||||
- /account/**===get
|
||||
- /setting/**===get
|
||||
- /passport/**===get
|
||||
- /status/**===get
|
||||
- /**/*.html===get
|
||||
- /**/*.js===get
|
||||
- /**/*.css===get
|
||||
- /**/*.ico===get
|
||||
- /**/*.ttf===get
|
||||
- /**/*.png===get
|
||||
- /**/*.gif===get
|
||||
- /**/*.jpg===get
|
||||
- /**/*.svg===get
|
||||
- /**/*.json===get
|
||||
- /**/*.woff===get
|
||||
- /**/*.eot===get
|
||||
# swagger ui resource
|
||||
- /swagger-resources/**===get
|
||||
- /v2/api-docs===get
|
||||
- /v3/api-docs===get
|
||||
# h2 database
|
||||
- /h2-console/**===*
|
||||
|
||||
# account info config
|
||||
# eg: admin has role [admin,user], password is hertzbeat
|
||||
# eg: tom has role [user], password is hertzbeat
|
||||
# eg: lili has role [guest], plain password is lili, salt is 123, salted password is 1A676730B0C7F54654B0E09184448289
|
||||
account:
|
||||
- appId: admin
|
||||
credential: hertzbeat
|
||||
role: [admin]
|
||||
- appId: tom
|
||||
credential: hertzbeat
|
||||
role: [user]
|
||||
- appId: guest
|
||||
credential: hertzbeat
|
||||
role: [guest]
|
||||
- appId: lili
|
||||
# credential = MD5(password + salt)
|
||||
# plain password: hertzbeat
|
||||
# attention: digest authentication does not support salted encrypted password accounts
|
||||
credential: 94C6B34E7A199A9F9D4E1F208093B489
|
||||
salt: 123
|
||||
role: [user]
|
||||
@@ -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.
|
||||
|
||||
# Vector configuration for HertzBeat E2E testing
|
||||
|
||||
api:
|
||||
enabled: true
|
||||
address: "0.0.0.0:8686"
|
||||
|
||||
sources:
|
||||
generate_syslog:
|
||||
type: "demo_logs"
|
||||
format: "syslog"
|
||||
count: 100000
|
||||
interval: 1
|
||||
|
||||
transforms:
|
||||
remap_syslog:
|
||||
inputs: ["generate_syslog"]
|
||||
type: "remap"
|
||||
source: |
|
||||
syslog = parse_syslog!(.message)
|
||||
|
||||
severity_text = if includes(["emerg", "err", "crit", "alert"], syslog.severity) {
|
||||
"ERROR"
|
||||
} else if syslog.severity == "warning" {
|
||||
"WARN"
|
||||
} else if syslog.severity == "debug" {
|
||||
"DEBUG"
|
||||
} else if includes(["info", "notice"], syslog.severity) {
|
||||
"INFO"
|
||||
} else {
|
||||
syslog.severity
|
||||
}
|
||||
|
||||
severity_number = if includes(["emerg", "err", "crit", "alert"], syslog.severity) {
|
||||
17
|
||||
} else if syslog.severity == "warning" {
|
||||
13
|
||||
} else if syslog.severity == "debug" {
|
||||
5
|
||||
} else if includes(["info", "notice"], syslog.severity) {
|
||||
9
|
||||
} else {
|
||||
9
|
||||
}
|
||||
|
||||
.resourceLogs = [{
|
||||
"resource": {
|
||||
"attributes": [
|
||||
{ "key": "source_type", "value": { "stringValue": .source_type } },
|
||||
{ "key": "service.name", "value": { "stringValue": syslog.appname } },
|
||||
{ "key": "host.hostname", "value": { "stringValue": syslog.hostname } }
|
||||
]
|
||||
},
|
||||
"scopeLogs": [{
|
||||
"scope": {
|
||||
"name": syslog.msgid
|
||||
},
|
||||
"logRecords": [{
|
||||
"timeUnixNano": to_unix_timestamp!(syslog.timestamp, unit: "nanoseconds"),
|
||||
"body": { "stringValue": syslog.message },
|
||||
"severityText": severity_text,
|
||||
"severityNumber": severity_number,
|
||||
"attributes": [
|
||||
{ "key": "syslog.procid", "value": { "stringValue": to_string(syslog.procid) } },
|
||||
{ "key": "syslog.facility", "value": { "stringValue": syslog.facility } },
|
||||
{ "key": "syslog.version", "value": { "stringValue": to_string(syslog.version) } }
|
||||
]
|
||||
}]
|
||||
}]
|
||||
}]
|
||||
|
||||
del(.message)
|
||||
del(.timestamp)
|
||||
del(.service)
|
||||
del(.source_type)
|
||||
|
||||
sinks:
|
||||
# Debug sink to log all processed data
|
||||
debug_console:
|
||||
inputs: [ "remap_syslog" ]
|
||||
type: console
|
||||
encoding:
|
||||
codec: json
|
||||
|
||||
# Send to HertzBeat with increased timeout and retry
|
||||
emit_syslog:
|
||||
inputs: [ "remap_syslog" ]
|
||||
type: opentelemetry
|
||||
protocol:
|
||||
type: http
|
||||
uri: "http://host.testcontainers.internal:${HERTZBEAT_PORT:-1157}/api/logs/ingest/otlp"
|
||||
method: post
|
||||
encoding:
|
||||
codec: json
|
||||
framing:
|
||||
method: newline_delimited
|
||||
auth:
|
||||
strategy: basic
|
||||
user: admin
|
||||
password: hertzbeat
|
||||
# Increase timeout and retry settings for stability in CI environments
|
||||
request:
|
||||
headers:
|
||||
content-type: application/json
|
||||
timeout_secs: 60
|
||||
retry_attempts: 10
|
||||
retry_initial_backoff_secs: 2
|
||||
retry_max_duration_secs: 120
|
||||
# Batch settings for better throughput
|
||||
batch:
|
||||
max_bytes: 524288
|
||||
timeout_secs: 1
|
||||
Reference in New Issue
Block a user