Skip to content

Commit 4f04ab9

Browse files
committed
Add ExileConfig service and related tests for configuration monitoring
- Introduced the ExileConfig service to monitor client configuration changes and notify the domain service accordingly. - Implemented methods for starting and stopping the configuration monitoring loop, along with state management. - Added comprehensive tests for ExileConfig, including scenarios for configuration changes, service initialization, and error handling. - Created a module for dependency injection, providing clean access to the ExileConfig service and its methods. - Ensured adherence to the ports and adapters architecture by implementing necessary interfaces and providing mock implementations for testing.
1 parent 4148cb9 commit 4f04ab9

File tree

5 files changed

+1066
-0
lines changed

5 files changed

+1066
-0
lines changed
Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
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

Comments
 (0)