feat(alerting): V12 flyway migration for alerting tables
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
-- V12 — Alerting tables
|
||||
-- Enums (outbound_method_enum / outbound_auth_kind_enum / trust_mode_enum already exist from V11)
|
||||
CREATE TYPE severity_enum AS ENUM ('CRITICAL','WARNING','INFO');
|
||||
CREATE TYPE condition_kind_enum AS ENUM ('ROUTE_METRIC','EXCHANGE_MATCH','AGENT_STATE','DEPLOYMENT_STATE','LOG_PATTERN','JVM_METRIC');
|
||||
CREATE TYPE alert_state_enum AS ENUM ('PENDING','FIRING','ACKNOWLEDGED','RESOLVED');
|
||||
CREATE TYPE target_kind_enum AS ENUM ('USER','GROUP','ROLE');
|
||||
CREATE TYPE notification_status_enum AS ENUM ('PENDING','DELIVERED','FAILED');
|
||||
|
||||
CREATE TABLE alert_rules (
|
||||
id uuid PRIMARY KEY,
|
||||
environment_id uuid NOT NULL REFERENCES environments(id) ON DELETE CASCADE,
|
||||
name varchar(200) NOT NULL,
|
||||
description text,
|
||||
severity severity_enum NOT NULL,
|
||||
enabled boolean NOT NULL DEFAULT true,
|
||||
condition_kind condition_kind_enum NOT NULL,
|
||||
condition jsonb NOT NULL,
|
||||
evaluation_interval_seconds int NOT NULL DEFAULT 60 CHECK (evaluation_interval_seconds >= 5),
|
||||
for_duration_seconds int NOT NULL DEFAULT 0 CHECK (for_duration_seconds >= 0),
|
||||
re_notify_minutes int NOT NULL DEFAULT 60 CHECK (re_notify_minutes >= 0),
|
||||
notification_title_tmpl text NOT NULL,
|
||||
notification_message_tmpl text NOT NULL,
|
||||
webhooks jsonb NOT NULL DEFAULT '[]',
|
||||
next_evaluation_at timestamptz NOT NULL DEFAULT now(),
|
||||
claimed_by varchar(64),
|
||||
claimed_until timestamptz,
|
||||
eval_state jsonb NOT NULL DEFAULT '{}',
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
created_by text NOT NULL REFERENCES users(user_id),
|
||||
updated_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_by text NOT NULL REFERENCES users(user_id)
|
||||
);
|
||||
CREATE INDEX alert_rules_env_idx ON alert_rules (environment_id);
|
||||
CREATE INDEX alert_rules_claim_due_idx ON alert_rules (next_evaluation_at) WHERE enabled = true;
|
||||
|
||||
CREATE TABLE alert_rule_targets (
|
||||
id uuid PRIMARY KEY,
|
||||
rule_id uuid NOT NULL REFERENCES alert_rules(id) ON DELETE CASCADE,
|
||||
target_kind target_kind_enum NOT NULL,
|
||||
target_id varchar(128) NOT NULL,
|
||||
UNIQUE (rule_id, target_kind, target_id)
|
||||
);
|
||||
CREATE INDEX alert_rule_targets_lookup_idx ON alert_rule_targets (target_kind, target_id);
|
||||
|
||||
CREATE TABLE alert_instances (
|
||||
id uuid PRIMARY KEY,
|
||||
rule_id uuid REFERENCES alert_rules(id) ON DELETE SET NULL,
|
||||
rule_snapshot jsonb NOT NULL,
|
||||
environment_id uuid NOT NULL REFERENCES environments(id) ON DELETE CASCADE,
|
||||
state alert_state_enum NOT NULL,
|
||||
severity severity_enum NOT NULL,
|
||||
fired_at timestamptz NOT NULL,
|
||||
acked_at timestamptz,
|
||||
acked_by text REFERENCES users(user_id),
|
||||
resolved_at timestamptz,
|
||||
last_notified_at timestamptz,
|
||||
silenced boolean NOT NULL DEFAULT false,
|
||||
current_value numeric,
|
||||
threshold numeric,
|
||||
context jsonb NOT NULL,
|
||||
title text NOT NULL,
|
||||
message text NOT NULL,
|
||||
target_user_ids text[] NOT NULL DEFAULT '{}',
|
||||
target_group_ids uuid[] NOT NULL DEFAULT '{}',
|
||||
target_role_names text[] NOT NULL DEFAULT '{}'
|
||||
);
|
||||
CREATE INDEX alert_instances_inbox_idx ON alert_instances (environment_id, state, fired_at DESC);
|
||||
CREATE INDEX alert_instances_open_rule_idx ON alert_instances (rule_id, state) WHERE rule_id IS NOT NULL;
|
||||
CREATE INDEX alert_instances_resolved_idx ON alert_instances (resolved_at) WHERE state = 'RESOLVED';
|
||||
CREATE INDEX alert_instances_target_u_idx ON alert_instances USING GIN (target_user_ids);
|
||||
CREATE INDEX alert_instances_target_g_idx ON alert_instances USING GIN (target_group_ids);
|
||||
CREATE INDEX alert_instances_target_r_idx ON alert_instances USING GIN (target_role_names);
|
||||
|
||||
CREATE TABLE alert_silences (
|
||||
id uuid PRIMARY KEY,
|
||||
environment_id uuid NOT NULL REFERENCES environments(id) ON DELETE CASCADE,
|
||||
matcher jsonb NOT NULL,
|
||||
reason text,
|
||||
starts_at timestamptz NOT NULL,
|
||||
ends_at timestamptz NOT NULL CHECK (ends_at > starts_at),
|
||||
created_by text NOT NULL REFERENCES users(user_id),
|
||||
created_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
CREATE INDEX alert_silences_active_idx ON alert_silences (environment_id, ends_at);
|
||||
|
||||
CREATE TABLE alert_notifications (
|
||||
id uuid PRIMARY KEY,
|
||||
alert_instance_id uuid NOT NULL REFERENCES alert_instances(id) ON DELETE CASCADE,
|
||||
webhook_id uuid,
|
||||
outbound_connection_id uuid REFERENCES outbound_connections(id) ON DELETE SET NULL,
|
||||
status notification_status_enum NOT NULL DEFAULT 'PENDING',
|
||||
attempts int NOT NULL DEFAULT 0,
|
||||
next_attempt_at timestamptz NOT NULL DEFAULT now(),
|
||||
claimed_by varchar(64),
|
||||
claimed_until timestamptz,
|
||||
last_response_status int,
|
||||
last_response_snippet text,
|
||||
payload jsonb NOT NULL,
|
||||
delivered_at timestamptz,
|
||||
created_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
CREATE INDEX alert_notifications_pending_idx ON alert_notifications (next_attempt_at) WHERE status = 'PENDING';
|
||||
CREATE INDEX alert_notifications_instance_idx ON alert_notifications (alert_instance_id);
|
||||
|
||||
CREATE TABLE alert_reads (
|
||||
user_id text NOT NULL REFERENCES users(user_id) ON DELETE CASCADE,
|
||||
alert_instance_id uuid NOT NULL REFERENCES alert_instances(id) ON DELETE CASCADE,
|
||||
read_at timestamptz NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (user_id, alert_instance_id)
|
||||
);
|
||||
@@ -0,0 +1,59 @@
|
||||
package com.cameleer.server.app.alerting.storage;
|
||||
|
||||
import com.cameleer.server.app.AbstractPostgresIT;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class V12MigrationIT extends AbstractPostgresIT {
|
||||
|
||||
@Test
|
||||
void allAlertingTablesAndEnumsExist() {
|
||||
var tables = jdbcTemplate.queryForList(
|
||||
"SELECT table_name FROM information_schema.tables WHERE table_schema='public' " +
|
||||
"AND table_name IN ('alert_rules','alert_rule_targets','alert_instances'," +
|
||||
"'alert_silences','alert_notifications','alert_reads')",
|
||||
String.class);
|
||||
assertThat(tables).containsExactlyInAnyOrder(
|
||||
"alert_rules","alert_rule_targets","alert_instances",
|
||||
"alert_silences","alert_notifications","alert_reads");
|
||||
|
||||
var enums = jdbcTemplate.queryForList(
|
||||
"SELECT typname FROM pg_type WHERE typname IN " +
|
||||
"('severity_enum','condition_kind_enum','alert_state_enum'," +
|
||||
"'target_kind_enum','notification_status_enum')",
|
||||
String.class);
|
||||
assertThat(enums).hasSize(5);
|
||||
}
|
||||
|
||||
@Test
|
||||
void deletingEnvironmentCascadesAlertingRows() {
|
||||
var envId = java.util.UUID.randomUUID();
|
||||
jdbcTemplate.update(
|
||||
"INSERT INTO environments (id, slug, display_name) VALUES (?, ?, ?)",
|
||||
envId, "test-cascade-env", "Test Cascade Env");
|
||||
jdbcTemplate.update(
|
||||
"INSERT INTO users (user_id, provider, email) " +
|
||||
"VALUES (?, ?, ?)", "u1", "local", "a@b.test");
|
||||
var ruleId = java.util.UUID.randomUUID();
|
||||
jdbcTemplate.update(
|
||||
"INSERT INTO alert_rules (id, environment_id, name, severity, condition_kind, condition, " +
|
||||
"notification_title_tmpl, notification_message_tmpl, created_by, updated_by) " +
|
||||
"VALUES (?, ?, 'r', 'WARNING', 'AGENT_STATE', '{}'::jsonb, 't', 'm', 'u1', 'u1')",
|
||||
ruleId, envId);
|
||||
var instanceId = java.util.UUID.randomUUID();
|
||||
jdbcTemplate.update(
|
||||
"INSERT INTO alert_instances (id, rule_id, rule_snapshot, environment_id, state, severity, " +
|
||||
"fired_at, context, title, message) VALUES (?, ?, '{}'::jsonb, ?, 'FIRING', 'WARNING', " +
|
||||
"now(), '{}'::jsonb, 't', 'm')",
|
||||
instanceId, ruleId, envId);
|
||||
|
||||
jdbcTemplate.update("DELETE FROM environments WHERE id = ?", envId);
|
||||
|
||||
assertThat(jdbcTemplate.queryForObject(
|
||||
"SELECT count(*) FROM alert_rules WHERE environment_id = ?",
|
||||
Integer.class, envId)).isZero();
|
||||
assertThat(jdbcTemplate.queryForObject(
|
||||
"SELECT count(*) FROM alert_instances WHERE environment_id = ?",
|
||||
Integer.class, envId)).isZero();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user