265 lines
11 KiB
Go
265 lines
11 KiB
Go
package main
|
||
|
||
import (
|
||
"database/sql"
|
||
"fmt"
|
||
"log"
|
||
"os"
|
||
"path/filepath"
|
||
"strings"
|
||
"time"
|
||
)
|
||
|
||
var _ = sql.ErrNoRows // ensure import
|
||
|
||
func processDrainingAngieConfigs() {
|
||
currentTime := time.Now().Unix()
|
||
timeout := int64(MigrationDrainTimeout)
|
||
hostname, err := os.Hostname()
|
||
if err != nil {
|
||
log.Printf("⚠ Ошибка получения hostname: %v", err)
|
||
return
|
||
}
|
||
tempConfig := getDefaultConfig()
|
||
db := connectToDB(tempConfig)
|
||
defer db.Close()
|
||
|
||
matches, err := filepath.Glob("/tmp/ptaf-angie-drain-*")
|
||
if err != nil {
|
||
log.Printf("⚠ Ошибка поиска маркеров Angie: %v", err)
|
||
return
|
||
}
|
||
|
||
for _, markerFile := range matches {
|
||
parts := strings.Split(filepath.Base(markerFile), "-")
|
||
if len(parts) < 4 {
|
||
continue
|
||
}
|
||
clientTitle := strings.Join(parts[3:], "-")
|
||
drainTimestamp, found := getAngieConfigDrainTimestamp(clientTitle)
|
||
if !found {
|
||
continue
|
||
}
|
||
elapsedTime := currentTime - drainTimestamp
|
||
remainingTime := timeout - elapsedTime
|
||
angieConfigPath := fmt.Sprintf("/etc/angie/http.d/angie-ptaf-%s.conf", clientTitle)
|
||
|
||
if elapsedTime >= timeout {
|
||
log.Printf(" Конфиг Angie для %s завершил drain (прошло %d сек), проверяю актуальность...", clientTitle, elapsedTime)
|
||
clientInfo, err := getClientInfoByClientTitle(db, clientTitle)
|
||
if err == nil && clientInfo.WAFInstance != "" {
|
||
belongsNow, checkErr := checkHostBelongsToInstance(db, hostname, clientInfo.WAFInstance)
|
||
if checkErr == nil && belongsNow {
|
||
log.Printf(" ⚠ Клиент %s снова принадлежит этому хосту, ОТМЕНЯЮ удаление конфига", clientTitle)
|
||
os.Remove(markerFile)
|
||
continue
|
||
}
|
||
}
|
||
log.Printf(" → Клиент %s не принадлежит этому хосту, удаляю конфиг", clientTitle)
|
||
if _, err := os.Stat(angieConfigPath); err == nil {
|
||
if err := os.Remove(angieConfigPath); err != nil {
|
||
log.Printf(" ⚠ Ошибка удаления конфига Angie: %v", err)
|
||
} else {
|
||
log.Printf(" ✓ Конфиг Angie удалён: %s", angieConfigPath)
|
||
os.Remove(markerFile)
|
||
reloadAngie()
|
||
}
|
||
} else {
|
||
log.Printf(" Конфиг Angie для %s уже не существует, удаляю маркер", clientTitle)
|
||
os.Remove(markerFile)
|
||
}
|
||
} else {
|
||
log.Printf(" ○ Конфиг Angie для %s в процессе drain [миграция] (осталось %d сек)", clientTitle, remainingTime)
|
||
}
|
||
}
|
||
}
|
||
|
||
func processAllDrainingContainersGlobally(db *sql.DB, hostname string) {
|
||
log.Println("\n--- Глобальная проверка клиентов и drain-контейнеров ---")
|
||
entries, err := os.ReadDir("/home/install/ptaf")
|
||
if err != nil {
|
||
log.Printf("⚠ Не удалось прочитать директорию: %v", err)
|
||
return
|
||
}
|
||
|
||
processedClients := 0
|
||
markedForDrain := 0
|
||
|
||
for _, entry := range entries {
|
||
if !entry.IsDir() {
|
||
continue
|
||
}
|
||
clientTitle := entry.Name()
|
||
log.Printf(" → Проверка клиента: %s", clientTitle)
|
||
|
||
clientInfo, err := getClientInfoByClientTitle(db, clientTitle)
|
||
if err != nil {
|
||
log.Printf(" ⚠ Ошибка получения данных клиента из БД: %v", err)
|
||
checkAndProcessExistingDrainMarkers(clientTitle, &processedClients)
|
||
continue
|
||
}
|
||
|
||
shouldBeHere := false
|
||
skipDrainCheck := false
|
||
if clientInfo.WAFInstance != "" {
|
||
belongsToInstance, err := checkHostBelongsToInstance(db, hostname, clientInfo.WAFInstance)
|
||
if err != nil {
|
||
log.Printf(" ⚠ Ошибка проверки принадлежности хоста: %v", err)
|
||
} else {
|
||
shouldBeHere = belongsToInstance
|
||
}
|
||
} else {
|
||
log.Printf(" ℹ waf_instance не указан (NULL), ресурс может быть на любом хосте")
|
||
shouldBeHere = true
|
||
skipDrainCheck = true
|
||
}
|
||
|
||
if !shouldBeHere && !skipDrainCheck {
|
||
log.Printf(" ⚠ Клиент %s не должен быть на этом хосте (waf_instance='%s')", clientTitle, clientInfo.WAFInstance)
|
||
hasExistingMarkers := false
|
||
for containerNum := 1; containerNum <= MaxContainersPerClient; containerNum++ {
|
||
if _, err := os.Stat(fmt.Sprintf("/tmp/ptaf-drain-ptaf_%s_%03d", clientTitle, containerNum)); err == nil {
|
||
hasExistingMarkers = true
|
||
break
|
||
}
|
||
}
|
||
if !hasExistingMarkers {
|
||
log.Printf(" → Помечаю контейнеры для миграции")
|
||
for containerNum := 1; containerNum <= MaxContainersPerClient; containerNum++ {
|
||
containerFullName := fmt.Sprintf("ptaf_%s_%03d", clientTitle, containerNum)
|
||
exists, _ := checkContainerStatus(containerFullName)
|
||
if exists {
|
||
if err := markContainerForDrain(containerFullName, true); err != nil {
|
||
log.Printf(" ⚠ Ошибка пометки контейнера %s: %v", containerFullName, err)
|
||
} else {
|
||
log.Printf(" ✓ Контейнер %s помечен для drain (миграция)", containerFullName)
|
||
markedForDrain++
|
||
}
|
||
}
|
||
}
|
||
angieConfigPath := fmt.Sprintf("/etc/angie/http.d/angie-ptaf-%s.conf", clientTitle)
|
||
if _, err := os.Stat(angieConfigPath); err == nil {
|
||
if err := markAngieConfigForDrain(clientTitle); err != nil {
|
||
log.Printf(" ⚠ Ошибка пометки Angie конфига: %v", err)
|
||
} else {
|
||
log.Printf(" ✓ Angie конфиг помечен для удаления")
|
||
}
|
||
}
|
||
}
|
||
processDrainingContainers(clientTitle, 0)
|
||
processedClients++
|
||
} else {
|
||
log.Printf(" ✓ Клиент должен быть здесь, проверяю устаревшие drain-маркеры")
|
||
checkAndProcessExistingDrainMarkers(clientTitle, &processedClients)
|
||
}
|
||
}
|
||
|
||
if markedForDrain > 0 {
|
||
log.Printf("\n✓ Помечено для миграции: %d контейнеров", markedForDrain)
|
||
}
|
||
if processedClients > 0 {
|
||
log.Printf("✓ Обработано клиентов: %d", processedClients)
|
||
} else {
|
||
log.Println(" Нет клиентов требующих обработки")
|
||
}
|
||
processDrainingAngieConfigs()
|
||
}
|
||
|
||
func checkAndProcessExistingDrainMarkers(clientTitle string, processedCount *int) {
|
||
hasAnyDrainMarkers := false
|
||
for containerNum := 1; containerNum <= MaxContainersPerClient; containerNum++ {
|
||
if _, err := os.Stat(fmt.Sprintf("/tmp/ptaf-drain-ptaf_%s_%03d", clientTitle, containerNum)); err == nil {
|
||
hasAnyDrainMarkers = true
|
||
break
|
||
}
|
||
}
|
||
if !hasAnyDrainMarkers {
|
||
if _, err := os.Stat(fmt.Sprintf("/tmp/ptaf-angie-drain-%s", clientTitle)); err == nil {
|
||
hasAnyDrainMarkers = true
|
||
}
|
||
}
|
||
if hasAnyDrainMarkers {
|
||
log.Printf(" → Найдены drain-маркеры, обрабатываю")
|
||
processDrainingContainers(clientTitle, 0)
|
||
*processedCount++
|
||
}
|
||
}
|
||
|
||
func processDrainingContainers(clientTitle string, maxContainerNum int) {
|
||
baseDir := filepath.Join("/home/install/ptaf", clientTitle)
|
||
currentTime := time.Now().Unix()
|
||
hostname, err := os.Hostname()
|
||
if err != nil {
|
||
log.Printf("⚠ Ошибка получения hostname: %v", err)
|
||
return
|
||
}
|
||
tempConfig := getDefaultConfig()
|
||
db := connectToDB(tempConfig)
|
||
defer db.Close()
|
||
|
||
for containerNum := maxContainerNum + 1; containerNum <= MaxContainersPerClient; containerNum++ {
|
||
containerName := fmt.Sprintf("%s-ptaf-agent%03d", clientTitle, containerNum)
|
||
containerFullName := fmt.Sprintf("ptaf_%s_%03d", clientTitle, containerNum)
|
||
containerDir := filepath.Join(baseDir, containerName)
|
||
composeFile := filepath.Join(containerDir, "docker-compose.yml")
|
||
exists, _ := checkContainerStatus(containerFullName)
|
||
if !exists {
|
||
markerFile := fmt.Sprintf("/tmp/ptaf-drain-%s", containerFullName)
|
||
if _, err := os.Stat(markerFile); err == nil {
|
||
log.Printf(" Удаляю файл-маркер для несуществующего контейнера %s", containerFullName)
|
||
os.Remove(markerFile)
|
||
}
|
||
continue
|
||
}
|
||
drainTimestamp, isMigration, isDraining := getContainerDrainInfo(containerFullName)
|
||
if isDraining {
|
||
var timeout int64
|
||
timeoutName := "масштабирование"
|
||
if isMigration {
|
||
timeout = int64(MigrationDrainTimeout)
|
||
timeoutName = "миграция"
|
||
} else {
|
||
timeout = int64(ContainerDrainTimeout)
|
||
}
|
||
elapsedTime := currentTime - drainTimestamp
|
||
remainingTime := timeout - elapsedTime
|
||
if elapsedTime >= timeout {
|
||
if isMigration {
|
||
log.Printf(" Контейнер %s завершил drain [миграция] (прошло %d сек), проверяю актуальность...", containerFullName, elapsedTime)
|
||
clientInfo, err := getClientInfoByClientTitle(db, clientTitle)
|
||
if err == nil && clientInfo.WAFInstance != "" {
|
||
belongsNow, checkErr := checkHostBelongsToInstance(db, hostname, clientInfo.WAFInstance)
|
||
if checkErr == nil && belongsNow {
|
||
log.Printf(" ⚠ Клиент %s снова принадлежит этому хосту, ОТМЕНЯЮ удаление контейнера %s", clientTitle, containerFullName)
|
||
os.Remove(fmt.Sprintf("/tmp/ptaf-drain-%s", containerFullName))
|
||
continue
|
||
}
|
||
}
|
||
log.Printf(" → Клиент %s не принадлежит этому хосту, удаляю контейнер %s", clientTitle, containerFullName)
|
||
} else {
|
||
log.Printf(" Контейнер %s завершил drain [%s] (прошло %d сек), удаляем", containerFullName, timeoutName, elapsedTime)
|
||
}
|
||
if err := stopAndRemoveContainer(containerFullName, composeFile, containerDir); err != nil {
|
||
log.Printf(" ⚠ Ошибка удаления контейнера %s: %v", containerFullName, err)
|
||
continue
|
||
}
|
||
if err := removeContainerFiles(clientTitle, containerNum); err != nil {
|
||
log.Printf(" ⚠ Ошибка удаления файлов контейнера %s: %v", containerFullName, err)
|
||
} else {
|
||
if err := cleanupClientDirectoryIfEmpty(clientTitle); err != nil {
|
||
log.Printf(" ⚠ Ошибка очистки директорий клиента: %v", err)
|
||
}
|
||
}
|
||
} else {
|
||
log.Printf(" ○ Контейнер %s в процессе drain [%s] (осталось %d сек)", containerFullName, timeoutName, remainingTime)
|
||
}
|
||
} else {
|
||
log.Printf(" Контейнер %s больше не нужен, помечаем для drain", containerFullName)
|
||
if err := markContainerForDrain(containerFullName, false); err != nil {
|
||
log.Printf(" ⚠ Ошибка пометки контейнера для drain: %v", err)
|
||
} else {
|
||
log.Printf(" ✓ Контейнер %s помечен для drain, удаление через %d сек", containerFullName, ContainerDrainTimeout)
|
||
}
|
||
}
|
||
}
|
||
}
|