This is an automated email from the ASF dual-hosted git repository.

zqr10159 pushed a commit to branch 2.0.0
in repository https://gitbox.apache.org/repos/asf/hertzbeat.git

commit d6a801e36fefb3d0ba3e90289d90bfedc7f0cb69
Author: Logic <[email protected]>
AuthorDate: Wed Aug 26 20:45:09 2026 +0800

    test: launch observability e2e through startup boundary
---
 .../alert/LogPeriodicAlertE2eTest.java             |  65 +++--------
 .../alert/LogRealTimeAlertE2eTest.java             |  27 ++---
 .../observability/fixture/GreptimeE2eSupport.java  |  47 ++++++++
 .../observability/fixture/VectorE2eContainer.java  |  55 +++++++++
 .../ingestion/LogIngestionE2eTest.java             |  27 ++---
 ...ticatedGreptimeThreeSignalPublicApiE2eTest.java |  37 +------
 .../storage/GreptimeLogStorageE2eTest.java         | 123 +++++----------------
 .../storage/GreptimeThreeSignalE2eSupport.java     |  79 ++++++++-----
 .../GreptimeThreeSignalInstrumentationE2eTest.java |  82 +++++---------
 .../PrometheusActiveSourcePublicApiE2eTest.java    |  30 +----
 .../apache/hertzbeat/startup/TrustedStartup.java   |  26 +++++
 .../hertzbeat/startup/TrustedStartupSupport.java   |  31 ++++++
 .../runtime/TrustedSpringBootContextLoader.java    |  41 +++++++
 .../src/test/resources/application-e2e.yml         |  25 +++++
 .../src/test/resources/vector.yml                  |  28 ++---
 15 files changed, 382 insertions(+), 341 deletions(-)

diff --git 
a/hertzbeat-e2e/hertzbeat-observability-e2e/src/test/java/org/apache/hertzbeat/observability/alert/LogPeriodicAlertE2eTest.java
 
b/hertzbeat-e2e/hertzbeat-observability-e2e/src/test/java/org/apache/hertzbeat/observability/alert/LogPeriodicAlertE2eTest.java
index 9a330a13c3..e175152fe6 100644
--- 
a/hertzbeat-e2e/hertzbeat-observability-e2e/src/test/java/org/apache/hertzbeat/observability/alert/LogPeriodicAlertE2eTest.java
+++ 
b/hertzbeat-e2e/hertzbeat-observability-e2e/src/test/java/org/apache/hertzbeat/observability/alert/LogPeriodicAlertE2eTest.java
@@ -23,6 +23,9 @@ 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.apache.hertzbeat.observability.fixture.GreptimeE2eSupport;
+import org.apache.hertzbeat.observability.fixture.VectorE2eContainer;
+import org.apache.hertzbeat.startup.TrustedStartup;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.TestInstance;
@@ -30,15 +33,9 @@ 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;
@@ -61,21 +58,14 @@ 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)
+@TrustedStartup
 @TestPropertySource(properties = {
-        "warehouse.store.duckdb.enabled=false",
-        "warehouse.store.greptime.enabled=true"
+        "warehouse.store.duckdb.enabled=false"
 })
 @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;
+public class LogPeriodicAlertE2eTest extends GreptimeE2eSupport {
+
     private static final Duration CONTAINER_STARTUP_TIMEOUT = 
Duration.ofSeconds(120);
 
     @LocalServerPort
@@ -92,30 +82,9 @@ public class LogPeriodicAlertE2eTest {
 
     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 {
+        initializeAdministrator();
         // Setup test alert definitions
         setupTestAlertDefines();
         Testcontainers.exposeHostPorts(port);
@@ -124,15 +93,9 @@ public class LogPeriodicAlertE2eTest {
         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 = VectorE2eContainer.create(
+                port, CONTAINER_STARTUP_TIMEOUT,
+                outputFrame -> log.info("Vector: {}", 
outputFrame.getUtf8String()));
         vector.start();
     }
 
@@ -207,7 +170,8 @@ public class LogPeriodicAlertE2eTest {
                 .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'")
+                .expr("SELECT COUNT(*) as error_count FROM hertzbeat_logs "
+                        + "WHERE timestamp > NOW() - INTERVAL '10 minutes'")
                 .period(10) // Faster schedule for tests
                 .template("High error count detected: {{ error_count }} errors 
in last period")
                 .datasource("sql")
@@ -226,7 +190,8 @@ public class LogPeriodicAlertE2eTest {
                 .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")
+                        + "WHERE severity_text = 'ERROR' AND timestamp > NOW() 
- INTERVAL '5 minutes' "
+                        + "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")
diff --git 
a/hertzbeat-e2e/hertzbeat-observability-e2e/src/test/java/org/apache/hertzbeat/observability/alert/LogRealTimeAlertE2eTest.java
 
b/hertzbeat-e2e/hertzbeat-observability-e2e/src/test/java/org/apache/hertzbeat/observability/alert/LogRealTimeAlertE2eTest.java
index 2af0e6e870..6f79c75184 100644
--- 
a/hertzbeat-e2e/hertzbeat-observability-e2e/src/test/java/org/apache/hertzbeat/observability/alert/LogRealTimeAlertE2eTest.java
+++ 
b/hertzbeat-e2e/hertzbeat-observability-e2e/src/test/java/org/apache/hertzbeat/observability/alert/LogRealTimeAlertE2eTest.java
@@ -23,6 +23,9 @@ 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.apache.hertzbeat.observability.fixture.GreptimeE2eSupport;
+import org.apache.hertzbeat.observability.fixture.VectorE2eContainer;
+import org.apache.hertzbeat.startup.TrustedStartup;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.TestInstance;
@@ -30,11 +33,7 @@ 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;
@@ -56,14 +55,11 @@ 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)
+@TrustedStartup
 @Slf4j
 @TestInstance(TestInstance.Lifecycle.PER_CLASS)
-public class LogRealTimeAlertE2eTest {
+public class LogRealTimeAlertE2eTest extends GreptimeE2eSupport {
 
-    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);
 
@@ -80,6 +76,7 @@ public class LogRealTimeAlertE2eTest {
 
     @BeforeAll
     void setUpAll() throws InterruptedException {
+        initializeAdministrator();
         // Setup test alert definitions
         setupTestAlertDefines();
 
@@ -90,15 +87,9 @@ public class LogRealTimeAlertE2eTest {
         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 = VectorE2eContainer.create(
+                port, CONTAINER_STARTUP_TIMEOUT,
+                outputFrame -> log.info("Vector: {}", 
outputFrame.getUtf8String()));
         vector.start();
     }
 
diff --git 
a/hertzbeat-e2e/hertzbeat-observability-e2e/src/test/java/org/apache/hertzbeat/observability/fixture/GreptimeE2eSupport.java
 
b/hertzbeat-e2e/hertzbeat-observability-e2e/src/test/java/org/apache/hertzbeat/observability/fixture/GreptimeE2eSupport.java
new file mode 100644
index 0000000000..0aea8dbed9
--- /dev/null
+++ 
b/hertzbeat-e2e/hertzbeat-observability-e2e/src/test/java/org/apache/hertzbeat/observability/fixture/GreptimeE2eSupport.java
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+
+package org.apache.hertzbeat.observability.fixture;
+
+import java.time.Duration;
+import org.apache.hertzbeat.startup.TrustedStartupSupport;
+import org.springframework.test.context.DynamicPropertyRegistry;
+import org.springframework.test.context.DynamicPropertySource;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.wait.strategy.Wait;
+import org.testcontainers.utility.DockerImageName;
+
+/** Shared pinned GreptimeDB data-plane fixture for full-application 
observability E2E tests. */
+public abstract class GreptimeE2eSupport extends TrustedStartupSupport {
+
+    private static final int HTTP_PORT = 4000;
+    private static final int GRPC_PORT = 4001;
+
+    @SuppressWarnings("resource")
+    protected static final GenericContainer<?> GREPTIME = new 
GenericContainer<>(
+            DockerImageName.parse("greptime/greptimedb:v1.0.1"))
+            .withExposedPorts(HTTP_PORT, GRPC_PORT)
+            .withCommand("standalone", "start",
+                    "--http-addr", "0.0.0.0:" + HTTP_PORT,
+                    "--rpc-bind-addr", "0.0.0.0:" + GRPC_PORT)
+            .waitingFor(Wait.forListeningPorts(HTTP_PORT, GRPC_PORT))
+            .withStartupTimeout(Duration.ofSeconds(120));
+
+    static {
+        GREPTIME.start();
+    }
+
+    @DynamicPropertySource
+    static void greptimeProperties(DynamicPropertyRegistry registry) {
+        registry.add("warehouse.store.greptime.http-endpoint", () -> "http://"; 
+ GREPTIME.getHost()
+                + ":" + GREPTIME.getMappedPort(HTTP_PORT));
+        registry.add("warehouse.store.greptime.grpc-endpoints", () -> 
GREPTIME.getHost()
+                + ":" + GREPTIME.getMappedPort(GRPC_PORT));
+        registry.add("warehouse.store.greptime.username", () -> "");
+        registry.add("warehouse.store.greptime.password", () -> "");
+    }
+}
diff --git 
a/hertzbeat-e2e/hertzbeat-observability-e2e/src/test/java/org/apache/hertzbeat/observability/fixture/VectorE2eContainer.java
 
b/hertzbeat-e2e/hertzbeat-observability-e2e/src/test/java/org/apache/hertzbeat/observability/fixture/VectorE2eContainer.java
new file mode 100644
index 0000000000..1839960c17
--- /dev/null
+++ 
b/hertzbeat-e2e/hertzbeat-observability-e2e/src/test/java/org/apache/hertzbeat/observability/fixture/VectorE2eContainer.java
@@ -0,0 +1,55 @@
+/*
+ * 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.
+ */
+
+package org.apache.hertzbeat.observability.fixture;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+import java.util.function.Consumer;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.output.OutputFrame;
+import org.testcontainers.containers.wait.strategy.Wait;
+import org.testcontainers.images.builder.Transferable;
+import org.testcontainers.utility.DockerImageName;
+
+/** Builds the pinned Vector fixture with a fully rendered, non-templated 
HertzBeat endpoint. */
+public final class VectorE2eContainer {
+
+    private static final String IMAGE = "timberio/vector:0.57.0-alpine";
+    private static final String CONFIG_RESOURCE = "vector.yml";
+    private static final String CONFIG_PATH = "/etc/vector/vector.yml";
+    private static final String PORT_PLACEHOLDER = "__HERTZBEAT_PORT__";
+    private static final int API_PORT = 8686;
+
+    private VectorE2eContainer() {
+    }
+
+    public static GenericContainer<?> create(
+            int hertzbeatPort, Duration startupTimeout, Consumer<OutputFrame> 
logConsumer) {
+        String config = readConfig().replace(PORT_PLACEHOLDER, 
Integer.toString(hertzbeatPort));
+        return new GenericContainer<>(DockerImageName.parse(IMAGE))
+                .withExposedPorts(API_PORT)
+                .withCopyToContainer(Transferable.of(config), CONFIG_PATH)
+                .withCommand("--config", CONFIG_PATH, "--verbose")
+                .withLogConsumer(logConsumer)
+                .waitingFor(Wait.forListeningPort())
+                .withStartupTimeout(startupTimeout);
+    }
+
+    private static String readConfig() {
+        try (InputStream input = 
VectorE2eContainer.class.getClassLoader().getResourceAsStream(CONFIG_RESOURCE)) 
{
+            if (input == null) {
+                throw new IllegalStateException("Vector E2E configuration is 
missing");
+            }
+            return new String(input.readAllBytes(), StandardCharsets.UTF_8);
+        } catch (IOException failure) {
+            throw new IllegalStateException("Cannot read Vector E2E 
configuration", failure);
+        }
+    }
+}
diff --git 
a/hertzbeat-e2e/hertzbeat-observability-e2e/src/test/java/org/apache/hertzbeat/observability/ingestion/LogIngestionE2eTest.java
 
b/hertzbeat-e2e/hertzbeat-observability-e2e/src/test/java/org/apache/hertzbeat/observability/ingestion/LogIngestionE2eTest.java
index 780b233108..be8228380f 100644
--- 
a/hertzbeat-e2e/hertzbeat-observability-e2e/src/test/java/org/apache/hertzbeat/observability/ingestion/LogIngestionE2eTest.java
+++ 
b/hertzbeat-e2e/hertzbeat-observability-e2e/src/test/java/org/apache/hertzbeat/observability/ingestion/LogIngestionE2eTest.java
@@ -20,6 +20,9 @@ package org.apache.hertzbeat.observability.ingestion;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.hertzbeat.common.entity.log.LogEntry;
 import org.apache.hertzbeat.common.queue.CommonDataQueue;
+import org.apache.hertzbeat.observability.fixture.GreptimeE2eSupport;
+import org.apache.hertzbeat.observability.fixture.VectorE2eContainer;
+import org.apache.hertzbeat.startup.TrustedStartup;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.TestInstance;
@@ -27,11 +30,7 @@ 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;
@@ -45,14 +44,11 @@ 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)
+@TrustedStartup
 @Slf4j
 @TestInstance(TestInstance.Lifecycle.PER_CLASS)
-public class LogIngestionE2eTest {
+public class LogIngestionE2eTest extends GreptimeE2eSupport {
 
-    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
@@ -65,21 +61,16 @@ public class LogIngestionE2eTest {
 
     @BeforeAll
     void setUpAll() throws InterruptedException {
+        initializeAdministrator();
         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 = VectorE2eContainer.create(
+                port, CONTAINER_STARTUP_TIMEOUT,
+                outputFrame -> log.info("Vector: {}", 
outputFrame.getUtf8String()));
         vector.start();
     }
 
diff --git 
a/hertzbeat-e2e/hertzbeat-observability-e2e/src/test/java/org/apache/hertzbeat/observability/storage/AuthenticatedGreptimeThreeSignalPublicApiE2eTest.java
 
b/hertzbeat-e2e/hertzbeat-observability-e2e/src/test/java/org/apache/hertzbeat/observability/storage/AuthenticatedGreptimeThreeSignalPublicApiE2eTest.java
index b9af814535..ce681addda 100644
--- 
a/hertzbeat-e2e/hertzbeat-observability-e2e/src/test/java/org/apache/hertzbeat/observability/storage/AuthenticatedGreptimeThreeSignalPublicApiE2eTest.java
+++ 
b/hertzbeat-e2e/hertzbeat-observability-e2e/src/test/java/org/apache/hertzbeat/observability/storage/AuthenticatedGreptimeThreeSignalPublicApiE2eTest.java
@@ -33,15 +33,7 @@ import java.time.Duration;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
-import org.apache.hertzbeat.common.constants.CommonConstants;
-import org.apache.hertzbeat.common.entity.manager.Collector;
-import org.apache.hertzbeat.manager.dao.CollectorDao;
-import 
org.apache.hertzbeat.manager.instrumentation.intake.CollectorIntakeAdvertisementCodec;
-import 
org.apache.hertzbeat.manager.instrumentation.intake.CollectorIntakeAdvertisementRequest;
-import 
org.apache.hertzbeat.manager.pojo.dto.CollectorInstrumentationIntake.Capability;
-import 
org.apache.hertzbeat.manager.pojo.dto.CollectorInstrumentationIntake.Gateway;
 import org.junit.jupiter.api.Test;
-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.junit.jupiter.Testcontainers;
@@ -58,7 +50,6 @@ import org.testcontainers.junit.jupiter.Testcontainers;
                 "scheduler.server.enabled=false",
                 
"spring.datasource.url=jdbc:h2:mem:hertzbeat-authenticated-greptime-e2e;MODE=MYSQL;DB_CLOSE_DELAY=-1",
                 "warehouse.store.duckdb.enabled=false",
-                "warehouse.store.greptime.enabled=true",
                 "warehouse.store.greptime.username=",
                 "warehouse.store.greptime.password="
         })
@@ -75,11 +66,9 @@ class AuthenticatedGreptimeThreeSignalPublicApiE2eTest 
extends GreptimeThreeSign
     @LocalServerPort
     private int serverPort;
 
-    @Autowired
-    private CollectorDao collectorDao;
-
     @Test
     void 
authenticatedPublicApiIngestsDetectsAndQueriesThreeSignalsInGreptime() throws 
Exception {
+        initializeAdministrator();
         advertiseCollectorProfile();
         long startedAt = System.currentTimeMillis() - 1_000;
         long signalTimeNanos = System.currentTimeMillis() * 1_000_000L;
@@ -180,7 +169,9 @@ class AuthenticatedGreptimeThreeSignalPublicApiE2eTest 
extends GreptimeThreeSign
         HttpResponse<byte[]> response = 
send(postJson("/api/instrumentation/detect", detectionBody, adminToken));
         assertThat(response.statusCode()).isEqualTo(200);
         JsonNode envelope = OBJECT_MAPPER.readTree(response.body());
-        assertThat(envelope.path("code").asInt()).isZero();
+        assertThat(envelope.path("code").asInt())
+                .as("scoped detection response: %s", envelope)
+                .isZero();
         JsonNode data = envelope.path("data");
         
assertThat(data.path("signals").path("metrics").path("status").asText()).isNotEqualTo("received");
         
assertThat(data.path("signals").path("logs").path("status").asText()).isNotEqualTo("received");
@@ -407,26 +398,6 @@ class AuthenticatedGreptimeThreeSignalPublicApiE2eTest 
extends GreptimeThreeSign
                 "primaryIdentity", primary);
     }
 
-    /**
-     * Collector persistence is deterministic test setup only. All behavior 
under proof starts at the public HTTP
-     * boundary; no ingestion, detection, or query service is invoked directly.
-     */
-    private void advertiseCollectorProfile() {
-        String advertisement = new CollectorIntakeAdvertisementCodec().encode(
-                new CollectorIntakeAdvertisementRequest(
-                        1,
-                        Gateway.COLLECTOR,
-                        List.of(Capability.OTLP_HTTP_PROTOBUF),
-                        "http://127.0.0.1:4318";,
-                        null));
-        collectorDao.save(Collector.builder()
-                .name(COLLECTOR_ID)
-                .ip("127.0.0.1")
-                .status(CommonConstants.COLLECTOR_STATUS_ONLINE)
-                .instrumentationIntake(advertisement)
-                .build());
-    }
-
     private HttpRequest postProtobuf(String path, byte[] body, String token) {
         HttpRequest.Builder builder = request(path)
                 .header("Content-Type", "application/x-protobuf")
diff --git 
a/hertzbeat-e2e/hertzbeat-observability-e2e/src/test/java/org/apache/hertzbeat/observability/storage/GreptimeLogStorageE2eTest.java
 
b/hertzbeat-e2e/hertzbeat-observability-e2e/src/test/java/org/apache/hertzbeat/observability/storage/GreptimeLogStorageE2eTest.java
index 054dbc5eb4..d23964b79a 100644
--- 
a/hertzbeat-e2e/hertzbeat-observability-e2e/src/test/java/org/apache/hertzbeat/observability/storage/GreptimeLogStorageE2eTest.java
+++ 
b/hertzbeat-e2e/hertzbeat-observability-e2e/src/test/java/org/apache/hertzbeat/observability/storage/GreptimeLogStorageE2eTest.java
@@ -19,7 +19,9 @@ package org.apache.hertzbeat.observability.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.observability.fixture.GreptimeE2eSupport;
+import org.apache.hertzbeat.observability.fixture.VectorE2eContainer;
+import org.apache.hertzbeat.startup.TrustedStartup;
 import 
org.apache.hertzbeat.warehouse.store.history.tsdb.greptime.GreptimeDbDataStorage;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
@@ -27,20 +29,14 @@ 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.time.Duration;
+import java.util.Collections;
 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;
@@ -50,57 +46,29 @@ 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)
+@TrustedStartup
 @TestPropertySource(properties = {
-        "warehouse.store.duckdb.enabled=false",
-        "warehouse.store.greptime.enabled=true"
+        "warehouse.store.duckdb.enabled=false"
 })
 @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;
+public class GreptimeLogStorageE2eTest extends GreptimeE2eSupport {
+
     private static final Duration CONTAINER_STARTUP_TIMEOUT = 
Duration.ofSeconds(120);
+    private static final String VECTOR_SERVICE_NAME = "hertzbeat-vector-e2e";
 
     @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", () -> "");
-    }
-
+    private long vectorStartedAtMillis;
 
     @BeforeAll
     void setUpAll() throws InterruptedException {
+        initializeAdministrator();
         // Expose host ports for testcontainers
         Testcontainers.exposeHostPorts(port);
 
@@ -108,15 +76,10 @@ public class GreptimeLogStorageE2eTest {
         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);
+        vectorStartedAtMillis = System.currentTimeMillis();
+        vector = VectorE2eContainer.create(
+                port, CONTAINER_STARTUP_TIMEOUT,
+                outputFrame -> log.info("Vector: {}", 
outputFrame.getUtf8String()));
         vector.start();
     }
 
@@ -124,53 +87,16 @@ public class GreptimeLogStorageE2eTest {
     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))
+        // The production OTLP path writes directly to GreptimeDB; verify the 
persisted rows.
+        await().atMost(Duration.ofSeconds(60))
                 .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");
+                    LogEntry firstLog = storedLogs.get(0);
+                    assertNotNull(firstLog.getBody(), "Stored log body should 
not be null");
+                    assertNotNull(firstLog.getSeverityText(), "Stored log 
severity should not be null");
                 });
     }
 
@@ -178,9 +104,10 @@ public class GreptimeLogStorageE2eTest {
      * Helper method to query stored logs directly from GreptimeDB
      */
     private List<LogEntry> queryStoredLogs() {
-        // Query without time condition to verify data exists
+        // Scope the query to this Vector run so rows from other E2E tests 
cannot satisfy the proof.
         List<LogEntry> result = 
greptimeDbDataStorage.queryLogsByMultipleConditions(
-                null, null, null, null, null, null, null);
+                vectorStartedAtMillis, System.currentTimeMillis(), null, null, 
null, null, null,
+                Collections.emptySet(), true, null, VECTOR_SERVICE_NAME, null, 
null);
         log.info("queryLogsByMultipleConditions returned {} entries", 
result.size());
         return result;
     }
diff --git 
a/hertzbeat-e2e/hertzbeat-observability-e2e/src/test/java/org/apache/hertzbeat/observability/storage/GreptimeThreeSignalE2eSupport.java
 
b/hertzbeat-e2e/hertzbeat-observability-e2e/src/test/java/org/apache/hertzbeat/observability/storage/GreptimeThreeSignalE2eSupport.java
index bbeb4492ae..aa7fe183fa 100644
--- 
a/hertzbeat-e2e/hertzbeat-observability-e2e/src/test/java/org/apache/hertzbeat/observability/storage/GreptimeThreeSignalE2eSupport.java
+++ 
b/hertzbeat-e2e/hertzbeat-observability-e2e/src/test/java/org/apache/hertzbeat/observability/storage/GreptimeThreeSignalE2eSupport.java
@@ -35,18 +35,25 @@ import io.opentelemetry.proto.resource.v1.Resource;
 import io.opentelemetry.proto.trace.v1.ResourceSpans;
 import io.opentelemetry.proto.trace.v1.ScopeSpans;
 import io.opentelemetry.proto.trace.v1.Span;
-import java.time.Duration;
+import java.time.Instant;
 import java.util.HexFormat;
 import java.util.List;
-import org.springframework.test.context.DynamicPropertyRegistry;
-import org.springframework.test.context.DynamicPropertySource;
-import org.testcontainers.containers.GenericContainer;
-import org.testcontainers.containers.wait.strategy.Wait;
-import org.testcontainers.junit.jupiter.Container;
-import org.testcontainers.utility.DockerImageName;
+import org.apache.hertzbeat.common.constants.CommonConstants;
+import org.apache.hertzbeat.common.entity.dto.ManagedOtelRuntimeStatus;
+import org.apache.hertzbeat.common.entity.manager.Collector;
+import org.apache.hertzbeat.manager.dao.CollectorDao;
+import 
org.apache.hertzbeat.manager.instrumentation.intake.CollectorIntakeAdvertisementCodec;
+import 
org.apache.hertzbeat.manager.instrumentation.intake.CollectorIntakeAdvertisementRequest;
+import 
org.apache.hertzbeat.manager.pojo.dto.CollectorInstrumentationIntake.Capability;
+import 
org.apache.hertzbeat.manager.pojo.dto.CollectorInstrumentationIntake.Gateway;
+import 
org.apache.hertzbeat.manager.scheduler.runtime.CollectorRuntimeStatusRegistry;
+import org.apache.hertzbeat.observability.fixture.GreptimeE2eSupport;
+import org.apache.hertzbeat.startup.TrustedStartup;
+import org.springframework.beans.factory.annotation.Autowired;
 
 /** Shared real-Greptime container and deterministic OTLP payloads for 
three-signal E2E tests. */
-abstract class GreptimeThreeSignalE2eSupport {
+@TrustedStartup
+abstract class GreptimeThreeSignalE2eSupport extends GreptimeE2eSupport {
 
     static final String SERVICE_NAME = "checkout-api";
     static final String SERVICE_NAMESPACE = "commerce";
@@ -62,26 +69,46 @@ abstract class GreptimeThreeSignalE2eSupport {
     static final String LOG_BODY = "three-signal-e2e";
     static final String SPAN_NAME = "GET /checkout";
 
-    private static final int GREPTIME_HTTP_PORT = 4000;
-    private static final int GREPTIME_GRPC_PORT = 4001;
+    @Autowired
+    private CollectorDao collectorDao;
 
-    @Container
-    @SuppressWarnings("resource")
-    static final GenericContainer<?> GREPTIME = new GenericContainer<>(
-            DockerImageName.parse("greptime/greptimedb:v1.0.1"))
-            .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(Duration.ofSeconds(120));
+    @Autowired
+    private CollectorRuntimeStatusRegistry collectorRuntimeStatusRegistry;
 
-    @DynamicPropertySource
-    static void greptimeProperties(DynamicPropertyRegistry registry) {
-        registry.add("warehouse.store.greptime.http-endpoint", () -> "http://"; 
+ GREPTIME.getHost()
-                + ":" + GREPTIME.getMappedPort(GREPTIME_HTTP_PORT));
-        registry.add("warehouse.store.greptime.grpc-endpoints", () -> 
GREPTIME.getHost()
-                + ":" + GREPTIME.getMappedPort(GREPTIME_GRPC_PORT));
+    final void advertiseCollectorProfile() {
+        String advertisement = new CollectorIntakeAdvertisementCodec().encode(
+                new CollectorIntakeAdvertisementRequest(
+                        1,
+                        Gateway.COLLECTOR,
+                        List.of(Capability.OTLP_HTTP_PROTOBUF),
+                        "http://127.0.0.1:4318";,
+                        null));
+        collectorDao.save(Collector.builder()
+                .name(COLLECTOR_ID)
+                .ip("127.0.0.1")
+                .status(CommonConstants.COLLECTOR_STATUS_ONLINE)
+                .instrumentationIntake(advertisement)
+                .build());
+        collectorRuntimeStatusRegistry.report(COLLECTOR_ID, 
availableCollectorGateway());
+    }
+
+    private ManagedOtelRuntimeStatus availableCollectorGateway() {
+        return new ManagedOtelRuntimeStatus(
+                ManagedOtelRuntimeStatus.CURRENT_SCHEMA_VERSION,
+                true,
+                ManagedOtelRuntimeStatus.RuntimeState.RUNNING,
+                1,
+                1,
+                -1,
+                ManagedOtelRuntimeStatus.IntakeCredentialState.CONFIGURED,
+                0,
+                Instant.now(),
+                "",
+                ManagedOtelRuntimeStatus.FailureCode.NONE,
+                ManagedOtelRuntimeStatus.RuntimeTelemetry.unavailable(false),
+                List.of(),
+                ManagedOtelRuntimeStatus.OtlpGatewayStatus.available(
+                        
List.of(ManagedOtelRuntimeStatus.OtlpGatewayTransport.HTTP_PROTOBUF)));
     }
 
     static ExportMetricsServiceRequest metrics(long timeNanos) {
diff --git 
a/hertzbeat-e2e/hertzbeat-observability-e2e/src/test/java/org/apache/hertzbeat/observability/storage/GreptimeThreeSignalInstrumentationE2eTest.java
 
b/hertzbeat-e2e/hertzbeat-observability-e2e/src/test/java/org/apache/hertzbeat/observability/storage/GreptimeThreeSignalInstrumentationE2eTest.java
index 6e2b0e07c9..c0a3367c1d 100644
--- 
a/hertzbeat-e2e/hertzbeat-observability-e2e/src/test/java/org/apache/hertzbeat/observability/storage/GreptimeThreeSignalInstrumentationE2eTest.java
+++ 
b/hertzbeat-e2e/hertzbeat-observability-e2e/src/test/java/org/apache/hertzbeat/observability/storage/GreptimeThreeSignalInstrumentationE2eTest.java
@@ -27,16 +27,10 @@ import static org.awaitility.Awaitility.await;
 import java.time.Duration;
 import java.util.List;
 import java.util.Map;
-import org.apache.hertzbeat.common.constants.CommonConstants;
 import org.apache.hertzbeat.common.entity.log.LogEntry;
-import org.apache.hertzbeat.common.entity.manager.Collector;
+import 
org.apache.hertzbeat.common.observability.gateway.AuthTokenRequestContext;
 import 
org.apache.hertzbeat.common.observability.dto.metrics.OtlpMetricsConsoleDto;
 import org.apache.hertzbeat.common.observability.dto.trace.TraceListItemDto;
-import org.apache.hertzbeat.manager.dao.CollectorDao;
-import 
org.apache.hertzbeat.manager.instrumentation.intake.CollectorIntakeAdvertisementCodec;
-import 
org.apache.hertzbeat.manager.instrumentation.intake.CollectorIntakeAdvertisementRequest;
-import 
org.apache.hertzbeat.manager.pojo.dto.CollectorInstrumentationIntake.Capability;
-import 
org.apache.hertzbeat.manager.pojo.dto.CollectorInstrumentationIntake.Gateway;
 import 
org.apache.hertzbeat.observability.ingestion.service.OtlpGrpcIngestionService;
 import 
org.apache.hertzbeat.observability.instrumentation.api.InstrumentationApiContract.DetectionRequest;
 import 
org.apache.hertzbeat.observability.instrumentation.api.InstrumentationApiContract.DetectionResponse;
@@ -74,7 +68,6 @@ import org.testcontainers.junit.jupiter.Testcontainers;
                 "scheduler.server.enabled=false",
                 
"spring.datasource.url=jdbc:h2:mem:hertzbeat-e2e;MODE=MYSQL;DB_CLOSE_DELAY=-1",
                 "warehouse.store.duckdb.enabled=false",
-                "warehouse.store.greptime.enabled=true",
                 "warehouse.store.greptime.username=",
                 "warehouse.store.greptime.password="
         })
@@ -90,9 +83,6 @@ class GreptimeThreeSignalInstrumentationE2eTest extends 
GreptimeThreeSignalE2eSu
     @Autowired
     private InstrumentationDetectionV2Service currentDetectionService;
 
-    @Autowired
-    private CollectorDao collectorDao;
-
     @Autowired
     private InstrumentationSignalDetectionStore signalDetectionStore;
 
@@ -114,9 +104,15 @@ class GreptimeThreeSignalInstrumentationE2eTest extends 
GreptimeThreeSignalE2eSu
         long startedAt = System.currentTimeMillis() - 1_000;
         long signalTimeNanos = System.currentTimeMillis() * 1_000_000L;
 
-        ingestionService.ingestMetricsGrpc(metrics(signalTimeNanos));
-        ingestionService.ingestLogsGrpc(logs(signalTimeNanos));
-        ingestionService.ingestTracesGrpc(traces(signalTimeNanos));
+        AuthTokenRequestContext.bindWorkspaceId("default");
+        AuthTokenRequestContext.bindCollectorId(COLLECTOR_ID);
+        try {
+            ingestionService.ingestMetricsGrpc(metrics(signalTimeNanos));
+            ingestionService.ingestLogsGrpc(logs(signalTimeNanos));
+            ingestionService.ingestTracesGrpc(traces(signalTimeNanos));
+        } finally {
+            AuthTokenRequestContext.clear();
+        }
 
         
await().atMost(Duration.ofSeconds(20)).pollInterval(Duration.ofSeconds(1)).untilAsserted(()
 -> {
             List<Map<String, Object>> rows = queryExecutor.executeStrict(
@@ -194,39 +190,15 @@ class GreptimeThreeSignalInstrumentationE2eTest extends 
GreptimeThreeSignalE2eSu
         assertNotReceived(requestWithContext(request, INSTANCE_ID, "/other"));
 
         DetectionResponse detected = detectionService.detect(request);
-        assertProductionQueries(
-                enabledJump(detected, METRICS).context(),
-                enabledJump(detected, LOGS).context(),
-                enabledJump(detected, TRACES).context());
-    }
-
-    private void advertiseCollectorProfile() {
-        CollectorIntakeAdvertisementCodec codec = new 
CollectorIntakeAdvertisementCodec();
-        String advertisement = codec.encode(
-                new CollectorIntakeAdvertisementRequest(
-                        1,
-                        Gateway.COLLECTOR,
-                        List.of(Capability.OTLP_HTTP_PROTOBUF),
-                        "http://127.0.0.1:4318";,
-                        null));
-        collectorDao.save(Collector.builder()
-                .name(COLLECTOR_ID)
-                .ip("127.0.0.1")
-                .status(CommonConstants.COLLECTOR_STATUS_ONLINE)
-                .instrumentationIntake(advertisement)
-                .build());
-        String serverAdvertisement = codec.encode(new 
CollectorIntakeAdvertisementRequest(
-                1,
-                Gateway.SERVER,
-                List.of(Capability.OTLP_HTTP_PROTOBUF),
-                "http://127.0.0.1:4318";,
-                null));
-        collectorDao.save(Collector.builder()
-                .name(SERVER_PROFILE_ID)
-                .ip("127.0.0.1")
-                .status(CommonConstants.COLLECTOR_STATUS_ONLINE)
-                .instrumentationIntake(serverAdvertisement)
-                .build());
+        AuthTokenRequestContext.bindWorkspaceId("default");
+        try {
+            assertProductionQueries(
+                    enabledJump(detected, METRICS).context(),
+                    enabledJump(detected, LOGS).context(),
+                    enabledJump(detected, TRACES).context());
+        } finally {
+            AuthTokenRequestContext.clear();
+        }
     }
 
     private void assertCurrentDetectionContract(long startedAt) {
@@ -265,7 +237,7 @@ class GreptimeThreeSignalInstrumentationE2eTest extends 
GreptimeThreeSignalE2eSu
                         Platform.LINUX_AMD64,
                         new ServiceIdentity(
                                 SERVICE_NAME, SERVICE_NAMESPACE, ENVIRONMENT, 
INSTANCE_ID, ENDPOINT),
-                        "server:" + SERVER_PROFILE_ID,
+                        SERVER_PROFILE_ID,
                         startedAt));
         assertThat(directServerResponse.signals().values())
                 .allMatch(signal -> signal.status()
@@ -277,8 +249,11 @@ class GreptimeThreeSignalInstrumentationE2eTest extends 
GreptimeThreeSignalE2eSu
         assertThat(queryExecutor.executeStrict("""
                 SELECT service_name, resource_attributes, log_attributes, 
timestamp
                 FROM hertzbeat_logs
+                WHERE service_name = '%s'
+                  AND timestamp >= to_timestamp_millis(%d)
+                  AND timestamp < to_timestamp_millis(%d)
                 ORDER BY timestamp DESC LIMIT 1
-                """))
+                """.formatted(SERVICE_NAME, startedAt, detectedAt + 1)))
                 .singleElement()
                 .satisfies(row -> {
                     assertThat(String.valueOf(row.get("resource_attributes")))
@@ -313,8 +288,11 @@ class GreptimeThreeSignalInstrumentationE2eTest extends 
GreptimeThreeSignalE2eSu
                   "span_attributes.http.route" AS http_route,
                   timestamp
                 FROM hzb_traces
+                WHERE service_name = '%s'
+                  AND timestamp >= to_timestamp_millis(%d)
+                  AND timestamp < to_timestamp_millis(%d)
                 ORDER BY timestamp DESC LIMIT 1
-                """))
+                """.formatted(SERVICE_NAME, startedAt, detectedAt + 1)))
                 .singleElement()
                 .satisfies(row -> {
                     
assertThat(row.get("service_instance_id")).hasToString(INSTANCE_ID);
@@ -335,7 +313,7 @@ class GreptimeThreeSignalInstrumentationE2eTest extends 
GreptimeThreeSignalE2eSu
             QueryJumpContext tracesContext) {
         long end = metricsContext.detectedAt() + 60_000;
         OtlpMetricsConsoleDto metrics = metricsQueryService.query(new 
CollectorScopedMetricsQueryService.Request(
-                null, null, null, metricsContext.startedAt(), end, 
metricsContext.serviceName(),
+                "default", null, null, metricsContext.startedAt(), end, 
metricsContext.serviceName(),
                 metricsContext.serviceNamespace(), 
metricsContext.environment(), metricsContext.collectorId(),
                 INSTANCE_ID, ENDPOINT, "hertzbeat_e2e_requests", null, null,
                 null, null, "1s", "20", null));
@@ -354,7 +332,7 @@ class GreptimeThreeSignalInstrumentationE2eTest extends 
GreptimeThreeSignalE2eSu
                 .anySatisfy(row -> 
assertThat(Double.parseDouble(String.valueOf(row[1]))).isEqualTo(1.0));
         OtlpMetricsConsoleDto missingInstanceMetrics = 
metricsQueryService.query(
                 new CollectorScopedMetricsQueryService.Request(
-                        null, null, null, metricsContext.startedAt(), end, 
metricsContext.serviceName(),
+                        "default", null, null, metricsContext.startedAt(), 
end, metricsContext.serviceName(),
                         metricsContext.serviceNamespace(), 
metricsContext.environment(), metricsContext.collectorId(),
                         "other-instance", ENDPOINT, "hertzbeat_e2e_requests", 
null, null,
                         null, null, "1s", "20", null));
diff --git 
a/hertzbeat-e2e/hertzbeat-observability-e2e/src/test/java/org/apache/hertzbeat/observability/storage/PrometheusActiveSourcePublicApiE2eTest.java
 
b/hertzbeat-e2e/hertzbeat-observability-e2e/src/test/java/org/apache/hertzbeat/observability/storage/PrometheusActiveSourcePublicApiE2eTest.java
index a9b37eba5e..8c22ee9b3c 100644
--- 
a/hertzbeat-e2e/hertzbeat-observability-e2e/src/test/java/org/apache/hertzbeat/observability/storage/PrometheusActiveSourcePublicApiE2eTest.java
+++ 
b/hertzbeat-e2e/hertzbeat-observability-e2e/src/test/java/org/apache/hertzbeat/observability/storage/PrometheusActiveSourcePublicApiE2eTest.java
@@ -22,16 +22,8 @@ import java.nio.file.Path;
 import java.time.Duration;
 import java.util.LinkedHashMap;
 import java.util.Map;
-import org.apache.hertzbeat.common.constants.CommonConstants;
-import org.apache.hertzbeat.common.entity.manager.Collector;
-import org.apache.hertzbeat.manager.dao.CollectorDao;
-import 
org.apache.hertzbeat.manager.instrumentation.intake.CollectorIntakeAdvertisementCodec;
-import 
org.apache.hertzbeat.manager.instrumentation.intake.CollectorIntakeAdvertisementRequest;
-import 
org.apache.hertzbeat.manager.pojo.dto.CollectorInstrumentationIntake.Capability;
-import 
org.apache.hertzbeat.manager.pojo.dto.CollectorInstrumentationIntake.Gateway;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.io.TempDir;
-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.junit.jupiter.Testcontainers;
@@ -46,7 +38,6 @@ import org.testcontainers.junit.jupiter.Testcontainers;
                 "scheduler.server.enabled=false",
                 
"spring.datasource.url=jdbc:h2:mem:hertzbeat-active-prometheus-e2e;MODE=MYSQL;DB_CLOSE_DELAY=-1",
                 "warehouse.store.duckdb.enabled=false",
-                "warehouse.store.greptime.enabled=true",
                 "warehouse.store.greptime.username=",
                 "warehouse.store.greptime.password="
         })
@@ -61,14 +52,12 @@ class PrometheusActiveSourcePublicApiE2eTest extends 
GreptimeThreeSignalE2eSuppo
     @LocalServerPort
     private int serverPort;
 
-    @Autowired
-    private CollectorDao collectorDao;
-
     @TempDir
     private Path tempDir;
 
     @Test
     void managedPrometheusSourceScrapesWritesAndSurfacesPersistedEvidence() 
throws Exception {
+        initializeAdministrator();
         advertiseCollectorProfile();
         String adminToken = login();
         long entityId = createEntity(adminToken);
@@ -191,23 +180,6 @@ class PrometheusActiveSourcePublicApiE2eTest extends 
GreptimeThreeSignalE2eSuppo
         return OBJECT_MAPPER.writeValueAsBytes(request);
     }
 
-    /** Collector persistence is control-plane setup; telemetry starts only at 
the real scrape endpoint. */
-    private void advertiseCollectorProfile() {
-        String advertisement = new CollectorIntakeAdvertisementCodec().encode(
-                new CollectorIntakeAdvertisementRequest(
-                        1,
-                        Gateway.COLLECTOR,
-                        java.util.List.of(Capability.OTLP_HTTP_PROTOBUF),
-                        "http://127.0.0.1:4318";,
-                        null));
-        collectorDao.save(Collector.builder()
-                .name(COLLECTOR_ID)
-                .ip("127.0.0.1")
-                .status(CommonConstants.COLLECTOR_STATUS_ONLINE)
-                .instrumentationIntake(advertisement)
-                .build());
-    }
-
     private JsonNode successfulJson(HttpResponse<byte[]> response) throws 
Exception {
         assertThat(response.statusCode()).isEqualTo(200);
         JsonNode envelope = OBJECT_MAPPER.readTree(response.body());
diff --git 
a/hertzbeat-e2e/hertzbeat-observability-e2e/src/test/java/org/apache/hertzbeat/startup/TrustedStartup.java
 
b/hertzbeat-e2e/hertzbeat-observability-e2e/src/test/java/org/apache/hertzbeat/startup/TrustedStartup.java
new file mode 100644
index 0000000000..bc6cd3f874
--- /dev/null
+++ 
b/hertzbeat-e2e/hertzbeat-observability-e2e/src/test/java/org/apache/hertzbeat/startup/TrustedStartup.java
@@ -0,0 +1,26 @@
+/*
+ * 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.
+ */
+
+package org.apache.hertzbeat.startup;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.apache.hertzbeat.startup.runtime.TrustedSpringBootContextLoader;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.ContextConfiguration;
+
+/** Runs an E2E full-application test through the official trusted startup 
boundary. */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+@ActiveProfiles({"test", "e2e"})
+@ContextConfiguration(loader = TrustedSpringBootContextLoader.class)
+public @interface TrustedStartup {
+}
diff --git 
a/hertzbeat-e2e/hertzbeat-observability-e2e/src/test/java/org/apache/hertzbeat/startup/TrustedStartupSupport.java
 
b/hertzbeat-e2e/hertzbeat-observability-e2e/src/test/java/org/apache/hertzbeat/startup/TrustedStartupSupport.java
new file mode 100644
index 0000000000..a672e1dc20
--- /dev/null
+++ 
b/hertzbeat-e2e/hertzbeat-observability-e2e/src/test/java/org/apache/hertzbeat/startup/TrustedStartupSupport.java
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+
+package org.apache.hertzbeat.startup;
+
+import org.apache.hertzbeat.manager.setup.identity.AdministratorCredentials;
+import org.apache.hertzbeat.manager.setup.identity.DatabaseAccountRepository;
+import 
org.apache.hertzbeat.manager.setup.identity.IdentityInitializationService;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/** Shared identity fixture for E2E tests that exercise authenticated public 
boundaries. */
+@TrustedStartup
+public abstract class TrustedStartupSupport {
+
+    @Autowired
+    private IdentityInitializationService identityInitializationService;
+
+    @Autowired
+    private DatabaseAccountRepository databaseAccountRepository;
+
+    protected final void initializeAdministrator() {
+        if (!databaseAccountRepository.existsByUsername("admin")) {
+            identityInitializationService.createFirstAdministrator(
+                    new AdministratorCredentials("admin", 
"hertzbeat".toCharArray()));
+        }
+    }
+}
diff --git 
a/hertzbeat-e2e/hertzbeat-observability-e2e/src/test/java/org/apache/hertzbeat/startup/runtime/TrustedSpringBootContextLoader.java
 
b/hertzbeat-e2e/hertzbeat-observability-e2e/src/test/java/org/apache/hertzbeat/startup/runtime/TrustedSpringBootContextLoader.java
new file mode 100644
index 0000000000..6506dab3bc
--- /dev/null
+++ 
b/hertzbeat-e2e/hertzbeat-observability-e2e/src/test/java/org/apache/hertzbeat/startup/runtime/TrustedSpringBootContextLoader.java
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+
+package org.apache.hertzbeat.startup.runtime;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import org.apache.hertzbeat.manager.setup.runtime.SetupRuntimeTransition;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.test.context.SpringBootContextLoader;
+import org.springframework.core.env.StandardEnvironment;
+
+/** Launches E2E Spring tests through the same trusted admission used in 
production. */
+public final class TrustedSpringBootContextLoader extends 
SpringBootContextLoader {
+
+    @Override
+    protected SpringApplication getSpringApplication() {
+        Path installationRoot = testInstallationRoot();
+        StandardEnvironment environment = new StandardEnvironment();
+        
environment.getPropertySources().addFirst(StartupLaunchAdmission.internalPropertySource(
+                StartupDecision.normal(), installationRoot, 
StartupLaunchAdmission.Mode.ORDINARY));
+        SpringApplication application = super.getSpringApplication();
+        application.setEnvironment(environment);
+        application.addInitializers(context -> 
context.getBeanFactory().registerSingleton(
+                "setupRuntimeTransition", (SetupRuntimeTransition) () -> { }));
+        return application;
+    }
+
+    private static Path testInstallationRoot() {
+        try {
+            return Files.createTempDirectory("hertzbeat-e2e-");
+        } catch (IOException failure) {
+            throw new IllegalStateException("Cannot prepare the E2E 
installation root", failure);
+        }
+    }
+}
diff --git 
a/hertzbeat-e2e/hertzbeat-observability-e2e/src/test/resources/application-e2e.yml
 
b/hertzbeat-e2e/hertzbeat-observability-e2e/src/test/resources/application-e2e.yml
new file mode 100644
index 0000000000..7cad087c13
--- /dev/null
+++ 
b/hertzbeat-e2e/hertzbeat-observability-e2e/src/test/resources/application-e2e.yml
@@ -0,0 +1,25 @@
+# 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.
+
+# Keep managed values classpath-backed so the production setup projection can
+# retain honest source attribution when a full application context starts.
+spring:
+  jpa:
+    hibernate:
+      ddl-auto: none
+  flyway:
+    enabled: true
+    locations: classpath:db/migration/h2
+
+warehouse:
+  store:
+    greptime:
+      enabled: true
+hertzbeat:
+  instrumentation:
+    server:
+      profile-id: server-e2e
+      otlp-http-endpoint: http://127.0.0.1:4318
+      authentication: bearer_token
diff --git 
a/hertzbeat-e2e/hertzbeat-observability-e2e/src/test/resources/vector.yml 
b/hertzbeat-e2e/hertzbeat-observability-e2e/src/test/resources/vector.yml
index d0add7ed93..1fe85eaf94 100644
--- a/hertzbeat-e2e/hertzbeat-observability-e2e/src/test/resources/vector.yml
+++ b/hertzbeat-e2e/hertzbeat-observability-e2e/src/test/resources/vector.yml
@@ -61,7 +61,7 @@ transforms:
         "resource": {
           "attributes": [
             { "key": "source_type", "value": { "stringValue": .source_type } },
-            { "key": "service.name", "value": { "stringValue": syslog.appname 
} },
+            { "key": "service.name", "value": { "stringValue": 
"hertzbeat-vector-e2e" } },
             { "key": "host.hostname", "value": { "stringValue": 
syslog.hostname } }
           ]
         },
@@ -102,25 +102,19 @@ sinks:
     type: opentelemetry
     protocol:
       type: http
-      uri: 
"http://host.testcontainers.internal:${HERTZBEAT_PORT:-1157}/api/logs/ingest/otlp";
+      uri: 
"http://host.testcontainers.internal:__HERTZBEAT_PORT__/api/logs/otlp/v1/logs";
       method: post
       encoding:
-        codec: json
-      framing:
-        method: newline_delimited
-      headers:
-        content-type: application/json
+        codec: otlp
       auth:
         strategy: basic
         user: admin
         password: hertzbeat
-    # Increase timeout and retry settings for stability in CI environments
-    request:
-      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: 5
+      request:
+        timeout_secs: 60
+        retry_attempts: 10
+        retry_initial_backoff_secs: 2
+        retry_max_duration_secs: 120
+      batch:
+        max_bytes: 524288
+        timeout_secs: 5


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to