Netpulse_SasS/db/tests/smoke.sql
zotac 15d22a8d8a Етап 1-2: схема БД та protobuf-контракт агент-сервер
Схема PostgreSQL 16+/TimescaleDB: 11 міграцій, 7 схем, топологія
(neighbors -> links -> maps -> nodes/edges), time-series з CAGG,
NCM, alerting, білінг з entitlements, RLS.

Контракт agent<->server: 6 proto-файлів, gRPC, інтернування серій,
at-least-once з ack, чанкування конфігів.

Перевірено на стенді Debian 13 / PG 17.11 / TimescaleDB 2.29.1:
міграції + 8 функціональних перевірок схеми, buf lint + 5 наскрізних
gRPC-тестів контракту.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 03:28:40 +03:00

302 lines
16 KiB
PL/PgSQL
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- =====================================================================
-- NetPulse :: smoke.sql
-- Функціональна перевірка схеми на чистій БД після всіх міграцій.
-- Запуск: psql -d netpulse -v ON_ERROR_STOP=1 -f db/tests/smoke.sql
-- Виконувати від суперкористувача (частина перевірок робить SET ROLE).
-- =====================================================================
\set ON_ERROR_STOP on
\timing off
BEGIN;
-- ---------------------------------------------------------------------
-- 1. Два тенанти
-- ---------------------------------------------------------------------
INSERT INTO core.tenants (id, slug, name, status) VALUES
('11111111-1111-1111-1111-111111111111', 'acme', 'ACME ISP', 'active'),
('22222222-2222-2222-2222-222222222222', 'globex','Globex MSP', 'active');
INSERT INTO bill.entitlements (tenant_id, plan_key, max_devices, max_maps, max_map_nodes, features)
SELECT '11111111-1111-1111-1111-111111111111', key, max_devices, max_maps, max_map_nodes, features
FROM bill.plans WHERE key = 'free';
INSERT INTO bill.entitlements (tenant_id, plan_key, max_devices, max_maps, max_map_nodes, features)
SELECT '22222222-2222-2222-2222-222222222222', key, max_devices, max_maps, max_map_nodes, features
FROM bill.plans WHERE key = 'pro';
-- ---------------------------------------------------------------------
-- 2. Сайт, агент, пристрої, інтерфейси
-- ---------------------------------------------------------------------
INSERT INTO inv.sites (id, tenant_id, name, code, lat, lon) VALUES
('33333333-0000-0000-0000-000000000001', '11111111-1111-1111-1111-111111111111',
'Kyiv DC1', 'KYV-DC1', 50.4501, 30.5234);
INSERT INTO core.agents (id, tenant_id, site_id, name, token_hash, status, enabled_modules) VALUES
('44444444-0000-0000-0000-000000000001', '11111111-1111-1111-1111-111111111111',
'33333333-0000-0000-0000-000000000001', 'probe-kyv-01',
digest('agent-token-1','sha256'), 'online', '{icmp,snmp,topology}');
INSERT INTO inv.devices (id, tenant_id, site_id, agent_id, name, address, kind, vendor, chassis_id, system_name) VALUES
('55555555-0000-0000-0000-000000000001', '11111111-1111-1111-1111-111111111111',
'33333333-0000-0000-0000-000000000001', '44444444-0000-0000-0000-000000000001',
'core-sw-01', '10.0.0.1', 'switch', 'cisco', '0011.2233.4455', 'core-sw-01'),
('55555555-0000-0000-0000-000000000002', '11111111-1111-1111-1111-111111111111',
'33333333-0000-0000-0000-000000000001', '44444444-0000-0000-0000-000000000001',
'edge-rtr-01', '10.0.0.2', 'router', 'mikrotik', '0011.2233.6677', 'edge-rtr-01'),
('55555555-0000-0000-0000-000000000003', '11111111-1111-1111-1111-111111111111',
'33333333-0000-0000-0000-000000000001', '44444444-0000-0000-0000-000000000001',
'ups-01', '10.0.0.3', 'ups', 'apc', NULL, NULL);
INSERT INTO inv.interfaces (id, tenant_id, device_id, if_index, name, speed_bps, oper_status, is_uplink) VALUES
('66666666-0000-0000-0000-000000000001', '11111111-1111-1111-1111-111111111111',
'55555555-0000-0000-0000-000000000001', 1, 'GigabitEthernet0/1', 1000000000, 'up', true),
('66666666-0000-0000-0000-000000000002', '11111111-1111-1111-1111-111111111111',
'55555555-0000-0000-0000-000000000002', 1, 'ether1', 1000000000, 'up', true);
-- ---------------------------------------------------------------------
-- 3. Автовиявлення: LLDP-сусід → зведений лінк
-- ---------------------------------------------------------------------
INSERT INTO topo.neighbors (tenant_id, device_id, interface_id, proto,
remote_chassis_id, remote_system_name, remote_port_id,
resolved_device_id, resolved_interface_id, confidence)
VALUES ('11111111-1111-1111-1111-111111111111',
'55555555-0000-0000-0000-000000000001', '66666666-0000-0000-0000-000000000001', 'lldp',
'0011.2233.6677', 'edge-rtr-01', 'ether1',
'55555555-0000-0000-0000-000000000002', '66666666-0000-0000-0000-000000000002', 95);
INSERT INTO topo.links (id, tenant_id, a_device_id, a_interface_id, b_device_id, b_interface_id,
kind, capacity_bps, discovered_by, status)
VALUES ('77777777-0000-0000-0000-000000000001', '11111111-1111-1111-1111-111111111111',
'55555555-0000-0000-0000-000000000001', '66666666-0000-0000-0000-000000000001',
'55555555-0000-0000-0000-000000000002', '66666666-0000-0000-0000-000000000002',
'physical', 1000000000, 'lldp', 'up');
-- Перевірка нормалізації пари: дзеркальний лінк B→A має впертись в unique index
DO $$
BEGIN
INSERT INTO topo.links (tenant_id, a_device_id, a_interface_id, b_device_id, b_interface_id)
VALUES ('11111111-1111-1111-1111-111111111111',
'55555555-0000-0000-0000-000000000002', '66666666-0000-0000-0000-000000000002',
'55555555-0000-0000-0000-000000000001', '66666666-0000-0000-0000-000000000001');
RAISE EXCEPTION 'FAIL: дзеркальний лінк B->A створився (нормалізація пари не працює)';
EXCEPTION WHEN unique_violation THEN
RAISE NOTICE 'PASS: дзеркальний лінк B->A відхилено (links_pair_uniq)';
END $$;
-- ---------------------------------------------------------------------
-- 4. Мапа з підкладкою, вузлами та ребром port->port
-- ---------------------------------------------------------------------
INSERT INTO topo.maps (id, tenant_id, site_id, name, slug, kind, layout_algo) VALUES
('88888888-0000-0000-0000-000000000001', '11111111-1111-1111-1111-111111111111',
'33333333-0000-0000-0000-000000000001', 'Kyiv DC1 — основна', 'kyiv-dc1', 'floor_plan', 'manual');
INSERT INTO topo.map_backgrounds (tenant_id, map_id, kind, storage_key, mime_type,
natural_width, natural_height, width, height, opacity)
VALUES ('11111111-1111-1111-1111-111111111111', '88888888-0000-0000-0000-000000000001',
'image', 's3://netpulse/acme/floorplan-dc1.svg', 'image/svg+xml',
2400, 1600, 2400, 1600, 0.6);
INSERT INTO topo.map_nodes (id, tenant_id, map_id, kind, device_id, label, x, y, style) VALUES
('99999999-0000-0000-0000-000000000001', '11111111-1111-1111-1111-111111111111',
'88888888-0000-0000-0000-000000000001', 'device', '55555555-0000-0000-0000-000000000001',
'core-sw-01', 400, 300, '{"icon":"switch","color":"#22c55e"}'),
('99999999-0000-0000-0000-000000000002', '11111111-1111-1111-1111-111111111111',
'88888888-0000-0000-0000-000000000001', 'device', '55555555-0000-0000-0000-000000000002',
'edge-rtr-01', 800, 300, '{"icon":"router"}'),
('99999999-0000-0000-0000-000000000003', '11111111-1111-1111-1111-111111111111',
'88888888-0000-0000-0000-000000000001', 'text', NULL,
'Серверна №2', 400, 120, '{"fontSize":18}');
-- Нода kind='device' без device_id має впертись у CHECK
DO $$
BEGIN
INSERT INTO topo.map_nodes (tenant_id, map_id, kind, x, y)
VALUES ('11111111-1111-1111-1111-111111111111','88888888-0000-0000-0000-000000000001','device',0,0);
RAISE EXCEPTION 'FAIL: device-нода без device_id створилась';
EXCEPTION WHEN check_violation THEN
RAISE NOTICE 'PASS: device-нода без device_id відхилена (map_nodes_kind_ref_chk)';
END $$;
INSERT INTO topo.map_edges (tenant_id, map_id, source_node_id, target_node_id,
source_interface_id, target_interface_id, link_id,
label, style, animation, thresholds)
VALUES ('11111111-1111-1111-1111-111111111111', '88888888-0000-0000-0000-000000000001',
'99999999-0000-0000-0000-000000000001', '99999999-0000-0000-0000-000000000002',
'66666666-0000-0000-0000-000000000001', '66666666-0000-0000-0000-000000000002',
'77777777-0000-0000-0000-000000000001',
'Gi0/1 → ether1', 'smoothstep',
'{"enabled":true,"speed_source":"utilization","direction":"a_to_b","max_speed":3}',
'{"warn_pct":70,"crit_pct":90}');
-- ---------------------------------------------------------------------
-- 5. Телеметрія
-- ---------------------------------------------------------------------
INSERT INTO ts.icmp_samples (ts, device_id, tenant_id, agent_id, rtt_avg_ms, rtt_min_ms, rtt_max_ms,
jitter_ms, loss_pct, packets_sent, packets_recv, reachable)
SELECT now() - (g || ' minutes')::interval,
'55555555-0000-0000-0000-000000000001', '11111111-1111-1111-1111-111111111111',
'44444444-0000-0000-0000-000000000001',
1.2 + g * 0.05, 1.0, 2.5, 0.3, CASE WHEN g = 0 THEN 0 ELSE 0 END, 3, 3, true
FROM generate_series(0, 59) g;
INSERT INTO ts.if_counters (ts, interface_id, device_id, tenant_id,
in_octets, out_octets, in_bps, out_bps,
util_in_pct, util_out_pct, oper_up)
SELECT now() - (g || ' minutes')::interval,
'66666666-0000-0000-0000-000000000001', '55555555-0000-0000-0000-000000000001',
'11111111-1111-1111-1111-111111111111',
1000000 * g, 900000 * g,
420000000, 780000000,
42.0, 78.0, true
FROM generate_series(0, 59) g;
-- Узагальнені метрики через series/samples
INSERT INTO ts.series (tenant_id, device_id, plugin_key, metric_key, unit, labels)
VALUES ('11111111-1111-1111-1111-111111111111','55555555-0000-0000-0000-000000000001',
'snmp','cpu.util','pct','{"core":"0"}');
INSERT INTO ts.samples (ts, series_id, value)
SELECT now() - (g || ' minutes')::interval, s.id, 30 + (g % 17)
FROM generate_series(0, 59) g,
ts.series s
WHERE s.metric_key = 'cpu.util';
-- ---------------------------------------------------------------------
-- 6. NCM: репо, конфіг, diff
-- ---------------------------------------------------------------------
INSERT INTO ncm.repos (id, tenant_id, name, storage_path) VALUES
('aaaaaaaa-0000-0000-0000-000000000001', '11111111-1111-1111-1111-111111111111',
'acme', '/var/lib/netpulse/git/acme.git');
INSERT INTO ncm.configs (id, tenant_id, device_id, repo_id, commit_sha, blob_sha, branch, path,
size_bytes, line_count, content_hash, is_change, lines_added, lines_removed)
VALUES
('bbbbbbbb-0000-0000-0000-000000000001','11111111-1111-1111-1111-111111111111',
'55555555-0000-0000-0000-000000000001','aaaaaaaa-0000-0000-0000-000000000001',
repeat('a',40), repeat('b',40), 'device/core-sw-01', 'kyv-dc1/core-sw-01/running.cfg',
4096, 180, digest('config-v1','sha256'), false, 0, 0),
('bbbbbbbb-0000-0000-0000-000000000002','11111111-1111-1111-1111-111111111111',
'55555555-0000-0000-0000-000000000001','aaaaaaaa-0000-0000-0000-000000000001',
repeat('c',40), repeat('d',40), 'device/core-sw-01', 'kyv-dc1/core-sw-01/running.cfg',
4130, 183, digest('config-v2','sha256'), true, 4, 1);
UPDATE ncm.configs SET prev_config_id = 'bbbbbbbb-0000-0000-0000-000000000001'
WHERE id = 'bbbbbbbb-0000-0000-0000-000000000002';
-- ---------------------------------------------------------------------
-- 7. Алерт
-- ---------------------------------------------------------------------
INSERT INTO alr.rules (id, tenant_id, name, source, severity, condition, for_seconds) VALUES
('cccccccc-0000-0000-0000-000000000001','11111111-1111-1111-1111-111111111111',
'Uplink > 90%', 'interface', 'high',
'{"metric":"util_out_pct","op":">","value":90}', 600);
INSERT INTO alr.alerts (tenant_id, rule_id, device_id, interface_id, link_id,
severity, title, dedup_key, value, threshold)
VALUES ('11111111-1111-1111-1111-111111111111','cccccccc-0000-0000-0000-000000000001',
'55555555-0000-0000-0000-000000000001','66666666-0000-0000-0000-000000000001',
'77777777-0000-0000-0000-000000000001',
'high','Uplink Gi0/1 завантажений на 94%','rule:uplink90:if:6666...0001', 94.0, 90.0);
-- Дедуплікація: другий активний алерт із тим самим ключем не пройде
DO $$
BEGIN
INSERT INTO alr.alerts (tenant_id, rule_id, device_id, severity, title, dedup_key)
VALUES ('11111111-1111-1111-1111-111111111111','cccccccc-0000-0000-0000-000000000001',
'55555555-0000-0000-0000-000000000001','high','дубль','rule:uplink90:if:6666...0001');
RAISE EXCEPTION 'FAIL: дубль активного алерту створився';
EXCEPTION WHEN unique_violation THEN
RAISE NOTICE 'PASS: дубль активного алерту відхилено (alerts_active_dedup_uniq)';
END $$;
COMMIT;
-- =====================================================================
-- ПЕРЕВІРКИ
-- =====================================================================
\echo ''
\echo '--- 1. Стан полотна мапи одним запитом ---'
SELECT n.label,
n.kind,
n.x, n.y,
d.status AS device_status,
round(i.rtt_avg_ms::numeric,2) AS rtt_ms,
i.loss_pct
FROM topo.map_nodes n
LEFT JOIN inv.devices d ON d.id = n.device_id
LEFT JOIN ts.device_last_icmp i ON i.device_id = n.device_id
WHERE n.map_id = '88888888-0000-0000-0000-000000000001'
ORDER BY n.x, n.y;
\echo ''
\echo '--- 2. Ребро мапи з портами і живим завантаженням (джерело анімації) ---'
SELECT e.label,
si.name AS source_port,
ti.name AS target_port,
lv.status,
round(lv.util_pct::numeric,1) AS util_pct,
e.animation->>'speed_source' AS speed_source,
e.thresholds->>'crit_pct' AS crit_pct
FROM topo.map_edges e
LEFT JOIN inv.interfaces si ON si.id = e.source_interface_id
LEFT JOIN inv.interfaces ti ON ti.id = e.target_interface_id
LEFT JOIN topo.link_live lv ON lv.link_id = e.link_id
WHERE e.map_id = '88888888-0000-0000-0000-000000000001';
\echo ''
\echo '--- 3. Continuous aggregate: 5-хвилинні роллапи трафіку ---'
CALL refresh_continuous_aggregate('ts.if_counters_5m', now() - interval '2 hours', now());
SELECT bucket, round(in_bps_avg::numeric/1e6,1) AS in_mbps, round(util_out_max::numeric,1) AS util_out_max
FROM ts.if_counters_5m
WHERE interface_id = '66666666-0000-0000-0000-000000000001'
ORDER BY bucket DESC LIMIT 5;
\echo ''
\echo '--- 4. Ліміт пристроїв тарифу Free (15) ---'
DO $$
DECLARE i int;
BEGIN
FOR i IN 4..15 LOOP
INSERT INTO inv.devices (tenant_id, name, address, kind)
VALUES ('11111111-1111-1111-1111-111111111111', 'filler-' || i,
('10.0.1.' || i)::inet, 'other');
END LOOP;
BEGIN
INSERT INTO inv.devices (tenant_id, name, address, kind)
VALUES ('11111111-1111-1111-1111-111111111111', 'over-limit', '10.0.9.9', 'other');
RAISE EXCEPTION 'FAIL: 16-й пристрій створився попри ліміт 15';
EXCEPTION WHEN check_violation THEN
RAISE NOTICE 'PASS: 16-й пристрій відхилено тригером bill.assert_device_limit';
END;
END $$;
\echo ''
\echo '--- 5. RLS: ізоляція тенантів (від імені netpulse_app) ---'
SET ROLE netpulse_app;
SET app.tenant_id = '11111111-1111-1111-1111-111111111111';
SELECT 'acme бачить пристроїв' AS check, count(*) FROM inv.devices;
SELECT 'acme бачить мап' AS check, count(*) FROM topo.maps;
SET app.tenant_id = '22222222-2222-2222-2222-222222222222';
SELECT 'globex бачить пристроїв ACME' AS check, count(*) FROM inv.devices;
SELECT 'globex бачить мап ACME' AS check, count(*) FROM topo.maps;
RESET app.tenant_id;
SELECT 'без app.tenant_id бачить пристроїв' AS check, count(*) FROM inv.devices;
RESET ROLE;
\echo ''
\echo '--- 6. Зведення по об''єктах схеми ---'
SELECT 'таблиць' AS obj, count(*) FROM pg_tables
WHERE schemaname IN ('core','inv','topo','ts','ncm','alr','bill')
UNION ALL SELECT 'hypertables', count(*) FROM timescaledb_information.hypertables
UNION ALL SELECT 'continuous aggregates', count(*) FROM timescaledb_information.continuous_aggregates
UNION ALL SELECT 'фонових job-ів', count(*) FROM timescaledb_information.jobs WHERE job_id >= 1000
UNION ALL SELECT 'RLS-політик', count(*) FROM pg_policies
WHERE schemaname IN ('core','inv','topo','ts','ncm','alr','bill')
UNION ALL SELECT 'індексів', count(*) FROM pg_indexes
WHERE schemaname IN ('core','inv','topo','ts','ncm','alr','bill');