diff --git a/drain.go b/drain.go index d4699a4..a5a411e 100755 --- a/drain.go +++ b/drain.go @@ -30,21 +30,28 @@ func getActualContainerCount(clientTitle string) int { } // Считаем по drain-маркерам в /tmp (контейнеры могли быть уже удалены с диска) - // Ищем маркеры по паттерну /tmp/ptaf-drain-{clientTitle}_* независимо от формата имени - matches, _ := filepath.Glob(fmt.Sprintf("/tmp/ptaf-drain-%s_*", clientTitle)) - for _, match := range matches { - // Извлекаем номер агента из конца имени файла (формат: ..._a001 или ..._001) - base := filepath.Base(match) - base = strings.TrimPrefix(base, "ptaf-drain-") - // Убираем префикс clientTitle_ - rest := strings.TrimPrefix(base, clientTitle+"_") - // Ищем номер в конце: _a001 или _001 - parts := strings.Split(rest, "_") - lastPart := parts[len(parts)-1] - lastPart = strings.TrimPrefix(lastPart, "a") - var num int - if _, err := fmt.Sscanf(lastPart, "%d", &num); err == nil && num > max { - max = num + // Ищем маркеры по двум паттернам — с и без префикса ptaf_ + patterns := []string{ + fmt.Sprintf("/tmp/ptaf-drain-%s_*", clientTitle), // новый формат: {clientTitle}_AZ1_n01_a001 + fmt.Sprintf("/tmp/ptaf-drain-ptaf_%s_*", clientTitle), // fallback формат: ptaf_{clientTitle}_001 + } + for _, pattern := range patterns { + matches, _ := filepath.Glob(pattern) + for _, match := range matches { + base := filepath.Base(match) + base = strings.TrimPrefix(base, "ptaf-drain-") + // Убираем возможный префикс ptaf_ + base = strings.TrimPrefix(base, "ptaf_") + // Убираем префикс clientTitle_ + rest := strings.TrimPrefix(base, clientTitle+"_") + // Ищем номер в конце: _a001 или _001 + parts := strings.Split(rest, "_") + lastPart := parts[len(parts)-1] + lastPart = strings.TrimPrefix(lastPart, "a") + var num int + if _, err := fmt.Sscanf(lastPart, "%d", &num); err == nil && num > max { + max = num + } } } @@ -142,6 +149,12 @@ func processAllDrainingContainersGlobally(db *sql.DB, hostname string) { continue } clientTitle := entry.Name() + + // Пропускаем блочные директории — они управляются через родительский клиент + if isBlockClientTitle(clientTitle) { + continue + } + log.Printf(" → Проверка клиента: %s", clientTitle) clientInfo, err := getClientInfoByClientTitle(db, clientTitle) @@ -204,7 +217,7 @@ func processAllDrainingContainersGlobally(db *sql.DB, hostname string) { processedClients++ } else { log.Printf(" ✓ Клиент должен быть здесь, проверяю устаревшие drain-маркеры") - checkAndProcessExistingDrainMarkers(clientTitle, clientInfo.ContainersCount, &processedClients) + checkAndProcessExistingDrainMarkers(clientTitle, 0, &processedClients) } } @@ -221,10 +234,16 @@ func processAllDrainingContainersGlobally(db *sql.DB, hostname string) { func checkAndProcessExistingDrainMarkers(clientTitle string, containersCount int, processedCount *int) { hasAnyDrainMarkers := false - // Ищем маркеры по паттерну независимо от формата имени контейнера - matches, _ := filepath.Glob(fmt.Sprintf("/tmp/ptaf-drain-%s_*", clientTitle)) - if len(matches) > 0 { - hasAnyDrainMarkers = true + // Ищем маркеры по двум паттернам — новый и fallback формат + for _, pattern := range []string{ + fmt.Sprintf("/tmp/ptaf-drain-%s_*", clientTitle), + fmt.Sprintf("/tmp/ptaf-drain-ptaf_%s_*", clientTitle), + } { + matches, _ := filepath.Glob(pattern) + if len(matches) > 0 { + hasAnyDrainMarkers = true + break + } } if !hasAnyDrainMarkers { if _, err := os.Stat(fmt.Sprintf("/tmp/ptaf-angie-drain-%s", clientTitle)); err == nil { diff --git a/main.go b/main.go index f4007b0..21e59f4 100755 --- a/main.go +++ b/main.go @@ -127,11 +127,6 @@ func main() { log.Fatalf("Ошибка загрузки конфигурации: %v", err) } - // Проверяем доступность Docker daemon - if err := checkDockerAvailable(); err != nil { - log.Fatalf("❌ Docker daemon недоступен: %v", err) - } - instanceNames, err := getInstancesByHostname(db, hostname) if err != nil { log.Fatalf("Ошибка получения instances для hostname %s: %v", hostname, err) @@ -146,8 +141,12 @@ func main() { // Проверка наличия Docker образов для каждого PTAF-клиента отдельно ptafClients := getPTAFClients(db, instanceNames) if len(ptafClients) == 0 { - log.Printf("ℹ PTAF-клиентов на хосте нет, проверка Docker образов пропускается") + log.Printf("ℹ PTAF-клиентов на хосте нет, проверка Docker пропускается") } else { + // Проверяем Docker только если есть PTAF-клиенты + if err := checkDockerAvailable(); err != nil { + log.Fatalf("❌ Docker daemon недоступен: %v", err) + } checkedImages := make(map[string]bool) // не проверяем один образ дважды for _, clientInfo := range ptafClients { image, url := getClientDockerImage(clientInfo, config) @@ -358,14 +357,14 @@ func processClient(db *sql.DB, config Config, hostname, instanceName string, cli log.Printf("WAF Vendor: %s", vendor) switch vendor { - case "ptaf": - return processPTAFClient(db, config, hostname, instanceName, clientInfo, appsSettingsList, portAllocator) - case "sw": - return processSWClient(db, config, hostname, instanceName, clientInfo, appsSettingsList, portAllocator) - default: - log.Printf("⚠ Неизвестный или не указан waf_vendor для клиента %s, пропускаем", clientInfo.ClientTitle) - log.Printf(" Ожидается: 'ptaf' или 'sw' в поле waf_vendor таблицы apps_settings") - return false + case "ptaf": + return processPTAFClient(db, config, hostname, instanceName, clientInfo, appsSettingsList, portAllocator) + case "sw": + return processSWClient(db, config, hostname, instanceName, clientInfo, appsSettingsList, portAllocator) + default: + log.Printf("⚠ Неизвестный или не указан waf_vendor для клиента %s, пропускаем", clientInfo.ClientTitle) + log.Printf(" Ожидается: 'ptaf' или 'sw' в поле waf_vendor таблицы apps_settings") + return false } } @@ -386,7 +385,7 @@ func determineVendor(appsSettingsList []AppsSettings) string { // handleClientMigration обрабатывает миграцию клиента с текущего хоста func handleClientMigration(clientInfo ClientInfo, hostname string) { log.Printf("⚠ Клиент %s должен быть на instance '%s', текущий хост '%s' не принадлежит этому instance", - clientInfo.ClientTitle, clientInfo.WAFInstance, hostname) + clientInfo.ClientTitle, clientInfo.WAFInstance, hostname) log.Printf(" Инициирую удаление контейнеров с текущего хоста (миграция)") actualCount := getActualContainerCount(clientInfo.ClientTitle) @@ -395,7 +394,7 @@ func handleClientMigration(clientInfo ClientInfo, hostname string) { exists, _ := checkContainerStatus(containerFullName) if exists { log.Printf(" → Помечаю контейнер %s для drain (миграция, таймаут %d сек)", - containerFullName, MigrationDrainTimeout) + containerFullName, MigrationDrainTimeout) if err := markContainerForDrain(containerFullName, true); err != nil { log.Printf(" ⚠ Ошибка пометки: %v", err) } @@ -437,7 +436,9 @@ func cleanStaleDrainMarkers(clientInfo ClientInfo, hostname string) { cleanedContainers++ } } else if found && !isMigration { - if containerNum <= clientInfo.ContainersCount { + // Не удаляем маркер если клиент в блочном режиме — + // контейнер помечен на drain из-за перехода one_container: true → false + if clientInfo.OneContainer && containerNum <= clientInfo.ContainersCount { if err := os.Remove(markerFile); err == nil { log.Printf(" ✓ Удалён устаревший маркер drain для контейнера %s (масштабирование отменено)", containerFullName) cleanedContainers++ @@ -449,9 +450,13 @@ func cleanStaleDrainMarkers(clientInfo ClientInfo, hostname string) { angieMarkerFile := fmt.Sprintf("/tmp/ptaf-angie-drain-%s", clientInfo.ClientTitle) if _, err := os.Stat(angieMarkerFile); err == nil { - if err := os.Remove(angieMarkerFile); err == nil { - log.Printf(" ✓ Удалён устаревший маркер drain для конфига Angie (миграция отменена)") - cleanedAngieMarker = true + // Не удаляем маркер Angie если клиент в блочном режиме — + // конфиг помечен на drain из-за перехода one_container: true → false + if clientInfo.OneContainer { + if err := os.Remove(angieMarkerFile); err == nil { + log.Printf(" ✓ Удалён устаревший маркер drain для конфига Angie (миграция отменена)") + cleanedAngieMarker = true + } } } @@ -521,7 +526,7 @@ func resolvePortsForClient(clientInfo ClientInfo, resourcesData []ResourceData, } log.Printf(" → Сохранены порты для %d существующих контейнеров, выделяю порты для %d новых (контейнеры %d-%d)", - lastLoadedContainer, newContainersCount, lastLoadedContainer+1, clientInfo.ContainersCount) + lastLoadedContainer, newContainersCount, lastLoadedContainer+1, clientInfo.ContainersCount) newPortMap, err := portAllocator.allocatePortsForResources(resourcesData, newContainersCount, existingPorts) if err != nil { diff --git a/ptaf_processor.go b/ptaf_processor.go index aa6438a..c2826ca 100755 --- a/ptaf_processor.go +++ b/ptaf_processor.go @@ -73,7 +73,10 @@ func processPTAFClient( } // Автоочистка устаревших маркеров drain - cleanStaleDrainMarkers(clientInfo, hostname) + // При блочном режиме не очищаем — маркеры управляются через cleanupOneContainerContainers + if clientInfo.OneContainer { + cleanStaleDrainMarkers(clientInfo, hostname) + } // Получение данных ресурсов resourcesData := fetchPTAFResourcesData(db, config, appsSettingsList) @@ -441,12 +444,16 @@ func cleanupStaleBlocks(clientTitle string, activeBlocks map[string]bool, hostna log.Printf(" → Удаление устаревшего блока %s для %s", blockName, clientTitle) blockClientTitle := fmt.Sprintf("%s_%s", clientTitle, blockName) - // Помечаем контейнеры на drain + // Помечаем контейнеры на drain (только если маркер ещё не существует) actualCount := getActualContainerCount(blockClientTitle) for containerNum := 1; containerNum <= actualCount; containerNum++ { containerFullName := buildContainerFullName(blockClientTitle, containerNum, hostname) exists, _ := checkContainerStatus(containerFullName) if exists { + markerFile := fmt.Sprintf("/tmp/ptaf-drain-%s", containerFullName) + if _, err := os.Stat(markerFile); err == nil { + continue // маркер уже есть — не перезаписываем + } if err := markContainerForDrain(containerFullName, false); err != nil { log.Printf(" ⚠ Ошибка пометки контейнера %s: %v", containerFullName, err) } else { @@ -455,11 +462,14 @@ func cleanupStaleBlocks(clientTitle string, activeBlocks map[string]bool, hostna } } - // Помечаем Angie конфиг для drain - if err := markAngieConfigForDrain(blockClientTitle, false); err != nil { - log.Printf(" ⚠ Ошибка пометки Angie конфига для %s: %v", blockClientTitle, err) - } else { - log.Printf(" ✓ Angie конфиг для блока %s помечен для drain", blockName) + // Помечаем Angie конфиг для drain (только если маркер ещё не существует) + angieMarker := fmt.Sprintf("/tmp/ptaf-angie-drain-%s", blockClientTitle) + if _, err := os.Stat(angieMarker); err != nil { + if err := markAngieConfigForDrain(blockClientTitle, false); err != nil { + log.Printf(" ⚠ Ошибка пометки Angie конфига для %s: %v", blockClientTitle, err) + } else { + log.Printf(" ✓ Angie конфиг для блока %s помечен для drain", blockName) + } } } } @@ -485,6 +495,12 @@ func cleanupOneContainerContainers(clientTitle string, hostname string) { containerFullName := buildContainerFullName(clientTitle, containerNum, hostname) exists, _ := checkContainerStatus(containerFullName) if exists { + // Проверяем что маркер ещё не существует чтобы не сбрасывать таймер + markerFile := fmt.Sprintf("/tmp/ptaf-drain-%s", containerFullName) + if _, err := os.Stat(markerFile); err == nil { + // Маркер уже есть — не перезаписываем + continue + } hasNonBlock = true if err := markContainerForDrain(containerFullName, false); err != nil { log.Printf(" ⚠ Ошибка пометки контейнера %s: %v", containerFullName, err) @@ -494,6 +510,11 @@ func cleanupOneContainerContainers(clientTitle string, hostname string) { } } if hasNonBlock { + angieMarker := fmt.Sprintf("/tmp/ptaf-angie-drain-%s", clientTitle) + if _, err := os.Stat(angieMarker); err == nil { + // Маркер Angie уже есть — не перезаписываем + return + } if err := markAngieConfigForDrain(clientTitle, false); err != nil { log.Printf(" ⚠ Ошибка пометки Angie конфига: %v", err) } diff --git a/utils.go b/utils.go index cfe5357..5525f55 100755 --- a/utils.go +++ b/utils.go @@ -424,3 +424,20 @@ func parseSidBlocks(sidBlock string) ([][]int, error) { } return blocks, nil } + +// isBlockClientTitle проверяет является ли clientTitle блочным виртуальным именем +// вида {clientTitle}_block{N} которые создаются при one_container=false +func isBlockClientTitle(clientTitle string) bool { + // Ищем паттерн _block{число} в конце строки + parts := strings.Split(clientTitle, "_block") + if len(parts) < 2 { + return false + } + lastPart := parts[len(parts)-1] + for _, c := range lastPart { + if c < '0' || c > '9' { + return false + } + } + return len(lastPart) > 0 +}