diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 0000000..a23ba88 --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,1155 @@ +/* + * 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. + +package config + +import ( + "fmt" + "strings" + "time" + + "github.com/BurntSushi/toml" +) + +type Config struct { + Cluster ClusterConfig `toml:"cluster"` + Storage StorageConfig `toml:"storage"` + Repl ReplConfig `toml:"repl"` + Log LogConfig `toml:"log"` + API APIConfig `toml:"api"` + Replication ReplicationConfig `toml:"replication"` + Plugins PluginsConfig `toml:"plugins"` + Compression CompressionConfig `toml:"compression"` + WebUI WebUIConfig `toml:"webui"` + Performance PerformanceConfig `toml:"performance"` + Security SecurityConfig `toml:"security"` + Monitoring MonitoringConfig `toml:"monitoring"` + Recovery RecoveryConfig `toml:"recovery"` + WAL WALConfig `toml:"wal"` + MVCC MVCCConfig `toml:"mvcc"` + Transactions TransactionsConfig `toml:"transactions"` + ACL ACLConfig `toml:"acl"` +} + +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"` +} + +type StorageConfig struct { + PageSizeMB int `toml:"page_size_mb"` + MaxCollections int `toml:"max_collections"` + MaxDocumentsPerCollection int `toml:"max_documents_per_collection"` +} + +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"` +} + +type APIConfig struct { + Port int `toml:"port"` +} + +type ReplicationConfig struct { + Enabled bool `toml:"enabled"` + MasterMaster bool `toml:"master_master"` + SyncReplication bool `toml:"sync_replication"` + ReplicationTimeoutMs int `toml:"replication_timeout_ms"` +} + +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"` +} + +type CompressionConfig struct { + Enabled bool `toml:"enabled"` + Algorithm string `toml:"algorithm"` + Level int `toml:"level"` + MinSize int `toml:"min_size"` +} + +type WebUIConfig struct { + Enabled bool `toml:"enabled"` + Port int `toml:"port"` + Theme string `toml:"theme"` +} + +type PerformanceConfig struct { + EnablePipeline bool `toml:"enable_pipeline"` + BatchSize int `toml:"batch_size"` + ReadFromFollower bool `toml:"read_from_follower"` + MaxConnections int `toml:"max_connections"` +} + +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"` +} + +// ========== НОВЫЕ КОНФИГУРАЦИОННЫЕ СТРУКТУРЫ ========== + +// WALConfig настройки Write-Ahead Log +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"` +} + +// MVCCConfig настройки Multi-Version Concurrency Control +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"` +} + +// TransactionsConfig настройки транзакций +type TransactionsConfig struct { + DefaultTimeoutSec int `toml:"default_timeout_sec"` + DeadlockCheckIntervalSec int `toml:"deadlock_check_interval_sec"` + MaxSavepointsPerTx int `toml:"max_savepoints_per_tx"` + TwoPhaseCommitTimeoutSec int `toml:"two_phase_commit_timeout_sec"` + Enabled bool `toml:"enabled"` + CheckpointIntervalSec int `toml:"checkpoint_interval_sec"` +} + +// ACLConfig настройки Access Control List +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"` +} + +// ========== Методы для новых конфигураций ========== + +// GetSegmentSize возвращает размер сегмента WAL в байтах +func (w *WALConfig) GetSegmentSize() int64 { + if w.SegmentSizeMB <= 0 { + return 64 * 1024 * 1024 + } + return int64(w.SegmentSizeMB) * 1024 * 1024 +} + +// GetSyncInterval возвращает интервал синхронизации WAL +func (w *WALConfig) GetSyncInterval() time.Duration { + if w.SyncIntervalSec <= 0 { + return 5 * time.Second + } + return time.Duration(w.SyncIntervalSec) * time.Second +} + +// GetBatchSize возвращает размер пакета WAL +func (w *WALConfig) GetBatchSize() int { + if w.BatchSize <= 0 { + return 100 + } + return w.BatchSize +} + +// GetRecoveryWorkers возвращает количество воркеров для parallel recovery +func (w *WALConfig) GetRecoveryWorkers() int { + if w.RecoveryWorkers <= 0 { + return 4 + } + return w.RecoveryWorkers +} + +// IsWALEnabled возвращает статус WAL +func (w *WALConfig) IsWALEnabled() bool { + return w.Enabled +} + +// GetMaxVersionsPerDoc возвращает максимальное количество версий на документ +func (m *MVCCConfig) GetMaxVersionsPerDoc() int { + if m.MaxVersionsPerDoc <= 0 { + return 10 + } + return m.MaxVersionsPerDoc +} + +// GetVisibilityMapSize возвращает размер карты видимости +func (m *MVCCConfig) GetVisibilityMapSize() int { + if m.VisibilityMapSize <= 0 { + return 1024 * 1024 + } + return m.VisibilityMapSize +} + +// GetPruneInterval возвращает интервал очистки версий +func (m *MVCCConfig) GetPruneInterval() time.Duration { + if m.PruneIntervalMin <= 0 { + return 5 * time.Minute + } + return time.Duration(m.PruneIntervalMin) * time.Minute +} + +// GetRetentionDays возвращает дни хранения версий +func (m *MVCCConfig) GetRetentionDays() int { + if m.RetentionDays <= 0 { + return 7 + } + return m.RetentionDays +} + +// GetReadCacheSize возвращает размер кэша чтения +func (m *MVCCConfig) GetReadCacheSize() int { + if m.ReadCacheSize <= 0 { + return 10000 + } + return m.ReadCacheSize +} + +// GetReadCacheTTL возвращает TTL кэша чтения +func (m *MVCCConfig) GetReadCacheTTL() time.Duration { + if m.ReadCacheTTLSec <= 0 { + return 300 * time.Second + } + return time.Duration(m.ReadCacheTTLSec) * time.Second +} + +// GetDefaultTimeout возвращает таймаут транзакции по умолчанию +func (t *TransactionsConfig) GetDefaultTimeout() time.Duration { + if t.DefaultTimeoutSec <= 0 { + return 30 * time.Second + } + return time.Duration(t.DefaultTimeoutSec) * time.Second +} + +// GetDeadlockCheckInterval возвращает интервал проверки дедлоков +func (t *TransactionsConfig) GetDeadlockCheckInterval() time.Duration { + if t.DeadlockCheckIntervalSec <= 0 { + return 1 * time.Second + } + return time.Duration(t.DeadlockCheckIntervalSec) * time.Second +} + +// GetMaxSavepointsPerTx возвращает максимальное количество точек сохранения +func (t *TransactionsConfig) GetMaxSavepointsPerTx() int { + if t.MaxSavepointsPerTx <= 0 { + return 100 + } + return t.MaxSavepointsPerTx +} + +// GetTwoPhaseCommitTimeout возвращает таймаут двухфазного коммита +func (t *TransactionsConfig) GetTwoPhaseCommitTimeout() time.Duration { + if t.TwoPhaseCommitTimeoutSec <= 0 { + return 10 * time.Second + } + return time.Duration(t.TwoPhaseCommitTimeoutSec) * time.Second +} + +// IsTransactionsEnabled возвращает статус поддержки транзакций +func (t *TransactionsConfig) IsTransactionsEnabled() bool { + return t.Enabled +} + +// GetCheckpointInterval возвращает интервал чекпоинтов +func (t *TransactionsConfig) GetCheckpointInterval() int64 { + if t.CheckpointIntervalSec <= 0 { + return 300 + } + return int64(t.CheckpointIntervalSec) +} + +// GetMaxDeniedLogSize возвращает максимальный размер лога отказов +func (a *ACLConfig) GetMaxDeniedLogSize() int { + if a.MaxDeniedLogSize <= 0 { + return 10000 + } + return a.MaxDeniedLogSize +} + +// GetTemporaryGrantTTL возвращает TTL временных разрешений +func (a *ACLConfig) GetTemporaryGrantTTL() time.Duration { + if a.TemporaryGrantTTLHours <= 0 { + return 24 * time.Hour + } + return time.Duration(a.TemporaryGrantTTLHours) * time.Hour +} + +// IsRoleHierarchyEnabled возвращает статус иерархии ролей +func (a *ACLConfig) IsRoleHierarchyEnabled() bool { + return a.EnableRoleHierarchy +} + +// GetCacheTTL возвращает TTL кэша ACL +func (a *ACLConfig) GetCacheTTL() time.Duration { + if a.CacheTTLSec <= 0 { + return 60 * time.Second + } + return time.Duration(a.CacheTTLSec) * time.Second +} + +// ========== Методы PluginsConfig (расширенные) ========== + +// GetMaxCPUTime возвращает лимит CPU для плагина +func (p *PluginsConfig) GetMaxCPUTime() time.Duration { + if p.MaxCPUTimeMs <= 0 { + return 100 * time.Millisecond + } + return time.Duration(p.MaxCPUTimeMs) * time.Millisecond +} + +// GetMaxMemory возвращает лимит памяти для плагина в байтах +func (p *PluginsConfig) GetMaxMemory() int64 { + if p.MaxMemoryMB <= 0 { + return 50 * 1024 * 1024 + } + return int64(p.MaxMemoryMB) * 1024 * 1024 +} + +// GetMaxExecutionTime возвращает лимит времени выполнения плагина +func (p *PluginsConfig) GetMaxExecutionTime() time.Duration { + if p.MaxExecutionTimeSec <= 0 { + return 5 * time.Second + } + return time.Duration(p.MaxExecutionTimeSec) * time.Second +} + +// GetMaxInstructions возвращает лимит инструкций Lua +func (p *PluginsConfig) GetMaxInstructions() int64 { + if p.MaxInstructions <= 0 { + return 1000000 + } + return p.MaxInstructions +} + +// GetHotReloadInterval возвращает интервал проверки изменений плагинов +func (p *PluginsConfig) GetHotReloadInterval() time.Duration { + if p.HotReloadIntervalSec <= 0 { + return 30 * time.Second + } + return time.Duration(p.HotReloadIntervalSec) * time.Second +} + +// GetMaxEventLogSize возвращает максимальный размер лога событий плагина +func (p *PluginsConfig) GetMaxEventLogSize() int { + if p.MaxEventLogSize <= 0 { + return 1000 + } + return p.MaxEventLogSize +} + +// GetLoadTimeout возвращает таймаут загрузки плагина +func (p *PluginsConfig) GetLoadTimeout() time.Duration { + if p.LoadTimeoutSec <= 0 { + return 10 * time.Second + } + return time.Duration(p.LoadTimeoutSec) * time.Second +} + +// ========== Остальные методы без изменений ========== + +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 +} + +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) IsMasterMasterEnabled() bool { + return r.MasterMaster +} + +func (r *ReplicationConfig) IsSyncReplicationEnabled() bool { + return r.SyncReplication +} + +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 +} + +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 (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 +} + +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 +} + +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 +} + +func (w *WebUIConfig) IsWebUIEnabled() bool { + return w.Enabled +} + +func (w *WebUIConfig) GetWebUIPort() int { + if w.Port <= 0 { + return 8080 + } + return w.Port +} + +func (w *WebUIConfig) GetTheme() string { + if w.Theme == "" { + return "dark" + } + return w.Theme +} + +// Validate выполняет базовую валидацию конфигурации +func (c *Config) Validate() error { + if c.Cluster.NodePort <= 0 || c.Cluster.NodePort > 65535 { + return fmt.Errorf("invalid node_port: %d", c.Cluster.NodePort) + } + if c.Cluster.RaftPort <= 0 || c.Cluster.RaftPort > 65535 { + return fmt.Errorf("invalid raft_port: %d", c.Cluster.RaftPort) + } + if c.Cluster.PriorityZone < 0 || c.Cluster.PriorityZone > 9 { + return fmt.Errorf("priority_zone must be between 0 and 9, got %d", c.Cluster.PriorityZone) + } + if c.Performance.BatchSize < 1 { + return fmt.Errorf("batch_size must be at least 1, got %d", c.Performance.BatchSize) + } + if c.Performance.MaxConnections < 1 { + return fmt.Errorf("max_connections must be at least 1, got %d", c.Performance.MaxConnections) + } + if c.Monitoring.TraceSampleRate < 0 || c.Monitoring.TraceSampleRate > 1 { + return fmt.Errorf("trace_sample_rate must be between 0 and 1, got %f", c.Monitoring.TraceSampleRate) + } + if c.API.Port == c.Cluster.NodePort { + return fmt.Errorf("API port %d conflicts with cluster node port", c.API.Port) + } + if c.WebUI.Port == c.Cluster.NodePort { + return fmt.Errorf("WebUI port %d conflicts with cluster node port", c.WebUI.Port) + } + if c.Monitoring.MetricsPort == c.Cluster.NodePort { + return fmt.Errorf("metrics port %d conflicts with cluster node port", c.Monitoring.MetricsPort) + } + if c.Security.IsTLSEnabled() { + if c.Security.CertFile == "" { + return fmt.Errorf("cert_file is required when TLS is enabled") + } + if c.Security.KeyFile == "" { + return fmt.Errorf("key_file is required when TLS is enabled") + } + } + if c.Compression.Enabled { + switch c.Compression.Algorithm { + case "snappy", "lz4", "zstd": + default: + return fmt.Errorf("unsupported compression algorithm: %s, supported: snappy, lz4, zstd", c.Compression.Algorithm) + } + } + + // Валидация новых настроек + if c.WAL.RecoveryWorkers < 1 { + return fmt.Errorf("wal.recovery_workers must be at least 1, got %d", c.WAL.RecoveryWorkers) + } + if c.MVCC.MaxVersionsPerDoc < 0 { + return fmt.Errorf("mvcc.max_versions_per_doc cannot be negative, got %d", c.MVCC.MaxVersionsPerDoc) + } + if c.Plugins.MaxCPUTimeMs < 0 { + return fmt.Errorf("plugins.max_cpu_time_ms cannot be negative, got %d", c.Plugins.MaxCPUTimeMs) + } + if c.Plugins.MaxMemoryMB < 0 { + return fmt.Errorf("plugins.max_memory_mb cannot be negative, got %d", c.Plugins.MaxMemoryMB) + } + + return nil +} + +// LoadConfig загружает и валидирует конфигурацию из TOML файла +func LoadConfig(path string) (*Config, error) { + 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.RaftDataDir == "" { + cfg.Cluster.RaftDataDir = "raft_data" + } + if cfg.Replication.ReplicationTimeoutMs == 0 { + cfg.Replication.ReplicationTimeoutMs = 5000 + } + if cfg.Plugins.ScriptDir == "" { + cfg.Plugins.ScriptDir = "plugins" + } + if cfg.API.Port == 0 { + cfg.API.Port = 8080 + } + 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.WebUI.Port == 0 { + cfg.WebUI.Port = 9080 + } + if cfg.WebUI.Theme == "" { + cfg.WebUI.Theme = "dark" + } + 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.Performance.BatchSize == 0 { + cfg.Performance.BatchSize = 100 + } + if cfg.Performance.MaxConnections == 0 { + cfg.Performance.MaxConnections = 1000 + } + 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" + } + + // Значения по умолчанию для WAL + 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 + } + + // Значения по умолчанию для MVCC + 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.TwoPhaseCommitTimeoutSec == 0 { + cfg.Transactions.TwoPhaseCommitTimeoutSec = 10 + } + if cfg.Transactions.CheckpointIntervalSec == 0 { + cfg.Transactions.CheckpointIntervalSec = 300 + } + + // Значения по умолчанию для ACL + 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.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 err := cfg.Validate(); err != nil { + return nil, fmt.Errorf("config validation failed: %v", err) + } + + // ========== НОВАЯ ПОЛНАЯ ВАЛИДАЦИЯ ========== + validationResult := ValidateConfig(&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()) + } + + // Выводим предупреждения, если есть (логгер ещё не инициализирован, используем fmt) + if len(validationResult.Warnings) > 0 { + fmt.Println("Configuration warnings:") + for _, warn := range validationResult.Warnings { + fmt.Printf(" - %s\n", warn.Error()) + } + } + + return &cfg, nil +} + +// GetConfigSummary возвращает сводку по конфигурации +func (c *Config) GetConfigSummary() map[string]interface{} { + return map[string]interface{}{ + "cluster": map[string]interface{}{ + "name": c.Cluster.Name, + "node_ip": c.Cluster.NodeIP, + "node_port": c.Cluster.NodePort, + "raft_port": c.Cluster.RaftPort, + "bootstrap": c.Cluster.Bootstrap, + "nodes_count": len(c.Cluster.Nodes), + "region": c.Cluster.GetRegion(), + "priority_zone": c.Cluster.GetPriorityZone(), + "split_brain_prevention": c.Cluster.IsSplitBrainPreventionEnabled(), + }, + "replication": map[string]interface{}{ + "enabled": c.Replication.IsReplicationEnabled(), + "master_master": c.Replication.IsMasterMasterEnabled(), + "sync": c.Replication.IsSyncReplicationEnabled(), + "timeout_ms": c.Replication.ReplicationTimeoutMs, + }, + "performance": map[string]interface{}{ + "pipeline": c.Performance.IsPipelineEnabled(), + "batch_size": c.Performance.GetBatchSize(), + "read_from_follower": c.Performance.IsReadFromFollowerEnabled(), + "max_connections": c.Performance.GetMaxConnections(), + }, + "security": map[string]interface{}{ + "tls_enabled": c.Security.IsTLSEnabled(), + "tls_version": c.Security.MinVersion, + }, + "monitoring": map[string]interface{}{ + "metrics_enabled": c.Monitoring.IsMetricsEnabled(), + "metrics_port": c.Monitoring.GetMetricsPort(), + "tracing_enabled": c.Monitoring.IsTracingEnabled(), + "trace_sample_rate": c.Monitoring.GetTraceSampleRate(), + }, + "compression": map[string]interface{}{ + "enabled": c.Compression.IsCompressionEnabled(), + "algorithm": c.Compression.GetAlgorithm(), + "level": c.Compression.GetLevel(), + "min_size": c.Compression.GetMinSize(), + }, + "wal": map[string]interface{}{ + "segment_size_mb": c.WAL.SegmentSizeMB, + "sync_interval_sec": c.WAL.SyncIntervalSec, + "batch_size": c.WAL.BatchSize, + "recovery_workers": c.WAL.RecoveryWorkers, + "enabled": c.WAL.Enabled, + }, + "mvcc": map[string]interface{}{ + "max_versions_per_doc": c.MVCC.MaxVersionsPerDoc, + "prune_interval_min": c.MVCC.PruneIntervalMin, + "retention_days": c.MVCC.RetentionDays, + }, + "transactions": map[string]interface{}{ + "default_timeout_sec": c.Transactions.DefaultTimeoutSec, + "enabled": c.Transactions.Enabled, + }, + "acl": map[string]interface{}{ + "enable_role_hierarchy": c.ACL.EnableRoleHierarchy, + }, + } +} +// Добавьте эти структуры в существующий файл config.go + +// ========== НОВЫЕ СЕКЦИИ КОНФИГУРАЦИИ ========== + +// TLSConfig настройки 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"` +} + +// BackpressureConfig настройки 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"` +} + +// RuntimeLimitsConfig настройки runtime-ограничений +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"` +} + +// AutoscalingConfig настройки автомасштабирования +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"` +} + +// SchemaMigrationConfig настройки миграции схемы +type SchemaMigrationConfig struct { + Enabled bool `toml:"enabled"` + MigrationDir string `toml:"migration_dir"` + AutoMigrate bool `toml:"auto_migrate"` + TargetVersion string `toml:"target_version"` +} + +// BackupConfig настройки бэкапов +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"` +} + +// Добавьте эти поля в структуру Config (внутри неё) +/* +type Config struct { + // ... существующие поля ... + + ClusterTLS TLSConfig `toml:"cluster_tls"` + Backpressure BackpressureConfig `toml:"backpressure"` + RuntimeLimits RuntimeLimitsConfig `toml:"runtime_limits"` + Autoscaling AutoscalingConfig `toml:"autoscaling"` + SchemaMigration SchemaMigrationConfig `toml:"schema_migration"` + Backup BackupConfig `toml:"backup"` +} +*/ + +// Добавьте методы-геттеры для новых конфигураций + +// GetTLSMinVersion возвращает минимальную версию TLS +func (t *TLSConfig) GetTLSMinVersion() string { + if t.MinVersion == "" { + return "1.2" + } + return t.MinVersion +} + +// IsTLSEnabled возвращает статус TLS +func (t *TLSConfig) IsTLSEnabled() bool { + return t.Enabled +} + +// IsMutualAuthEnabled возвращает статус взаимной аутентификации +func (t *TLSConfig) IsMutualAuthEnabled() bool { + return t.MutualAuth +} + +// GetKeyRotationDays возвращает дни ротации ключей +func (t *TLSConfig) GetKeyRotationDays() int { + if t.KeyRotationDays <= 0 { + return 30 + } + return t.KeyRotationDays +} + +// IsBackpressureEnabled возвращает статус backpressure +func (b *BackpressureConfig) IsBackpressureEnabled() bool { + return b.Enabled +} + +// GetCPUThreshold возвращает порог CPU +func (b *BackpressureConfig) GetCPUThreshold() float64 { + if b.CPUThreshold <= 0 { + return 0.8 + } + return b.CPUThreshold +} + +// GetMemoryThreshold возвращает порог памяти +func (b *BackpressureConfig) GetMemoryThreshold() float64 { + if b.MemoryThreshold <= 0 { + return 0.85 + } + return b.MemoryThreshold +} + +// IsRuntimeLimitsEnabled возвращает статус runtime-ограничений +func (r *RuntimeLimitsConfig) IsRuntimeLimitsEnabled() bool { + return r.Enabled +} + +// GetGlobalMaxDocSizeMB возвращает максимальный размер документа +func (r *RuntimeLimitsConfig) GetGlobalMaxDocSizeMB() int { + if r.GlobalMaxDocSizeMB <= 0 { + return 16 + } + return r.GlobalMaxDocSizeMB +} + +// IsAutoscalingEnabled возвращает статус автомасштабирования +func (a *AutoscalingConfig) IsAutoscalingEnabled() bool { + return a.Enabled +} + +// GetMinNodes возвращает минимальное количество узлов +func (a *AutoscalingConfig) GetMinNodes() int { + if a.MinNodes <= 0 { + return 1 + } + return a.MinNodes +} + +// GetMaxNodes возвращает максимальное количество узлов +func (a *AutoscalingConfig) GetMaxNodes() int { + if a.MaxNodes <= 0 { + return 10 + } + return a.MaxNodes +} + +// IsSchemaMigrationEnabled возвращает статус миграции схемы +func (s *SchemaMigrationConfig) IsSchemaMigrationEnabled() bool { + return s.Enabled +} + +// GetMigrationDir возвращает директорию миграций +func (s *SchemaMigrationConfig) GetMigrationDir() string { + if s.MigrationDir == "" { + return "migrations" + } + return s.MigrationDir +} + +// IsBackupEnabled возвращает статус бэкапов +func (b *BackupConfig) IsBackupEnabled() bool { + return b.Enabled +} + +// GetBackupDir возвращает директорию бэкапов +func (b *BackupConfig) GetBackupDir() string { + if b.BackupDir == "" { + return "backups" + } + return b.BackupDir +}