1
1
use crate :: {
2
+ Configuration , FINALIZER ,
2
3
errors:: { ControllerError , ExtKubeApiError } ,
3
4
object_sync_modifications:: ObjectSyncModifications ,
4
- resource_controller:: { ObjectSyncHandle , ResourceController } ,
5
- utils:: { delete_destinations, metric_name, remove_finalizer} ,
6
- Configuration , FINALIZER ,
5
+ resource_controller:: { IN_SYNC , ObjectSyncHandle , ResourceController } ,
6
+ utils:: {
7
+ ObjectSyncRef , SourceRef , delete_destinations, ensure_gvk, metric_name, remove_finalizer,
8
+ } ,
7
9
} ;
8
10
9
11
use futures:: StreamExt ;
10
12
use k8s_openapi:: api:: core:: v1:: Namespace ;
11
13
use kube:: {
12
- api:: { ApiResource , DynamicObject , GroupVersionKind , TypeMeta } ,
13
- discovery, Api , Client , ResourceExt ,
14
+ Api , Client , ResourceExt ,
15
+ api:: { ApiResource , DynamicObject , GroupVersionKind } ,
16
+ discovery,
14
17
} ;
15
18
use kube_runtime:: {
16
19
controller:: { Action , Controller } ,
@@ -19,9 +22,8 @@ use kube_runtime::{
19
22
} ;
20
23
use log:: { debug, info} ;
21
24
use opentelemetry:: {
22
- global,
25
+ KeyValue , global,
23
26
metrics:: { Counter , Histogram , Meter } ,
24
- KeyValue ,
25
27
} ;
26
28
use rustrial_k8s_object_syncer_apis:: { Condition , ObjectSync , SourceObject } ;
27
29
use std:: {
@@ -32,7 +34,7 @@ use std::{
32
34
} ;
33
35
use tokio:: {
34
36
sync:: RwLock ,
35
- time:: { sleep , Duration } ,
37
+ time:: { Duration , sleep } ,
36
38
} ;
37
39
38
40
const READY : & ' static str = "Ready" ;
@@ -42,18 +44,34 @@ pub(crate) const FAILURE: &'static str = "Failure";
42
44
const OBJECT_SYNC_CONTROLLER : & ' static str = "object_sync_controller" ;
43
45
44
46
/// Opaque handle for a [`ObjectSync`] object's registration with a [`ResourceController`].
47
+ /// Basically, this is the registration of one specific source object with the
48
+ /// [`ResourceController`] of its GVK.
45
49
///
50
+ #[ derive( Clone ) ]
46
51
struct ObjectSyncInstance {
47
52
/// Strong reference to [`ResourceController`] to make sure the corresponding
48
53
/// controller is alive as long as at least one [`ObjectSync`] is registered
49
54
/// with it.
50
55
_resource_controller : Arc < ResourceController > ,
51
56
/// If async_dropped will remove the [ObjectSync] from the corresponding [ResourceController].
52
- _resource_controller_handle : ObjectSyncHandle ,
57
+ /// Also used to track whether [ObjectSync] spec changed (e.g. whether .spec.source.name or
58
+ /// .spec.source.namespace changed).
59
+ resource_controller_handle : Arc < ObjectSyncHandle > ,
53
60
/// The GVK of the [ObjectSync] source object, used to track whether [ObjectSync] spec changed.
54
61
gvk : GroupVersionKind ,
55
62
}
56
63
64
+ impl ObjectSyncInstance {
65
+ /// Check if GVK or name/namespace of source object changed.
66
+ fn source_changed ( & self , new : & ObjectSync , gvk : & GroupVersionKind ) -> bool {
67
+ let old_src = & self . resource_controller_handle . src ;
68
+ let new_source_namespace = new. source_namespace ( ) ;
69
+ self . gvk != * gvk
70
+ || old_src. name != new. spec . source . name
71
+ || Some ( old_src. namespace . as_ref ( ) ) != new_source_namespace. as_deref ( )
72
+ }
73
+ }
74
+
57
75
/// The main controller which will spawn one [`ResourceController`] instance per
58
76
/// GVK (GroupVersionKind) combination.
59
77
pub ( crate ) struct ObjectSyncController {
@@ -63,15 +81,15 @@ pub(crate) struct ObjectSyncController {
63
81
/// Weak reference to [`ResourceController`] instances needed to register [ObjectSyncInstance] instances.
64
82
/// We use weak references to make sure that unused [`ResourceController`] instances are dropped
65
83
/// to make sure there is no unecessary load on the Kubernetes API servers and that there are
66
- /// no stale controllers for remove API resoures (e.g. deleted CustomResourceDefinitions).
84
+ /// no stale controllers for removed API resoures (e.g. deleted CustomResourceDefinitions).
67
85
resource_controllers : Arc < RwLock < HashMap < GroupVersionKind , Weak < ResourceController > > > > ,
68
86
69
87
/// Mapping of [ObjectSync] (namespace/name) to the corresponding [ObjectSyncInstance] object.
70
88
/// Note, we use the peristent ID `{namespasce}/{name}` instead of the object's UID as key to make
71
89
/// sure the system is eventual consistent in case the controller should ever miss any events
72
90
/// caused by replacement (delete/create) of the underlying object. Practically, it should see all
73
91
/// events, but let's be defensive it can't hurt.
74
- instances : Arc < RwLock < HashMap < String , ObjectSyncInstance > > > ,
92
+ instances : Arc < RwLock < HashMap < ObjectSyncRef , ObjectSyncInstance > > > ,
75
93
76
94
reconcile_object_sync_count : Counter < u64 > ,
77
95
reconcile_object_sync_duration : Histogram < u64 > ,
@@ -187,7 +205,7 @@ impl ObjectSyncController {
187
205
) ;
188
206
Some ( gvk)
189
207
// The opaque ObjectSyncInstance handle `instance` is dropped here and
190
- // its `Drop` implementation will make sure it is deregisterd from its
208
+ // its `Drop` implementation will make sure it is deregistered from its
191
209
// `ResourceController`.
192
210
} else {
193
211
warn ! (
@@ -226,7 +244,7 @@ impl ObjectSyncController {
226
244
event. id ( ) ,
227
245
ObjectSyncInstance {
228
246
_resource_controller : resource_controller,
229
- _resource_controller_handle : resource_controller_handle,
247
+ resource_controller_handle : Arc :: new ( resource_controller_handle) ,
230
248
gvk,
231
249
} ,
232
250
) ;
@@ -237,50 +255,74 @@ impl ObjectSyncController {
237
255
self . configuration . client . clone ( )
238
256
}
239
257
240
- async fn delete ( & self , event : & mut ObjectSyncModifications ) -> Result < ( ) , ControllerError > {
241
- // Remove from ResourceController
242
- self . remove ( event) . await ;
243
- // Delete all remaining destinations
244
- if let Some ( destinations) = event
245
- . status
246
- . as_ref ( )
247
- . map ( |v| v. destinations . as_ref ( ) )
248
- . flatten ( )
249
- {
250
- let remaining = delete_destinations ( self . client ( ) , destinations) . await ?;
251
- if !remaining. is_empty ( ) {
252
- let errors = remaining. len ( ) ;
253
- event. update_destinations ( remaining) ;
254
- event. replace_status ( self . client ( ) ) . await ?;
258
+ pub async fn delete_destinations (
259
+ client : Client ,
260
+ event : & mut ObjectSyncModifications ,
261
+ ) -> Result < ( ) , ControllerError > {
262
+ if let Some ( destinations) = event. status_destinations ( ) {
263
+ let dest_count = destinations. len ( ) ;
264
+ let remaining = delete_destinations ( client. clone ( ) , destinations) . await ?;
265
+ let remaining_count = remaining. len ( ) ;
266
+ let in_sync_condition = if remaining_count > 0 {
267
+ Condition :: new (
268
+ IN_SYNC ,
269
+ Some ( false ) ,
270
+ FAILURE ,
271
+ format ! (
272
+ "failed to remove {} out of {} destination objects" ,
273
+ remaining_count,
274
+ destinations. len( )
275
+ ) ,
276
+ )
277
+ } else {
278
+ Condition :: new (
279
+ IN_SYNC ,
280
+ Some ( true ) ,
281
+ SUCCESS ,
282
+ format ! (
283
+ "successfully removed all {} destination objects" ,
284
+ destinations. len( )
285
+ ) ,
286
+ )
287
+ } ;
288
+ event. update_condition ( in_sync_condition) ;
289
+ event. update_destinations ( remaining) ;
290
+ event. replace_status ( client) . await ?;
291
+ if remaining_count > 0 {
255
292
return Err ( ControllerError :: DestinationRemovalError ( format ! (
256
- "failed to remove {} destinations of {}" ,
257
- errors,
293
+ "failed to deleted {} out of {} destinations of {}" ,
294
+ remaining_count,
295
+ dest_count,
258
296
event. id( )
259
297
) ) ) ;
298
+ } else {
299
+ info ! (
300
+ "successfully deleted all {} destination objects of {}" ,
301
+ dest_count,
302
+ event. id( )
303
+ ) ;
260
304
}
261
305
}
262
- info ! (
263
- "successfully removed all destination objects of {}" ,
264
- event. id( )
265
- ) ;
306
+ Ok ( ( ) )
307
+ }
308
+
309
+ async fn delete ( & self , event : & mut ObjectSyncModifications ) -> Result < ( ) , ControllerError > {
310
+ // Remove from ResourceController
311
+ self . remove ( event) . await ;
312
+ // Delete all remaining destinations
313
+ Self :: delete_destinations ( self . client ( ) , event) . await ?;
266
314
// Remove finalizer from source object.
267
315
let gvk = self . get_gvk ( & event. spec . source ) . await ?;
268
316
let api_resource = ApiResource :: from_gvk ( & gvk) ;
269
- let namespace = event
270
- . spec
271
- . source
272
- . namespace
273
- . clone ( )
274
- . or ( event. namespace ( ) )
275
- . unwrap_or_else ( || "" . to_string ( ) ) ;
317
+ let namespace = event. source_namespace ( ) . unwrap_or_else ( || "" . to_string ( ) ) ;
276
318
let api: Api < DynamicObject > =
277
319
Api :: namespaced_with ( self . client ( ) , namespace. as_str ( ) , & api_resource) ;
278
- match api. get ( event. spec . source . name . as_str ( ) ) . await {
320
+ match api
321
+ . get ( event. source_name ( ) )
322
+ . await
323
+ . map ( |v| ensure_gvk ( v, & gvk) )
324
+ {
279
325
Ok ( mut source) => {
280
- source. types = source. types . or ( Some ( TypeMeta {
281
- kind : gvk. kind . clone ( ) ,
282
- api_version : gvk. api_version ( ) ,
283
- } ) ) ;
284
326
remove_finalizer ( api, & mut source, FINALIZER ) . await ?;
285
327
}
286
328
Err ( e) if e. is_not_found ( ) => ( ) ,
@@ -301,28 +343,32 @@ impl ObjectSyncController {
301
343
let current_gvk = self . get_gvk ( & event. spec . source ) . await ?;
302
344
// Obtain the configuration checksum and immediately release the read lock
303
345
// guard again.
304
- let gvk = {
346
+ let active = {
305
347
let instances = self . instances . read ( ) . await ;
306
- instances. get ( & event. id ( ) ) . map ( |v| v. gvk . clone ( ) )
348
+ instances. get ( & event. id ( ) ) . map ( |v| v. clone ( ) )
307
349
} ;
308
- let condition = if let Some ( gvk ) = gvk {
309
- if gvk != current_gvk {
310
- // GVK changed so remove from current ResourceController
350
+ let condition = if let Some ( active ) = active {
351
+ if active . source_changed ( event , & current_gvk) {
352
+ // GVK or source object (name or namespace) changed so remove from current ResourceController
311
353
self . remove ( event) . await ;
312
- // and add it again with its GVK
354
+ // Cleanup destinations as source changed.
355
+ Self :: delete_destinations ( self . client ( ) , event) . await ?;
356
+ // and add it again with new GVK and source object
313
357
self . add ( & event, current_gvk. clone ( ) ) . await ?;
314
358
Some ( Condition :: new (
315
359
READY ,
316
360
Some ( true ) ,
317
361
SUCCESS ,
318
362
format ! (
319
- "Successfully moved from ResourceController {}/{}/{} to {}/{}/{}" ,
320
- gvk. group,
321
- gvk. version,
322
- gvk. kind,
363
+ "Successfully moved from ResourceController {}/{}/{} to {}/{}/{} and updated source from {} to {} " ,
364
+ active . gvk. group,
365
+ active . gvk. version,
366
+ active . gvk. kind,
323
367
current_gvk. group,
324
368
current_gvk. version,
325
- current_gvk. kind
369
+ current_gvk. kind,
370
+ active. resource_controller_handle. src,
371
+ SourceRef :: from( & * event)
326
372
) ,
327
373
) )
328
374
} else {
@@ -336,8 +382,11 @@ impl ObjectSyncController {
336
382
Some ( true ) ,
337
383
SUCCESS ,
338
384
format ! (
339
- "Successfully registered with ResourceController {}/{}/{}" ,
340
- current_gvk. group, current_gvk. version, current_gvk. kind
385
+ "Successfully registered with ResourceController {}/{}/{} and source {}" ,
386
+ current_gvk. group,
387
+ current_gvk. version,
388
+ current_gvk. kind,
389
+ SourceRef :: from( & * event)
341
390
) ,
342
391
) )
343
392
} ;
0 commit comments