From 160a989f9fd2c35b661049219bfa0ef45bd353ec Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Tue, 7 Apr 2026 23:56:21 +0200 Subject: [PATCH] feat: remove all ClickHouse dependencies from SaaS layer - Delete log/ package (ClickHouseConfig, ContainerLogService, LogController) - Delete observability/ package (AgentStatusService, AgentStatusController) - Remove clickhouse-jdbc dependency from pom.xml - Remove cameleer.clickhouse config section from application.yml - Delete associated test files Co-Authored-By: Claude Opus 4.6 (1M context) --- pom.xml | 8 - .../cameleer/saas/log/ClickHouseConfig.java | 48 ------ .../saas/log/ClickHouseProperties.java | 24 --- .../saas/log/ContainerLogService.java | 137 ------------------ .../cameleer/saas/log/LogController.java | 38 ----- .../cameleer/saas/log/dto/LogEntry.java | 8 - .../observability/AgentStatusController.java | 42 ------ .../observability/AgentStatusService.java | 133 ----------------- .../ConnectivityHealthCheck.java | 48 ------ .../dto/AgentStatusResponse.java | 13 -- .../dto/ObservabilityStatusResponse.java | 11 -- .../dto/UpdateRoutingRequest.java | 5 - src/main/resources/application.yml | 5 - .../saas/log/ContainerLogServiceTest.java | 20 --- .../observability/AgentStatusServiceTest.java | 95 ------------ 15 files changed, 635 deletions(-) delete mode 100644 src/main/java/net/siegeln/cameleer/saas/log/ClickHouseConfig.java delete mode 100644 src/main/java/net/siegeln/cameleer/saas/log/ClickHouseProperties.java delete mode 100644 src/main/java/net/siegeln/cameleer/saas/log/ContainerLogService.java delete mode 100644 src/main/java/net/siegeln/cameleer/saas/log/LogController.java delete mode 100644 src/main/java/net/siegeln/cameleer/saas/log/dto/LogEntry.java delete mode 100644 src/main/java/net/siegeln/cameleer/saas/observability/AgentStatusController.java delete mode 100644 src/main/java/net/siegeln/cameleer/saas/observability/AgentStatusService.java delete mode 100644 src/main/java/net/siegeln/cameleer/saas/observability/ConnectivityHealthCheck.java delete mode 100644 src/main/java/net/siegeln/cameleer/saas/observability/dto/AgentStatusResponse.java delete mode 100644 src/main/java/net/siegeln/cameleer/saas/observability/dto/ObservabilityStatusResponse.java delete mode 100644 src/main/java/net/siegeln/cameleer/saas/observability/dto/UpdateRoutingRequest.java delete mode 100644 src/test/java/net/siegeln/cameleer/saas/log/ContainerLogServiceTest.java delete mode 100644 src/test/java/net/siegeln/cameleer/saas/observability/AgentStatusServiceTest.java diff --git a/pom.xml b/pom.xml index 0f02c0a..7acca8a 100644 --- a/pom.xml +++ b/pom.xml @@ -92,14 +92,6 @@ 3.4.1 - - - com.clickhouse - clickhouse-jdbc - 0.7.1 - all - - org.springframework.boot diff --git a/src/main/java/net/siegeln/cameleer/saas/log/ClickHouseConfig.java b/src/main/java/net/siegeln/cameleer/saas/log/ClickHouseConfig.java deleted file mode 100644 index 64a8888..0000000 --- a/src/main/java/net/siegeln/cameleer/saas/log/ClickHouseConfig.java +++ /dev/null @@ -1,48 +0,0 @@ -package net.siegeln.cameleer.saas.log; - -import com.zaxxer.hikari.HikariDataSource; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Primary; -import org.springframework.jdbc.core.JdbcTemplate; - -import javax.sql.DataSource; - -@Configuration -@EnableConfigurationProperties(ClickHouseProperties.class) -@ConditionalOnProperty(name = "cameleer.clickhouse.enabled", havingValue = "true", matchIfMissing = true) -public class ClickHouseConfig { - - /** - * Explicit primary PG DataSource. Required because adding a second DataSource - * (ClickHouse) prevents Spring Boot auto-configuration from creating the default one. - */ - @Bean - @Primary - public DataSource dataSource(DataSourceProperties properties) { - return properties.initializeDataSourceBuilder().build(); - } - - @Bean - @Primary - public JdbcTemplate jdbcTemplate(@Qualifier("dataSource") DataSource dataSource) { - return new JdbcTemplate(dataSource); - } - - @Bean(name = "clickHouseDataSource") - public DataSource clickHouseDataSource(ClickHouseProperties props) { - HikariDataSource ds = new HikariDataSource(); - ds.setJdbcUrl(props.getUrl()); - ds.setUsername(props.getUsername()); - ds.setPassword(props.getPassword()); - ds.setMaximumPoolSize(10); - ds.setMinimumIdle(2); - ds.setConnectionTimeout(5000); - ds.setPoolName("clickhouse-pool"); - return ds; - } -} diff --git a/src/main/java/net/siegeln/cameleer/saas/log/ClickHouseProperties.java b/src/main/java/net/siegeln/cameleer/saas/log/ClickHouseProperties.java deleted file mode 100644 index 1978f5f..0000000 --- a/src/main/java/net/siegeln/cameleer/saas/log/ClickHouseProperties.java +++ /dev/null @@ -1,24 +0,0 @@ -package net.siegeln.cameleer.saas.log; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -@ConfigurationProperties(prefix = "cameleer.clickhouse") -public class ClickHouseProperties { - - private boolean enabled = true; - private String url = "jdbc:clickhouse://clickhouse:8123/cameleer"; - private String username = "default"; - private String password = ""; - - public boolean isEnabled() { return enabled; } - public void setEnabled(boolean enabled) { this.enabled = enabled; } - - public String getUrl() { return url; } - public void setUrl(String url) { this.url = url; } - - public String getUsername() { return username; } - public void setUsername(String username) { this.username = username; } - - public String getPassword() { return password; } - public void setPassword(String password) { this.password = password; } -} diff --git a/src/main/java/net/siegeln/cameleer/saas/log/ContainerLogService.java b/src/main/java/net/siegeln/cameleer/saas/log/ContainerLogService.java deleted file mode 100644 index e4fe00c..0000000 --- a/src/main/java/net/siegeln/cameleer/saas/log/ContainerLogService.java +++ /dev/null @@ -1,137 +0,0 @@ -package net.siegeln.cameleer.saas.log; - -import net.siegeln.cameleer.saas.log.dto.LogEntry; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.stereotype.Service; - -import javax.sql.DataSource; -import java.sql.Timestamp; -import java.time.Instant; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; -import java.util.concurrent.ConcurrentLinkedQueue; - -@Service -public class ContainerLogService { - - private static final Logger log = LoggerFactory.getLogger(ContainerLogService.class); - private static final int FLUSH_THRESHOLD = 100; - - private final DataSource clickHouseDataSource; - private final ConcurrentLinkedQueue buffer = new ConcurrentLinkedQueue<>(); - - @Autowired - public ContainerLogService( - @Autowired(required = false) @Qualifier("clickHouseDataSource") DataSource clickHouseDataSource) { - this.clickHouseDataSource = clickHouseDataSource; - if (clickHouseDataSource == null) { - log.warn("ClickHouse data source not available — ContainerLogService running in no-op mode"); - } else { - initSchema(); - } - } - - void initSchema() { - if (clickHouseDataSource == null) return; - try (var conn = clickHouseDataSource.getConnection(); - var stmt = conn.createStatement()) { - stmt.execute(""" - CREATE TABLE IF NOT EXISTS container_logs ( - tenant_id UUID, - environment_id UUID, - app_id UUID, - deployment_id UUID, - timestamp DateTime64(3), - stream String, - message String - ) ENGINE = MergeTree() - ORDER BY (tenant_id, environment_id, app_id, timestamp) - """); - } catch (Exception e) { - log.error("Failed to initialize ClickHouse schema", e); - } - } - - public void write(UUID tenantId, UUID envId, UUID appId, UUID deploymentId, - String stream, String message, long timestampMillis) { - if (clickHouseDataSource == null) return; - buffer.add(new Object[]{tenantId, envId, appId, deploymentId, timestampMillis, stream, message}); - if (buffer.size() >= FLUSH_THRESHOLD) { - flush(); - } - } - - public void flush() { - if (clickHouseDataSource == null || buffer.isEmpty()) return; - List batch = new ArrayList<>(FLUSH_THRESHOLD); - Object[] row; - while ((row = buffer.poll()) != null) { - batch.add(row); - } - if (batch.isEmpty()) return; - String sql = "INSERT INTO container_logs (tenant_id, environment_id, app_id, deployment_id, timestamp, stream, message) VALUES (?, ?, ?, ?, ?, ?, ?)"; - try (var conn = clickHouseDataSource.getConnection(); - var ps = conn.prepareStatement(sql)) { - for (Object[] entry : batch) { - ps.setObject(1, entry[0]); // tenant_id - ps.setObject(2, entry[1]); // environment_id - ps.setObject(3, entry[2]); // app_id - ps.setObject(4, entry[3]); // deployment_id - ps.setTimestamp(5, new Timestamp((Long) entry[4])); - ps.setString(6, (String) entry[5]); - ps.setString(7, (String) entry[6]); - ps.addBatch(); - } - ps.executeBatch(); - } catch (Exception e) { - log.error("Failed to flush log batch to ClickHouse ({} entries)", batch.size(), e); - } - } - - public List query(UUID appId, Instant since, Instant until, int limit, String stream) { - if (clickHouseDataSource == null) return List.of(); - StringBuilder sql = new StringBuilder( - "SELECT app_id, deployment_id, timestamp, stream, message FROM container_logs WHERE app_id = ?"); - List params = new ArrayList<>(); - params.add(appId); - if (since != null) { - sql.append(" AND timestamp >= ?"); - params.add(Timestamp.from(since)); - } - if (until != null) { - sql.append(" AND timestamp <= ?"); - params.add(Timestamp.from(until)); - } - if (stream != null && !"both".equalsIgnoreCase(stream)) { - sql.append(" AND stream = ?"); - params.add(stream); - } - sql.append(" ORDER BY timestamp LIMIT ?"); - params.add(limit); - List results = new ArrayList<>(); - try (var conn = clickHouseDataSource.getConnection(); - var ps = conn.prepareStatement(sql.toString())) { - for (int i = 0; i < params.size(); i++) { - ps.setObject(i + 1, params.get(i)); - } - try (var rs = ps.executeQuery()) { - while (rs.next()) { - results.add(new LogEntry( - UUID.fromString(rs.getString("app_id")), - UUID.fromString(rs.getString("deployment_id")), - rs.getTimestamp("timestamp").toInstant(), - rs.getString("stream"), - rs.getString("message") - )); - } - } - } catch (Exception e) { - log.error("Failed to query container logs for appId={}", appId, e); - } - return results; - } -} diff --git a/src/main/java/net/siegeln/cameleer/saas/log/LogController.java b/src/main/java/net/siegeln/cameleer/saas/log/LogController.java deleted file mode 100644 index 0431551..0000000 --- a/src/main/java/net/siegeln/cameleer/saas/log/LogController.java +++ /dev/null @@ -1,38 +0,0 @@ -package net.siegeln.cameleer.saas.log; - -import net.siegeln.cameleer.saas.log.dto.LogEntry; -import org.springframework.format.annotation.DateTimeFormat; -import org.springframework.http.ResponseEntity; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -import java.time.Instant; -import java.util.List; -import java.util.UUID; - -@RestController -@RequestMapping("/api/apps/{appId}/logs") -public class LogController { - - private final ContainerLogService containerLogService; - - public LogController(ContainerLogService containerLogService) { - this.containerLogService = containerLogService; - } - - @GetMapping - @PreAuthorize("hasAuthority('SCOPE_observe:read')") - public ResponseEntity> query( - @PathVariable UUID appId, - @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Instant since, - @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Instant until, - @RequestParam(defaultValue = "500") int limit, - @RequestParam(defaultValue = "both") String stream) { - List entries = containerLogService.query(appId, since, until, limit, stream); - return ResponseEntity.ok(entries); - } -} diff --git a/src/main/java/net/siegeln/cameleer/saas/log/dto/LogEntry.java b/src/main/java/net/siegeln/cameleer/saas/log/dto/LogEntry.java deleted file mode 100644 index 2c7afd0..0000000 --- a/src/main/java/net/siegeln/cameleer/saas/log/dto/LogEntry.java +++ /dev/null @@ -1,8 +0,0 @@ -package net.siegeln.cameleer.saas.log.dto; - -import java.time.Instant; -import java.util.UUID; - -public record LogEntry( - UUID appId, UUID deploymentId, Instant timestamp, String stream, String message -) {} diff --git a/src/main/java/net/siegeln/cameleer/saas/observability/AgentStatusController.java b/src/main/java/net/siegeln/cameleer/saas/observability/AgentStatusController.java deleted file mode 100644 index 934e068..0000000 --- a/src/main/java/net/siegeln/cameleer/saas/observability/AgentStatusController.java +++ /dev/null @@ -1,42 +0,0 @@ -package net.siegeln.cameleer.saas.observability; - -import net.siegeln.cameleer.saas.observability.dto.AgentStatusResponse; -import net.siegeln.cameleer.saas.observability.dto.ObservabilityStatusResponse; -import org.springframework.http.ResponseEntity; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.bind.annotation.*; - -import java.util.UUID; - -@RestController -@RequestMapping("/api/apps/{appId}") -public class AgentStatusController { - - private final AgentStatusService agentStatusService; - - public AgentStatusController(AgentStatusService agentStatusService) { - this.agentStatusService = agentStatusService; - } - - @GetMapping("/agent-status") - @PreAuthorize("hasAuthority('SCOPE_observe:read')") - public ResponseEntity getAgentStatus(@PathVariable UUID appId) { - - try { - return ResponseEntity.ok(agentStatusService.getAgentStatus(appId)); - } catch (IllegalArgumentException e) { - return ResponseEntity.notFound().build(); - } - } - - @GetMapping("/observability-status") - @PreAuthorize("hasAuthority('SCOPE_observe:read')") - public ResponseEntity getObservabilityStatus(@PathVariable UUID appId) { - - try { - return ResponseEntity.ok(agentStatusService.getObservabilityStatus(appId)); - } catch (IllegalArgumentException e) { - return ResponseEntity.notFound().build(); - } - } -} diff --git a/src/main/java/net/siegeln/cameleer/saas/observability/AgentStatusService.java b/src/main/java/net/siegeln/cameleer/saas/observability/AgentStatusService.java deleted file mode 100644 index de760a7..0000000 --- a/src/main/java/net/siegeln/cameleer/saas/observability/AgentStatusService.java +++ /dev/null @@ -1,133 +0,0 @@ -package net.siegeln.cameleer.saas.observability; - -import net.siegeln.cameleer.saas.app.AppRepository; -import net.siegeln.cameleer.saas.environment.EnvironmentRepository; -import net.siegeln.cameleer.saas.identity.ServerApiClient; -import net.siegeln.cameleer.saas.observability.dto.AgentStatusResponse; -import net.siegeln.cameleer.saas.observability.dto.ObservabilityStatusResponse; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.stereotype.Service; - -import javax.sql.DataSource; -import java.sql.Timestamp; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.UUID; - -@Service -public class AgentStatusService { - - private static final Logger log = LoggerFactory.getLogger(AgentStatusService.class); - - private final AppRepository appRepository; - private final EnvironmentRepository environmentRepository; - private final ServerApiClient serverApiClient; - - @Autowired(required = false) - @Qualifier("clickHouseDataSource") - private DataSource clickHouseDataSource; - - public AgentStatusService(AppRepository appRepository, - EnvironmentRepository environmentRepository, - ServerApiClient serverApiClient) { - this.appRepository = appRepository; - this.environmentRepository = environmentRepository; - this.serverApiClient = serverApiClient; - } - - public AgentStatusResponse getAgentStatus(UUID appId) { - var app = appRepository.findById(appId) - .orElseThrow(() -> new IllegalArgumentException("App not found: " + appId)); - - var env = environmentRepository.findById(app.getEnvironmentId()) - .orElseThrow(() -> new IllegalArgumentException("Environment not found: " + app.getEnvironmentId())); - - if (!serverApiClient.isAvailable()) { - return unknownStatus(app.getSlug(), env.getSlug()); - } - - try { - @SuppressWarnings("unchecked") - List> agents = serverApiClient.get("/api/v1/agents") - .retrieve() - .body(List.class); - - if (agents == null) { - return unknownStatus(app.getSlug(), env.getSlug()); - } - - for (Map agent : agents) { - String agentAppId = (String) agent.get("applicationId"); - String agentEnvId = (String) agent.get("environmentId"); - if (app.getSlug().equals(agentAppId) && env.getSlug().equals(agentEnvId)) { - String state = (String) agent.getOrDefault("state", "UNKNOWN"); - @SuppressWarnings("unchecked") - List routeIds = (List) agent.getOrDefault("routeIds", Collections.emptyList()); - return new AgentStatusResponse( - true, - state, - null, - routeIds, - app.getSlug(), - env.getSlug() - ); - } - } - - return unknownStatus(app.getSlug(), env.getSlug()); - - } catch (Exception e) { - log.warn("Failed to fetch agent status from cameleer3-server: {}", e.getMessage()); - return unknownStatus(app.getSlug(), env.getSlug()); - } - } - - public ObservabilityStatusResponse getObservabilityStatus(UUID appId) { - var app = appRepository.findById(appId) - .orElseThrow(() -> new IllegalArgumentException("App not found: " + appId)); - - var env = environmentRepository.findById(app.getEnvironmentId()) - .orElseThrow(() -> new IllegalArgumentException("Environment not found: " + app.getEnvironmentId())); - - if (clickHouseDataSource == null) { - return new ObservabilityStatusResponse(false, false, false, null, 0); - } - - try (var conn = clickHouseDataSource.getConnection()) { - String sql = "SELECT count() as cnt, max(start_time) as last_trace " + - "FROM executions " + - "WHERE application_id = ? AND environment_id = ? " + - "AND start_time >= now() - INTERVAL 24 HOUR"; - try (var stmt = conn.prepareStatement(sql)) { - stmt.setString(1, app.getSlug()); - stmt.setString(2, env.getSlug()); - try (var rs = stmt.executeQuery()) { - if (rs.next()) { - long count = rs.getLong("cnt"); - Timestamp lastTrace = rs.getTimestamp("last_trace"); - boolean hasTraces = count > 0; - return new ObservabilityStatusResponse( - hasTraces, - false, - false, - hasTraces && lastTrace != null ? lastTrace.toInstant() : null, - count - ); - } - } - } - } catch (Exception e) { - log.warn("Failed to query ClickHouse for observability status: {}", e.getMessage()); - } - - return new ObservabilityStatusResponse(false, false, false, null, 0); - } - - private AgentStatusResponse unknownStatus(String applicationId, String environmentId) { - return new AgentStatusResponse(false, "UNKNOWN", null, Collections.emptyList(), applicationId, environmentId); - } -} diff --git a/src/main/java/net/siegeln/cameleer/saas/observability/ConnectivityHealthCheck.java b/src/main/java/net/siegeln/cameleer/saas/observability/ConnectivityHealthCheck.java deleted file mode 100644 index 3935850..0000000 --- a/src/main/java/net/siegeln/cameleer/saas/observability/ConnectivityHealthCheck.java +++ /dev/null @@ -1,48 +0,0 @@ -package net.siegeln.cameleer.saas.observability; - -import net.siegeln.cameleer.saas.runtime.RuntimeConfig; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.boot.context.event.ApplicationReadyEvent; -import org.springframework.context.event.EventListener; -import org.springframework.stereotype.Component; -import org.springframework.web.client.RestClient; - -@Component -public class ConnectivityHealthCheck { - - private static final Logger log = LoggerFactory.getLogger(ConnectivityHealthCheck.class); - - private final RuntimeConfig runtimeConfig; - - public ConnectivityHealthCheck(RuntimeConfig runtimeConfig) { - this.runtimeConfig = runtimeConfig; - } - - @EventListener(ApplicationReadyEvent.class) - public void verifyConnectivity() { - checkCameleer3Server(); - } - - private void checkCameleer3Server() { - try { - var client = RestClient.builder() - .baseUrl(runtimeConfig.getCameleer3ServerEndpoint()) - .build(); - var response = client.get() - .uri("/api/v1/health") - .retrieve() - .toBodilessEntity(); - if (response.getStatusCode().is2xxSuccessful()) { - log.info("cameleer3-server connectivity: OK ({})", - runtimeConfig.getCameleer3ServerEndpoint()); - } else { - log.warn("cameleer3-server connectivity: HTTP {} ({})", - response.getStatusCode(), runtimeConfig.getCameleer3ServerEndpoint()); - } - } catch (Exception e) { - log.warn("cameleer3-server connectivity: FAILED ({}) - {}", - runtimeConfig.getCameleer3ServerEndpoint(), e.getMessage()); - } - } -} diff --git a/src/main/java/net/siegeln/cameleer/saas/observability/dto/AgentStatusResponse.java b/src/main/java/net/siegeln/cameleer/saas/observability/dto/AgentStatusResponse.java deleted file mode 100644 index 2749aff..0000000 --- a/src/main/java/net/siegeln/cameleer/saas/observability/dto/AgentStatusResponse.java +++ /dev/null @@ -1,13 +0,0 @@ -package net.siegeln.cameleer.saas.observability.dto; - -import java.time.Instant; -import java.util.List; - -public record AgentStatusResponse( - boolean registered, - String state, - Instant lastHeartbeat, - List routeIds, - String applicationId, - String environmentId -) {} diff --git a/src/main/java/net/siegeln/cameleer/saas/observability/dto/ObservabilityStatusResponse.java b/src/main/java/net/siegeln/cameleer/saas/observability/dto/ObservabilityStatusResponse.java deleted file mode 100644 index cfbb574..0000000 --- a/src/main/java/net/siegeln/cameleer/saas/observability/dto/ObservabilityStatusResponse.java +++ /dev/null @@ -1,11 +0,0 @@ -package net.siegeln.cameleer.saas.observability.dto; - -import java.time.Instant; - -public record ObservabilityStatusResponse( - boolean hasTraces, - boolean hasMetrics, - boolean hasDiagrams, - Instant lastTraceAt, - long traceCount24h -) {} diff --git a/src/main/java/net/siegeln/cameleer/saas/observability/dto/UpdateRoutingRequest.java b/src/main/java/net/siegeln/cameleer/saas/observability/dto/UpdateRoutingRequest.java deleted file mode 100644 index 385e78f..0000000 --- a/src/main/java/net/siegeln/cameleer/saas/observability/dto/UpdateRoutingRequest.java +++ /dev/null @@ -1,5 +0,0 @@ -package net.siegeln.cameleer.saas.observability.dto; - -public record UpdateRoutingRequest( - Integer exposedPort -) {} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index a0daed8..e742392 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -56,8 +56,3 @@ cameleer: container-cpu-shares: ${CAMELEER_CONTAINER_CPU_SHARES:512} cameleer3-server-endpoint: ${CAMELEER3_SERVER_ENDPOINT:http://cameleer3-server:8081} domain: ${DOMAIN:localhost} - clickhouse: - enabled: ${CLICKHOUSE_ENABLED:true} - url: ${CLICKHOUSE_URL:jdbc:clickhouse://clickhouse:8123/cameleer} - username: ${CLICKHOUSE_USERNAME:default} - password: ${CLICKHOUSE_PASSWORD:} diff --git a/src/test/java/net/siegeln/cameleer/saas/log/ContainerLogServiceTest.java b/src/test/java/net/siegeln/cameleer/saas/log/ContainerLogServiceTest.java deleted file mode 100644 index 62c3342..0000000 --- a/src/test/java/net/siegeln/cameleer/saas/log/ContainerLogServiceTest.java +++ /dev/null @@ -1,20 +0,0 @@ -package net.siegeln.cameleer.saas.log; - -import org.junit.jupiter.api.Test; - -import java.util.concurrent.ConcurrentLinkedQueue; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -class ContainerLogServiceTest { - - @Test - void buffer_shouldAccumulateEntries() { - var buffer = new ConcurrentLinkedQueue(); - buffer.add("entry1"); - buffer.add("entry2"); - assertEquals(2, buffer.size()); - assertEquals("entry1", buffer.poll()); - assertEquals(1, buffer.size()); - } -} diff --git a/src/test/java/net/siegeln/cameleer/saas/observability/AgentStatusServiceTest.java b/src/test/java/net/siegeln/cameleer/saas/observability/AgentStatusServiceTest.java deleted file mode 100644 index 6443618..0000000 --- a/src/test/java/net/siegeln/cameleer/saas/observability/AgentStatusServiceTest.java +++ /dev/null @@ -1,95 +0,0 @@ -package net.siegeln.cameleer.saas.observability; - -import net.siegeln.cameleer.saas.app.AppEntity; -import net.siegeln.cameleer.saas.app.AppRepository; -import net.siegeln.cameleer.saas.environment.EnvironmentEntity; -import net.siegeln.cameleer.saas.environment.EnvironmentRepository; -import net.siegeln.cameleer.saas.identity.ServerApiClient; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.mockito.junit.jupiter.MockitoSettings; -import org.mockito.quality.Strictness; - -import java.util.Optional; -import java.util.UUID; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; - -@ExtendWith(MockitoExtension.class) -@MockitoSettings(strictness = Strictness.LENIENT) -class AgentStatusServiceTest { - - @Mock private AppRepository appRepository; - @Mock private EnvironmentRepository environmentRepository; - @Mock private ServerApiClient serverApiClient; - - private AgentStatusService agentStatusService; - - @BeforeEach - void setUp() { - when(serverApiClient.isAvailable()).thenReturn(false); - agentStatusService = new AgentStatusService(appRepository, environmentRepository, serverApiClient); - } - - @Test - void getAgentStatus_appNotFound_shouldThrow() { - when(appRepository.findById(any())).thenReturn(Optional.empty()); - assertThrows(IllegalArgumentException.class, - () -> agentStatusService.getAgentStatus(UUID.randomUUID())); - } - - @Test - void getAgentStatus_shouldReturnUnknownWhenServerUnreachable() { - var appId = UUID.randomUUID(); - var envId = UUID.randomUUID(); - - var app = new AppEntity(); - app.setId(appId); - app.setEnvironmentId(envId); - app.setSlug("my-app"); - when(appRepository.findById(appId)).thenReturn(Optional.of(app)); - - var env = new EnvironmentEntity(); - env.setId(envId); - env.setSlug("default"); - when(environmentRepository.findById(envId)).thenReturn(Optional.of(env)); - - // Server at localhost:9999 won't be running — should return UNKNOWN gracefully - var result = agentStatusService.getAgentStatus(appId); - - assertNotNull(result); - assertFalse(result.registered()); - assertEquals("UNKNOWN", result.state()); - assertEquals("my-app", result.applicationId()); - assertEquals("default", result.environmentId()); - } - - @Test - void getObservabilityStatus_shouldReturnEmptyWhenClickHouseUnavailable() { - var appId = UUID.randomUUID(); - var envId = UUID.randomUUID(); - - var app = new AppEntity(); - app.setId(appId); - app.setEnvironmentId(envId); - app.setSlug("my-app"); - when(appRepository.findById(appId)).thenReturn(Optional.of(app)); - - var env = new EnvironmentEntity(); - env.setId(envId); - env.setSlug("default"); - when(environmentRepository.findById(envId)).thenReturn(Optional.of(env)); - - // No ClickHouse DataSource injected — should return empty status - var result = agentStatusService.getObservabilityStatus(appId); - - assertNotNull(result); - assertFalse(result.hasTraces()); - assertEquals(0, result.traceCount24h()); - } -}