Update cmd/futriis/main.go
This commit is contained in:
1 parent
7804ce2338
commit
1607f4108d
1 file changed
+20
-101
+20
-101
@@ -10,58 +10,13 @@
|
||||
* Файл: cmd/futriis/main.go
|
||||
* Назначение: Точка входа в приложение СУБД futriis.
|
||||
*
|
||||
* ИСПРАВЛЕНО:
|
||||
* - Валидация пути к конфигурации и проверка прав доступа к файлу
|
||||
* - Валидация конфигурации после загрузки (ValidateConfig)
|
||||
* - WAL и data-dir привязаны к директории futriis, а не CWD
|
||||
* - Graceful shutdown: убраны os.Exit(1) до завершения defer'ов,
|
||||
* используется переменная exitCode и os.Exit в самом конце
|
||||
* - Ожидание запуска HTTP-сервера с таймаутом
|
||||
* - SAGA оркестратор останавливается всегда (даже если disabled)
|
||||
* - Проверка allow-list для плагинов перед StartPlugin
|
||||
* - Заменён SIGHUP на SIGUSR1 (reload) + SIGTERM/SIGINT (shutdown)
|
||||
* - Проверка nil для raftCoordinator перед использованием
|
||||
* - repl.NewRepl теперь возвращает (*Repl, error)
|
||||
* - Интеграция с Prometheus: /metrics endpoint
|
||||
* - ДОБАВЛЕНО: поддержка TLS на HTTP API (вариант A — TLS внутри futriiX).
|
||||
* Теперь используется api.NewHTTPServerWithTLS(..., &cfg.Security).
|
||||
* - ИСПРАВЛЕНО (UI): убраны лишние пустые строки перед баннером, чтобы
|
||||
* строка "futriis 3i²(by 29.09.2026)" шла сразу после сообщения ACL
|
||||
* о созданном admin-пользователе.
|
||||
* - ИСПРАВЛЕНО (UI): текст баннера окрашивается в точный #00bfff
|
||||
* (Deep Sky Blue) через utils.PrintlnDeepSkyBlueReset, чтобы совпадать
|
||||
* с цветом надписи "futriis 3i²(by 29.09.2026)".
|
||||
* - ИСПРАВЛЕНО: acl.NewACLManager() теперь возвращает (*ACLManager, error).
|
||||
* Обрабатываем ошибку и завершаемся через os.Exit(1) с логом,
|
||||
* без panic и stack trace.
|
||||
*
|
||||
* ИСПРАВЛЕНО (2026-10): после замены ACID-транзакций на batch-операции
|
||||
* функции storage.InitTransactionManager, storage.SetTransactionLogger,
|
||||
* storage.StopTransactionManager были удалены. Заменены на:
|
||||
* - storage.InitBatchManager(walPath) — инициализирует batch-менеджер,
|
||||
* WAL и AOF (append-only file для мутаций вне batch);
|
||||
* - storage.SetBatchLogger(logger) — устанавливает логгер;
|
||||
* - storage.StopBatchManager() — останавливает batch-менеджер,
|
||||
* WAL и AOF.
|
||||
* Дополнительно: при shutdown логируется состояние batch-статистики
|
||||
* (количество выполненных batch'ей, операций, ошибок) для диагностики.
|
||||
*
|
||||
* ДОБАВЛЕНО (2026-10, выборы лидера SAGA через Raft):
|
||||
* - SAGA-оркестратор теперь создаётся через
|
||||
* storage.NewSagaOrchestratorWithRaft и получает bridge из
|
||||
* raftCoordinator (raftCoordinator.GetSagaLeaderBridge()).
|
||||
* Это позволяет в кластерном режиме выбирать SAGA-лидера через Raft
|
||||
* и выполнять компенсации только на одном узле.
|
||||
* - В single-node режиме bridge также возвращает корректные значения
|
||||
* (IsLeader() == true), так что поведение не меняется.
|
||||
* - Раньше оркестратор создавался через NewSagaOrchestratorWithConfig
|
||||
* с raftAccessor == nil, что в кластерном режиме приводило к тому,
|
||||
* что каждый узел считал себя SAGA-лидером. Идемпотентность шагов
|
||||
* через saga_executed_keys.json защищала от дублей операций, но не
|
||||
* от одновременной компенсации одной SAGA с двух узлов.
|
||||
* - Порядок shutdown безопасен: sagaOrchestrator.Stop() вызывается
|
||||
* раньше raftCoordinator.Stop(), поэтому bridge ещё жив, когда
|
||||
* оркестратор отписывается от него.
|
||||
* ДОБАВЛЕНО (2026-10, динамическая конфигурация):
|
||||
* После создания REPL передаём ему ссылку на DynamicConfigManager
|
||||
* (через SetDynamicConfigManager), чтобы команды `config set`,
|
||||
* `compression config set`, `cluster pipeline on/off` могли
|
||||
* применять изменения через Raft и сохранять их на всех узлах.
|
||||
* В fallback-режиме (Raft недоступен или узел не лидер) REPL сам
|
||||
* запишет изменения в config.toml через config.SaveConfig.
|
||||
*/
|
||||
|
||||
package main
|
||||
@@ -146,13 +101,6 @@ func main() {
|
||||
store := storage.NewStorage(cfg.Storage.PageSizeMB, logger)
|
||||
storage.SetGlobalStorage(store)
|
||||
|
||||
// ========== BATCH MANAGER (замена TransactionManager) ==========
|
||||
//
|
||||
// ИСПРАВЛЕНО (2026-10): InitTransactionManager заменён на InitBatchManager.
|
||||
// InitBatchManager инициализирует:
|
||||
// - WAL (сегментированный журнал для batch-операций);
|
||||
// - AOF (append-only file для мутаций вне batch).
|
||||
// Директория AOF создаётся автоматически как filepath.Join(filepath.Dir(walPath), "aof").
|
||||
walPath := filepath.Join(dataDir, "futriis.wal")
|
||||
if err := storage.InitBatchManager(walPath); err != nil {
|
||||
logger.Warn("Failed to initialize batch manager: " + err.Error())
|
||||
@@ -166,8 +114,6 @@ func main() {
|
||||
storage.InitTriggerManager(logger)
|
||||
logger.Info("Trigger manager initialized")
|
||||
|
||||
// ИСПРАВЛЕНО: NewACLManager теперь возвращает (*ACLManager, error).
|
||||
// Обрабатываем ошибку управляемо (без panic) и завершаемся с кодом 1.
|
||||
aclManager, err := acl.NewACLManager()
|
||||
if err != nil {
|
||||
logger.Error("Failed to initialize ACL manager: " + err.Error())
|
||||
@@ -184,27 +130,11 @@ func main() {
|
||||
}
|
||||
|
||||
// ========== SAGA ORCHESTRATOR ==========
|
||||
//
|
||||
// ДОБАВЛЕНО (2026-10): используем NewSagaOrchestratorWithRaft и
|
||||
// передаём bridge из raftCoordinator. Это позволяет в кластерном
|
||||
// режиме выбирать SAGA-лидера через Raft, чтобы компенсации
|
||||
// выполнялись только на одном узле.
|
||||
//
|
||||
// В single-node режиме bridge возвращает IsLeader() == true, так что
|
||||
// поведение не меняется. В кластерном режиме только лидер Raft
|
||||
// будет IsSagaLeader() == true, и только он будет выполнять SAGA.
|
||||
//
|
||||
// Если raftCoordinator == nil (не должно случаться — выше os.Exit(1)
|
||||
// при ошибке), создаём оркестратор без Raft (single-node fallback).
|
||||
var sagaOrchestrator *storage.SagaOrchestrator
|
||||
if cfg.Saga.Enabled {
|
||||
sagaConfig := &cfg.Saga
|
||||
nodeID := cfg.Cluster.NodeIP + ":" + fmt.Sprint(cfg.Cluster.NodePort)
|
||||
|
||||
// Передаём bridge из Raft в качестве storage.SagaRaftAccessor.
|
||||
// Тип bridge реализует интерфейс storage.SagaRaftAccessor
|
||||
// (IsSagaLeader, GetSagaLeaderID, GetSagaCurrentTerm,
|
||||
// RegisterSagaLeaderObserver).
|
||||
var raftAccessor storage.SagaRaftAccessor
|
||||
if raftCoordinator != nil {
|
||||
raftAccessor = raftCoordinator.GetSagaLeaderBridge()
|
||||
@@ -311,8 +241,6 @@ func main() {
|
||||
}
|
||||
|
||||
// ========== HTTP API SERVER ==========
|
||||
// ДОБАВЛЕНО: используем NewHTTPServerWithTLS и передаём cfg.Security.
|
||||
// Если в config.toml [security].enable_tls = true, сервер поднимется по HTTPS.
|
||||
httpPort := cfg.API.Port
|
||||
httpServer := api.NewHTTPServerWithTLS(httpPort, store, raftCoordinator, aclManager, logger, &cfg.Security)
|
||||
if metricsCollector != nil {
|
||||
@@ -362,10 +290,6 @@ func main() {
|
||||
logger.Info(" - Automatic data persistence - ENABLED")
|
||||
}
|
||||
|
||||
// ИСПРАВЛЕНО (UI): убраны лишние пустые строки перед баннером.
|
||||
// Внутри displayBanner уже есть utils.Println("") первой строкой,
|
||||
// чтобы отделить баннер от ACL-сообщения. Строка "futriis 3i²(by
|
||||
// 29.09.2026)" идёт сразу после сообщения ACL.
|
||||
displayBanner(cfg.Cluster.Name, httpPort, raftCoordinator, cfg.Saga.Enabled, cfg.Metrics.Enabled)
|
||||
|
||||
replInstance, err := repl.NewRepl(store, raftCoordinator, logger, cfg, aclManager, pluginManager)
|
||||
@@ -376,6 +300,19 @@ func main() {
|
||||
}
|
||||
defer replInstance.Close()
|
||||
|
||||
// ДОБАВЛЕНО (2026-10): передаём REPL ссылку на DynamicConfigManager,
|
||||
// чтобы команды `config set`, `compression config set`,
|
||||
// `cluster pipeline on/off` могли применять изменения через Raft.
|
||||
// В fallback-режиме REPL пишет config.toml через config.SaveConfig.
|
||||
if raftCoordinator != nil {
|
||||
replInstance.SetDynamicConfigManager(
|
||||
raftCoordinator.GetDynamicConfigManager(),
|
||||
configPath,
|
||||
)
|
||||
} else {
|
||||
replInstance.SetDynamicConfigManager(nil, configPath)
|
||||
}
|
||||
|
||||
// ========== GRACEFUL SHUTDOWN ==========
|
||||
sigChan := make(chan os.Signal, 1)
|
||||
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGUSR1)
|
||||
@@ -431,10 +368,6 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
// ВАЖНО: sagaOrchestrator останавливается ДО raftCoordinator.
|
||||
// Это гарантирует, что оркестратор отпишется от sagaLeaderBridge
|
||||
// (и остановит leaderElector) до того, как bridge будет остановлен
|
||||
// вместе с координатором. Двойной остановки bridge не будет.
|
||||
if sagaOrchestrator != nil {
|
||||
logger.Info("Stopping SAGA orchestrator...")
|
||||
sagaStopDone := make(chan struct{})
|
||||
@@ -497,8 +430,6 @@ func main() {
|
||||
logger.Warn("Node shutdown timeout")
|
||||
}
|
||||
|
||||
// ИСПРАВЛЕНО (2026-10): StopTransactionManager заменён на StopBatchManager.
|
||||
// StopBatchManager синхронизирует WAL, закрывает его и закрывает AOF.
|
||||
logger.Info("Stopping batch manager (WAL + AOF)...")
|
||||
if stats := storage.GetBatchStats(); stats != nil {
|
||||
logger.Info(fmt.Sprintf(" Batch stats: total_batches=%v, total_operations=%v, total_failed=%v, active_batches=%v",
|
||||
@@ -515,7 +446,6 @@ func main() {
|
||||
}()
|
||||
select {
|
||||
case <-batchStopDone:
|
||||
// уже залогировано внутри горутины
|
||||
case <-time.After(10 * time.Second):
|
||||
logger.Warn("Batch manager shutdown timeout")
|
||||
}
|
||||
@@ -679,15 +609,6 @@ func isPortListening(port int) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// displayBanner выводит стартовый баннер.
|
||||
//
|
||||
// ИСПРАВЛЕНО (UI): текст баннера окрашивается точным цветом
|
||||
// #00bfff (Deep Sky Blue) через utils.PrintlnDeepSkyBlueReset, чтобы
|
||||
// совпадать с цветом надписи "futriis 3i²(by 29.09.2026)".
|
||||
//
|
||||
// Внутри уже есть utils.Println("") первой строкой, чтобы отделить
|
||||
// баннер от ACL-сообщения. Дополнительные пустые строки в main() не
|
||||
// добавляются (см. комментарий перед вызовом displayBanner).
|
||||
func displayBanner(clusterName string, httpPort int, coordinator *cluster.RaftCoordinator, sagaEnabled, metricsEnabled bool) {
|
||||
utils.Println("")
|
||||
bannerLines := []string{
|
||||
@@ -738,8 +659,6 @@ func displayBanner(clusterName string, httpPort int, coordinator *cluster.RaftCo
|
||||
}...)
|
||||
|
||||
for _, line := range bannerLines {
|
||||
// ИСПРАВЛЕНО: используем точный цвет #00bfff (Deep Sky Blue),
|
||||
// совпадающий с цветом заголовка баннера.
|
||||
utils.PrintlnDeepSkyBlueReset(line)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user