@@ -41,11 +41,35 @@ const (
4141
4242type RemoteConfigUpdater struct {
4343 kubeClient kubeclient.Client
44- rcClient * client. Client
45- rcService * service. CoreAgentService
44+ rcClient rcRuntimeClient
45+ rcService rcService
4646 serviceConf RcServiceConfiguration
4747 logger logr.Logger
4848 mu sync.RWMutex
49+
50+ lifecycleMu sync.Mutex
51+ activeAPIKey string
52+ subscriptions []rcSubscription
53+ installerState []* pbgo.PackageState
54+ remoteConfigFactory rcRuntimeFactory
55+ }
56+
57+ type rcRuntimeFactory func (conf RcServiceConfiguration ) (rcService , rcRuntimeClient , error )
58+
59+ type rcService interface {
60+ Start ()
61+ Stop () error
62+ }
63+
64+ type rcRuntimeClient interface {
65+ RCClient
66+ Start ()
67+ Close ()
68+ }
69+
70+ type rcSubscription struct {
71+ product string
72+ fn func (update map [string ]state.RawConfig , applyStateCallback func (string , state.ApplyStatus ))
4973}
5074
5175type RcServiceConfiguration struct {
@@ -68,7 +92,7 @@ type RCClient interface {
6892
6993// Client returns the underlying RC client.
7094func (r * RemoteConfigUpdater ) Client () RCClient {
71- return r . rcClient
95+ return r
7296}
7397
7498// DatadogProductRemoteConfig is an interface for Datadog product remote configuration
@@ -135,98 +159,90 @@ type dummyTelemetryReporter struct{}
135159func (d dummyTelemetryReporter ) IncRateLimit () {}
136160func (d dummyTelemetryReporter ) IncTimeout () {}
137161
138- func (r * RemoteConfigUpdater ) Setup (creds config.Creds ) error {
139- apiKey := creds .APIKey
140- if apiKey == "" {
162+ func (r * RemoteConfigUpdater ) Setup (credsManager * config.CredentialManager ) error {
163+ creds , err := credsManager .GetCredentials ()
164+ if err != nil {
165+ return err
166+ }
167+ if creds .APIKey == "" {
141168 return errors .New ("error obtaining API key" )
142169 }
143170
171+ r .lifecycleMu .Lock ()
172+ defer r .lifecycleMu .Unlock ()
173+ if r .rcClient != nil || r .rcService != nil {
174+ return nil
175+ }
176+
177+ if err := r .startRuntime (creds .APIKey ); err != nil {
178+ return err
179+ }
180+ r .activeAPIKey = creds .APIKey
181+ return nil
182+ }
183+
184+ // startRuntime will start the remote configuration client and service. This must be called a held lock.
185+ func (r * RemoteConfigUpdater ) startRuntime (apiKey string ) error {
144186 site := os .Getenv (constants .DDSite ) // TODO support DD_URL as well
145187 clusterName := os .Getenv (constants .DDClusterName )
146188 directorRoot := os .Getenv ("DD_REMOTE_CONFIGURATION_DIRECTOR_ROOT" )
147189 configRoot := os .Getenv ("DD_REMOTE_CONFIGURATION_CONFIG_ROOT" )
148190 endpoint := os .Getenv ("DD_REMOTE_CONFIGURATION_RC_DD_URL" )
149191
150- if r .rcClient == nil && r .rcService == nil {
151- // Setup rcClient and rcService
152- err := r .Start (apiKey , site , clusterName , directorRoot , configRoot , endpoint )
153- if err != nil {
154- return err
155- }
156- }
157-
158- return nil
159-
192+ return r .startRuntimeWithConfig (apiKey , site , clusterName , directorRoot , configRoot , endpoint )
160193}
161194
162- func (r * RemoteConfigUpdater ) Start (apiKey string , site string , clusterName string , directorRoot string , configRoot string , endpoint string ) error {
163-
195+ func (r * RemoteConfigUpdater ) startRuntimeWithConfig (apiKey string , site string , clusterName string , directorRoot string , configRoot string , endpoint string ) error {
164196 r .logger .Info ("Starting Remote Configuration client and service" )
165197
166- err := r .configureService (apiKey , site , clusterName , directorRoot , configRoot , endpoint )
198+ serviceConf , err := r .newServiceConfiguration (apiKey , site , clusterName , directorRoot , configRoot , endpoint )
167199 if err != nil {
168200 r .logger .Error (err , "Failed to configure Remote Configuration service" )
169201 return err
170202 }
171203
172- rcService , err := service .NewService (
173- r .serviceConf .cfg ,
174- "" ,
175- r .serviceConf .baseRawURL ,
176- r .serviceConf .hostname ,
177- func () []string { return []string {"cluster_name:" + r .serviceConf .clusterName } },
178- r .serviceConf .telemetryReporter ,
179- r .serviceConf .agentVersion ,
180- service .WithAPIKey (apiKey ),
181- service .WithDatabaseFileName (filepath .Join (r .serviceConf .rcDatabaseDir , fmt .Sprintf ("remote-config-%s.db" , uuid .New ()))),
182- service .WithDirectorRootOverride (r .serviceConf .cfg .GetString ("site" ), r .serviceConf .cfg .GetString ("remote_configuration.director_root" )),
183- service .WithConfigRootOverride (r .serviceConf .cfg .GetString ("site" ), r .serviceConf .cfg .GetString ("remote_configuration.config_root" )),
184- )
204+ newService , newClient , err := r .remoteConfigFactory (serviceConf )
185205 if err != nil {
186- r .logger .Error (err , "Failed to create Remote Configuration service " )
206+ r .logger .Error (err , "Failed to create Remote Configuration runtime " )
187207 return err
188208 }
189- r .rcService = rcService
190209
191- updaterTags := []string {"updater_type:datadog-operator" }
192- if r .serviceConf .clusterName != "" {
193- updaterTags = append (updaterTags , "cluster_name:" + r .serviceConf .clusterName )
194- }
195- rcClient , err := client .NewClient (
196- rcService ,
197- client .WithUpdater (updaterTags ... ),
198- client .WithProducts (state .ProductAgentConfig , state .ProductOrchestratorK8sCRDs ),
199- client .WithDirectorRootOverride (r .serviceConf .cfg .GetString ("site" ), r .serviceConf .cfg .GetString ("remote_configuration.director_root" )),
200- client .WithPollInterval (pollInterval ),
201- )
202- if err != nil {
203- r .logger .Error (err , "Failed to create Remote Configuration client" )
204- return err
205- }
206- r .rcClient = rcClient
207- rcClient .SetInstallerState ([]* pbgo.PackageState {
208- {
209- Package : "datadog-operator" ,
210- StableVersion : "0.0.1" ,
211- StableConfigVersion : "0.0.1" ,
212- },
213- })
210+ oldSvc := r .rcService
211+ oldClient := r .rcClient
214212
215- rcService .Start ()
213+ newClient .SetInstallerState (r .installerState )
214+
215+ newService .Start ()
216216 r .logger .Info ("Remote Configuration service started" )
217217
218- rcClient .Start ()
219- r .logger .Info ("Remote Configuration client started" )
218+ for _ , subscription := range r .subscriptions {
219+ newClient .Subscribe (subscription .product , subscription .fn )
220+ }
220221
221- rcClient .Subscribe (string (state .ProductAgentConfig ), r .agentConfigUpdateCallback )
222+ newClient .Start ()
223+ r .logger .Info ("Remote Configuration client started" )
222224
223- rcClient .Subscribe (string (state .ProductOrchestratorK8sCRDs ), r .crdConfigUpdateCallback )
225+ r .serviceConf = serviceConf
226+ r .rcService = newService
227+ r .rcClient = newClient
228+
229+ // Clean up old service/client after the new ones are setup and swapped.
230+ if oldSvc != nil {
231+ if err := oldSvc .Stop (); err != nil {
232+ // The new runtime is already active; returning this error would leave
233+ // activeAPIKey stale and cause repeated restart attempts.
234+ r .logger .Error (err , "Failed to stop previous Remote Configuration service" )
235+ }
236+ }
237+ if oldClient != nil {
238+ oldClient .Close ()
239+ }
224240
225241 return nil
226242}
227243
228- // configureService fills the configuration needed to start the rc service
229- func (r * RemoteConfigUpdater ) configureService (apiKey , site , clusterName , directorRoot , configRoot , endpoint string ) error {
244+ // newServiceConfiguration builds the configuration needed to start the rc service.
245+ func (r * RemoteConfigUpdater ) newServiceConfiguration (apiKey , site , clusterName , directorRoot , configRoot , endpoint string ) ( RcServiceConfiguration , error ) {
230246 cfg := model .NewConfig ("datadog" , "DD" , strings .NewReplacer ("." , "_" ))
231247
232248 cfg .SetWithoutSource ("api_key" , apiKey )
@@ -242,10 +258,10 @@ func (r *RemoteConfigUpdater) configureService(apiKey, site, clusterName, direct
242258 // TODO consider different dir
243259 baseDir := filepath .Join (os .TempDir (), "datadog-operator" )
244260 if err := os .MkdirAll (baseDir , 0777 ); err != nil {
245- return err
261+ return RcServiceConfiguration {}, err
246262 }
247263
248- serviceConf := RcServiceConfiguration {
264+ return RcServiceConfiguration {
249265 cfg : cfg ,
250266 apiKey : apiKey ,
251267 baseRawURL : endpoint ,
@@ -255,9 +271,43 @@ func (r *RemoteConfigUpdater) configureService(apiKey, site, clusterName, direct
255271 // TODO fix when other values accepted
256272 agentVersion : "7.50.0" ,
257273 rcDatabaseDir : baseDir ,
274+ }, nil
275+ }
276+
277+ func defaultRCFactory (conf RcServiceConfiguration ) (rcService , rcRuntimeClient , error ) {
278+ rcService , err := service .NewService (
279+ conf .cfg ,
280+ "" ,
281+ conf .baseRawURL ,
282+ conf .hostname ,
283+ func () []string { return []string {"cluster_name:" + conf .clusterName } },
284+ conf .telemetryReporter ,
285+ conf .agentVersion ,
286+ service .WithAPIKey (conf .apiKey ),
287+ service .WithDatabaseFileName (filepath .Join (conf .rcDatabaseDir , fmt .Sprintf ("remote-config-%s.db" , uuid .New ()))),
288+ service .WithDirectorRootOverride (conf .cfg .GetString ("site" ), conf .cfg .GetString ("remote_configuration.director_root" )),
289+ service .WithConfigRootOverride (conf .cfg .GetString ("site" ), conf .cfg .GetString ("remote_configuration.config_root" )),
290+ )
291+ if err != nil {
292+ return nil , nil , err
258293 }
259- r .serviceConf = serviceConf
260- return nil
294+
295+ updaterTags := []string {"updater_type:datadog-operator" }
296+ if conf .clusterName != "" {
297+ updaterTags = append (updaterTags , "cluster_name:" + conf .clusterName )
298+ }
299+ rcClient , err := client .NewClient (
300+ rcService ,
301+ client .WithUpdater (updaterTags ... ),
302+ client .WithProducts (state .ProductAgentConfig , state .ProductOrchestratorK8sCRDs ),
303+ client .WithDirectorRootOverride (conf .cfg .GetString ("site" ), conf .cfg .GetString ("remote_configuration.director_root" )),
304+ client .WithPollInterval (pollInterval ),
305+ )
306+ if err != nil {
307+ return nil , nil , err
308+ }
309+
310+ return rcService , rcClient , nil
261311}
262312
263313// getEndpoint returns the Remote Config endpoint, based on `site` and the prefix
@@ -268,6 +318,76 @@ func getEndpoint(prefix, site string) string {
268318 return prefix + defaultSite
269319}
270320
321+ func (r * RemoteConfigUpdater ) Subscribe (product string , fn func (update map [string ]state.RawConfig , applyStateCallback func (string , state.ApplyStatus ))) {
322+ r .lifecycleMu .Lock ()
323+ defer r .lifecycleMu .Unlock ()
324+
325+ subscription := rcSubscription {product : product , fn : fn }
326+ r .subscriptions = append (r .subscriptions , subscription )
327+ if r .rcClient != nil {
328+ r .rcClient .Subscribe (product , fn )
329+ }
330+ }
331+
332+ func (r * RemoteConfigUpdater ) GetInstallerState () []* pbgo.PackageState {
333+ r .lifecycleMu .Lock ()
334+ defer r .lifecycleMu .Unlock ()
335+ if r .rcClient != nil {
336+ return r .rcClient .GetInstallerState ()
337+ }
338+ return r .installerState
339+ }
340+
341+ func (r * RemoteConfigUpdater ) SetInstallerState (packages []* pbgo.PackageState ) {
342+ r .lifecycleMu .Lock ()
343+ defer r .lifecycleMu .Unlock ()
344+
345+ // Save the installer state in the updater so that it can be used to restore the state of the installer
346+ // when swapping out remote config clients.
347+ r .installerState = packages
348+ if r .rcClient != nil {
349+ r .rcClient .SetInstallerState (packages )
350+ }
351+ }
352+
353+ func (r * RemoteConfigUpdater ) syncCredentials (credsManager * config.CredentialManager ) error {
354+ creds , err := credsManager .GetCredentials ()
355+ if err != nil {
356+ return err
357+ }
358+ if creds .APIKey == "" {
359+ return errors .New ("error obtaining API key" )
360+ }
361+
362+ r .lifecycleMu .Lock ()
363+ defer r .lifecycleMu .Unlock ()
364+
365+ if creds .APIKey == r .activeAPIKey {
366+ return nil
367+ }
368+
369+ if err := r .startRuntime (creds .APIKey ); err != nil {
370+ return err
371+ }
372+ r .activeAPIKey = creds .APIKey
373+ r .logger .Info ("Remote Configuration credentials changed, runtime restarted" )
374+ return nil
375+ }
376+
377+ func (r * RemoteConfigUpdater ) StartCredentialWatchRoutine (credsManager * config.CredentialManager , interval time.Duration ) {
378+ r .logger .Info ("Starting Remote Configuration credential watch routine" , "interval" , interval )
379+
380+ ticker := time .NewTicker (interval )
381+ defer ticker .Stop ()
382+
383+ for {
384+ <- ticker .C
385+ if err := r .syncCredentials (credsManager ); err != nil {
386+ r .logger .Error (err , "Failed to sync Remote Configuration credentials" )
387+ }
388+ }
389+ }
390+
271391// getAndUpdateDatadogAgent is used to prevent race conditions when updating the DDA's status
272392// we do not want to modify the status without using this function or we could have conflicts
273393func (r * RemoteConfigUpdater ) getAndUpdateDatadogAgent (ctx context.Context , cfg DatadogProductRemoteConfig , f func (v2alpha1.DatadogAgent , DatadogProductRemoteConfig ) error ) error {
@@ -585,6 +705,9 @@ func (r *RemoteConfigUpdater) updateInstanceStatus(dda v2alpha1.DatadogAgent, co
585705}
586706
587707func (r * RemoteConfigUpdater ) Stop () error {
708+ r .lifecycleMu .Lock ()
709+ defer r .lifecycleMu .Unlock ()
710+
588711 if r .rcService != nil {
589712 err := r .rcService .Stop ()
590713 if err != nil {
@@ -600,8 +723,21 @@ func (r *RemoteConfigUpdater) Stop() error {
600723}
601724
602725func NewRemoteConfigUpdater (client kubeclient.Client , logger logr.Logger ) * RemoteConfigUpdater {
603- return & RemoteConfigUpdater {
726+ r := & RemoteConfigUpdater {
604727 kubeClient : client ,
605728 logger : logger ,
729+ installerState : []* pbgo.PackageState {
730+ {
731+ Package : "datadog-operator" ,
732+ StableVersion : "0.0.1" ,
733+ StableConfigVersion : "0.0.1" ,
734+ },
735+ },
736+ remoteConfigFactory : defaultRCFactory ,
737+ }
738+ r .subscriptions = []rcSubscription {
739+ {product : state .ProductAgentConfig , fn : r .agentConfigUpdateCallback },
740+ {product : state .ProductOrchestratorK8sCRDs , fn : r .crdConfigUpdateCallback },
606741 }
742+ return r
607743}
0 commit comments