feat(license): LicenseUsageReader aggregates current usage
One COUNT per entity table; one SUM-grouped query over non-stopped deployments for compute caps. SQL traverses deployed_config_snapshot->'containerConfig' (corrected from the plan's top-level path; the snapshot record nests containerConfig under that key). agentCount is fed in by the controller since it's an in-memory registry value, not a DB row. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
package com.cameleer.server.app.license;
|
||||
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Read-side usage snapshot used by the /api/v1/admin/license/usage endpoint and license metrics.
|
||||
*
|
||||
* <p>Counts come straight from PostgreSQL row counts; compute aggregates SUM over
|
||||
* non-stopped deployments and read replica/cpu/memory from the
|
||||
* {@code deployed_config_snapshot.containerConfig} JSONB sub-object. Pre-RUNNING deployments
|
||||
* (STARTING with no snapshot yet) contribute defaults (1 replica, 0 cpu, 0 memory) until they
|
||||
* roll forward.</p>
|
||||
*
|
||||
* <p>{@code max_agents} is not in PG — the registry is in-memory; callers feed the live count
|
||||
* into {@link #agentCount(int)} which echoes it for assembly into the snapshot map.</p>
|
||||
*/
|
||||
@Component
|
||||
public class LicenseUsageReader {
|
||||
|
||||
private final JdbcTemplate jdbc;
|
||||
|
||||
public LicenseUsageReader(JdbcTemplate jdbc) {
|
||||
this.jdbc = jdbc;
|
||||
}
|
||||
|
||||
public Map<String, Long> snapshot() {
|
||||
Map<String, Long> out = new LinkedHashMap<>();
|
||||
out.put("max_environments", count("environments"));
|
||||
out.put("max_apps", count("apps"));
|
||||
out.put("max_users", count("users"));
|
||||
out.put("max_outbound_connections", count("outbound_connections"));
|
||||
out.put("max_alert_rules", count("alert_rules"));
|
||||
Map<String, Long> compute = jdbc.queryForObject(
|
||||
"SELECT " +
|
||||
" COALESCE(SUM(replicas * cpu_millis), 0) AS cpu, " +
|
||||
" COALESCE(SUM(replicas * memory_mb), 0) AS mem, " +
|
||||
" COALESCE(SUM(replicas), 0) AS reps " +
|
||||
"FROM ( " +
|
||||
" SELECT " +
|
||||
" COALESCE((d.deployed_config_snapshot->'containerConfig'->>'replicas')::int, 1) AS replicas, " +
|
||||
" COALESCE((d.deployed_config_snapshot->'containerConfig'->>'cpuLimit')::int, 0) AS cpu_millis, " +
|
||||
" COALESCE((d.deployed_config_snapshot->'containerConfig'->>'memoryLimitMb')::int, 0) AS memory_mb " +
|
||||
" FROM deployments d " +
|
||||
" WHERE d.status IN ('STARTING','RUNNING','DEGRADED','STOPPING') " +
|
||||
") s",
|
||||
(rs, n) -> Map.of(
|
||||
"max_total_cpu_millis", rs.getLong("cpu"),
|
||||
"max_total_memory_mb", rs.getLong("mem"),
|
||||
"max_total_replicas", rs.getLong("reps")
|
||||
));
|
||||
out.putAll(compute);
|
||||
return out;
|
||||
}
|
||||
|
||||
/** Echoes the live agent count fed in by the controller (registry is in-memory). */
|
||||
public long agentCount(int liveAgents) {
|
||||
return liveAgents;
|
||||
}
|
||||
|
||||
private long count(String table) {
|
||||
return jdbc.queryForObject("SELECT COUNT(*) FROM " + table, Long.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.cameleer.server.app.license;
|
||||
|
||||
import com.cameleer.server.app.AbstractPostgresIT;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class LicenseUsageReaderIT extends AbstractPostgresIT {
|
||||
|
||||
@Autowired LicenseUsageReader reader;
|
||||
|
||||
@Test
|
||||
void emptyDb_returnsZeros() {
|
||||
var snap = reader.snapshot();
|
||||
assertThat(snap.get("max_apps")).isEqualTo(0L);
|
||||
assertThat(snap.get("max_environments")).isLessThanOrEqualTo(1L); // V1 seeds default env
|
||||
assertThat(snap.get("max_total_cpu_millis")).isEqualTo(0L);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user