diff --git a/DATABASE_SCHEMA.md b/DATABASE_SCHEMA.md index 3030bce..fddf5c4 100755 --- a/DATABASE_SCHEMA.md +++ b/DATABASE_SCHEMA.md @@ -44,7 +44,7 @@ SECONDARY_DB_HOST=10.100.13.5 ### Назначение -Хранит информацию о хостах/серверах в кластере WAF. +Хранит информацию о хостах/серверах в кластере WAF. Таблица `instances` (старая) переименована в `instances_old` и не используется — все запросы идут к `instances_new`. ### Структура @@ -89,7 +89,9 @@ WHERE hostname = $1 AND instance = $2; | Поле | Тип | Описание | Пример | |------|-----|----------|--------| +| `id` | SERIAL | Первичный ключ (автоинкремент) | `1` | | `client_title` | TEXT | Идентификатор клиента | `CLIENT001` | +| `parent_client_title` | TEXT | Родительский клиент (для grafana_gen) | `NULL` | | `containers_count` | INTEGER | Количество контейнеров (0 = drain всех) | `3` | | `ptaf_config` | TEXT | Строка подключения к PTAF-серверу | `{...}` | | `fluent_bit_port` | INTEGER | Порт Fluent Bit | `24224` | @@ -160,6 +162,14 @@ WHERE client_title = $1; ### Миграции ```sql +-- Переименование PK (таблица была переименована из nodes в client_info) +ALTER TABLE client_info DROP CONSTRAINT nodes_pkey; +ALTER TABLE client_info ADD COLUMN id SERIAL PRIMARY KEY; + +-- Добавление parent_client_title для grafana_gen +ALTER TABLE client_info + ADD COLUMN IF NOT EXISTS parent_client_title TEXT DEFAULT NULL; + ALTER TABLE client_info ADD COLUMN IF NOT EXISTS docker_image TEXT DEFAULT NULL; diff --git a/ptaf_processor.go b/ptaf_processor.go index 2149318..aa6438a 100755 --- a/ptaf_processor.go +++ b/ptaf_processor.go @@ -6,6 +6,7 @@ import ( "log" "net" "os" + "path/filepath" "strings" "time" ) @@ -83,9 +84,14 @@ func processPTAFClient( // Если one_container = false — разбиваем SID по блокам и обрабатываем каждый блок отдельно if !clientInfo.OneContainer && clientInfo.SidBlock.Valid && clientInfo.SidBlock.String != "" { + // Переход true → false: дренируем обычные контейнеры + cleanupOneContainerContainers(clientInfo.ClientTitle, hostname) return processPTAFClientBlocks(db, config, hostname, instanceName, clientInfo, resourcesData, portAllocator) } + // Переход false → true: дренируем блочные контейнеры + cleanupBlockContainers(clientInfo.ClientTitle, hostname) + // Выделение портов resourcePortMap, err := resolvePortsForClient(clientInfo, resourcesData, portAllocator) if err != nil { @@ -324,9 +330,12 @@ func processPTAFClientBlocks( } success := true + activeBlocks := make(map[string]bool) + for blockIdx, sidList := range blocks { blockNum := blockIdx + 1 blockName := fmt.Sprintf("block%d", blockNum) + activeBlocks[blockName] = true log.Printf(" → Обработка блока %d: SID %v", blockNum, sidList) // Собираем ресурсы для этого блока @@ -404,5 +413,89 @@ func processPTAFClientBlocks( log.Printf(" ✓ Блок %d обработан успешно", blockNum) } + // Очистка лишних блоков — drain контейнеров и удаление Angie конфигов + // которые больше не входят в sid_block + cleanupStaleBlocks(clientInfo.ClientTitle, activeBlocks, hostname) + return success } + +// cleanupStaleBlocks дренирует контейнеры и удаляет Angie конфиги +// для блоков которые больше не входят в sid_block +func cleanupStaleBlocks(clientTitle string, activeBlocks map[string]bool, hostname string) { + // Ищем существующие блочные Angie конфиги + pattern := fmt.Sprintf("/etc/angie/http.d/angie-ptaf-%s_block*.conf", clientTitle) + matches, _ := filepath.Glob(pattern) + for _, confFile := range matches { + // Извлекаем blockName из имени файла: angie-ptaf-{clientTitle}_{blockName}.conf + base := filepath.Base(confFile) + base = strings.TrimSuffix(base, ".conf") + base = strings.TrimPrefix(base, fmt.Sprintf("angie-ptaf-%s_", clientTitle)) + blockName := base // например "block1" + + if activeBlocks[blockName] { + continue + } + + // Блок устарел — дренируем контейнеры + log.Printf(" → Удаление устаревшего блока %s для %s", blockName, clientTitle) + blockClientTitle := fmt.Sprintf("%s_%s", clientTitle, blockName) + + // Помечаем контейнеры на drain + actualCount := getActualContainerCount(blockClientTitle) + for containerNum := 1; containerNum <= actualCount; containerNum++ { + containerFullName := buildContainerFullName(blockClientTitle, containerNum, hostname) + exists, _ := checkContainerStatus(containerFullName) + if exists { + if err := markContainerForDrain(containerFullName, false); err != nil { + log.Printf(" ⚠ Ошибка пометки контейнера %s: %v", containerFullName, err) + } else { + log.Printf(" ✓ Контейнер %s помечен для drain", containerFullName) + } + } + } + + // Помечаем Angie конфиг для drain + if err := markAngieConfigForDrain(blockClientTitle, false); err != nil { + log.Printf(" ⚠ Ошибка пометки Angie конфига для %s: %v", blockClientTitle, err) + } else { + log.Printf(" ✓ Angie конфиг для блока %s помечен для drain", blockName) + } + } +} + +// cleanupBlockContainers дренирует все блочные контейнеры клиента +// Используется при переходе one_container: false → true +func cleanupBlockContainers(clientTitle string, hostname string) { + pattern := fmt.Sprintf("/etc/angie/http.d/angie-ptaf-%s_block*.conf", clientTitle) + matches, _ := filepath.Glob(pattern) + if len(matches) == 0 { + return + } + log.Printf(" → Обнаружены блочные конфиги при one_container=true, инициируем drain") + cleanupStaleBlocks(clientTitle, map[string]bool{}, hostname) +} + +// cleanupOneContainerContainers дренирует обычный (не блочный) контейнер клиента +// Используется при переходе one_container: true → false +func cleanupOneContainerContainers(clientTitle string, hostname string) { + actualCount := getActualContainerCount(clientTitle) + hasNonBlock := false + for containerNum := 1; containerNum <= actualCount; containerNum++ { + containerFullName := buildContainerFullName(clientTitle, containerNum, hostname) + exists, _ := checkContainerStatus(containerFullName) + if exists { + hasNonBlock = true + if err := markContainerForDrain(containerFullName, false); err != nil { + log.Printf(" ⚠ Ошибка пометки контейнера %s: %v", containerFullName, err) + } else { + log.Printf(" ✓ Контейнер %s помечен для drain (переход в блочный режим)", containerFullName) + } + } + } + if hasNonBlock { + if err := markAngieConfigForDrain(clientTitle, false); err != nil { + log.Printf(" ⚠ Ошибка пометки Angie конфига: %v", err) + } + } +}