Один коміт, а не десяток тематичних, свідомо: теми переплетені в
спільних файлах (store.go, docker-compose.yml, deploy/README.md), і
розділити їх можна було б лише індексуванням шматків. Коміти, які не
збираються, гірші за один великий — тим паче що це рівно той стан, який
перевірявся разом.
ЩО ПРАЦЮЄ НА СТЕНДІ Й ПЕРЕВІРЕНО ТАМ
0058 подієві алерти: syslog, ncm, compliance спрацьовують у мить
події; правило з нереалізованим джерелом більше не зберігається
мовчки
0059 snmp.walk і прототипи шаблонів — таблиці з динамічним індексом
описуються шаблоном, а не Go
0060 відкат конфігу: план як різниця, маскування паролів із підписом
плану, обов'язковий контрольний збір, verifying при обриві
0061 кнопки Telegram: довге опитування, авторизація не з callback_data
0062 аудит і архів хостів; тест на AST, що падає на ключі без назви
0063 RLS: три ролі, окремий пул для фонових тактів
0064 строки зберігання даних і сторінка сховища
0065 приймач SNMP-трапів; перевірено справжніми пакетами по дроту,
переклад v1→v2 за RFC 3584 дає правильний OID
0066 ескалації сповіщень
0067 алерт про вичерпання диска
0068 поля заливки конфігу переїхали в каталог профілів
Плюс: 137 тестів вебу з нуля (їх не було взагалі), одинадцять справжніх
вад, знайдених ними й виправлених, і виправлення двох інтеграційних
тестів grpcapi, які мовчки пропускались півтора року.
ЩО ЩЕ НЕ ЗАПУСКАЛОСЬ
netpulse установник: одна команда замість 18 змінних і
593 рядків інструкції
RLS з першого запуску нова інсталяція під політиками одразу;
RLS-EXISTING-INSTALL.md лишається тільки для
старих інсталяцій
.forgejo + CI раннер не зареєстрований
Ці три перевірені компіляцією й міркуванням, але не виконанням.
ГОЛОВНИЙ ВИСНОВОК ДВОХ СЕСІЙ
Зелена перевірка доводить рівно те, що вона перевіряє. Тест ізоляції RLS
був правильний і зелений — і пропустив зламаний вхід, бо перевіряв «чи
не видно чужого», коли зламалось «чи видно своє». Інтеграційні тести
grpcapi були зелені, бо не виконувались. Схема, довідник і протокол
описували те, чого в коді не існувало, і виглядало це як готове.
Тому в кожному завданні цих сесій стояла вимога назвати НЕПОКРИТЕ, а
чотири задачі закінчились не можливістю, а відмовою: правило з
нереалізованим джерелом не зберігається, профіль без команд заливки
каже про це замість мовчазної кнопки, міграція RLS валить сама себе на
таблиці без політики, тест словника аудиту падає на ключі без назви.
Подробиці — HISTORY.md, розділи за 26 і 27 серпня.
95 lines
7.1 KiB
SQL
95 lines
7.1 KiB
SQL
-- =====================================================================
|
||
-- NetPulse :: 0062_device_restore.sql
|
||
-- Повернення хоста з архіву: дві колонки, без яких «відновити» означало
|
||
-- б «повернути рядок, але не хост».
|
||
--
|
||
-- Що було. Видалення хоста має два режими: архівне (deleted_at = now())
|
||
-- і повне (0057). Архівне задумувалось як оборотне — «прибрали з
|
||
-- переліку, зібране лишилось», — але оборотним воно ніколи не було:
|
||
-- переліку прибраних хостів у продукті немає, дії «відновити» теж, і
|
||
-- рядок із deleted_at недосяжний нізвідки, крім SQL. Тобто кнопка
|
||
-- «прибрати з переліку» працювала як пастка: хост не видалений, місце
|
||
-- займає, ряди метрик лишає, а ні повернути його, ні добити не можна.
|
||
--
|
||
-- 0057 закрив половину проблеми — дав спосіб добити. Ця міграція
|
||
-- закриває другу: спосіб повернути.
|
||
--
|
||
-- ЧОМУ ЦЬОГО НЕ ЗРОБИТИ ОДНИМ `deleted_at = NULL`
|
||
--
|
||
-- Архівування вимикає не лише сам хост: разом із ним воно вимикає всі
|
||
-- його увімкнені перевірки (див. DeleteDevice і BulkDeleteDevices —
|
||
-- увімкнений чек мертвого хоста назавжди лишався б «таким, що пропустив
|
||
-- свій такт» на сторінці «Черги»). Після зняття deleted_at хост
|
||
-- повертається в переліки — і мовчки не опитується, бо чеки лишились
|
||
-- вимкненими, а enabled лишився false. Людина бачить хост на місці,
|
||
-- графіки порожні, і причини цього не видно ніде.
|
||
--
|
||
-- Вгадати тут нічого не можна: у базі не записано, який чек вимкнула
|
||
-- людина, а який — архівування. Тому архівування тепер САМЕ це й
|
||
-- записує, а відновлення вмикає рівно те, що вимикало воно, і забуває
|
||
-- запис. Дві колонки — рівно на це, і жодна з них не є «станом хоста»:
|
||
-- обидві живуть лише між архівуванням і поверненням.
|
||
--
|
||
-- Міграція нічого не змінює в поведінці сама. Хости, заархівовані ДО
|
||
-- неї, мають archived_enabled = NULL — такий повертається вимкненим, і
|
||
-- інтерфейс про це прямо каже. Це чесніше, ніж увімкнути навмання те,
|
||
-- що людина могла вимкнути свідомо ще до видалення.
|
||
-- =====================================================================
|
||
|
||
-- ---------------------------------------------------------------------
|
||
-- Яким хост був до архівування
|
||
-- ---------------------------------------------------------------------
|
||
|
||
ALTER TABLE inv.devices
|
||
-- Значення enabled на момент архівування.
|
||
--
|
||
-- NULL означає «хост не в архіві» або «заархівований до 0062»: у
|
||
-- першому випадку колонка не має сенсу, у другому — відповіді немає.
|
||
-- Обидва читаються однаково — повертати вимкненим, — і саме тому це
|
||
-- одна колонка, а не колонка плюс прапорець.
|
||
--
|
||
-- Окремої колонки під «була прив'язка до зонда» тут немає навмисно:
|
||
-- self_agent_id архівування не чіпає, і після повернення вона на
|
||
-- місці. Знімати її доводиться лише тоді, коли за час в архіві ту
|
||
-- саму машину оголосив своєю інший ЖИВИЙ хост, — і це вирішується в
|
||
-- момент відновлення, а не зберігається наперед.
|
||
ADD COLUMN archived_enabled boolean;
|
||
|
||
COMMENT ON COLUMN inv.devices.archived_enabled IS
|
||
'Яким був enabled на момент архівування; NULL — хост не в архіві або заархівований до 0062';
|
||
|
||
-- Перелік архіву.
|
||
--
|
||
-- Усі наявні індекси на inv.devices — часткові `WHERE deleted_at IS
|
||
-- NULL` (devices_tenant_name_uniq, devices_tenant_status_idx): вони
|
||
-- заведені під питання «що зараз працює» і на питання «що прибрано»
|
||
-- не відповідають узагалі. Без цього індексу сторінка архіву читала б
|
||
-- увесь інвентар тенанта заради десятка рядків — і тим більше, чим
|
||
-- більший парк, тобто рівно навпаки до того, як воно має поводитись.
|
||
CREATE INDEX devices_archived_idx
|
||
ON inv.devices (tenant_id, deleted_at DESC)
|
||
WHERE deleted_at IS NOT NULL;
|
||
|
||
-- ---------------------------------------------------------------------
|
||
-- Який чек вимкнула людина, а який — архівування
|
||
-- ---------------------------------------------------------------------
|
||
|
||
ALTER TABLE core.checks
|
||
-- Чек вимкнено разом з архівуванням хоста, а не рукою людини.
|
||
--
|
||
-- Прапорець живе рівно до відновлення й гаситься ним же. Через це
|
||
-- він не «історія», яку хтось колись читатиме, а те єдине, чого
|
||
-- бракує, щоб повернення хоста не вимагало від людини згадати
|
||
-- напам'ять, які з двадцяти перевірок вона тримала вимкненими
|
||
-- свідомо.
|
||
--
|
||
-- DEFAULT false, а не NULL: «не архівуванням» — це стан кожного
|
||
-- рядка, який існує, а не відсутність відповіді.
|
||
ADD COLUMN archived_off boolean NOT NULL DEFAULT false;
|
||
|
||
COMMENT ON COLUMN core.checks.archived_off IS
|
||
'Чек вимкнено архівуванням хоста; відновлення вмикає назад саме такі';
|
||
|
||
-- Окремого індексу під archived_off немає й не треба: єдиний запит, що
|
||
-- його читає, уже звужений до одного хоста (checks_device_idx), а
|
||
-- прапорець там лише відсіює зайве.
|