|
| 1 | +package exileconfig |
| 2 | + |
| 3 | +import ( |
| 4 | + "context" |
| 5 | + "sync" |
| 6 | + "time" |
| 7 | + |
| 8 | + "github.com/rs/zerolog" |
| 9 | + "github.com/tcncloud/sati-go/pkg/ports" |
| 10 | +) |
| 11 | + |
| 12 | +// ExileConfig is a service that monitors the exile client configuration and notifies the domain service when changes occur. |
| 13 | +// It provides handlers that the exile client can call when it starts/stops, and runs a main loop to check for configuration changes. |
| 14 | +type ExileConfig struct { |
| 15 | + exileClient ports.ClientInterface |
| 16 | + domainService ports.DomainService |
| 17 | + log *zerolog.Logger |
| 18 | + |
| 19 | + // State management |
| 20 | + mu sync.RWMutex |
| 21 | + isRunning bool |
| 22 | + lastConfig *ports.GetClientConfigurationResult |
| 23 | + stopChan chan struct{} |
| 24 | + ticker *time.Ticker |
| 25 | +} |
| 26 | + |
| 27 | +// NewExileConfig creates a new ExileConfig instance. |
| 28 | +func NewExileConfig( |
| 29 | + exileClient ports.ClientInterface, |
| 30 | + domainService ports.DomainService, |
| 31 | + log *zerolog.Logger, |
| 32 | +) *ExileConfig { |
| 33 | + return &ExileConfig{ |
| 34 | + exileClient: exileClient, |
| 35 | + domainService: domainService, |
| 36 | + log: log, |
| 37 | + stopChan: make(chan struct{}), |
| 38 | + } |
| 39 | +} |
| 40 | + |
| 41 | +// OnExileClientStarted is the handler that should be called by the exile client when it starts. |
| 42 | +// This starts the configuration monitoring loop. |
| 43 | +func (ec *ExileConfig) OnExileClientStarted(ctx context.Context) error { |
| 44 | + ec.mu.Lock() |
| 45 | + defer ec.mu.Unlock() |
| 46 | + |
| 47 | + if ec.isRunning { |
| 48 | + ec.log.Warn().Msg("Exile config monitoring already running") |
| 49 | + return nil |
| 50 | + } |
| 51 | + |
| 52 | + ec.log.Info().Msg("Starting exile config monitoring") |
| 53 | + |
| 54 | + // Start the configuration monitoring goroutine |
| 55 | + go ec.monitorConfiguration(ctx) |
| 56 | + |
| 57 | + ec.isRunning = true |
| 58 | + ec.log.Info().Msg("Exile config monitoring started successfully") |
| 59 | + |
| 60 | + return nil |
| 61 | +} |
| 62 | + |
| 63 | +// OnExileClientStopped is the handler that should be called by the exile client when it stops. |
| 64 | +// This stops the configuration monitoring loop. |
| 65 | +func (ec *ExileConfig) OnExileClientStopped() error { |
| 66 | + ec.mu.Lock() |
| 67 | + defer ec.mu.Unlock() |
| 68 | + |
| 69 | + if !ec.isRunning { |
| 70 | + ec.log.Warn().Msg("Exile config monitoring not running") |
| 71 | + return nil |
| 72 | + } |
| 73 | + |
| 74 | + ec.log.Info().Msg("Stopping exile config monitoring") |
| 75 | + |
| 76 | + // Signal stop |
| 77 | + close(ec.stopChan) |
| 78 | + |
| 79 | + // Stop ticker if running |
| 80 | + if ec.ticker != nil { |
| 81 | + ec.ticker.Stop() |
| 82 | + ec.ticker = nil |
| 83 | + } |
| 84 | + |
| 85 | + ec.isRunning = false |
| 86 | + ec.log.Info().Msg("Exile config monitoring stopped successfully") |
| 87 | + |
| 88 | + return nil |
| 89 | +} |
| 90 | + |
| 91 | +// IsRunning returns true if the configuration monitoring is currently active. |
| 92 | +func (ec *ExileConfig) IsRunning() bool { |
| 93 | + ec.mu.RLock() |
| 94 | + defer ec.mu.RUnlock() |
| 95 | + |
| 96 | + return ec.isRunning |
| 97 | +} |
| 98 | + |
| 99 | +// monitorConfiguration runs the main loop that checks for configuration changes every 1 minute. |
| 100 | +func (ec *ExileConfig) monitorConfiguration(ctx context.Context) { |
| 101 | + // Check configuration every 1 minute |
| 102 | + ec.ticker = time.NewTicker(1 * time.Minute) |
| 103 | + defer ec.ticker.Stop() |
| 104 | + |
| 105 | + ec.log.Info().Msg("Configuration monitoring loop started") |
| 106 | + |
| 107 | + for { |
| 108 | + select { |
| 109 | + case <-ctx.Done(): |
| 110 | + ec.log.Info().Msg("Context cancelled, stopping config monitoring") |
| 111 | + return |
| 112 | + case <-ec.stopChan: |
| 113 | + ec.log.Info().Msg("Stop signal received, stopping config monitoring") |
| 114 | + return |
| 115 | + case <-ec.ticker.C: |
| 116 | + if err := ec.checkConfiguration(ctx); err != nil { |
| 117 | + ec.log.Error().Err(err).Msg("Failed to check configuration") |
| 118 | + } |
| 119 | + } |
| 120 | + } |
| 121 | +} |
| 122 | + |
| 123 | +// checkConfiguration checks the current configuration and handles changes. |
| 124 | +func (ec *ExileConfig) checkConfiguration(ctx context.Context) error { |
| 125 | + // Create a timeout context for the configuration check |
| 126 | + checkCtx, cancel := context.WithTimeout(ctx, 10*time.Second) |
| 127 | + defer cancel() |
| 128 | + |
| 129 | + // Get current configuration |
| 130 | + params := ports.GetClientConfigurationParams{} |
| 131 | + result, err := ec.exileClient.GetClientConfiguration(checkCtx, params) |
| 132 | + if err != nil { |
| 133 | + return err |
| 134 | + } |
| 135 | + |
| 136 | + ec.mu.Lock() |
| 137 | + configChanged := ec.lastConfig == nil || ec.hasConfigurationChanged(ec.lastConfig, &result) |
| 138 | + oldConfig := ec.lastConfig |
| 139 | + ec.lastConfig = &result |
| 140 | + ec.mu.Unlock() |
| 141 | + |
| 142 | + if configChanged { |
| 143 | + ec.log.Info(). |
| 144 | + Str("org_id", result.OrgID). |
| 145 | + Str("org_name", result.OrgName). |
| 146 | + Str("config_name", result.ConfigName). |
| 147 | + Msg("Configuration changed, notifying domain service") |
| 148 | + |
| 149 | + // Notify the domain service about the configuration change |
| 150 | + if err := ec.domainService.ClientConfigurationChanged(oldConfig, &result); err != nil { |
| 151 | + ec.log.Error().Err(err).Msg("Failed to notify domain service of configuration change") |
| 152 | + return err |
| 153 | + } |
| 154 | + |
| 155 | + ec.log.Info().Msg("Domain service notified of configuration change") |
| 156 | + } |
| 157 | + |
| 158 | + return nil |
| 159 | +} |
| 160 | + |
| 161 | +// hasConfigurationChanged checks if the configuration has changed. |
| 162 | +func (ec *ExileConfig) hasConfigurationChanged(old, new *ports.GetClientConfigurationResult) bool { |
| 163 | + if old == nil || new == nil { |
| 164 | + return old != new |
| 165 | + } |
| 166 | + |
| 167 | + return old.OrgID != new.OrgID || |
| 168 | + old.OrgName != new.OrgName || |
| 169 | + old.ConfigName != new.ConfigName || |
| 170 | + old.ConfigPayload != new.ConfigPayload |
| 171 | +} |
| 172 | + |
| 173 | +// GetLastConfiguration returns the last known configuration. |
| 174 | +func (ec *ExileConfig) GetLastConfiguration() *ports.GetClientConfigurationResult { |
| 175 | + ec.mu.RLock() |
| 176 | + defer ec.mu.RUnlock() |
| 177 | + |
| 178 | + return ec.lastConfig |
| 179 | +} |
| 180 | + |
| 181 | +// ConfigChangeHandler interface implementation |
| 182 | + |
| 183 | +// OnConfigChanged is called when the configuration changes. |
| 184 | +func (ec *ExileConfig) OnConfigChanged(oldConfig, newConfig *ports.GetClientConfigurationResult) { |
| 185 | + ec.log.Info(). |
| 186 | + Str("old_org_id", oldConfig.OrgID). |
| 187 | + Str("new_org_id", newConfig.OrgID). |
| 188 | + Str("old_config_name", oldConfig.ConfigName). |
| 189 | + Str("new_config_name", newConfig.ConfigName). |
| 190 | + Msg("Configuration change detected") |
| 191 | + |
| 192 | + // Log the change details |
| 193 | + ec.log.Debug(). |
| 194 | + Interface("old_config", oldConfig). |
| 195 | + Interface("new_config", newConfig). |
| 196 | + Msg("Configuration change details") |
| 197 | +} |
| 198 | + |
| 199 | +// ShouldRestartProcesses returns true if processes should be restarted. |
| 200 | +func (ec *ExileConfig) ShouldRestartProcesses(oldConfig, newConfig *ports.GetClientConfigurationResult) bool { |
| 201 | + if oldConfig == nil || newConfig == nil { |
| 202 | + return oldConfig != newConfig |
| 203 | + } |
| 204 | + |
| 205 | + // Restart processes if any critical configuration changed |
| 206 | + return oldConfig.OrgID != newConfig.OrgID || |
| 207 | + oldConfig.OrgName != newConfig.OrgName || |
| 208 | + oldConfig.ConfigName != newConfig.ConfigName || |
| 209 | + oldConfig.ConfigPayload != newConfig.ConfigPayload |
| 210 | +} |
| 211 | + |
| 212 | +// ConfigWatcher interface implementation |
| 213 | + |
| 214 | +// Start begins watching for configuration changes. |
| 215 | +func (ec *ExileConfig) Start(ctx context.Context) error { |
| 216 | + return ec.OnExileClientStarted(ctx) |
| 217 | +} |
| 218 | + |
| 219 | +// Stop stops the configuration watcher. |
| 220 | +func (ec *ExileConfig) Stop() error { |
| 221 | + return ec.OnExileClientStopped() |
| 222 | +} |
| 223 | + |
| 224 | +// IsWatching returns true if the watcher is currently active. |
| 225 | +func (ec *ExileConfig) IsWatching() bool { |
| 226 | + return ec.IsRunning() |
| 227 | +} |
| 228 | + |
| 229 | +// Ensure ExileConfig implements required interfaces. |
| 230 | +var _ ports.ConfigWatcher = (*ExileConfig)(nil) |
| 231 | +var _ ports.ConfigChangeHandler = (*ExileConfig)(nil) |
0 commit comments