feat: implement multitenancy with tenant isolation + environment support
Adds configurable tenant ID (CAMELEER_TENANT_ID env var, default: "default") and environment as a first-class concept. Each server instance serves one tenant with multiple environments. Changes across 36 files: - TenantProperties config bean for tenant ID injection - AgentInfo: added environmentId field - AgentRegistrationRequest: added environmentId field - All 9 ClickHouse stores: inject tenant ID, replace hardcoded "default" constant, add environment to writes/reads - ChunkAccumulator: configurable tenant ID + environment resolver - MergedExecution/ProcessorBatch/BufferedLogEntry: added environment - ClickHouse init.sql: added environment column to all tables, updated ORDER BY (tenant→time→env→app), added tenant_id to usage_events, updated all MV GROUP BY clauses - Controllers: pass environmentId through registration/auto-heal - K8s deploy: added CAMELEER_TENANT_ID env var - All tests updated for new signatures Closes #123 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -30,7 +30,7 @@ public class TestSecurityHelper {
|
||||
* Registers a test agent and returns a valid JWT access token with AGENT role.
|
||||
*/
|
||||
public String registerTestAgent(String instanceId) {
|
||||
agentRegistryService.register(instanceId, "test", "test-group", "1.0", List.of(), Map.of());
|
||||
agentRegistryService.register(instanceId, "test", "test-group", "default", "1.0", List.of(), Map.of());
|
||||
return jwtService.createAccessToken(instanceId, "test-group", List.of("AGENT"));
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ class ClickHouseLogStoreIT {
|
||||
ClickHouseTestHelper.executeInitSql(jdbc);
|
||||
jdbc.execute("TRUNCATE TABLE logs");
|
||||
|
||||
store = new ClickHouseLogStore(jdbc);
|
||||
store = new ClickHouseLogStore("default", jdbc);
|
||||
}
|
||||
|
||||
// ── Helpers ──────────────────────────────────────────────────────────
|
||||
|
||||
@@ -47,15 +47,15 @@ class ClickHouseSearchIndexIT {
|
||||
jdbc.execute("TRUNCATE TABLE executions");
|
||||
jdbc.execute("TRUNCATE TABLE processor_executions");
|
||||
|
||||
ClickHouseExecutionStore store = new ClickHouseExecutionStore(jdbc);
|
||||
searchIndex = new ClickHouseSearchIndex(jdbc);
|
||||
ClickHouseExecutionStore store = new ClickHouseExecutionStore("default", jdbc);
|
||||
searchIndex = new ClickHouseSearchIndex("default", jdbc);
|
||||
|
||||
// Seed test data
|
||||
Instant baseTime = Instant.parse("2026-03-31T10:00:00Z");
|
||||
|
||||
// exec-1: COMPLETED, route-timer, agent-a, my-app, corr-1, 500ms, input_body with order number, attributes
|
||||
MergedExecution exec1 = new MergedExecution(
|
||||
"default", 1L, "exec-1", "route-timer", "agent-a", "my-app",
|
||||
"default", 1L, "exec-1", "route-timer", "agent-a", "my-app", "default",
|
||||
"COMPLETED", "corr-1", "exchange-1",
|
||||
baseTime,
|
||||
baseTime.plusMillis(500),
|
||||
@@ -70,7 +70,7 @@ class ClickHouseSearchIndexIT {
|
||||
|
||||
// exec-2: FAILED, route-timer, agent-a, my-app, corr-2, 200ms, with error
|
||||
MergedExecution exec2 = new MergedExecution(
|
||||
"default", 1L, "exec-2", "route-timer", "agent-a", "my-app",
|
||||
"default", 1L, "exec-2", "route-timer", "agent-a", "my-app", "default",
|
||||
"FAILED", "corr-2", "exchange-2",
|
||||
baseTime.plusSeconds(1),
|
||||
baseTime.plusSeconds(1).plusMillis(200),
|
||||
@@ -87,7 +87,7 @@ class ClickHouseSearchIndexIT {
|
||||
|
||||
// exec-3: COMPLETED, route-rest, agent-b, other-app, 100ms, no error
|
||||
MergedExecution exec3 = new MergedExecution(
|
||||
"default", 1L, "exec-3", "route-rest", "agent-b", "other-app",
|
||||
"default", 1L, "exec-3", "route-rest", "agent-b", "other-app", "default",
|
||||
"COMPLETED", "", "exchange-3",
|
||||
baseTime.plusSeconds(2),
|
||||
baseTime.plusSeconds(2).plusMillis(100),
|
||||
|
||||
@@ -39,7 +39,7 @@ class ClickHouseAgentEventRepositoryIT {
|
||||
ClickHouseTestHelper.executeInitSql(jdbc);
|
||||
jdbc.execute("TRUNCATE TABLE agent_events");
|
||||
|
||||
repo = new ClickHouseAgentEventRepository(jdbc);
|
||||
repo = new ClickHouseAgentEventRepository("default", jdbc);
|
||||
}
|
||||
|
||||
// ── Helpers ──────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -59,13 +59,14 @@ class ClickHouseChunkPipelineIT {
|
||||
jdbc.execute("TRUNCATE TABLE executions");
|
||||
jdbc.execute("TRUNCATE TABLE processor_executions");
|
||||
|
||||
executionStore = new ClickHouseExecutionStore(jdbc);
|
||||
searchIndex = new ClickHouseSearchIndex(jdbc);
|
||||
executionStore = new ClickHouseExecutionStore("default", jdbc);
|
||||
searchIndex = new ClickHouseSearchIndex("default", jdbc);
|
||||
|
||||
executionBuffer = new ArrayList<>();
|
||||
processorBuffer = new ArrayList<>();
|
||||
DiagramStore noOpDiagramStore = org.mockito.Mockito.mock(DiagramStore.class);
|
||||
accumulator = new ChunkAccumulator(executionBuffer::add, processorBuffer::add, noOpDiagramStore, Duration.ofMinutes(5));
|
||||
accumulator = new ChunkAccumulator("default", executionBuffer::add, processorBuffer::add,
|
||||
noOpDiagramStore, Duration.ofMinutes(5), id -> "default");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -41,7 +41,7 @@ class ClickHouseDiagramStoreIT {
|
||||
ClickHouseTestHelper.executeInitSql(jdbc);
|
||||
jdbc.execute("TRUNCATE TABLE route_diagrams");
|
||||
|
||||
store = new ClickHouseDiagramStore(jdbc);
|
||||
store = new ClickHouseDiagramStore("default", jdbc);
|
||||
}
|
||||
|
||||
// ── Helpers ──────────────────────────────────────────────────────────
|
||||
|
||||
@@ -47,7 +47,7 @@ class ClickHouseExecutionReadIT {
|
||||
jdbc.execute("TRUNCATE TABLE executions");
|
||||
jdbc.execute("TRUNCATE TABLE processor_executions");
|
||||
|
||||
store = new ClickHouseExecutionStore(jdbc);
|
||||
store = new ClickHouseExecutionStore("default", jdbc);
|
||||
detailService = new DetailService(store);
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ class ClickHouseExecutionReadIT {
|
||||
|
||||
private MergedExecution minimalExecution(String executionId) {
|
||||
return new MergedExecution(
|
||||
"default", 1L, executionId, "route-a", "agent-1", "my-app",
|
||||
"default", 1L, executionId, "route-a", "agent-1", "my-app", "default",
|
||||
"COMPLETED", "corr-1", "exchange-1",
|
||||
Instant.parse("2026-04-01T10:00:00Z"),
|
||||
Instant.parse("2026-04-01T10:00:01Z"),
|
||||
|
||||
@@ -43,13 +43,13 @@ class ClickHouseExecutionStoreIT {
|
||||
jdbc.execute("TRUNCATE TABLE executions");
|
||||
jdbc.execute("TRUNCATE TABLE processor_executions");
|
||||
|
||||
store = new ClickHouseExecutionStore(jdbc);
|
||||
store = new ClickHouseExecutionStore("default", jdbc);
|
||||
}
|
||||
|
||||
@Test
|
||||
void insertExecutionBatch_writesToClickHouse() {
|
||||
MergedExecution exec = new MergedExecution(
|
||||
"default", 1L, "exec-1", "route-a", "agent-1", "my-app",
|
||||
"default", 1L, "exec-1", "route-a", "agent-1", "my-app", "default",
|
||||
"COMPLETED", "corr-1", "exchange-1",
|
||||
Instant.parse("2026-03-31T10:00:00Z"),
|
||||
Instant.parse("2026-03-31T10:00:01Z"),
|
||||
@@ -181,7 +181,7 @@ class ClickHouseExecutionStoreIT {
|
||||
@Test
|
||||
void insertExecutionBatch_replacingMergeTree_keepsLatestVersion() {
|
||||
MergedExecution v1 = new MergedExecution(
|
||||
"default", 1L, "exec-r", "route-a", "agent-1", "my-app",
|
||||
"default", 1L, "exec-r", "route-a", "agent-1", "my-app", "default",
|
||||
"RUNNING", "corr-1", "exchange-1",
|
||||
Instant.parse("2026-03-31T10:00:00Z"),
|
||||
null, null,
|
||||
@@ -194,7 +194,7 @@ class ClickHouseExecutionStoreIT {
|
||||
);
|
||||
|
||||
MergedExecution v2 = new MergedExecution(
|
||||
"default", 2L, "exec-r", "route-a", "agent-1", "my-app",
|
||||
"default", 2L, "exec-r", "route-a", "agent-1", "my-app", "default",
|
||||
"COMPLETED", "corr-1", "exchange-1",
|
||||
Instant.parse("2026-03-31T10:00:00Z"),
|
||||
Instant.parse("2026-03-31T10:00:05Z"),
|
||||
|
||||
@@ -60,7 +60,7 @@ class ClickHouseMetricsQueryStoreIT {
|
||||
"agent-1", "memory.free", 1000.0 - i * 100, java.sql.Timestamp.from(ts));
|
||||
}
|
||||
|
||||
queryStore = new ClickHouseMetricsQueryStore(jdbc);
|
||||
queryStore = new ClickHouseMetricsQueryStore("default", jdbc);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -51,7 +51,7 @@ class ClickHouseMetricsStoreIT {
|
||||
|
||||
jdbc.execute("TRUNCATE TABLE agent_metrics");
|
||||
|
||||
store = new ClickHouseMetricsStore(jdbc);
|
||||
store = new ClickHouseMetricsStore("default", jdbc);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -75,7 +75,7 @@ class ClickHouseStatsStoreIT {
|
||||
System.out.println("LOG: " + entry.get("type") + " | " + entry.get("q"));
|
||||
}
|
||||
|
||||
store = new ClickHouseStatsStore(jdbc);
|
||||
store = new ClickHouseStatsStore("default", jdbc);
|
||||
}
|
||||
|
||||
private void seedTestData() {
|
||||
|
||||
Reference in New Issue
Block a user