2205 lines
71 KiB
Go
2205 lines
71 KiB
Go
/*
|
||
* Copyright 2026 Safronov Grigorii
|
||
*
|
||
* Licensed under the CDDL, Version 1.0 (the "License");
|
||
* you may not use this file except in compliance with the License.
|
||
*
|
||
* You may obtain a copy of the License at
|
||
* https://opensource.org/licenses/CDDL-1.0
|
||
*/
|
||
|
||
// Файл: internal/config/config.go
|
||
// Назначение: Загрузка и парсинг TOML-конфигурации, валидация параметров,
|
||
// предоставление доступа к настройкам кластера, хранилища и REPL.
|
||
//
|
||
// Данный пакет отвечает за:
|
||
// - Чтение конфигурационного файла в формате TOML
|
||
// - Установку значений по умолчанию для всех параметров
|
||
// - Валидацию корректности настроек
|
||
// - Предоставление типобезопасного доступа к конфигурации через методы-геттеры
|
||
// - Сохранение конфигурации в TOML (SaveConfig) — используется REPL-командой
|
||
// "config set" в fallback-режиме, когда Raft недоступен.
|
||
//
|
||
// ИСПРАВЛЕНО: добавлена секция [metrics] (MetricsConfig) — иначе
|
||
// cmd/futriis/main.go не компилируется из-за cfg.Metrics.
|
||
//
|
||
// ИСПРАВЛЕНО (аудит, кросс-платформенность Linux/OpenIndiana):
|
||
// - BackupConfig дополнен полями IncludeSagaState и EnableIncremental.
|
||
// - Добавлены геттеры IsIncludeSagaStateEnabled / IsIncrementalEnabled.
|
||
// - В LoadConfig добавлены значения по умолчанию для новых полей.
|
||
// - В ValidateConfigFull добавлена валидация [backup] и [engines].
|
||
//
|
||
// ДОБАВЛЕНО (2026-10):
|
||
// - SaveConfig — атомарная запись конфигурации в TOML (tempfile + fsync + rename).
|
||
// - fsyncDir — синхронизация директории (no-op на Windows).
|
||
|
||
package config
|
||
|
||
import (
|
||
"fmt"
|
||
"net"
|
||
"os"
|
||
"path/filepath"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/BurntSushi/toml"
|
||
)
|
||
|
||
// =============================================================================
|
||
// КОНСТАНТЫ ДЛЯ ВАЛИДАЦИИ
|
||
// =============================================================================
|
||
|
||
const (
|
||
// Максимальный размер файла конфигурации (1 MB)
|
||
maxConfigFileSize = 1 * 1024 * 1024
|
||
// Минимальная длина API-ключа
|
||
minAPIKeyLength = 8
|
||
)
|
||
|
||
// allowedLogLevels — допустимые уровни логирования
|
||
var allowedLogLevels = map[string]bool{
|
||
"debug": true,
|
||
"info": true,
|
||
"warn": true,
|
||
"error": true,
|
||
"fatal": true,
|
||
}
|
||
|
||
// allowedMigrationModes — допустимые режимы миграции
|
||
var allowedMigrationModes = map[string]bool{
|
||
"manual": true,
|
||
"semi_auto": true,
|
||
"auto": true,
|
||
}
|
||
|
||
// allowedCompressionAlgorithms — допустимые алгоритмы сжатия
|
||
var allowedCompressionAlgorithms = map[string]bool{
|
||
"snappy": true,
|
||
"lz4": true,
|
||
"zstd": true,
|
||
}
|
||
|
||
// =============================================================================
|
||
// ОСНОВНАЯ СТРУКТУРА КОНФИГУРАЦИИ
|
||
// =============================================================================
|
||
|
||
// Config представляет собой корневую структуру конфигурации,
|
||
// объединяющую все подсистемы приложения.
|
||
// Каждое поле соответствует секции в TOML-файле.
|
||
type Config struct {
|
||
Cluster ClusterConfig `toml:"cluster"` // Настройки кластеризации (Raft, узлы)
|
||
Storage StorageConfig `toml:"storage"` // Настройки хранилища (движки, ограничения)
|
||
Repl ReplConfig `toml:"repl"` // Настройки REPL (интерактивной оболочки)
|
||
Log LogConfig `toml:"log"` // Настройки логирования
|
||
API APIConfig `toml:"api"` // Настройки HTTP API
|
||
Replication ReplicationConfig `toml:"replication"` // Настройки репликации
|
||
Plugins PluginsConfig `toml:"plugins"` // Настройки системы плагинов (Lua)
|
||
Compression CompressionConfig `toml:"compression"` // Настройки сжатия данных
|
||
Performance PerformanceConfig `toml:"performance"` // Настройки производительности
|
||
Security SecurityConfig `toml:"security"` // Настройки безопасности (TLS)
|
||
Monitoring MonitoringConfig `toml:"monitoring"` // Настройки мониторинга (метрики, трассировка)
|
||
Recovery RecoveryConfig `toml:"recovery"` // Настройки восстановления после сбоев
|
||
WAL WALConfig `toml:"wal"` // Настройки WAL (Write-Ahead Log)
|
||
MVCC MVCCConfig `toml:"mvcc"` // Настройки MVCC
|
||
Transactions TransactionsConfig `toml:"transactions"` // Настройки транзакций
|
||
Saga SagaConfig `toml:"saga"` // Настройки SAGA оркестратора
|
||
ACL ACLConfig `toml:"acl"` // Настройки ACL
|
||
ClusterTLS TLSConfig `toml:"cluster_tls"` // Настройки TLS для кластерного взаимодействия
|
||
Backpressure BackpressureConfig `toml:"backpressure"` // Настройки механизма обратного давления
|
||
RuntimeLimits RuntimeLimitsConfig `toml:"runtime_limits"` // Ограничения времени выполнения
|
||
Autoscaling AutoscalingConfig `toml:"autoscaling"` // Настройки автоматического масштабирования
|
||
SchemaMigration SchemaMigrationConfig `toml:"schema_migration"` // Настройки миграции схемы
|
||
Backup BackupConfig `toml:"backup"` // Настройки резервного копирования
|
||
Engines EnginesConfig `toml:"engines"` // Настройки движков хранения
|
||
Migration MigrationConfig `toml:"migration"` // Настройки кросс-датацентровой миграции
|
||
Metrics MetricsConfig `toml:"metrics"` // Настройки Prometheus/Grafana
|
||
}
|
||
|
||
// =============================================================================
|
||
// КОНФИГУРАЦИЯ МЕТРИК
|
||
// =============================================================================
|
||
|
||
// MetricsConfig содержит настройки экспорта метрик в формате Prometheus.
|
||
type MetricsConfig struct {
|
||
Enabled bool `toml:"enabled"`
|
||
CollectIntervalSec int `toml:"collect_interval_sec"`
|
||
}
|
||
|
||
// IsMetricsEnabled возвращает флаг включения метрик.
|
||
func (m *MetricsConfig) IsMetricsEnabled() bool { return m.Enabled }
|
||
|
||
// GetCollectInterval возвращает интервал сбора метрик как time.Duration.
|
||
func (m *MetricsConfig) GetCollectInterval() time.Duration {
|
||
if m.CollectIntervalSec <= 0 {
|
||
return 15 * time.Second
|
||
}
|
||
return time.Duration(m.CollectIntervalSec) * time.Second
|
||
}
|
||
|
||
// =============================================================================
|
||
// КОНФИГУРАЦИЯ КЛАСТЕРА
|
||
// =============================================================================
|
||
|
||
// ClusterConfig содержит настройки для работы в кластерном режиме.
|
||
type ClusterConfig struct {
|
||
Name string `toml:"name"`
|
||
|
||
NodeIP string `toml:"node_ip"`
|
||
NodePort int `toml:"node_port"`
|
||
RaftPort int `toml:"raft_port"`
|
||
|
||
RaftDataDir string `toml:"raft_data_dir"`
|
||
Bootstrap bool `toml:"bootstrap"`
|
||
Nodes []string `toml:"nodes"`
|
||
|
||
HeartbeatTimeoutMs int `toml:"heartbeat_timeout_ms"`
|
||
ElectionTimeoutMs int `toml:"election_timeout_ms"`
|
||
CommitTimeoutMs int `toml:"commit_timeout_ms"`
|
||
|
||
SnapshotIntervalMin int `toml:"snapshot_interval_min"`
|
||
SnapshotThreshold int `toml:"snapshot_threshold"`
|
||
|
||
SplitBrainPrevention bool `toml:"split_brain_prevention"`
|
||
RecoveryTimeoutSec int `toml:"recovery_timeout_sec"`
|
||
|
||
Region string `toml:"region"`
|
||
PriorityZone int `toml:"priority_zone"`
|
||
}
|
||
|
||
// =============================================================================
|
||
// КОНФИГУРАЦИЯ SAGA
|
||
// =============================================================================
|
||
|
||
// SagaConfig содержит настройки SAGA оркестратора.
|
||
type SagaConfig struct {
|
||
Enabled bool `toml:"enabled"`
|
||
CoordinatorCount int `toml:"coordinator_count"`
|
||
StateDir string `toml:"state_dir"`
|
||
|
||
MaxRetries int `toml:"max_retries"`
|
||
RetryBackoffMs int `toml:"retry_backoff_ms"`
|
||
SagaTimeoutSec int `toml:"saga_timeout_sec"`
|
||
StuckCheckIntervalSec int `toml:"stuck_check_interval_sec"`
|
||
|
||
LeaderElectionIntervalSec int `toml:"leader_election_interval_sec"`
|
||
RecoveryIntervalSec int `toml:"recovery_interval_sec"`
|
||
MetricsIntervalSec int `toml:"metrics_interval_sec"`
|
||
|
||
CleanupPeriodHours int `toml:"cleanup_period_hours"`
|
||
MaxCacheSize int `toml:"max_cache_size"`
|
||
OperationRetentionDays int `toml:"operation_retention_days"`
|
||
|
||
ChannelBufferSize int `toml:"channel_buffer_size"`
|
||
AsyncRecoveryWorkers int `toml:"async_recovery_workers"`
|
||
AsyncRecoveryTimeoutSec int `toml:"async_recovery_timeout_sec"`
|
||
|
||
FsyncEnabled bool `toml:"fsync_enabled"`
|
||
FsyncMaxRetries int `toml:"fsync_max_retries"`
|
||
FsyncRetryDelayMs int `toml:"fsync_retry_delay_ms"`
|
||
}
|
||
|
||
// =============================================================================
|
||
// КОНФИГУРАЦИЯ ХРАНИЛИЩА
|
||
// =============================================================================
|
||
|
||
type StorageConfig struct {
|
||
PageSizeMB int `toml:"page_size_mb"`
|
||
MaxCollections int `toml:"max_collections"`
|
||
MaxDocumentsPerCollection int `toml:"max_documents_per_collection"`
|
||
DefaultEngine string `toml:"default_engine"`
|
||
EnableCustomEngines bool `toml:"enable_custom_engines"`
|
||
}
|
||
|
||
// =============================================================================
|
||
// КОНФИГУРАЦИЯ REPL
|
||
// =============================================================================
|
||
|
||
type ReplConfig struct {
|
||
PromptColor string `toml:"prompt_color"`
|
||
HistorySize int `toml:"history_size"`
|
||
}
|
||
|
||
// =============================================================================
|
||
// КОНФИГУРАЦИЯ ЛОГИРОВАНИЯ
|
||
// =============================================================================
|
||
|
||
type LogConfig struct {
|
||
LogFile string `toml:"log_file"`
|
||
LogLevel string `toml:"log_level"`
|
||
}
|
||
|
||
// =============================================================================
|
||
// КОНФИГУРАЦИЯ HTTP API
|
||
// =============================================================================
|
||
|
||
type APIConfig struct {
|
||
Port int `toml:"port"`
|
||
}
|
||
|
||
// =============================================================================
|
||
// КОНФИГУРАЦИЯ РЕПЛИКАЦИИ
|
||
// =============================================================================
|
||
|
||
type ReplicationConfig struct {
|
||
Enabled bool `toml:"enabled"`
|
||
SyncReplication bool `toml:"sync_replication"`
|
||
ReplicationTimeoutMs int `toml:"replication_timeout_ms"`
|
||
MaxReplicaLagMs int `toml:"max_replica_lag_ms"`
|
||
}
|
||
|
||
// =============================================================================
|
||
// КОНФИГУРАЦИЯ ПЛАГИНОВ (LUA)
|
||
// =============================================================================
|
||
|
||
type PluginsConfig struct {
|
||
Enabled bool `toml:"enabled"`
|
||
ScriptDir string `toml:"script_dir"`
|
||
AllowList []string `toml:"allow_list"`
|
||
MaxCPUTimeMs int `toml:"max_cpu_time_ms"`
|
||
MaxMemoryMB int `toml:"max_memory_mb"`
|
||
MaxExecutionTimeSec int `toml:"max_execution_time_sec"`
|
||
MaxInstructions int64 `toml:"max_instructions"`
|
||
HotReloadIntervalSec int `toml:"hot_reload_interval_sec"`
|
||
MaxEventLogSize int `toml:"max_event_log_size"`
|
||
LoadTimeoutSec int `toml:"load_timeout_sec"`
|
||
MaxLuaStates int `toml:"max_lua_states"`
|
||
LuaStateTTLSec int `toml:"lua_state_ttl_sec"`
|
||
EnginePluginDir string `toml:"engine_plugin_dir"`
|
||
}
|
||
|
||
// =============================================================================
|
||
// КОНФИГУРАЦИЯ СЖАТИЯ
|
||
// =============================================================================
|
||
|
||
type CompressionConfig struct {
|
||
Enabled bool `toml:"enabled"`
|
||
Algorithm string `toml:"algorithm"`
|
||
Level int `toml:"level"`
|
||
MinSize int `toml:"min_size"`
|
||
}
|
||
|
||
// =============================================================================
|
||
// КОНФИГУРАЦИЯ ПРОИЗВОДИТЕЛЬНОСТИ
|
||
// =============================================================================
|
||
|
||
type PerformanceConfig struct {
|
||
EnablePipeline bool `toml:"enable_pipeline"`
|
||
BatchSize int `toml:"batch_size"`
|
||
ReadFromFollower bool `toml:"read_from_follower"`
|
||
MaxConnections int `toml:"max_connections"`
|
||
ReadReplicaDelayMs int `toml:"read_replica_delay_ms"`
|
||
}
|
||
|
||
// =============================================================================
|
||
// КОНФИГУРАЦИЯ БЕЗОПАСНОСТИ
|
||
// =============================================================================
|
||
|
||
type SecurityConfig struct {
|
||
EnableTLS bool `toml:"enable_tls"`
|
||
CertFile string `toml:"cert_file"`
|
||
KeyFile string `toml:"key_file"`
|
||
CAFile string `toml:"ca_file"`
|
||
MinVersion string `toml:"min_version"`
|
||
}
|
||
|
||
// =============================================================================
|
||
// КОНФИГУРАЦИЯ МОНИТОРИНГА
|
||
// =============================================================================
|
||
|
||
type MonitoringConfig struct {
|
||
EnableMetrics bool `toml:"enable_metrics"`
|
||
MetricsPort int `toml:"metrics_port"`
|
||
EnableTracing bool `toml:"enable_tracing"`
|
||
TraceSampleRate float64 `toml:"trace_sample_rate"`
|
||
}
|
||
|
||
// =============================================================================
|
||
// КОНФИГУРАЦИЯ ВОССТАНОВЛЕНИЯ
|
||
// =============================================================================
|
||
|
||
type RecoveryConfig struct {
|
||
AutoRejoin bool `toml:"auto_rejoin"`
|
||
MaxRetrySec int `toml:"max_retry_sec"`
|
||
DataReplicationTimeoutSec int `toml:"data_replication_timeout_sec"`
|
||
StaleReadTimeoutSec int `toml:"stale_read_timeout_sec"`
|
||
}
|
||
|
||
// =============================================================================
|
||
// КОНФИГУРАЦИЯ WAL
|
||
// =============================================================================
|
||
|
||
type WALConfig struct {
|
||
SegmentSizeMB int `toml:"segment_size_mb"`
|
||
SyncIntervalSec int `toml:"sync_interval_sec"`
|
||
BatchSize int `toml:"batch_size"`
|
||
RecoveryWorkers int `toml:"recovery_workers"`
|
||
Enabled bool `toml:"enabled"`
|
||
AsyncRecovery bool `toml:"async_recovery"`
|
||
AsyncRecoveryWorkers int `toml:"async_recovery_workers"`
|
||
AsyncRecoveryBuffer int `toml:"async_recovery_buffer"`
|
||
}
|
||
|
||
// =============================================================================
|
||
// КОНФИГУРАЦИЯ MVCC
|
||
// =============================================================================
|
||
|
||
type MVCCConfig struct {
|
||
MaxVersionsPerDoc int `toml:"max_versions_per_doc"`
|
||
VisibilityMapSize int `toml:"visibility_map_size"`
|
||
PruneIntervalMin int `toml:"prune_interval_min"`
|
||
RetentionDays int `toml:"retention_days"`
|
||
ReadCacheSize int `toml:"read_cache_size"`
|
||
ReadCacheTTLSec int `toml:"read_cache_ttl_sec"`
|
||
}
|
||
|
||
// =============================================================================
|
||
// КОНФИГУРАЦИЯ ТРАНЗАКЦИЙ
|
||
// =============================================================================
|
||
|
||
type TransactionsConfig struct {
|
||
DefaultTimeoutSec int `toml:"default_timeout_sec"`
|
||
DeadlockCheckIntervalSec int `toml:"deadlock_check_interval_sec"`
|
||
MaxSavepointsPerTx int `toml:"max_savepoints_per_tx"`
|
||
Enabled bool `toml:"enabled"`
|
||
CheckpointIntervalSec int `toml:"checkpoint_interval_sec"`
|
||
}
|
||
|
||
// =============================================================================
|
||
// КОНФИГУРАЦИЯ ACL
|
||
// =============================================================================
|
||
|
||
type ACLConfig struct {
|
||
MaxDeniedLogSize int `toml:"max_denied_log_size"`
|
||
TemporaryGrantTTLHours int `toml:"temporary_grant_ttl_hours"`
|
||
EnableRoleHierarchy bool `toml:"enable_role_hierarchy"`
|
||
CacheTTLSec int `toml:"cache_ttl_sec"`
|
||
}
|
||
|
||
// =============================================================================
|
||
// КОНФИГУРАЦИЯ TLS ДЛЯ КЛАСТЕРА
|
||
// =============================================================================
|
||
|
||
type TLSConfig struct {
|
||
Enabled bool `toml:"enabled"`
|
||
CertFile string `toml:"cert_file"`
|
||
KeyFile string `toml:"key_file"`
|
||
CAFile string `toml:"ca_file"`
|
||
MinVersion string `toml:"min_version"`
|
||
MutualAuth bool `toml:"mutual_auth"`
|
||
KeyRotationDays int `toml:"key_rotation_days"`
|
||
AutoGenerate bool `toml:"auto_generate"`
|
||
}
|
||
|
||
// =============================================================================
|
||
// КОНФИГУРАЦИЯ BACKPRESSURE
|
||
// =============================================================================
|
||
|
||
type BackpressureConfig struct {
|
||
Enabled bool `toml:"enabled"`
|
||
CPUThreshold float64 `toml:"cpu_threshold"`
|
||
MemoryThreshold float64 `toml:"memory_threshold"`
|
||
QueueSizeThreshold int `toml:"queue_size_threshold"`
|
||
ConnectionThreshold int `toml:"connection_threshold"`
|
||
CheckIntervalMs int `toml:"check_interval_ms"`
|
||
LowDelayMs int64 `toml:"low_delay_ms"`
|
||
MediumRejectProb uint32 `toml:"medium_reject_prob"`
|
||
HighRejectProb uint32 `toml:"high_reject_prob"`
|
||
}
|
||
|
||
// =============================================================================
|
||
// КОНФИГУРАЦИЯ RUNTIME LIMITS
|
||
// =============================================================================
|
||
|
||
type RuntimeLimitsConfig struct {
|
||
Enabled bool `toml:"enabled"`
|
||
GlobalMaxDocSizeMB int `toml:"global_max_doc_size_mb"`
|
||
GlobalMaxCollSizeMB int64 `toml:"global_max_coll_size_mb"`
|
||
GlobalMaxDocsPerColl int64 `toml:"global_max_docs_per_coll"`
|
||
}
|
||
|
||
// =============================================================================
|
||
// КОНФИГУРАЦИЯ AUTOSCALING
|
||
// =============================================================================
|
||
|
||
type AutoscalingConfig struct {
|
||
Enabled bool `toml:"enabled"`
|
||
MinNodes int `toml:"min_nodes"`
|
||
MaxNodes int `toml:"max_nodes"`
|
||
ScaleUpThreshold float64 `toml:"scale_up_threshold"`
|
||
ScaleDownThreshold float64 `toml:"scale_down_threshold"`
|
||
ScaleUpCooldownSec int `toml:"scale_up_cooldown_sec"`
|
||
ScaleDownCooldownSec int `toml:"scale_down_cooldown_sec"`
|
||
EvaluationIntervalSec int `toml:"evaluation_interval_sec"`
|
||
PredictiveEnabled bool `toml:"predictive_enabled"`
|
||
MaxScaleUpNodes int `toml:"max_scale_up_nodes"`
|
||
MaxScaleDownNodes int `toml:"max_scale_down_nodes"`
|
||
}
|
||
|
||
// =============================================================================
|
||
// КОНФИГУРАЦИЯ МИГРАЦИИ СХЕМЫ
|
||
// =============================================================================
|
||
|
||
type SchemaMigrationConfig struct {
|
||
Enabled bool `toml:"enabled"`
|
||
MigrationDir string `toml:"migration_dir"`
|
||
AutoMigrate bool `toml:"auto_migrate"`
|
||
TargetVersion string `toml:"target_version"`
|
||
}
|
||
|
||
// =============================================================================
|
||
// КОНФИГУРАЦИЯ BACKUP
|
||
// =============================================================================
|
||
|
||
type BackupConfig struct {
|
||
Enabled bool `toml:"enabled"`
|
||
BackupDir string `toml:"backup_dir"`
|
||
MaxConcurrent int `toml:"max_concurrent"`
|
||
CompressEnabled bool `toml:"compress_enabled"`
|
||
RetentionDays int `toml:"retention_days"`
|
||
|
||
IncludeSagaState bool `toml:"include_saga_state"`
|
||
EnableIncremental bool `toml:"enable_incremental"`
|
||
}
|
||
|
||
// =============================================================================
|
||
// КОНФИГУРАЦИЯ ДВИЖКОВ ХРАНЕНИЯ
|
||
// =============================================================================
|
||
|
||
type EnginesConfig struct {
|
||
Row EngineConfig `toml:"row"`
|
||
Columnar EngineConfig `toml:"columnar"`
|
||
Document EngineConfig `toml:"document"`
|
||
KV EngineConfig `toml:"kv"`
|
||
TS EngineConfig `toml:"ts"`
|
||
Graph EngineConfig `toml:"graph"`
|
||
}
|
||
|
||
type EngineConfig struct {
|
||
Enabled bool `toml:"enabled"`
|
||
Description string `toml:"description"`
|
||
Config map[string]interface{} `toml:"config"`
|
||
}
|
||
|
||
// =============================================================================
|
||
// КОНФИГУРАЦИЯ КРОСС-ДАТАЦЕНТРОВОЙ МИГРАЦИИ
|
||
// =============================================================================
|
||
|
||
type MigrationConfig struct {
|
||
Enabled bool `toml:"enabled"`
|
||
Mode string `toml:"mode"`
|
||
Source *DatacenterConfig `toml:"source"`
|
||
Target *DatacenterConfig `toml:"target"`
|
||
Settings *MigrationSettings `toml:"settings"`
|
||
Delta *DeltaSyncConfig `toml:"delta"`
|
||
Validation *ValidationConfig `toml:"validation"`
|
||
}
|
||
|
||
type DatacenterConfig struct {
|
||
Name string `toml:"name"`
|
||
Endpoint string `toml:"endpoint"`
|
||
APIKey string `toml:"api_key"`
|
||
TimeoutSec int `toml:"timeout_sec"`
|
||
}
|
||
|
||
type MigrationSettings struct {
|
||
BatchSize int `toml:"batch_size"`
|
||
Workers int `toml:"workers"`
|
||
Compression string `toml:"compression"`
|
||
ResumeEnabled bool `toml:"resume_enabled"`
|
||
CheckpointIntervalSec int `toml:"checkpoint_interval_sec"`
|
||
MaxRetries int `toml:"max_retries"`
|
||
RetryBackoffSec int `toml:"retry_backoff_sec"`
|
||
Collections []string `toml:"collections"`
|
||
ExcludeCollections []string `toml:"exclude_collections"`
|
||
}
|
||
|
||
type DeltaSyncConfig struct {
|
||
Enabled bool `toml:"enabled"`
|
||
IntervalSec int `toml:"interval_sec"`
|
||
MaxLagSec int `toml:"max_lag_sec"`
|
||
}
|
||
|
||
type ValidationConfig struct {
|
||
Enabled bool `toml:"enabled"`
|
||
SamplePercent int `toml:"sample_percent"`
|
||
MaxErrors int `toml:"max_errors"`
|
||
}
|
||
|
||
// =============================================================================
|
||
// ВСПОМОГАТЕЛЬНЫЕ СТРУКТУРЫ
|
||
// =============================================================================
|
||
|
||
type ValidationResult struct {
|
||
Valid bool
|
||
Errors []error
|
||
Warnings []error
|
||
}
|
||
|
||
// =============================================================================
|
||
// ГЕТТЕРЫ ДЛЯ SagaConfig
|
||
// =============================================================================
|
||
|
||
func (s *SagaConfig) IsSagaEnabled() bool { return s.Enabled }
|
||
|
||
func (s *SagaConfig) GetCoordinatorCount() int {
|
||
if s.CoordinatorCount < 1 {
|
||
return 3
|
||
}
|
||
return s.CoordinatorCount
|
||
}
|
||
|
||
func (s *SagaConfig) GetStateDir() string {
|
||
if s.StateDir == "" {
|
||
return "saga_states"
|
||
}
|
||
return s.StateDir
|
||
}
|
||
|
||
func (s *SagaConfig) GetMaxRetries() int {
|
||
if s.MaxRetries <= 0 {
|
||
return 5
|
||
}
|
||
return s.MaxRetries
|
||
}
|
||
|
||
func (s *SagaConfig) GetRetryBackoff() time.Duration {
|
||
if s.RetryBackoffMs <= 0 {
|
||
return 100 * time.Millisecond
|
||
}
|
||
return time.Duration(s.RetryBackoffMs) * time.Millisecond
|
||
}
|
||
|
||
func (s *SagaConfig) GetSagaTimeout() time.Duration {
|
||
if s.SagaTimeoutSec <= 0 {
|
||
return 300 * time.Second
|
||
}
|
||
return time.Duration(s.SagaTimeoutSec) * time.Second
|
||
}
|
||
|
||
func (s *SagaConfig) GetStuckCheckInterval() time.Duration {
|
||
if s.StuckCheckIntervalSec <= 0 {
|
||
return 10 * time.Second
|
||
}
|
||
return time.Duration(s.StuckCheckIntervalSec) * time.Second
|
||
}
|
||
|
||
func (s *SagaConfig) GetLeaderElectionInterval() time.Duration {
|
||
if s.LeaderElectionIntervalSec <= 0 {
|
||
return 5 * time.Second
|
||
}
|
||
return time.Duration(s.LeaderElectionIntervalSec) * time.Second
|
||
}
|
||
|
||
func (s *SagaConfig) GetRecoveryInterval() time.Duration {
|
||
if s.RecoveryIntervalSec <= 0 {
|
||
return 30 * time.Second
|
||
}
|
||
return time.Duration(s.RecoveryIntervalSec) * time.Second
|
||
}
|
||
|
||
func (s *SagaConfig) GetMetricsInterval() time.Duration {
|
||
if s.MetricsIntervalSec <= 0 {
|
||
return 60 * time.Second
|
||
}
|
||
return time.Duration(s.MetricsIntervalSec) * time.Second
|
||
}
|
||
|
||
func (s *SagaConfig) GetCleanupPeriod() time.Duration {
|
||
if s.CleanupPeriodHours <= 0 {
|
||
return 24 * time.Hour
|
||
}
|
||
return time.Duration(s.CleanupPeriodHours) * time.Hour
|
||
}
|
||
|
||
func (s *SagaConfig) GetMaxCacheSize() int {
|
||
if s.MaxCacheSize <= 0 {
|
||
return 10000
|
||
}
|
||
return s.MaxCacheSize
|
||
}
|
||
|
||
func (s *SagaConfig) GetOperationRetentionDuration() time.Duration {
|
||
if s.OperationRetentionDays <= 0 {
|
||
return 7 * 24 * time.Hour
|
||
}
|
||
return time.Duration(s.OperationRetentionDays) * 24 * time.Hour
|
||
}
|
||
|
||
func (s *SagaConfig) GetChannelBufferSize() int {
|
||
if s.ChannelBufferSize <= 0 {
|
||
return 10000
|
||
}
|
||
return s.ChannelBufferSize
|
||
}
|
||
|
||
func (s *SagaConfig) GetAsyncRecoveryWorkers() int {
|
||
if s.AsyncRecoveryWorkers <= 0 {
|
||
return 4
|
||
}
|
||
return s.AsyncRecoveryWorkers
|
||
}
|
||
|
||
func (s *SagaConfig) GetAsyncRecoveryTimeout() time.Duration {
|
||
if s.AsyncRecoveryTimeoutSec <= 0 {
|
||
return 30 * time.Second
|
||
}
|
||
return time.Duration(s.AsyncRecoveryTimeoutSec) * time.Second
|
||
}
|
||
|
||
func (s *SagaConfig) IsFsyncEnabled() bool { return s.FsyncEnabled }
|
||
|
||
func (s *SagaConfig) GetFsyncMaxRetries() int {
|
||
if s.FsyncMaxRetries <= 0 {
|
||
return 3
|
||
}
|
||
return s.FsyncMaxRetries
|
||
}
|
||
|
||
func (s *SagaConfig) GetFsyncRetryDelay() time.Duration {
|
||
if s.FsyncRetryDelayMs <= 0 {
|
||
return 100 * time.Millisecond
|
||
}
|
||
return time.Duration(s.FsyncRetryDelayMs) * time.Millisecond
|
||
}
|
||
|
||
// =============================================================================
|
||
// ГЕТТЕРЫ ДЛЯ ReplicationConfig
|
||
// =============================================================================
|
||
|
||
func (r *ReplicationConfig) GetReplicationTimeout() time.Duration {
|
||
if r.ReplicationTimeoutMs <= 0 {
|
||
return 5 * time.Second
|
||
}
|
||
return time.Duration(r.ReplicationTimeoutMs) * time.Millisecond
|
||
}
|
||
|
||
func (r *ReplicationConfig) IsReplicationEnabled() bool { return r.Enabled }
|
||
|
||
func (r *ReplicationConfig) IsSyncReplicationEnabled() bool { return r.SyncReplication }
|
||
|
||
func (r *ReplicationConfig) GetMaxReplicaLag() time.Duration {
|
||
if r.MaxReplicaLagMs <= 0 {
|
||
return 5 * time.Second
|
||
}
|
||
return time.Duration(r.MaxReplicaLagMs) * time.Millisecond
|
||
}
|
||
|
||
// =============================================================================
|
||
// ГЕТТЕРЫ ДЛЯ ClusterConfig
|
||
// =============================================================================
|
||
|
||
func (c *ClusterConfig) GetHeartbeatTimeout() time.Duration {
|
||
if c.HeartbeatTimeoutMs <= 0 {
|
||
return 1000 * time.Millisecond
|
||
}
|
||
return time.Duration(c.HeartbeatTimeoutMs) * time.Millisecond
|
||
}
|
||
|
||
func (c *ClusterConfig) GetElectionTimeout() time.Duration {
|
||
if c.ElectionTimeoutMs <= 0 {
|
||
return 1000 * time.Millisecond
|
||
}
|
||
return time.Duration(c.ElectionTimeoutMs) * time.Millisecond
|
||
}
|
||
|
||
func (c *ClusterConfig) GetCommitTimeout() time.Duration {
|
||
if c.CommitTimeoutMs <= 0 {
|
||
return 500 * time.Millisecond
|
||
}
|
||
return time.Duration(c.CommitTimeoutMs) * time.Millisecond
|
||
}
|
||
|
||
func (c *ClusterConfig) GetSnapshotInterval() time.Duration {
|
||
if c.SnapshotIntervalMin <= 0 {
|
||
return 30 * time.Minute
|
||
}
|
||
return time.Duration(c.SnapshotIntervalMin) * time.Minute
|
||
}
|
||
|
||
func (c *ClusterConfig) GetSnapshotThreshold() uint64 {
|
||
if c.SnapshotThreshold <= 0 {
|
||
return 1000
|
||
}
|
||
return uint64(c.SnapshotThreshold)
|
||
}
|
||
|
||
func (c *ClusterConfig) IsSplitBrainPreventionEnabled() bool { return c.SplitBrainPrevention }
|
||
|
||
func (c *ClusterConfig) GetRecoveryTimeout() time.Duration {
|
||
if c.RecoveryTimeoutSec <= 0 {
|
||
return 30 * time.Second
|
||
}
|
||
return time.Duration(c.RecoveryTimeoutSec) * time.Second
|
||
}
|
||
|
||
func (c *ClusterConfig) GetRegion() string {
|
||
if c.Region == "" {
|
||
return "default"
|
||
}
|
||
return c.Region
|
||
}
|
||
|
||
func (c *ClusterConfig) GetPriorityZone() int {
|
||
if c.PriorityZone < 0 {
|
||
return 0
|
||
}
|
||
if c.PriorityZone > 9 {
|
||
return 9
|
||
}
|
||
return c.PriorityZone
|
||
}
|
||
|
||
// =============================================================================
|
||
// ГЕТТЕРЫ ДЛЯ PluginsConfig
|
||
// =============================================================================
|
||
|
||
func (p *PluginsConfig) GetMaxCPUTime() time.Duration {
|
||
if p.MaxCPUTimeMs <= 0 {
|
||
return 100 * time.Millisecond
|
||
}
|
||
return time.Duration(p.MaxCPUTimeMs) * time.Millisecond
|
||
}
|
||
|
||
func (p *PluginsConfig) GetMaxMemory() int64 {
|
||
if p.MaxMemoryMB <= 0 {
|
||
return 50 * 1024 * 1024
|
||
}
|
||
return int64(p.MaxMemoryMB) * 1024 * 1024
|
||
}
|
||
|
||
func (p *PluginsConfig) GetMaxExecutionTime() time.Duration {
|
||
if p.MaxExecutionTimeSec <= 0 {
|
||
return 5 * time.Second
|
||
}
|
||
return time.Duration(p.MaxExecutionTimeSec) * time.Second
|
||
}
|
||
|
||
func (p *PluginsConfig) GetMaxInstructions() int64 {
|
||
if p.MaxInstructions <= 0 {
|
||
return 1000000
|
||
}
|
||
return p.MaxInstructions
|
||
}
|
||
|
||
func (p *PluginsConfig) GetHotReloadInterval() time.Duration {
|
||
if p.HotReloadIntervalSec <= 0 {
|
||
return 30 * time.Second
|
||
}
|
||
return time.Duration(p.HotReloadIntervalSec) * time.Second
|
||
}
|
||
|
||
func (p *PluginsConfig) GetMaxEventLogSize() int {
|
||
if p.MaxEventLogSize <= 0 {
|
||
return 1000
|
||
}
|
||
return p.MaxEventLogSize
|
||
}
|
||
|
||
func (p *PluginsConfig) GetLoadTimeout() time.Duration {
|
||
if p.LoadTimeoutSec <= 0 {
|
||
return 10 * time.Second
|
||
}
|
||
return time.Duration(p.LoadTimeoutSec) * time.Second
|
||
}
|
||
|
||
func (p *PluginsConfig) GetMaxLuaStates() int {
|
||
if p.MaxLuaStates <= 0 {
|
||
return 100
|
||
}
|
||
return p.MaxLuaStates
|
||
}
|
||
|
||
func (p *PluginsConfig) GetLuaStateTTL() time.Duration {
|
||
if p.LuaStateTTLSec <= 0 {
|
||
return 10 * time.Minute
|
||
}
|
||
return time.Duration(p.LuaStateTTLSec) * time.Second
|
||
}
|
||
|
||
func (p *PluginsConfig) GetEnginePluginDir() string {
|
||
if p.EnginePluginDir == "" {
|
||
return "engines"
|
||
}
|
||
return p.EnginePluginDir
|
||
}
|
||
|
||
// =============================================================================
|
||
// ГЕТТЕРЫ ДЛЯ WALConfig
|
||
// =============================================================================
|
||
|
||
func (w *WALConfig) GetSegmentSize() int64 {
|
||
if w.SegmentSizeMB <= 0 {
|
||
return 64 * 1024 * 1024
|
||
}
|
||
return int64(w.SegmentSizeMB) * 1024 * 1024
|
||
}
|
||
|
||
func (w *WALConfig) GetSyncInterval() time.Duration {
|
||
if w.SyncIntervalSec <= 0 {
|
||
return 5 * time.Second
|
||
}
|
||
return time.Duration(w.SyncIntervalSec) * time.Second
|
||
}
|
||
|
||
func (w *WALConfig) GetBatchSize() int {
|
||
if w.BatchSize <= 0 {
|
||
return 100
|
||
}
|
||
return w.BatchSize
|
||
}
|
||
|
||
func (w *WALConfig) GetRecoveryWorkers() int {
|
||
if w.RecoveryWorkers <= 0 {
|
||
return 4
|
||
}
|
||
return w.RecoveryWorkers
|
||
}
|
||
|
||
func (w *WALConfig) IsWALEnabled() bool { return w.Enabled }
|
||
|
||
func (w *WALConfig) IsAsyncRecoveryEnabled() bool { return w.AsyncRecovery }
|
||
|
||
func (w *WALConfig) GetAsyncRecoveryWorkers() int {
|
||
if w.AsyncRecoveryWorkers <= 0 {
|
||
return 4
|
||
}
|
||
return w.AsyncRecoveryWorkers
|
||
}
|
||
|
||
func (w *WALConfig) GetAsyncRecoveryBuffer() int {
|
||
if w.AsyncRecoveryBuffer <= 0 {
|
||
return 10000
|
||
}
|
||
return w.AsyncRecoveryBuffer
|
||
}
|
||
|
||
// =============================================================================
|
||
// ГЕТТЕРЫ ДЛЯ MVCCConfig
|
||
// =============================================================================
|
||
|
||
func (m *MVCCConfig) GetMaxVersionsPerDoc() int {
|
||
if m.MaxVersionsPerDoc <= 0 {
|
||
return 10
|
||
}
|
||
return m.MaxVersionsPerDoc
|
||
}
|
||
|
||
func (m *MVCCConfig) GetVisibilityMapSize() int {
|
||
if m.VisibilityMapSize <= 0 {
|
||
return 1024 * 1024
|
||
}
|
||
return m.VisibilityMapSize
|
||
}
|
||
|
||
func (m *MVCCConfig) GetPruneInterval() time.Duration {
|
||
if m.PruneIntervalMin <= 0 {
|
||
return 5 * time.Minute
|
||
}
|
||
return time.Duration(m.PruneIntervalMin) * time.Minute
|
||
}
|
||
|
||
func (m *MVCCConfig) GetRetentionDays() int {
|
||
if m.RetentionDays <= 0 {
|
||
return 7
|
||
}
|
||
return m.RetentionDays
|
||
}
|
||
|
||
func (m *MVCCConfig) GetReadCacheSize() int {
|
||
if m.ReadCacheSize <= 0 {
|
||
return 10000
|
||
}
|
||
return m.ReadCacheSize
|
||
}
|
||
|
||
func (m *MVCCConfig) GetReadCacheTTL() time.Duration {
|
||
if m.ReadCacheTTLSec <= 0 {
|
||
return 300 * time.Second
|
||
}
|
||
return time.Duration(m.ReadCacheTTLSec) * time.Second
|
||
}
|
||
|
||
// =============================================================================
|
||
// ГЕТТЕРЫ ДЛЯ TransactionsConfig
|
||
// =============================================================================
|
||
|
||
func (t *TransactionsConfig) GetDefaultTimeout() time.Duration {
|
||
if t.DefaultTimeoutSec <= 0 {
|
||
return 30 * time.Second
|
||
}
|
||
return time.Duration(t.DefaultTimeoutSec) * time.Second
|
||
}
|
||
|
||
func (t *TransactionsConfig) GetDeadlockCheckInterval() time.Duration {
|
||
if t.DeadlockCheckIntervalSec <= 0 {
|
||
return 1 * time.Second
|
||
}
|
||
return time.Duration(t.DeadlockCheckIntervalSec) * time.Second
|
||
}
|
||
|
||
func (t *TransactionsConfig) GetMaxSavepointsPerTx() int {
|
||
if t.MaxSavepointsPerTx <= 0 {
|
||
return 100
|
||
}
|
||
return t.MaxSavepointsPerTx
|
||
}
|
||
|
||
func (t *TransactionsConfig) IsTransactionsEnabled() bool { return t.Enabled }
|
||
|
||
func (t *TransactionsConfig) GetCheckpointInterval() int64 {
|
||
if t.CheckpointIntervalSec <= 0 {
|
||
return 300
|
||
}
|
||
return int64(t.CheckpointIntervalSec)
|
||
}
|
||
|
||
// =============================================================================
|
||
// ГЕТТЕРЫ ДЛЯ ACLConfig
|
||
// =============================================================================
|
||
|
||
func (a *ACLConfig) GetMaxDeniedLogSize() int {
|
||
if a.MaxDeniedLogSize <= 0 {
|
||
return 10000
|
||
}
|
||
return a.MaxDeniedLogSize
|
||
}
|
||
|
||
func (a *ACLConfig) GetTemporaryGrantTTL() time.Duration {
|
||
if a.TemporaryGrantTTLHours <= 0 {
|
||
return 24 * time.Hour
|
||
}
|
||
return time.Duration(a.TemporaryGrantTTLHours) * time.Hour
|
||
}
|
||
|
||
func (a *ACLConfig) IsRoleHierarchyEnabled() bool { return a.EnableRoleHierarchy }
|
||
|
||
func (a *ACLConfig) GetCacheTTL() time.Duration {
|
||
if a.CacheTTLSec <= 0 {
|
||
return 60 * time.Second
|
||
}
|
||
return time.Duration(a.CacheTTLSec) * time.Second
|
||
}
|
||
|
||
// =============================================================================
|
||
// ГЕТТЕРЫ ДЛЯ TLSConfig
|
||
// =============================================================================
|
||
|
||
func (t *TLSConfig) GetTLSMinVersion() string {
|
||
if t.MinVersion == "" {
|
||
return "1.2"
|
||
}
|
||
return t.MinVersion
|
||
}
|
||
|
||
func (t *TLSConfig) IsTLSEnabled() bool { return t.Enabled }
|
||
|
||
func (t *TLSConfig) IsMutualAuthEnabled() bool { return t.MutualAuth }
|
||
|
||
func (t *TLSConfig) GetKeyRotationDays() int {
|
||
if t.KeyRotationDays <= 0 {
|
||
return 30
|
||
}
|
||
return t.KeyRotationDays
|
||
}
|
||
|
||
func (t *TLSConfig) IsAutoGenerateEnabled() bool { return t.AutoGenerate }
|
||
|
||
// =============================================================================
|
||
// ГЕТТЕРЫ ДЛЯ BackpressureConfig
|
||
// =============================================================================
|
||
|
||
func (b *BackpressureConfig) IsBackpressureEnabled() bool { return b.Enabled }
|
||
|
||
func (b *BackpressureConfig) GetCPUThreshold() float64 {
|
||
if b.CPUThreshold <= 0 {
|
||
return 0.8
|
||
}
|
||
if b.CPUThreshold > 1.0 {
|
||
return 1.0
|
||
}
|
||
return b.CPUThreshold
|
||
}
|
||
|
||
func (b *BackpressureConfig) GetMemoryThreshold() float64 {
|
||
if b.MemoryThreshold <= 0 {
|
||
return 0.85
|
||
}
|
||
if b.MemoryThreshold > 1.0 {
|
||
return 1.0
|
||
}
|
||
return b.MemoryThreshold
|
||
}
|
||
|
||
func (b *BackpressureConfig) GetQueueSizeThreshold() int {
|
||
if b.QueueSizeThreshold <= 0 {
|
||
return 10000
|
||
}
|
||
return b.QueueSizeThreshold
|
||
}
|
||
|
||
func (b *BackpressureConfig) GetConnectionThreshold() int {
|
||
if b.ConnectionThreshold <= 0 {
|
||
return 5000
|
||
}
|
||
return b.ConnectionThreshold
|
||
}
|
||
|
||
func (b *BackpressureConfig) GetCheckInterval() time.Duration {
|
||
if b.CheckIntervalMs <= 0 {
|
||
return 1 * time.Second
|
||
}
|
||
return time.Duration(b.CheckIntervalMs) * time.Millisecond
|
||
}
|
||
|
||
func (b *BackpressureConfig) GetLowDelay() time.Duration {
|
||
if b.LowDelayMs <= 0 {
|
||
return 100 * time.Millisecond
|
||
}
|
||
return time.Duration(b.LowDelayMs) * time.Millisecond
|
||
}
|
||
|
||
func (b *BackpressureConfig) GetMediumRejectProb() uint32 {
|
||
if b.MediumRejectProb > 100 {
|
||
return 100
|
||
}
|
||
return b.MediumRejectProb
|
||
}
|
||
|
||
func (b *BackpressureConfig) GetHighRejectProb() uint32 {
|
||
if b.HighRejectProb > 100 {
|
||
return 100
|
||
}
|
||
return b.HighRejectProb
|
||
}
|
||
|
||
// =============================================================================
|
||
// ГЕТТЕРЫ ДЛЯ RuntimeLimitsConfig
|
||
// =============================================================================
|
||
|
||
func (r *RuntimeLimitsConfig) IsRuntimeLimitsEnabled() bool { return r.Enabled }
|
||
|
||
func (r *RuntimeLimitsConfig) GetGlobalMaxDocSizeMB() int {
|
||
if r.GlobalMaxDocSizeMB <= 0 {
|
||
return 16
|
||
}
|
||
return r.GlobalMaxDocSizeMB
|
||
}
|
||
|
||
func (r *RuntimeLimitsConfig) GetGlobalMaxCollSizeMB() int64 {
|
||
if r.GlobalMaxCollSizeMB <= 0 {
|
||
return 10240
|
||
}
|
||
return r.GlobalMaxCollSizeMB
|
||
}
|
||
|
||
func (r *RuntimeLimitsConfig) GetGlobalMaxDocsPerColl() int64 {
|
||
if r.GlobalMaxDocsPerColl <= 0 {
|
||
return 10000000
|
||
}
|
||
return r.GlobalMaxDocsPerColl
|
||
}
|
||
|
||
// =============================================================================
|
||
// ГЕТТЕРЫ ДЛЯ AutoscalingConfig
|
||
// =============================================================================
|
||
|
||
func (a *AutoscalingConfig) IsAutoscalingEnabled() bool { return a.Enabled }
|
||
|
||
func (a *AutoscalingConfig) GetMinNodes() int {
|
||
if a.MinNodes <= 0 {
|
||
return 1
|
||
}
|
||
return a.MinNodes
|
||
}
|
||
|
||
func (a *AutoscalingConfig) GetMaxNodes() int {
|
||
if a.MaxNodes <= 0 {
|
||
return 10
|
||
}
|
||
return a.MaxNodes
|
||
}
|
||
|
||
func (a *AutoscalingConfig) GetScaleUpThreshold() float64 {
|
||
if a.ScaleUpThreshold <= 0 {
|
||
return 0.75
|
||
}
|
||
if a.ScaleUpThreshold > 1.0 {
|
||
return 1.0
|
||
}
|
||
return a.ScaleUpThreshold
|
||
}
|
||
|
||
func (a *AutoscalingConfig) GetScaleDownThreshold() float64 {
|
||
if a.ScaleDownThreshold <= 0 {
|
||
return 0.30
|
||
}
|
||
return a.ScaleDownThreshold
|
||
}
|
||
|
||
func (a *AutoscalingConfig) GetScaleUpCooldown() time.Duration {
|
||
if a.ScaleUpCooldownSec <= 0 {
|
||
return 5 * time.Minute
|
||
}
|
||
return time.Duration(a.ScaleUpCooldownSec) * time.Second
|
||
}
|
||
|
||
func (a *AutoscalingConfig) GetScaleDownCooldown() time.Duration {
|
||
if a.ScaleDownCooldownSec <= 0 {
|
||
return 10 * time.Minute
|
||
}
|
||
return time.Duration(a.ScaleDownCooldownSec) * time.Second
|
||
}
|
||
|
||
func (a *AutoscalingConfig) GetEvaluationInterval() time.Duration {
|
||
if a.EvaluationIntervalSec <= 0 {
|
||
return 30 * time.Second
|
||
}
|
||
return time.Duration(a.EvaluationIntervalSec) * time.Second
|
||
}
|
||
|
||
func (a *AutoscalingConfig) IsPredictiveEnabled() bool { return a.PredictiveEnabled }
|
||
|
||
func (a *AutoscalingConfig) GetMaxScaleUpNodes() int {
|
||
if a.MaxScaleUpNodes <= 0 {
|
||
return 3
|
||
}
|
||
return a.MaxScaleUpNodes
|
||
}
|
||
|
||
func (a *AutoscalingConfig) GetMaxScaleDownNodes() int {
|
||
if a.MaxScaleDownNodes <= 0 {
|
||
return 2
|
||
}
|
||
return a.MaxScaleDownNodes
|
||
}
|
||
|
||
// =============================================================================
|
||
// ГЕТТЕРЫ ДЛЯ SchemaMigrationConfig
|
||
// =============================================================================
|
||
|
||
func (s *SchemaMigrationConfig) IsSchemaMigrationEnabled() bool { return s.Enabled }
|
||
|
||
func (s *SchemaMigrationConfig) GetMigrationDir() string {
|
||
if s.MigrationDir == "" {
|
||
return "migrations"
|
||
}
|
||
return s.MigrationDir
|
||
}
|
||
|
||
func (s *SchemaMigrationConfig) IsAutoMigrateEnabled() bool { return s.AutoMigrate }
|
||
|
||
func (s *SchemaMigrationConfig) GetTargetVersion() string {
|
||
if s.TargetVersion == "" {
|
||
return "latest"
|
||
}
|
||
return s.TargetVersion
|
||
}
|
||
|
||
// =============================================================================
|
||
// ГЕТТЕРЫ ДЛЯ BackupConfig
|
||
// =============================================================================
|
||
|
||
func (b *BackupConfig) IsBackupEnabled() bool { return b.Enabled }
|
||
|
||
func (b *BackupConfig) GetBackupDir() string {
|
||
if b.BackupDir == "" {
|
||
return "backups"
|
||
}
|
||
return b.BackupDir
|
||
}
|
||
|
||
func (b *BackupConfig) GetMaxConcurrentBackups() int {
|
||
if b.MaxConcurrent <= 0 {
|
||
return 1
|
||
}
|
||
return b.MaxConcurrent
|
||
}
|
||
|
||
func (b *BackupConfig) IsCompressEnabled() bool { return b.CompressEnabled }
|
||
|
||
func (b *BackupConfig) GetBackupRetentionDays() int {
|
||
if b.RetentionDays <= 0 {
|
||
return 7
|
||
}
|
||
return b.RetentionDays
|
||
}
|
||
|
||
func (b *BackupConfig) IsIncludeSagaStateEnabled() bool { return b.IncludeSagaState }
|
||
|
||
func (b *BackupConfig) IsIncrementalEnabled() bool { return b.EnableIncremental }
|
||
|
||
// =============================================================================
|
||
// ГЕТТЕРЫ ДЛЯ StorageConfig
|
||
// =============================================================================
|
||
|
||
func (s *StorageConfig) GetDefaultEngine() string {
|
||
if s.DefaultEngine == "" {
|
||
return "row"
|
||
}
|
||
return s.DefaultEngine
|
||
}
|
||
|
||
func (s *StorageConfig) IsCustomEnginesEnabled() bool { return s.EnableCustomEngines }
|
||
|
||
// =============================================================================
|
||
// МЕТОДЫ ДЛЯ EnginesConfig
|
||
// =============================================================================
|
||
|
||
func (e *EnginesConfig) IsEngineEnabled(name string) bool {
|
||
switch name {
|
||
case "row":
|
||
return e.Row.Enabled
|
||
case "columnar":
|
||
return e.Columnar.Enabled
|
||
case "document":
|
||
return e.Document.Enabled
|
||
case "kv":
|
||
return e.KV.Enabled
|
||
case "ts":
|
||
return e.TS.Enabled
|
||
case "graph":
|
||
return e.Graph.Enabled
|
||
default:
|
||
return false
|
||
}
|
||
}
|
||
|
||
func (e *EnginesConfig) GetEngineConfig(name string) map[string]interface{} {
|
||
switch name {
|
||
case "row":
|
||
return e.Row.Config
|
||
case "columnar":
|
||
return e.Columnar.Config
|
||
case "document":
|
||
return e.Document.Config
|
||
case "kv":
|
||
return e.KV.Config
|
||
case "ts":
|
||
return e.TS.Config
|
||
case "graph":
|
||
return e.Graph.Config
|
||
default:
|
||
return make(map[string]interface{})
|
||
}
|
||
}
|
||
|
||
func (e *EnginesConfig) GetEngineDescription(name string) string {
|
||
switch name {
|
||
case "row":
|
||
return e.Row.Description
|
||
case "columnar":
|
||
return e.Columnar.Description
|
||
case "document":
|
||
return e.Document.Description
|
||
case "kv":
|
||
return e.KV.Description
|
||
case "ts":
|
||
return e.TS.Description
|
||
case "graph":
|
||
return e.Graph.Description
|
||
default:
|
||
return ""
|
||
}
|
||
}
|
||
|
||
// =============================================================================
|
||
// ГЕТТЕРЫ ДЛЯ RecoveryConfig
|
||
// =============================================================================
|
||
|
||
func (r *RecoveryConfig) GetMaxRetryDuration() time.Duration {
|
||
if r.MaxRetrySec <= 0 {
|
||
return 300 * time.Second
|
||
}
|
||
return time.Duration(r.MaxRetrySec) * time.Second
|
||
}
|
||
|
||
func (r *RecoveryConfig) GetDataReplicationTimeout() time.Duration {
|
||
if r.DataReplicationTimeoutSec <= 0 {
|
||
return 60 * time.Second
|
||
}
|
||
return time.Duration(r.DataReplicationTimeoutSec) * time.Second
|
||
}
|
||
|
||
func (r *RecoveryConfig) GetStaleReadTimeout() time.Duration {
|
||
if r.StaleReadTimeoutSec <= 0 {
|
||
return 30 * time.Second
|
||
}
|
||
return time.Duration(r.StaleReadTimeoutSec) * time.Second
|
||
}
|
||
|
||
func (r *RecoveryConfig) IsAutoRejoinEnabled() bool { return r.AutoRejoin }
|
||
|
||
// =============================================================================
|
||
// ГЕТТЕРЫ ДЛЯ PerformanceConfig
|
||
// =============================================================================
|
||
|
||
func (p *PerformanceConfig) GetBatchSize() int {
|
||
if p.BatchSize <= 0 {
|
||
return 100
|
||
}
|
||
return p.BatchSize
|
||
}
|
||
|
||
func (p *PerformanceConfig) GetMaxConnections() int {
|
||
if p.MaxConnections <= 0 {
|
||
return 1000
|
||
}
|
||
return p.MaxConnections
|
||
}
|
||
|
||
func (p *PerformanceConfig) IsReadFromFollowerEnabled() bool { return p.ReadFromFollower }
|
||
|
||
func (p *PerformanceConfig) IsPipelineEnabled() bool { return p.EnablePipeline }
|
||
|
||
func (p *PerformanceConfig) GetReadReplicaDelay() time.Duration {
|
||
if p.ReadReplicaDelayMs <= 0 {
|
||
return 100 * time.Millisecond
|
||
}
|
||
return time.Duration(p.ReadReplicaDelayMs) * time.Millisecond
|
||
}
|
||
|
||
// =============================================================================
|
||
// ГЕТТЕРЫ ДЛЯ MonitoringConfig
|
||
// =============================================================================
|
||
|
||
func (m *MonitoringConfig) GetMetricsPort() int {
|
||
if m.MetricsPort <= 0 {
|
||
return 9090
|
||
}
|
||
return m.MetricsPort
|
||
}
|
||
|
||
func (m *MonitoringConfig) GetTraceSampleRate() float64 {
|
||
if m.TraceSampleRate <= 0 || m.TraceSampleRate > 1 {
|
||
return 0.01
|
||
}
|
||
return m.TraceSampleRate
|
||
}
|
||
|
||
func (m *MonitoringConfig) IsMetricsEnabled() bool { return m.EnableMetrics }
|
||
|
||
func (m *MonitoringConfig) IsTracingEnabled() bool { return m.EnableTracing }
|
||
|
||
// =============================================================================
|
||
// ГЕТТЕРЫ ДЛЯ SecurityConfig
|
||
// =============================================================================
|
||
|
||
func (s *SecurityConfig) GetTLSMinVersion() uint16 {
|
||
switch s.MinVersion {
|
||
case "1.0":
|
||
return 0x0301
|
||
case "1.1":
|
||
return 0x0302
|
||
case "1.2":
|
||
return 0x0303
|
||
case "1.3":
|
||
return 0x0304
|
||
default:
|
||
return 0x0303
|
||
}
|
||
}
|
||
|
||
func (s *SecurityConfig) IsTLSEnabled() bool {
|
||
return s.EnableTLS && s.CertFile != "" && s.KeyFile != ""
|
||
}
|
||
|
||
func (s *SecurityConfig) GetCertFile() string { return s.CertFile }
|
||
|
||
func (s *SecurityConfig) GetKeyFile() string { return s.KeyFile }
|
||
|
||
func (s *SecurityConfig) GetCAFile() string { return s.CAFile }
|
||
|
||
// =============================================================================
|
||
// ГЕТТЕРЫ ДЛЯ CompressionConfig
|
||
// =============================================================================
|
||
|
||
func (c *CompressionConfig) IsCompressionEnabled() bool { return c.Enabled }
|
||
|
||
func (c *CompressionConfig) GetAlgorithm() string {
|
||
if c.Algorithm == "" {
|
||
return "snappy"
|
||
}
|
||
return c.Algorithm
|
||
}
|
||
|
||
func (c *CompressionConfig) GetLevel() int {
|
||
if c.Level < 1 {
|
||
return 3
|
||
}
|
||
if c.Level > 9 {
|
||
return 9
|
||
}
|
||
return c.Level
|
||
}
|
||
|
||
func (c *CompressionConfig) GetMinSize() int {
|
||
if c.MinSize <= 0 {
|
||
return 1024
|
||
}
|
||
return c.MinSize
|
||
}
|
||
|
||
// =============================================================================
|
||
// ЗАГРУЗКА И ВАЛИДАЦИЯ КОНФИГУРАЦИИ
|
||
// =============================================================================
|
||
|
||
// LoadConfig загружает и валидирует конфигурацию из TOML-файла.
|
||
func LoadConfig(path string) (*Config, error) {
|
||
info, err := os.Stat(path)
|
||
if err != nil {
|
||
if os.IsNotExist(err) {
|
||
return nil, fmt.Errorf("config file not found: %s", path)
|
||
}
|
||
return nil, fmt.Errorf("failed to stat config file: %w", err)
|
||
}
|
||
|
||
if !info.Mode().IsRegular() {
|
||
return nil, fmt.Errorf("config path is not a regular file: %s", path)
|
||
}
|
||
|
||
if info.Size() > maxConfigFileSize {
|
||
return nil, fmt.Errorf("config file too large: %d bytes (max %d)", info.Size(), maxConfigFileSize)
|
||
}
|
||
|
||
var cfg Config
|
||
if _, err := toml.DecodeFile(path, &cfg); err != nil {
|
||
return nil, fmt.Errorf("failed to decode config file: %v", err)
|
||
}
|
||
|
||
// ===== УСТАНОВКА ЗНАЧЕНИЙ ПО УМОЛЧАНИЮ =====
|
||
|
||
if cfg.Cluster.RaftPort == 0 {
|
||
cfg.Cluster.RaftPort = 9878
|
||
}
|
||
if cfg.Cluster.NodePort == 0 {
|
||
cfg.Cluster.NodePort = 9877
|
||
}
|
||
if cfg.Cluster.NodeIP == "" {
|
||
cfg.Cluster.NodeIP = "0.0.0.0"
|
||
}
|
||
if cfg.Cluster.RaftDataDir == "" {
|
||
cfg.Cluster.RaftDataDir = "raft_data"
|
||
}
|
||
if cfg.Cluster.HeartbeatTimeoutMs == 0 {
|
||
cfg.Cluster.HeartbeatTimeoutMs = 1000
|
||
}
|
||
if cfg.Cluster.ElectionTimeoutMs == 0 {
|
||
cfg.Cluster.ElectionTimeoutMs = 1000
|
||
}
|
||
if cfg.Cluster.CommitTimeoutMs == 0 {
|
||
cfg.Cluster.CommitTimeoutMs = 500
|
||
}
|
||
if cfg.Cluster.SnapshotIntervalMin == 0 {
|
||
cfg.Cluster.SnapshotIntervalMin = 30
|
||
}
|
||
if cfg.Cluster.SnapshotThreshold == 0 {
|
||
cfg.Cluster.SnapshotThreshold = 1000
|
||
}
|
||
if cfg.Cluster.RecoveryTimeoutSec == 0 {
|
||
cfg.Cluster.RecoveryTimeoutSec = 30
|
||
}
|
||
if cfg.Cluster.Region == "" {
|
||
cfg.Cluster.Region = "default"
|
||
}
|
||
|
||
if cfg.Saga.CoordinatorCount == 0 {
|
||
cfg.Saga.CoordinatorCount = 3
|
||
}
|
||
if cfg.Saga.StateDir == "" {
|
||
cfg.Saga.StateDir = "saga_states"
|
||
}
|
||
if cfg.Saga.MaxRetries == 0 {
|
||
cfg.Saga.MaxRetries = 5
|
||
}
|
||
if cfg.Saga.RetryBackoffMs == 0 {
|
||
cfg.Saga.RetryBackoffMs = 100
|
||
}
|
||
if cfg.Saga.SagaTimeoutSec == 0 {
|
||
cfg.Saga.SagaTimeoutSec = 300
|
||
}
|
||
if cfg.Saga.StuckCheckIntervalSec == 0 {
|
||
cfg.Saga.StuckCheckIntervalSec = 10
|
||
}
|
||
if cfg.Saga.LeaderElectionIntervalSec == 0 {
|
||
cfg.Saga.LeaderElectionIntervalSec = 5
|
||
}
|
||
if cfg.Saga.RecoveryIntervalSec == 0 {
|
||
cfg.Saga.RecoveryIntervalSec = 30
|
||
}
|
||
if cfg.Saga.MetricsIntervalSec == 0 {
|
||
cfg.Saga.MetricsIntervalSec = 60
|
||
}
|
||
if cfg.Saga.CleanupPeriodHours == 0 {
|
||
cfg.Saga.CleanupPeriodHours = 24
|
||
}
|
||
if cfg.Saga.MaxCacheSize == 0 {
|
||
cfg.Saga.MaxCacheSize = 10000
|
||
}
|
||
if cfg.Saga.OperationRetentionDays == 0 {
|
||
cfg.Saga.OperationRetentionDays = 7
|
||
}
|
||
if cfg.Saga.ChannelBufferSize == 0 {
|
||
cfg.Saga.ChannelBufferSize = 10000
|
||
}
|
||
if cfg.Saga.AsyncRecoveryWorkers == 0 {
|
||
cfg.Saga.AsyncRecoveryWorkers = 4
|
||
}
|
||
if cfg.Saga.AsyncRecoveryTimeoutSec == 0 {
|
||
cfg.Saga.AsyncRecoveryTimeoutSec = 30
|
||
}
|
||
if cfg.Saga.FsyncMaxRetries == 0 {
|
||
cfg.Saga.FsyncMaxRetries = 3
|
||
}
|
||
if cfg.Saga.FsyncRetryDelayMs == 0 {
|
||
cfg.Saga.FsyncRetryDelayMs = 100
|
||
}
|
||
|
||
if cfg.Replication.ReplicationTimeoutMs == 0 {
|
||
cfg.Replication.ReplicationTimeoutMs = 5000
|
||
}
|
||
if cfg.Replication.MaxReplicaLagMs == 0 {
|
||
cfg.Replication.MaxReplicaLagMs = 5000
|
||
}
|
||
|
||
if cfg.Plugins.ScriptDir == "" {
|
||
cfg.Plugins.ScriptDir = "plugins"
|
||
}
|
||
if cfg.Plugins.EnginePluginDir == "" {
|
||
cfg.Plugins.EnginePluginDir = "engines"
|
||
}
|
||
if cfg.Plugins.MaxCPUTimeMs == 0 {
|
||
cfg.Plugins.MaxCPUTimeMs = 100
|
||
}
|
||
if cfg.Plugins.MaxMemoryMB == 0 {
|
||
cfg.Plugins.MaxMemoryMB = 50
|
||
}
|
||
if cfg.Plugins.MaxExecutionTimeSec == 0 {
|
||
cfg.Plugins.MaxExecutionTimeSec = 5
|
||
}
|
||
if cfg.Plugins.MaxInstructions == 0 {
|
||
cfg.Plugins.MaxInstructions = 1000000
|
||
}
|
||
if cfg.Plugins.HotReloadIntervalSec == 0 {
|
||
cfg.Plugins.HotReloadIntervalSec = 30
|
||
}
|
||
if cfg.Plugins.MaxEventLogSize == 0 {
|
||
cfg.Plugins.MaxEventLogSize = 1000
|
||
}
|
||
if cfg.Plugins.LoadTimeoutSec == 0 {
|
||
cfg.Plugins.LoadTimeoutSec = 10
|
||
}
|
||
if cfg.Plugins.MaxLuaStates == 0 {
|
||
cfg.Plugins.MaxLuaStates = 100
|
||
}
|
||
if cfg.Plugins.LuaStateTTLSec == 0 {
|
||
cfg.Plugins.LuaStateTTLSec = 600
|
||
}
|
||
|
||
if cfg.API.Port == 0 {
|
||
cfg.API.Port = 8080
|
||
}
|
||
|
||
if cfg.Metrics.CollectIntervalSec == 0 {
|
||
cfg.Metrics.CollectIntervalSec = 15
|
||
}
|
||
|
||
if cfg.Compression.Algorithm == "" {
|
||
cfg.Compression.Algorithm = "snappy"
|
||
}
|
||
if cfg.Compression.MinSize == 0 {
|
||
cfg.Compression.MinSize = 1024
|
||
}
|
||
if cfg.Compression.Level == 0 {
|
||
cfg.Compression.Level = 3
|
||
}
|
||
|
||
if cfg.Performance.BatchSize == 0 {
|
||
cfg.Performance.BatchSize = 100
|
||
}
|
||
if cfg.Performance.MaxConnections == 0 {
|
||
cfg.Performance.MaxConnections = 1000
|
||
}
|
||
if cfg.Performance.ReadReplicaDelayMs == 0 {
|
||
cfg.Performance.ReadReplicaDelayMs = 100
|
||
}
|
||
|
||
if cfg.Monitoring.MetricsPort == 0 {
|
||
cfg.Monitoring.MetricsPort = 9090
|
||
}
|
||
if cfg.Monitoring.TraceSampleRate == 0 {
|
||
cfg.Monitoring.TraceSampleRate = 0.01
|
||
}
|
||
|
||
if cfg.Recovery.MaxRetrySec == 0 {
|
||
cfg.Recovery.MaxRetrySec = 300
|
||
}
|
||
if cfg.Recovery.DataReplicationTimeoutSec == 0 {
|
||
cfg.Recovery.DataReplicationTimeoutSec = 60
|
||
}
|
||
if cfg.Recovery.StaleReadTimeoutSec == 0 {
|
||
cfg.Recovery.StaleReadTimeoutSec = 30
|
||
}
|
||
|
||
if cfg.Security.MinVersion == "" {
|
||
cfg.Security.MinVersion = "1.2"
|
||
}
|
||
|
||
if cfg.Storage.DefaultEngine == "" {
|
||
cfg.Storage.DefaultEngine = "row"
|
||
}
|
||
|
||
if cfg.WAL.SegmentSizeMB == 0 {
|
||
cfg.WAL.SegmentSizeMB = 64
|
||
}
|
||
if cfg.WAL.SyncIntervalSec == 0 {
|
||
cfg.WAL.SyncIntervalSec = 5
|
||
}
|
||
if cfg.WAL.BatchSize == 0 {
|
||
cfg.WAL.BatchSize = 100
|
||
}
|
||
if cfg.WAL.RecoveryWorkers == 0 {
|
||
cfg.WAL.RecoveryWorkers = 4
|
||
}
|
||
if cfg.WAL.AsyncRecoveryWorkers == 0 {
|
||
cfg.WAL.AsyncRecoveryWorkers = 4
|
||
}
|
||
if cfg.WAL.AsyncRecoveryBuffer == 0 {
|
||
cfg.WAL.AsyncRecoveryBuffer = 10000
|
||
}
|
||
|
||
if cfg.MVCC.MaxVersionsPerDoc == 0 {
|
||
cfg.MVCC.MaxVersionsPerDoc = 10
|
||
}
|
||
if cfg.MVCC.VisibilityMapSize == 0 {
|
||
cfg.MVCC.VisibilityMapSize = 1024 * 1024
|
||
}
|
||
if cfg.MVCC.PruneIntervalMin == 0 {
|
||
cfg.MVCC.PruneIntervalMin = 5
|
||
}
|
||
if cfg.MVCC.RetentionDays == 0 {
|
||
cfg.MVCC.RetentionDays = 7
|
||
}
|
||
if cfg.MVCC.ReadCacheSize == 0 {
|
||
cfg.MVCC.ReadCacheSize = 10000
|
||
}
|
||
if cfg.MVCC.ReadCacheTTLSec == 0 {
|
||
cfg.MVCC.ReadCacheTTLSec = 300
|
||
}
|
||
|
||
if cfg.Transactions.DefaultTimeoutSec == 0 {
|
||
cfg.Transactions.DefaultTimeoutSec = 30
|
||
}
|
||
if cfg.Transactions.DeadlockCheckIntervalSec == 0 {
|
||
cfg.Transactions.DeadlockCheckIntervalSec = 1
|
||
}
|
||
if cfg.Transactions.MaxSavepointsPerTx == 0 {
|
||
cfg.Transactions.MaxSavepointsPerTx = 100
|
||
}
|
||
if cfg.Transactions.CheckpointIntervalSec == 0 {
|
||
cfg.Transactions.CheckpointIntervalSec = 300
|
||
}
|
||
|
||
if cfg.ACL.MaxDeniedLogSize == 0 {
|
||
cfg.ACL.MaxDeniedLogSize = 10000
|
||
}
|
||
if cfg.ACL.TemporaryGrantTTLHours == 0 {
|
||
cfg.ACL.TemporaryGrantTTLHours = 24
|
||
}
|
||
if cfg.ACL.CacheTTLSec == 0 {
|
||
cfg.ACL.CacheTTLSec = 60
|
||
}
|
||
|
||
if cfg.ClusterTLS.MinVersion == "" {
|
||
cfg.ClusterTLS.MinVersion = "1.2"
|
||
}
|
||
if cfg.ClusterTLS.KeyRotationDays == 0 {
|
||
cfg.ClusterTLS.KeyRotationDays = 30
|
||
}
|
||
|
||
if cfg.Backpressure.CheckIntervalMs == 0 {
|
||
cfg.Backpressure.CheckIntervalMs = 1000
|
||
}
|
||
if cfg.Backpressure.LowDelayMs == 0 {
|
||
cfg.Backpressure.LowDelayMs = 100
|
||
}
|
||
|
||
if cfg.RuntimeLimits.GlobalMaxDocSizeMB == 0 {
|
||
cfg.RuntimeLimits.GlobalMaxDocSizeMB = 16
|
||
}
|
||
if cfg.RuntimeLimits.GlobalMaxCollSizeMB == 0 {
|
||
cfg.RuntimeLimits.GlobalMaxCollSizeMB = 10240
|
||
}
|
||
if cfg.RuntimeLimits.GlobalMaxDocsPerColl == 0 {
|
||
cfg.RuntimeLimits.GlobalMaxDocsPerColl = 10000000
|
||
}
|
||
|
||
if cfg.Autoscaling.MinNodes == 0 {
|
||
cfg.Autoscaling.MinNodes = 1
|
||
}
|
||
if cfg.Autoscaling.MaxNodes == 0 {
|
||
cfg.Autoscaling.MaxNodes = 10
|
||
}
|
||
if cfg.Autoscaling.ScaleUpCooldownSec == 0 {
|
||
cfg.Autoscaling.ScaleUpCooldownSec = 300
|
||
}
|
||
if cfg.Autoscaling.ScaleDownCooldownSec == 0 {
|
||
cfg.Autoscaling.ScaleDownCooldownSec = 600
|
||
}
|
||
if cfg.Autoscaling.EvaluationIntervalSec == 0 {
|
||
cfg.Autoscaling.EvaluationIntervalSec = 30
|
||
}
|
||
|
||
if cfg.SchemaMigration.MigrationDir == "" {
|
||
cfg.SchemaMigration.MigrationDir = "migrations"
|
||
}
|
||
|
||
if cfg.Backup.BackupDir == "" {
|
||
cfg.Backup.BackupDir = "backups"
|
||
}
|
||
if cfg.Backup.RetentionDays == 0 {
|
||
cfg.Backup.RetentionDays = 7
|
||
}
|
||
if !cfg.Backup.IncludeSagaState {
|
||
cfg.Backup.IncludeSagaState = true
|
||
}
|
||
if !cfg.Backup.EnableIncremental {
|
||
cfg.Backup.EnableIncremental = true
|
||
}
|
||
|
||
if cfg.Migration.Source == nil {
|
||
cfg.Migration.Source = &DatacenterConfig{
|
||
Name: "dc-primary",
|
||
Endpoint: "localhost:8080",
|
||
TimeoutSec: 30,
|
||
}
|
||
}
|
||
if cfg.Migration.Target == nil {
|
||
cfg.Migration.Target = &DatacenterConfig{
|
||
Name: "dc-secondary",
|
||
Endpoint: "localhost:8081",
|
||
TimeoutSec: 30,
|
||
}
|
||
}
|
||
if cfg.Migration.Settings == nil {
|
||
cfg.Migration.Settings = &MigrationSettings{
|
||
BatchSize: 1000,
|
||
Workers: 4,
|
||
Compression: "snappy",
|
||
ResumeEnabled: true,
|
||
CheckpointIntervalSec: 30,
|
||
MaxRetries: 3,
|
||
RetryBackoffSec: 5,
|
||
}
|
||
}
|
||
if cfg.Migration.Delta == nil {
|
||
cfg.Migration.Delta = &DeltaSyncConfig{
|
||
Enabled: true,
|
||
IntervalSec: 60,
|
||
MaxLagSec: 300,
|
||
}
|
||
}
|
||
if cfg.Migration.Validation == nil {
|
||
cfg.Migration.Validation = &ValidationConfig{
|
||
Enabled: true,
|
||
SamplePercent: 10,
|
||
MaxErrors: 100,
|
||
}
|
||
}
|
||
if cfg.Migration.Mode == "" {
|
||
cfg.Migration.Mode = "semi_auto"
|
||
}
|
||
|
||
// ===== ИНИЦИАЛИЗАЦИЯ ДВИЖКОВ =====
|
||
if !cfg.Engines.Row.Enabled {
|
||
cfg.Engines.Row.Enabled = true
|
||
cfg.Engines.Row.Description = "Row-based storage engine (default)"
|
||
}
|
||
if cfg.Engines.Row.Config == nil {
|
||
cfg.Engines.Row.Config = make(map[string]interface{})
|
||
}
|
||
if cfg.Engines.Columnar.Config == nil {
|
||
cfg.Engines.Columnar.Config = make(map[string]interface{})
|
||
}
|
||
if cfg.Engines.Document.Config == nil {
|
||
cfg.Engines.Document.Config = make(map[string]interface{})
|
||
}
|
||
if cfg.Engines.KV.Config == nil {
|
||
cfg.Engines.KV.Config = make(map[string]interface{})
|
||
}
|
||
if cfg.Engines.TS.Config == nil {
|
||
cfg.Engines.TS.Config = make(map[string]interface{})
|
||
}
|
||
if cfg.Engines.Graph.Config == nil {
|
||
cfg.Engines.Graph.Config = make(map[string]interface{})
|
||
}
|
||
|
||
// ===== ВАЛИДАЦИЯ КОНФИГУРАЦИИ =====
|
||
validationResult := ValidateConfigFull(&cfg)
|
||
if !validationResult.Valid {
|
||
var errMsg strings.Builder
|
||
errMsg.WriteString("config validation failed:\n")
|
||
for _, err := range validationResult.Errors {
|
||
errMsg.WriteString(fmt.Sprintf(" - %s\n", err.Error()))
|
||
}
|
||
if len(validationResult.Warnings) > 0 {
|
||
errMsg.WriteString("Warnings:\n")
|
||
for _, warn := range validationResult.Warnings {
|
||
errMsg.WriteString(fmt.Sprintf(" - %s\n", warn.Error()))
|
||
}
|
||
}
|
||
return nil, fmt.Errorf("%s", errMsg.String())
|
||
}
|
||
|
||
if len(validationResult.Warnings) > 0 {
|
||
fmt.Println("Configuration warnings:")
|
||
for _, warn := range validationResult.Warnings {
|
||
fmt.Printf(" - %s\n", warn.Error())
|
||
}
|
||
}
|
||
|
||
return &cfg, nil
|
||
}
|
||
|
||
// ValidateConfig выполняет валидацию конфигурации и возвращает первую ошибку.
|
||
func ValidateConfig(cfg *Config) error {
|
||
if cfg == nil {
|
||
return fmt.Errorf("config is nil")
|
||
}
|
||
result := ValidateConfigFull(cfg)
|
||
if !result.Valid {
|
||
if len(result.Errors) > 0 {
|
||
return result.Errors[0]
|
||
}
|
||
return fmt.Errorf("config validation failed")
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// ValidateConfigFull выполняет полную валидацию конфигурации.
|
||
func ValidateConfigFull(cfg *Config) *ValidationResult {
|
||
result := &ValidationResult{
|
||
Valid: true,
|
||
Errors: make([]error, 0),
|
||
Warnings: make([]error, 0),
|
||
}
|
||
|
||
if cfg.Cluster.NodePort < 0 || cfg.Cluster.NodePort > 65535 {
|
||
result.Errors = append(result.Errors, fmt.Errorf("invalid node_port: %d (must be 0-65535, 0 = auto)", cfg.Cluster.NodePort))
|
||
}
|
||
if cfg.Cluster.RaftPort <= 0 || cfg.Cluster.RaftPort > 65535 {
|
||
result.Errors = append(result.Errors, fmt.Errorf("invalid raft_port: %d (must be 1-65535)", cfg.Cluster.RaftPort))
|
||
}
|
||
if cfg.Cluster.PriorityZone < 0 || cfg.Cluster.PriorityZone > 9 {
|
||
result.Errors = append(result.Errors, fmt.Errorf("priority_zone must be between 0 and 9, got %d", cfg.Cluster.PriorityZone))
|
||
}
|
||
if cfg.Cluster.NodeIP != "" && cfg.Cluster.NodeIP != "0.0.0.0" && cfg.Cluster.NodeIP != "::" {
|
||
if net.ParseIP(cfg.Cluster.NodeIP) == nil {
|
||
result.Errors = append(result.Errors, fmt.Errorf("invalid node_ip: %s (must be a valid IP address)", cfg.Cluster.NodeIP))
|
||
}
|
||
}
|
||
|
||
if cfg.Saga.Enabled {
|
||
if cfg.Saga.CoordinatorCount < 1 {
|
||
result.Errors = append(result.Errors, fmt.Errorf("saga.coordinator_count must be at least 1, got %d", cfg.Saga.CoordinatorCount))
|
||
}
|
||
if cfg.Saga.CoordinatorCount < 3 {
|
||
result.Warnings = append(result.Warnings, fmt.Errorf("saga.coordinator_count is less than 3, may not be fully fault-tolerant"))
|
||
}
|
||
if cfg.Saga.MaxRetries < 0 {
|
||
result.Errors = append(result.Errors, fmt.Errorf("saga.max_retries cannot be negative, got %d", cfg.Saga.MaxRetries))
|
||
}
|
||
if cfg.Saga.RetryBackoffMs < 0 {
|
||
result.Errors = append(result.Errors, fmt.Errorf("saga.retry_backoff_ms cannot be negative, got %d", cfg.Saga.RetryBackoffMs))
|
||
}
|
||
if cfg.Saga.SagaTimeoutSec < 0 {
|
||
result.Errors = append(result.Errors, fmt.Errorf("saga.saga_timeout_sec cannot be negative, got %d", cfg.Saga.SagaTimeoutSec))
|
||
}
|
||
if cfg.Saga.StuckCheckIntervalSec < 0 {
|
||
result.Errors = append(result.Errors, fmt.Errorf("saga.stuck_check_interval_sec cannot be negative, got %d", cfg.Saga.StuckCheckIntervalSec))
|
||
}
|
||
if cfg.Saga.AsyncRecoveryWorkers < 0 {
|
||
result.Errors = append(result.Errors, fmt.Errorf("saga.async_recovery_workers cannot be negative, got %d", cfg.Saga.AsyncRecoveryWorkers))
|
||
}
|
||
if cfg.Saga.ChannelBufferSize < 0 {
|
||
result.Errors = append(result.Errors, fmt.Errorf("saga.channel_buffer_size cannot be negative, got %d", cfg.Saga.ChannelBufferSize))
|
||
}
|
||
}
|
||
|
||
if cfg.Performance.BatchSize < 1 {
|
||
result.Errors = append(result.Errors, fmt.Errorf("batch_size must be at least 1, got %d", cfg.Performance.BatchSize))
|
||
}
|
||
if cfg.Performance.MaxConnections < 1 {
|
||
result.Errors = append(result.Errors, fmt.Errorf("max_connections must be at least 1, got %d", cfg.Performance.MaxConnections))
|
||
}
|
||
|
||
if cfg.Monitoring.TraceSampleRate < 0 || cfg.Monitoring.TraceSampleRate > 1 {
|
||
result.Errors = append(result.Errors, fmt.Errorf("trace_sample_rate must be between 0 and 1, got %f", cfg.Monitoring.TraceSampleRate))
|
||
}
|
||
|
||
if cfg.Metrics.Enabled {
|
||
if cfg.Metrics.CollectIntervalSec <= 0 {
|
||
result.Errors = append(result.Errors, fmt.Errorf("metrics.collect_interval_sec must be > 0 when metrics.enabled = true"))
|
||
}
|
||
}
|
||
if cfg.Metrics.CollectIntervalSec < 0 {
|
||
result.Errors = append(result.Errors, fmt.Errorf("metrics.collect_interval_sec cannot be negative, got %d", cfg.Metrics.CollectIntervalSec))
|
||
}
|
||
|
||
if cfg.Log.LogLevel != "" {
|
||
level := strings.ToLower(cfg.Log.LogLevel)
|
||
if !allowedLogLevels[level] {
|
||
result.Errors = append(result.Errors, fmt.Errorf("invalid log_level: %s (allowed: debug, info, warn, error, fatal)", cfg.Log.LogLevel))
|
||
}
|
||
}
|
||
|
||
if cfg.API.Port == cfg.Cluster.NodePort {
|
||
result.Errors = append(result.Errors, fmt.Errorf("API port %d conflicts with cluster node port", cfg.API.Port))
|
||
}
|
||
if cfg.Monitoring.MetricsPort == cfg.Cluster.NodePort {
|
||
result.Errors = append(result.Errors, fmt.Errorf("metrics port %d conflicts with cluster node port", cfg.Monitoring.MetricsPort))
|
||
}
|
||
if cfg.API.Port == cfg.Monitoring.MetricsPort {
|
||
result.Warnings = append(result.Warnings, fmt.Errorf("API port %d conflicts with metrics port", cfg.API.Port))
|
||
}
|
||
|
||
if cfg.Security.IsTLSEnabled() {
|
||
if cfg.Security.CertFile == "" {
|
||
result.Errors = append(result.Errors, fmt.Errorf("cert_file is required when TLS is enabled"))
|
||
}
|
||
if cfg.Security.KeyFile == "" {
|
||
result.Errors = append(result.Errors, fmt.Errorf("key_file is required when TLS is enabled"))
|
||
}
|
||
}
|
||
|
||
if cfg.Compression.Enabled {
|
||
if !allowedCompressionAlgorithms[cfg.Compression.Algorithm] {
|
||
result.Errors = append(result.Errors, fmt.Errorf("unsupported compression algorithm: %s, supported: snappy, lz4, zstd", cfg.Compression.Algorithm))
|
||
}
|
||
}
|
||
|
||
if cfg.WAL.RecoveryWorkers < 1 {
|
||
result.Errors = append(result.Errors, fmt.Errorf("wal.recovery_workers must be at least 1, got %d", cfg.WAL.RecoveryWorkers))
|
||
}
|
||
if cfg.WAL.AsyncRecoveryWorkers < 1 && cfg.WAL.AsyncRecovery {
|
||
result.Warnings = append(result.Warnings, fmt.Errorf("wal.async_recovery_workers is less than 1, will use default 4"))
|
||
}
|
||
if cfg.WAL.AsyncRecoveryBuffer < 0 {
|
||
result.Errors = append(result.Errors, fmt.Errorf("wal.async_recovery_buffer cannot be negative, got %d", cfg.WAL.AsyncRecoveryBuffer))
|
||
}
|
||
|
||
if cfg.MVCC.MaxVersionsPerDoc < 0 {
|
||
result.Errors = append(result.Errors, fmt.Errorf("mvcc.max_versions_per_doc cannot be negative, got %d", cfg.MVCC.MaxVersionsPerDoc))
|
||
}
|
||
|
||
if cfg.Plugins.MaxCPUTimeMs < 0 {
|
||
result.Errors = append(result.Errors, fmt.Errorf("plugins.max_cpu_time_ms cannot be negative, got %d", cfg.Plugins.MaxCPUTimeMs))
|
||
}
|
||
if cfg.Plugins.MaxMemoryMB < 0 {
|
||
result.Errors = append(result.Errors, fmt.Errorf("plugins.max_memory_mb cannot be negative, got %d", cfg.Plugins.MaxMemoryMB))
|
||
}
|
||
if cfg.Plugins.MaxLuaStates < 1 {
|
||
result.Warnings = append(result.Warnings, fmt.Errorf("plugins.max_lua_states is less than 1, will use default 100"))
|
||
}
|
||
|
||
if cfg.Backpressure.Enabled {
|
||
if cfg.Backpressure.CPUThreshold < 0 || cfg.Backpressure.CPUThreshold > 1 {
|
||
result.Errors = append(result.Errors, fmt.Errorf("backpressure.cpu_threshold must be between 0 and 1, got %f", cfg.Backpressure.CPUThreshold))
|
||
}
|
||
if cfg.Backpressure.MemoryThreshold < 0 || cfg.Backpressure.MemoryThreshold > 1 {
|
||
result.Errors = append(result.Errors, fmt.Errorf("backpressure.memory_threshold must be between 0 and 1, got %f", cfg.Backpressure.MemoryThreshold))
|
||
}
|
||
if cfg.Backpressure.MediumRejectProb > 100 {
|
||
result.Warnings = append(result.Warnings, fmt.Errorf("backpressure.medium_reject_prob is > 100, will be capped to 100"))
|
||
}
|
||
if cfg.Backpressure.HighRejectProb > 100 {
|
||
result.Warnings = append(result.Warnings, fmt.Errorf("backpressure.high_reject_prob is > 100, will be capped to 100"))
|
||
}
|
||
}
|
||
|
||
if cfg.RuntimeLimits.Enabled {
|
||
if cfg.RuntimeLimits.GlobalMaxDocSizeMB < 0 {
|
||
result.Errors = append(result.Errors, fmt.Errorf("runtime_limits.global_max_doc_size_mb cannot be negative, got %d", cfg.RuntimeLimits.GlobalMaxDocSizeMB))
|
||
}
|
||
if cfg.RuntimeLimits.GlobalMaxCollSizeMB < 0 {
|
||
result.Errors = append(result.Errors, fmt.Errorf("runtime_limits.global_max_coll_size_mb cannot be negative, got %d", cfg.RuntimeLimits.GlobalMaxCollSizeMB))
|
||
}
|
||
if cfg.RuntimeLimits.GlobalMaxDocsPerColl < 0 {
|
||
result.Errors = append(result.Errors, fmt.Errorf("runtime_limits.global_max_docs_per_coll cannot be negative, got %d", cfg.RuntimeLimits.GlobalMaxDocsPerColl))
|
||
}
|
||
}
|
||
|
||
if cfg.Autoscaling.Enabled {
|
||
if cfg.Autoscaling.MinNodes < 1 {
|
||
result.Errors = append(result.Errors, fmt.Errorf("autoscaling.min_nodes must be at least 1, got %d", cfg.Autoscaling.MinNodes))
|
||
}
|
||
if cfg.Autoscaling.MaxNodes < cfg.Autoscaling.MinNodes {
|
||
result.Errors = append(result.Errors, fmt.Errorf("autoscaling.max_nodes (%d) must be >= min_nodes (%d)", cfg.Autoscaling.MaxNodes, cfg.Autoscaling.MinNodes))
|
||
}
|
||
if cfg.Autoscaling.ScaleUpThreshold <= 0 || cfg.Autoscaling.ScaleUpThreshold > 1 {
|
||
result.Warnings = append(result.Warnings, fmt.Errorf("autoscaling.scale_up_threshold is invalid, will use default 0.75"))
|
||
}
|
||
if cfg.Autoscaling.ScaleDownThreshold <= 0 || cfg.Autoscaling.ScaleDownThreshold >= cfg.Autoscaling.ScaleUpThreshold {
|
||
result.Warnings = append(result.Warnings, fmt.Errorf("autoscaling.scale_down_threshold should be less than scale_up_threshold"))
|
||
}
|
||
}
|
||
|
||
if cfg.Storage.EnableCustomEngines {
|
||
defaultEngine := cfg.Storage.GetDefaultEngine()
|
||
switch defaultEngine {
|
||
case "row", "columnar", "document", "kv", "ts", "graph":
|
||
default:
|
||
result.Warnings = append(result.Warnings, fmt.Errorf("default_engine '%s' is not a built-in engine, ensure plugin is loaded", defaultEngine))
|
||
}
|
||
}
|
||
|
||
if cfg.Storage.EnableCustomEngines {
|
||
anyEnabled := cfg.Engines.Row.Enabled ||
|
||
cfg.Engines.Columnar.Enabled ||
|
||
cfg.Engines.Document.Enabled ||
|
||
cfg.Engines.KV.Enabled ||
|
||
cfg.Engines.TS.Enabled ||
|
||
cfg.Engines.Graph.Enabled
|
||
if !anyEnabled {
|
||
result.Errors = append(result.Errors, fmt.Errorf("at least one engine must be enabled in [engines]"))
|
||
}
|
||
}
|
||
|
||
if cfg.Backup.Enabled {
|
||
if cfg.Backup.BackupDir == "" {
|
||
result.Errors = append(result.Errors, fmt.Errorf("backup.backup_dir is required when backup.enabled = true"))
|
||
}
|
||
if cfg.Backup.RetentionDays < 0 {
|
||
result.Errors = append(result.Errors, fmt.Errorf("backup.retention_days cannot be negative, got %d", cfg.Backup.RetentionDays))
|
||
}
|
||
if cfg.Backup.MaxConcurrent < 1 {
|
||
result.Warnings = append(result.Warnings, fmt.Errorf("backup.max_concurrent < 1, will use default 1"))
|
||
}
|
||
}
|
||
|
||
if cfg.Migration.Enabled {
|
||
if cfg.Migration.Source == nil || cfg.Migration.Source.Endpoint == "" {
|
||
result.Errors = append(result.Errors, fmt.Errorf("migration.source.endpoint is required when migration is enabled"))
|
||
}
|
||
if cfg.Migration.Target == nil || cfg.Migration.Target.Endpoint == "" {
|
||
result.Errors = append(result.Errors, fmt.Errorf("migration.target.endpoint is required when migration is enabled"))
|
||
}
|
||
if cfg.Migration.Settings == nil {
|
||
result.Warnings = append(result.Warnings, fmt.Errorf("migration.settings not configured, using defaults"))
|
||
}
|
||
if cfg.Migration.Delta == nil {
|
||
result.Warnings = append(result.Warnings, fmt.Errorf("migration.delta not configured, using defaults"))
|
||
}
|
||
if cfg.Migration.Validation == nil {
|
||
result.Warnings = append(result.Warnings, fmt.Errorf("migration.validation not configured, using defaults"))
|
||
}
|
||
if !allowedMigrationModes[cfg.Migration.Mode] {
|
||
result.Errors = append(result.Errors, fmt.Errorf("migration.mode must be one of: manual, semi_auto, auto, got %s", cfg.Migration.Mode))
|
||
}
|
||
if cfg.Migration.Settings != nil && cfg.Migration.Settings.BatchSize < 1 {
|
||
result.Errors = append(result.Errors, fmt.Errorf("migration.settings.batch_size must be at least 1, got %d", cfg.Migration.Settings.BatchSize))
|
||
}
|
||
if cfg.Migration.Settings != nil && cfg.Migration.Settings.Workers < 1 {
|
||
result.Errors = append(result.Errors, fmt.Errorf("migration.settings.workers must be at least 1, got %d", cfg.Migration.Settings.Workers))
|
||
}
|
||
if cfg.Migration.Settings != nil && cfg.Migration.Settings.MaxRetries < 0 {
|
||
result.Errors = append(result.Errors, fmt.Errorf("migration.settings.max_retries cannot be negative, got %d", cfg.Migration.Settings.MaxRetries))
|
||
}
|
||
if cfg.Migration.Validation != nil && (cfg.Migration.Validation.SamplePercent < 0 || cfg.Migration.Validation.SamplePercent > 100) {
|
||
result.Errors = append(result.Errors, fmt.Errorf("migration.validation.sample_percent must be between 0 and 100, got %d", cfg.Migration.Validation.SamplePercent))
|
||
}
|
||
if cfg.Migration.Delta != nil && cfg.Migration.Delta.IntervalSec < 1 {
|
||
result.Errors = append(result.Errors, fmt.Errorf("migration.delta.interval_sec must be at least 1, got %d", cfg.Migration.Delta.IntervalSec))
|
||
}
|
||
if cfg.Migration.Source != nil && cfg.Migration.Source.APIKey != "" {
|
||
if len(cfg.Migration.Source.APIKey) < minAPIKeyLength {
|
||
result.Warnings = append(result.Warnings, fmt.Errorf("migration.source.api_key is shorter than %d characters", minAPIKeyLength))
|
||
}
|
||
}
|
||
if cfg.Migration.Target != nil && cfg.Migration.Target.APIKey != "" {
|
||
if len(cfg.Migration.Target.APIKey) < minAPIKeyLength {
|
||
result.Warnings = append(result.Warnings, fmt.Errorf("migration.target.api_key is shorter than %d characters", minAPIKeyLength))
|
||
}
|
||
}
|
||
}
|
||
|
||
result.Valid = len(result.Errors) == 0
|
||
return result
|
||
}
|
||
|
||
// =============================================================================
|
||
// СОХРАНЕНИЕ КОНФИГУРАЦИИ (ДОБАВЛЕНО)
|
||
// =============================================================================
|
||
|
||
// SaveConfig записывает конфигурацию в TOML-файл атомарно:
|
||
// сначала во временный файл (с fsync), потом rename.
|
||
//
|
||
// Используется REPL-командой "config set" в fallback-режиме, когда
|
||
// DynamicConfigManager недоступен (Raft выключен или узел не лидер).
|
||
//
|
||
// Если path == "" — возвращает ошибку.
|
||
// Если cfg == nil — возвращает ошибку.
|
||
//
|
||
// После успешного rename выполняется fsync родительской директории,
|
||
// чтобы гарантировать сохранность переименования при крахе.
|
||
func SaveConfig(path string, cfg *Config) error {
|
||
if cfg == nil {
|
||
return fmt.Errorf("config is nil")
|
||
}
|
||
if path == "" {
|
||
return fmt.Errorf("config path is empty")
|
||
}
|
||
|
||
dir := filepath.Dir(path)
|
||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||
return fmt.Errorf("failed to create config directory %s: %w", dir, err)
|
||
}
|
||
|
||
tmpPath := path + ".tmp"
|
||
f, err := os.OpenFile(tmpPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
|
||
if err != nil {
|
||
return fmt.Errorf("failed to create temp config file: %w", err)
|
||
}
|
||
|
||
encoder := toml.NewEncoder(f)
|
||
if err := encoder.Encode(cfg); err != nil {
|
||
f.Close()
|
||
os.Remove(tmpPath)
|
||
return fmt.Errorf("failed to encode config: %w", err)
|
||
}
|
||
|
||
if err := f.Sync(); err != nil {
|
||
f.Close()
|
||
os.Remove(tmpPath)
|
||
return fmt.Errorf("failed to sync config: %w", err)
|
||
}
|
||
|
||
if err := f.Close(); err != nil {
|
||
os.Remove(tmpPath)
|
||
return fmt.Errorf("failed to close config: %w", err)
|
||
}
|
||
|
||
if err := os.Rename(tmpPath, path); err != nil {
|
||
os.Remove(tmpPath)
|
||
return fmt.Errorf("failed to rename config: %w", err)
|
||
}
|
||
|
||
// fsync директории — best-effort, на Windows no-op.
|
||
if err := fsyncDir(dir); err != nil {
|
||
// Не критично: файл уже синхронизирован, rename выполнен.
|
||
_ = err
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// fsyncDir синхронизирует директорию.
|
||
// На Linux/macOS/BSD это работает; на Windows — no-op.
|
||
func fsyncDir(dir string) error {
|
||
d, err := os.Open(dir)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
defer d.Close()
|
||
return d.Sync()
|
||
} |