kube_runtime/controller/mod.rs
1//! Runs a user-supplied reconciler function on objects when they (or related objects) are updated
2
3use self::runner::Runner;
4#[allow(deprecated)] use crate::watcher::metadata_watcher;
5use crate::{
6 reflector::{
7 self, ObjectRef, reflector,
8 store::{Store, Writer},
9 },
10 scheduler::{ScheduleRequest, debounced_scheduler},
11 utils::{
12 Backoff, CancelableJoinHandle, KubeRuntimeStreamExt, StreamBackoff, WatchStreamExt, trystream_try_via,
13 },
14 watcher::{self, DefaultBackoff, watcher},
15};
16use educe::Educe;
17use futures::{
18 FutureExt, Stream, StreamExt, TryFuture, TryFutureExt, TryStream, TryStreamExt, channel,
19 future::{self, BoxFuture},
20 stream,
21};
22use kube_client::api::{Api, DynamicObject, Resource};
23use pin_project::pin_project;
24use serde::de::DeserializeOwned;
25use std::{
26 fmt::{Debug, Display},
27 hash::Hash,
28 sync::Arc,
29 task::{Poll, ready},
30 time::Duration,
31};
32use stream::BoxStream;
33use thiserror::Error;
34use tokio::{runtime::Handle, time::Instant};
35use tracing::{Instrument, info_span};
36
37mod future_hash_map;
38mod runner;
39
40/// The reasons the internal runner can fail
41pub type RunnerError = runner::Error<reflector::store::WriterDropped>;
42
43/// Errors returned by the applier and visible in a controller stream if inspecting it
44///
45/// WARNING: These errors do not terminate `Controller::run`, and are not passed to the `reconcile` fn
46/// as they exist primarily for diagnostics.
47///
48/// To inspect these errors, you can run a `for_each` on the run stream:
49///
50/// ```compile_fail
51/// Controller::new(api, watcher_config)
52/// .run(reconcile, error_policy, context)
53/// .for_each(|res| async move {
54/// match res {
55/// Ok(o) => info!("reconciled {:?}", o),
56/// /// Reconciler errors visible here:
57/// Err(e) => warn!("reconcile failed: {}", e),
58/// }
59/// })
60/// .await;
61/// ```
62#[derive(Debug, Error)]
63pub enum Error<ReconcilerErr: 'static, QueueErr: 'static> {
64 /// A scheduled reconcile for an object refers to an object that no longer exists
65 ///
66 /// This is usually not a problem and often expected with certain relations.
67 /// See <https://github.com/kube-rs/kube/issues/1167#issuecomment-1636773541>
68 /// for a more detailed explanation of how/why this happens.
69 #[error("tried to reconcile object {0} that was not found in local store")]
70 ObjectNotFound(Box<ObjectRef<DynamicObject>>),
71
72 /// User's reconcile fn failed for the object
73 #[error("reconciler for object {1} failed: {0}")]
74 ReconcilerFailed(#[source] ReconcilerErr, Box<ObjectRef<DynamicObject>>),
75
76 /// The queue stream contained an error
77 #[error("event queue error: {0}")]
78 QueueError(#[source] QueueErr),
79
80 /// The internal runner returned an error
81 #[error("runner error: {0}")]
82 RunnerError(#[source] RunnerError),
83}
84
85/// Results of the reconciliation attempt
86#[derive(Debug, Clone, Eq, PartialEq)]
87pub struct Action {
88 /// Whether (and when) to next trigger the reconciliation if no external watch triggers hit
89 ///
90 /// For example, use this to query external systems for updates, expire time-limited resources, or
91 /// (in your `error_policy`) retry after errors.
92 requeue_after: Option<Duration>,
93}
94
95impl Action {
96 /// Action to the reconciliation at this time even if no external watch triggers hit
97 ///
98 /// This is the best-practice action that ensures eventual consistency of your controller
99 /// even in the case of missed changes (which can happen).
100 ///
101 /// Watch events are not normally missed, so running this once per hour (`Default`) as a fallback is reasonable.
102 #[must_use]
103 pub const fn requeue(duration: Duration) -> Self {
104 Self {
105 requeue_after: Some(duration),
106 }
107 }
108
109 /// Do nothing until a change is detected
110 ///
111 /// This stops the controller periodically reconciling this object until a relevant watch event
112 /// was **detected**.
113 ///
114 /// **Warning**: If you have watch desyncs, it is possible to miss changes entirely.
115 /// It is therefore not recommended to disable requeuing this way, unless you have
116 /// frequent changes to the underlying object, or some other hook to retain eventual consistency.
117 #[must_use]
118 pub const fn await_change() -> Self {
119 Self { requeue_after: None }
120 }
121}
122
123/// Helper for building custom trigger filters, see the implementations of [`trigger_self`] and [`trigger_owners`] for some examples.
124pub fn trigger_with<T, K, I, S>(
125 stream: S,
126 mapper: impl Fn(T) -> I,
127) -> impl Stream<Item = Result<ReconcileRequest<K>, S::Error>>
128where
129 S: TryStream<Ok = T>,
130 I: IntoIterator,
131 I::Item: Into<ReconcileRequest<K>>,
132 K: Resource,
133{
134 stream
135 .map_ok(move |obj| stream::iter(mapper(obj).into_iter().map(Into::into).map(Ok)))
136 .try_flatten()
137}
138
139/// Enqueues the object itself for reconciliation
140pub fn trigger_self<K, S>(
141 stream: S,
142 dyntype: K::DynamicType,
143) -> impl Stream<Item = Result<ReconcileRequest<K>, S::Error>>
144where
145 S: TryStream<Ok = K>,
146 K: Resource,
147 K::DynamicType: Clone,
148{
149 trigger_with(stream, move |obj| {
150 Some(ReconcileRequest {
151 obj_ref: ObjectRef::from_obj_with(&obj, dyntype.clone()),
152 reason: ReconcileReason::ObjectUpdated,
153 })
154 })
155}
156
157/// Enqueues the object itself for reconciliation when the object is behind a
158/// shared pointer
159#[cfg(feature = "unstable-runtime-subscribe")]
160fn trigger_self_shared<K, S>(
161 stream: S,
162 dyntype: K::DynamicType,
163) -> impl Stream<Item = Result<ReconcileRequest<K>, S::Error>>
164where
165 // Input stream has item as some Arc'd Resource (via
166 // Controller::for_shared_stream)
167 S: TryStream<Ok = Arc<K>>,
168 K: Resource,
169 K::DynamicType: Clone,
170{
171 trigger_with(stream, move |obj| {
172 Some(ReconcileRequest {
173 obj_ref: ObjectRef::from_obj_with(obj.as_ref(), dyntype.clone()),
174 reason: ReconcileReason::ObjectUpdated,
175 })
176 })
177}
178
179/// Enqueues any mapper returned `K` types for reconciliation
180fn trigger_others<S, K, I>(
181 stream: S,
182 mapper: impl Fn(S::Ok) -> I + Sync + Send + 'static,
183 dyntype: <S::Ok as Resource>::DynamicType,
184) -> impl Stream<Item = Result<ReconcileRequest<K>, S::Error>>
185where
186 // Input stream has items as some Resource (via Controller::watches)
187 S: TryStream,
188 S::Ok: Resource,
189 <S::Ok as Resource>::DynamicType: Clone,
190 // Output stream is requests for the root type K
191 K: Resource,
192 K::DynamicType: Clone,
193 // but the mapper can produce many of them
194 I: 'static + IntoIterator<Item = ObjectRef<K>>,
195 I::IntoIter: Send,
196{
197 trigger_with(stream, move |obj| {
198 let watch_ref = ObjectRef::from_obj_with(&obj, dyntype.clone()).erase();
199 mapper(obj)
200 .into_iter()
201 .map(move |mapped_obj_ref| ReconcileRequest {
202 obj_ref: mapped_obj_ref,
203 reason: ReconcileReason::RelatedObjectUpdated {
204 obj_ref: Box::new(watch_ref.clone()),
205 },
206 })
207 })
208}
209
210/// Enqueues any mapper returned `Arc<K>` types for reconciliation
211#[cfg(feature = "unstable-runtime-subscribe")]
212fn trigger_others_shared<S, O, K, I>(
213 stream: S,
214 mapper: impl Fn(S::Ok) -> I + Sync + Send + 'static,
215 dyntype: O::DynamicType,
216) -> impl Stream<Item = Result<ReconcileRequest<K>, S::Error>>
217where
218 // Input is some shared resource (`Arc<O>`) obtained via `reflect`
219 S: TryStream<Ok = Arc<O>>,
220 O: Resource,
221 O::DynamicType: Clone,
222 // Output stream is requests for the root type K
223 K: Resource,
224 K::DynamicType: Clone,
225 // but the mapper can produce many of them
226 I: 'static + IntoIterator<Item = ObjectRef<K>>,
227 I::IntoIter: Send,
228{
229 trigger_with(stream, move |obj| {
230 let watch_ref = ObjectRef::from_obj_with(obj.as_ref(), dyntype.clone()).erase();
231 mapper(obj)
232 .into_iter()
233 .map(move |mapped_obj_ref| ReconcileRequest {
234 obj_ref: mapped_obj_ref,
235 reason: ReconcileReason::RelatedObjectUpdated {
236 obj_ref: Box::new(watch_ref.clone()),
237 },
238 })
239 })
240}
241
242/// Enqueues any owners of type `KOwner` for reconciliation
243pub fn trigger_owners<KOwner, S>(
244 stream: S,
245 owner_type: KOwner::DynamicType,
246 child_type: <S::Ok as Resource>::DynamicType,
247) -> impl Stream<Item = Result<ReconcileRequest<KOwner>, S::Error>>
248where
249 S: TryStream,
250 S::Ok: Resource,
251 <S::Ok as Resource>::DynamicType: Clone,
252 KOwner: Resource,
253 KOwner::DynamicType: Clone,
254{
255 let mapper = move |mut obj: S::Ok| {
256 // `obj` is owned here, so take the metadata instead of deep-cloning the whole
257 // ObjectMeta (labels/annotations/managedFields) just to read two fields.
258 let meta = std::mem::take(obj.meta_mut());
259 let ns = meta.namespace;
260 let owner_type = owner_type.clone();
261 meta.owner_references
262 .into_iter()
263 .flatten()
264 .filter_map(move |owner| ObjectRef::from_owner_ref(ns.as_deref(), &owner, owner_type.clone()))
265 };
266 trigger_others(stream, mapper, child_type)
267}
268
269// TODO: do we really need to deal with a trystream? can we simplify this at
270// all?
271/// Enqueues any owners of type `KOwner` for reconciliation based on a stream of
272/// owned `K` objects
273#[cfg(feature = "unstable-runtime-subscribe")]
274fn trigger_owners_shared<KOwner, S, K>(
275 stream: S,
276 owner_type: KOwner::DynamicType,
277 child_type: K::DynamicType,
278) -> impl Stream<Item = Result<ReconcileRequest<KOwner>, S::Error>>
279where
280 S: TryStream<Ok = Arc<K>>,
281 K: Resource,
282 K::DynamicType: Clone,
283 KOwner: Resource,
284 KOwner::DynamicType: Clone,
285{
286 let mapper = move |obj: S::Ok| {
287 let meta = obj.meta().clone();
288 let ns = meta.namespace;
289 let owner_type = owner_type.clone();
290 meta.owner_references
291 .into_iter()
292 .flatten()
293 .filter_map(move |owner| ObjectRef::from_owner_ref(ns.as_deref(), &owner, owner_type.clone()))
294 };
295 trigger_others_shared(stream, mapper, child_type)
296}
297
298/// A request to reconcile an object, annotated with why that request was made.
299///
300/// NOTE: The reason is ignored for comparison purposes. This means that, for example,
301/// an object can only occupy one scheduler slot, even if it has been scheduled for multiple reasons.
302/// In this case, only *the first* reason is stored.
303#[derive(Educe)]
304#[educe(
305 Debug(bound("K::DynamicType: Debug")),
306 Clone(bound("K::DynamicType: Clone")),
307 PartialEq(bound("K::DynamicType: PartialEq")),
308 Hash(bound("K::DynamicType: Hash"))
309)]
310pub struct ReconcileRequest<K: Resource> {
311 /// A reference to the object to be reconciled
312 pub obj_ref: ObjectRef<K>,
313 /// The reason for why reconciliation was requested
314 #[educe(PartialEq(ignore), Hash(ignore))]
315 pub reason: ReconcileReason,
316}
317
318impl<K: Resource> Eq for ReconcileRequest<K> where K::DynamicType: Eq {}
319
320impl<K: Resource> From<ObjectRef<K>> for ReconcileRequest<K> {
321 fn from(obj_ref: ObjectRef<K>) -> Self {
322 ReconcileRequest {
323 obj_ref,
324 reason: ReconcileReason::Unknown,
325 }
326 }
327}
328
329/// The reason a reconcile was requested
330///
331/// Note that this reason is deliberately hidden from the reconciler.
332/// See <https://kube.rs/controllers/reconciler/#reasons-for-reconciliation>.
333#[derive(Debug, Clone)]
334pub enum ReconcileReason {
335 /// A custom reconcile triggered via `reconcile_on`
336 Unknown,
337
338 /// The main object was updated.
339 ObjectUpdated,
340
341 /// A related object was updated through a mapper
342 ///
343 /// The related object traversed its relation up to the object kind you are reconciling.
344 /// Your object may not have changed, but you may need to update child objects.
345 RelatedObjectUpdated {
346 /// An object ref to the related object
347 obj_ref: Box<ObjectRef<DynamicObject>>,
348 },
349
350 /// The users `reconcile` scheduled a reconciliation via an `Action`
351 ReconcilerRequestedRetry,
352
353 /// The users `error_policy` scheduled a reconciliation via an `Action`
354 ErrorPolicyRequestedRetry,
355
356 /// A bulk reconcile was requested via `reconcile_all_on`
357 BulkReconcile,
358
359 /// A custom reconcile reason for custom integrations.
360 ///
361 /// Can be used when injecting elements into the queue stream directly.
362 Custom {
363 /// A user provided reason through a custom integration
364 reason: String,
365 },
366}
367
368impl Display for ReconcileReason {
369 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
370 match self {
371 ReconcileReason::Unknown => f.write_str("unknown"),
372 ReconcileReason::ObjectUpdated => f.write_str("object updated"),
373 ReconcileReason::RelatedObjectUpdated { obj_ref: object } => {
374 f.write_fmt(format_args!("related object updated: {object}"))
375 }
376 ReconcileReason::BulkReconcile => f.write_str("bulk reconcile requested"),
377 ReconcileReason::ReconcilerRequestedRetry => f.write_str("reconciler requested retry"),
378 ReconcileReason::ErrorPolicyRequestedRetry => f.write_str("error policy requested retry"),
379 ReconcileReason::Custom { reason } => f.write_str(reason),
380 }
381 }
382}
383
384const APPLIER_REQUEUE_BUF_SIZE: usize = 100;
385
386/// Apply a reconciler to an input stream, with a given retry policy
387///
388/// Takes a `store` parameter for the core objects, which should usually be updated by a [`reflector()`].
389///
390/// The `queue` indicates which objects should be reconciled. For the core objects this will usually be
391/// the [`reflector()`] (piped through [`trigger_self`]). If your core objects own any subobjects then you
392/// can also make them trigger reconciliations by [merging](`futures::stream::select`) the [`reflector()`]
393/// with a [`watcher()`] or [`reflector()`] for the subobject.
394///
395/// This is the "hard-mode" version of [`Controller`], which allows you some more customization
396/// (such as triggering from arbitrary [`Stream`]s), at the cost of being a bit more verbose.
397#[allow(clippy::needless_pass_by_value)]
398#[allow(clippy::type_complexity)]
399pub fn applier<K, QueueStream, ReconcilerFut, Ctx>(
400 mut reconciler: impl FnMut(Arc<K>, Arc<Ctx>) -> ReconcilerFut,
401 error_policy: impl Fn(Arc<K>, &ReconcilerFut::Error, Arc<Ctx>) -> Action,
402 context: Arc<Ctx>,
403 store: Store<K>,
404 queue: QueueStream,
405 config: Config,
406) -> impl Stream<Item = Result<(ObjectRef<K>, Action), Error<ReconcilerFut::Error, QueueStream::Error>>>
407where
408 K: Clone + Resource + 'static,
409 K::DynamicType: Debug + Eq + Hash + Clone + Unpin,
410 ReconcilerFut: TryFuture<Ok = Action> + Unpin,
411 ReconcilerFut::Error: std::error::Error + 'static,
412 QueueStream: TryStream,
413 QueueStream::Ok: Into<ReconcileRequest<K>>,
414 QueueStream::Error: std::error::Error + 'static,
415{
416 let (scheduler_shutdown_tx, scheduler_shutdown_rx) = channel::oneshot::channel();
417 let (scheduler_tx, scheduler_rx) =
418 channel::mpsc::channel::<ScheduleRequest<ReconcileRequest<K>>>(APPLIER_REQUEUE_BUF_SIZE);
419 let error_policy = Arc::new(error_policy);
420 let delay_store = store.clone();
421 // Create a stream of ObjectRefs that need to be reconciled
422 trystream_try_via(
423 // input: stream combining scheduled tasks and user specified inputs event
424 Box::pin(stream::select(
425 // 1. inputs from users queue stream
426 queue
427 .map_err(Error::QueueError)
428 .map_ok(|request| ScheduleRequest {
429 message: request.into(),
430 run_at: Instant::now(),
431 })
432 .on_complete(async move {
433 // On error: scheduler has already been shut down and there is nothing for us to do
434 let _ = scheduler_shutdown_tx.send(());
435 tracing::debug!("applier queue terminated, starting graceful shutdown")
436 }),
437 // 2. requests sent to scheduler_tx
438 scheduler_rx
439 .map(Ok)
440 .take_until(scheduler_shutdown_rx)
441 .on_complete(async { tracing::debug!("applier scheduler consumer terminated") }),
442 )),
443 // all the Oks from the select gets passed through the scheduler stream, and are then executed
444 move |s| {
445 Runner::new(
446 debounced_scheduler(s, config.debounce),
447 config.concurrency,
448 move |request| {
449 let request = request.clone();
450 match store.get(&request.obj_ref) {
451 Some(obj) => {
452 let scheduler_tx = scheduler_tx.clone();
453 let error_policy_ctx = context.clone();
454 let error_policy = error_policy.clone();
455 let reconciler_span = info_span!(
456 "reconciling object",
457 "object.ref" = %request.obj_ref,
458 object.reason = %request.reason
459 );
460 TryFutureExt::into_future(
461 reconciler_span.in_scope(|| reconciler(Arc::clone(&obj), context.clone())),
462 )
463 .then(move |res| {
464 let error_policy = error_policy;
465 RescheduleReconciliation::new(
466 res,
467 |err| error_policy(obj, err, error_policy_ctx),
468 request.obj_ref.clone(),
469 scheduler_tx,
470 )
471 // Reconciler errors are OK from the applier's PoV, we need to apply the error policy
472 // to them separately
473 .map(|res| Ok((request.obj_ref, res)))
474 })
475 .instrument(reconciler_span)
476 .left_future()
477 }
478 None => {
479 std::future::ready(Err(Error::ObjectNotFound(Box::new(request.obj_ref.erase()))))
480 .right_future()
481 }
482 }
483 },
484 )
485 .delay_tasks_until(async move {
486 tracing::debug!("applier runner held until store is ready");
487 let res = delay_store.wait_until_ready().await;
488 tracing::debug!("store is ready, starting runner");
489 res
490 })
491 .map(|runner_res| runner_res.unwrap_or_else(|err| Err(Error::RunnerError(err))))
492 .on_complete(async { tracing::debug!("applier runner terminated") })
493 },
494 )
495 .on_complete(async { tracing::debug!("applier runner-merge terminated") })
496 // finally, for each completed reconcile call:
497 .and_then(move |(obj_ref, reconciler_result)| async move {
498 match reconciler_result {
499 Ok(action) => Ok((obj_ref, action)),
500 Err(err) => Err(Error::ReconcilerFailed(err, Box::new(obj_ref.erase()))),
501 }
502 })
503 .on_complete(async { tracing::debug!("applier terminated") })
504}
505
506/// Internal helper [`Future`] that reschedules reconciliation of objects (if required), in the scheduled context of the reconciler
507///
508/// This could be an `async fn`, but isn't because we want it to be [`Unpin`]
509#[pin_project]
510#[must_use]
511struct RescheduleReconciliation<K: Resource, ReconcilerErr> {
512 reschedule_tx: channel::mpsc::Sender<ScheduleRequest<ReconcileRequest<K>>>,
513
514 reschedule_request: Option<ScheduleRequest<ReconcileRequest<K>>>,
515 result: Option<Result<Action, ReconcilerErr>>,
516}
517
518impl<K, ReconcilerErr> RescheduleReconciliation<K, ReconcilerErr>
519where
520 K: Resource,
521{
522 fn new(
523 result: Result<Action, ReconcilerErr>,
524 error_policy: impl FnOnce(&ReconcilerErr) -> Action,
525 obj_ref: ObjectRef<K>,
526 reschedule_tx: channel::mpsc::Sender<ScheduleRequest<ReconcileRequest<K>>>,
527 ) -> Self {
528 let reconciler_finished_at = Instant::now();
529
530 let (action, reschedule_reason) = result.as_ref().map_or_else(
531 |err| (error_policy(err), ReconcileReason::ErrorPolicyRequestedRetry),
532 |action| (action.clone(), ReconcileReason::ReconcilerRequestedRetry),
533 );
534
535 Self {
536 reschedule_tx,
537 reschedule_request: action.requeue_after.map(|requeue_after| ScheduleRequest {
538 message: ReconcileRequest {
539 obj_ref,
540 reason: reschedule_reason,
541 },
542 run_at: reconciler_finished_at
543 .checked_add(requeue_after)
544 .unwrap_or_else(crate::scheduler::max_schedule_time),
545 }),
546 result: Some(result),
547 }
548 }
549}
550
551impl<K, ReconcilerErr> Future for RescheduleReconciliation<K, ReconcilerErr>
552where
553 K: Resource,
554{
555 type Output = Result<Action, ReconcilerErr>;
556
557 fn poll(self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
558 let this = self.get_mut();
559
560 if this.reschedule_request.is_some() {
561 let rescheduler_ready = ready!(this.reschedule_tx.poll_ready(cx));
562 let reschedule_request = this
563 .reschedule_request
564 .take()
565 .expect("PostReconciler::reschedule_request was taken during processing");
566 // Failure to schedule item = in graceful shutdown mode, ignore
567 if let Ok(()) = rescheduler_ready {
568 let _ = this.reschedule_tx.start_send(reschedule_request);
569 }
570 }
571
572 Poll::Ready(
573 this.result
574 .take()
575 .expect("PostReconciler::result was already taken"),
576 )
577 }
578}
579
580/// Accumulates all options that can be used on a [`Controller`] invocation.
581#[derive(Clone, Debug, Default)]
582pub struct Config {
583 debounce: Duration,
584 concurrency: u16,
585}
586
587impl Config {
588 /// The debounce duration used to deduplicate reconciliation requests.
589 ///
590 /// When set to a non-zero duration, debouncing is enabled in the [`scheduler`](crate::scheduler())
591 /// resulting in __trailing edge debouncing__ of reconciler requests.
592 /// This option can help to reduce the amount of unnecessary reconciler calls
593 /// when using multiple controller relations, or during rapid phase transitions.
594 ///
595 /// ## Warning
596 /// This option delays (and keeps delaying) reconcile requests for objects while
597 /// the object is updated. It can **permanently hide** updates from your reconciler
598 /// if set too high on objects that are updated frequently (like nodes).
599 #[must_use]
600 pub fn debounce(mut self, debounce: Duration) -> Self {
601 self.debounce = debounce;
602 self
603 }
604
605 /// The number of concurrent reconciliations of that are allowed to run at an given moment.
606 ///
607 /// This can be adjusted to the controller's needs to increase
608 /// performance and/or make performance predictable. By default, its 0 meaning
609 /// the controller runs with unbounded concurrency.
610 ///
611 /// Note that despite concurrency, a controller never schedules concurrent reconciles
612 /// on the same object.
613 #[must_use]
614 pub fn concurrency(mut self, concurrency: u16) -> Self {
615 self.concurrency = concurrency;
616 self
617 }
618}
619
620/// Controller for a Resource `K`
621///
622/// A controller is an infinite stream of objects to be reconciled.
623///
624/// Once `run` and continuously awaited, it continuously calls out to user provided
625/// `reconcile` and `error_policy` callbacks whenever relevant changes are detected
626/// or if errors are seen from `reconcile`.
627///
628/// Reconciles are generally requested for all changes on your root objects.
629/// Changes to managed child resources will also trigger the reconciler for the
630/// managing object by traversing owner references (for `Controller::owns`),
631/// or traverse a custom mapping (for `Controller::watches`).
632///
633/// This mapping mechanism ultimately hides the reason for the reconciliation request,
634/// and forces you to write an idempotent reconciler.
635///
636/// General setup:
637/// ```no_run
638/// use kube::{Api, Client, CustomResource};
639/// use kube::runtime::{controller::{Controller, Action}, watcher};
640/// # use serde::{Deserialize, Serialize};
641/// # use tokio::time::Duration;
642/// use futures::StreamExt;
643/// use k8s_openapi::api::core::v1::ConfigMap;
644/// use schemars::JsonSchema;
645/// # use std::sync::Arc;
646/// use thiserror::Error;
647///
648/// #[derive(Debug, Error)]
649/// enum Error {}
650///
651/// /// A custom resource
652/// #[derive(CustomResource, Debug, Clone, Deserialize, Serialize, JsonSchema)]
653/// #[kube(group = "nullable.se", version = "v1", kind = "ConfigMapGenerator", namespaced)]
654/// struct ConfigMapGeneratorSpec {
655/// content: String,
656/// }
657///
658/// /// The reconciler that will be called when either object change
659/// async fn reconcile(g: Arc<ConfigMapGenerator>, _ctx: Arc<()>) -> Result<Action, Error> {
660/// // .. use api here to reconcile a child ConfigMap with ownerreferences
661/// // see configmapgen_controller example for full info
662/// Ok(Action::requeue(Duration::from_secs(300)))
663/// }
664/// /// an error handler that will be called when the reconciler fails with access to both the
665/// /// object that caused the failure and the actual error
666/// fn error_policy(obj: Arc<ConfigMapGenerator>, _error: &Error, _ctx: Arc<()>) -> Action {
667/// Action::requeue(Duration::from_secs(60))
668/// }
669///
670/// /// something to drive the controller
671///
672/// async fn wrapper() -> Result<(), Box<dyn std::error::Error>> {
673/// # let client: Client = todo!();
674/// let context = Arc::new(()); // bad empty context - put client in here
675/// let cmgs = Api::<ConfigMapGenerator>::all(client.clone());
676/// let cms = Api::<ConfigMap>::all(client.clone());
677/// Controller::new(cmgs, watcher::Config::default())
678/// .owns(cms, watcher::Config::default())
679/// .run(reconcile, error_policy, context)
680/// .for_each(|res| async move {
681/// match res {
682/// Ok(o) => println!("reconciled {:?}", o),
683/// Err(e) => println!("reconcile failed: {:?}", e),
684/// }
685/// })
686/// .await; // controller does nothing unless polled
687/// # Ok(())
688/// # }
689/// ```
690pub struct Controller<K>
691where
692 K: Clone + Resource + Debug + 'static,
693 K::DynamicType: Eq + Hash,
694{
695 // NB: Need to Unpin for stream::select_all
696 trigger_selector: stream::SelectAll<BoxStream<'static, Result<ReconcileRequest<K>, watcher::Error>>>,
697 trigger_backoff: Box<dyn Backoff + Send>,
698 /// [`run`](crate::Controller::run) starts a graceful shutdown when any of these [`Future`]s complete,
699 /// refusing to start any new reconciliations but letting any existing ones finish.
700 graceful_shutdown_selector: Vec<BoxFuture<'static, ()>>,
701 /// [`run`](crate::Controller::run) terminates immediately when any of these [`Future`]s complete,
702 /// requesting that all running reconciliations be aborted.
703 /// However, note that they *will* keep running until their next yield point (`.await`),
704 /// blocking [`tokio::runtime::Runtime`] destruction (unless you follow up by calling [`std::process::exit`] after `run`).
705 forceful_shutdown_selector: Vec<BoxFuture<'static, ()>>,
706 dyntype: K::DynamicType,
707 reader: Store<K>,
708 config: Config,
709}
710
711impl<K> Controller<K>
712where
713 K: Clone + Resource + DeserializeOwned + Debug + Send + Sync + 'static,
714 K::DynamicType: Eq + Hash + Clone,
715{
716 /// Create a Controller for a resource `K`
717 ///
718 /// Takes an [`Api`] object that determines how the `Controller` listens for changes to the `K`.
719 ///
720 /// The [`watcher::Config`] controls to the possible subset of objects of `K` that you want to manage
721 /// and receive reconcile events for.
722 /// For the full set of objects `K` in the given `Api` scope, you can use [`watcher::Config::default`].
723 #[must_use]
724 pub fn new(main_api: Api<K>, wc: watcher::Config) -> Self
725 where
726 K::DynamicType: Default,
727 {
728 Self::new_with(main_api, wc, Default::default())
729 }
730
731 /// Create a Controller for a resource `K`
732 ///
733 /// Takes an [`Api`] object that determines how the `Controller` listens for changes to the `K`.
734 ///
735 /// The [`watcher::Config`] lets you define a possible subset of objects of `K` that you want the [`Api`]
736 /// to watch - in the Api's configured scope - and receive reconcile events for.
737 /// For the full set of objects `K` in the given `Api` scope, you can use [`Config::default`].
738 ///
739 /// This variant constructor is for [`dynamic`] types found through discovery. Prefer [`Controller::new`] for static types.
740 ///
741 /// [`watcher::Config`]: crate::watcher::Config
742 /// [`Api`]: kube_client::Api
743 /// [`dynamic`]: kube_client::core::dynamic
744 /// [`Config::default`]: crate::watcher::Config::default
745 pub fn new_with(main_api: Api<K>, wc: watcher::Config, dyntype: K::DynamicType) -> Self {
746 let writer = Writer::<K>::new(dyntype.clone());
747 let reader = writer.as_reader();
748 let mut trigger_selector = stream::SelectAll::new();
749 let self_watcher = trigger_self(
750 reflector(writer, watcher(main_api, wc)).applied_objects(),
751 dyntype.clone(),
752 )
753 .boxed();
754 trigger_selector.push(self_watcher);
755 Self {
756 trigger_selector,
757 trigger_backoff: Box::<DefaultBackoff>::default(),
758 graceful_shutdown_selector: vec![
759 // Fallback future, ensuring that we never terminate if no additional futures are added to the selector
760 future::pending().boxed(),
761 ],
762 forceful_shutdown_selector: vec![
763 // Fallback future, ensuring that we never terminate if no additional futures are added to the selector
764 future::pending().boxed(),
765 ],
766 dyntype,
767 reader,
768 config: Default::default(),
769 }
770 }
771
772 /// Create a Controller for a resource `K` from a stream of `K` objects
773 ///
774 /// Same as [`Controller::new`], but instead of an `Api`, a stream of resources is used.
775 /// This allows for customized and pre-filtered watch streams to be used as a trigger,
776 /// as well as sharing input streams between multiple controllers.
777 ///
778 /// # Example:
779 ///
780 /// ```no_run
781 /// # use futures::StreamExt;
782 /// # use k8s_openapi::api::apps::v1::Deployment;
783 /// # use kube::runtime::controller::{Action, Controller};
784 /// # use kube::runtime::{predicates, watcher, reflector, WatchStreamExt};
785 /// # use kube::{Api, Client, Error, ResourceExt};
786 /// # use std::sync::Arc;
787 /// # async fn reconcile(_: Arc<Deployment>, _: Arc<()>) -> Result<Action, Error> { Ok(Action::await_change()) }
788 /// # fn error_policy(_: Arc<Deployment>, _: &kube::Error, _: Arc<()>) -> Action { Action::await_change() }
789 /// # async fn doc(client: kube::Client) {
790 /// let api: Api<Deployment> = Api::default_namespaced(client);
791 /// let (reader, writer) = reflector::store();
792 /// let deploys = watcher(api, watcher::Config::default())
793 /// .default_backoff()
794 /// .reflect(writer)
795 /// .applied_objects()
796 /// .predicate_filter(predicates::generation, Default::default());
797 ///
798 /// Controller::for_stream(deploys, reader)
799 /// .run(reconcile, error_policy, Arc::new(()))
800 /// .for_each(|_| std::future::ready(()))
801 /// .await;
802 /// # }
803 /// ```
804 ///
805 /// Prefer [`Controller::new`] if you do not need to share the stream, or do not need pre-filtering.
806 #[cfg(feature = "unstable-runtime-stream-control")]
807 pub fn for_stream(
808 trigger: impl Stream<Item = Result<K, watcher::Error>> + Send + 'static,
809 reader: Store<K>,
810 ) -> Self
811 where
812 K::DynamicType: Default,
813 {
814 Self::for_stream_with(trigger, reader, Default::default())
815 }
816
817 /// Create a Controller for a resource `K` from a stream of `K` objects
818 ///
819 /// Same as [`Controller::new`], but instead of an `Api`, a stream of resources is used.
820 /// This allows for customized and pre-filtered watch streams to be used as a trigger,
821 /// as well as sharing input streams between multiple controllers.
822 ///
823 /// Prefer [`Controller::new`] if you do not need to share the stream, or do not need pre-filtering.
824 ///
825 /// This variant constructor is for [`dynamic`] types found through discovery. Prefer [`Controller::for_stream`] for static types.
826 ///
827 /// [`dynamic`]: kube_client::core::dynamic
828 #[cfg(feature = "unstable-runtime-stream-control")]
829 pub fn for_stream_with(
830 trigger: impl Stream<Item = Result<K, watcher::Error>> + Send + 'static,
831 reader: Store<K>,
832 dyntype: K::DynamicType,
833 ) -> Self {
834 let mut trigger_selector = stream::SelectAll::new();
835 let self_watcher = trigger_self(trigger, dyntype.clone()).boxed();
836 trigger_selector.push(self_watcher);
837 Self {
838 trigger_selector,
839 trigger_backoff: Box::<DefaultBackoff>::default(),
840 graceful_shutdown_selector: vec![
841 // Fallback future, ensuring that we never terminate if no additional futures are added to the selector
842 future::pending().boxed(),
843 ],
844 forceful_shutdown_selector: vec![
845 // Fallback future, ensuring that we never terminate if no additional futures are added to the selector
846 future::pending().boxed(),
847 ],
848 dyntype,
849 reader,
850 config: Default::default(),
851 }
852 }
853
854 /// This is the same as [`Controller::for_stream`]. Instead of taking an
855 /// `Api` (e.g. [`Controller::new`]), a stream of resources is used. Shared
856 /// streams can be created out-of-band by subscribing on a store `Writer`.
857 /// Through this interface, multiple controllers can use the same root
858 /// (shared) input stream of resources to keep memory overheads smaller.
859 ///
860 /// Prefer [`Controller::new`] or [`Controller::for_stream`] if you do not
861 /// need to share the stream.
862 ///
863 /// ## Warning:
864 ///
865 /// You **must** ensure the root stream (i.e. stream created through a `reflector()`)
866 /// is driven to readiness independently of this controller to ensure the
867 /// watcher never deadlocks.
868 ///
869 /// # Example:
870 ///
871 /// ```no_run
872 /// # use futures::StreamExt;
873 /// # use k8s_openapi::api::apps::v1::Deployment;
874 /// # use kube::runtime::controller::{Action, Controller};
875 /// # use kube::runtime::{predicates, watcher, reflector, WatchStreamExt};
876 /// # use kube::{Api, Client, Error, ResourceExt};
877 /// # use std::sync::Arc;
878 /// # async fn reconcile(_: Arc<Deployment>, _: Arc<()>) -> Result<Action, Error> { Ok(Action::await_change()) }
879 /// # fn error_policy(_: Arc<Deployment>, _: &kube::Error, _: Arc<()>) -> Action { Action::await_change() }
880 /// # async fn doc(client: kube::Client) {
881 /// let api: Api<Deployment> = Api::default_namespaced(client);
882 /// let (reader, writer) = reflector::store_shared(128);
883 /// let subscriber = writer
884 /// .subscribe()
885 /// .expect("subscribers can only be created from shared stores");
886 /// let deploys = watcher(api, watcher::Config::default())
887 /// .default_backoff()
888 /// .reflect(writer)
889 /// .applied_objects()
890 /// .for_each(|ev| async move {
891 /// match ev {
892 /// Ok(obj) => tracing::info!("got obj {obj:?}"),
893 /// Err(error) => tracing::error!(%error, "received error")
894 /// }
895 /// });
896 ///
897 /// let controller = Controller::for_shared_stream(subscriber, reader)
898 /// .run(reconcile, error_policy, Arc::new(()))
899 /// .for_each(|ev| async move {
900 /// tracing::info!("reconciled {ev:?}")
901 /// });
902 ///
903 /// // Drive streams using a select statement
904 /// tokio::select! {
905 /// _ = deploys => {},
906 /// _ = controller => {},
907 /// }
908 /// # }
909 #[cfg(feature = "unstable-runtime-subscribe")]
910 pub fn for_shared_stream(trigger: impl Stream<Item = Arc<K>> + Send + 'static, reader: Store<K>) -> Self
911 where
912 K::DynamicType: Default,
913 {
914 Self::for_shared_stream_with(trigger, reader, Default::default())
915 }
916
917 /// This is the same as [`Controller::for_stream`]. Instead of taking an
918 /// `Api` (e.g. [`Controller::new`]), a stream of resources is used. Shared
919 /// streams can be created out-of-band by subscribing on a store `Writer`.
920 /// Through this interface, multiple controllers can use the same root
921 /// (shared) input stream of resources to keep memory overheads smaller.
922 ///
923 /// Prefer [`Controller::new`] or [`Controller::for_stream`] if you do not
924 /// need to share the stream.
925 ///
926 /// This variant constructor is used for [`dynamic`] types found through
927 /// discovery. Prefer [`Controller::for_shared_stream`] for static types (i.e.
928 /// known at compile time).
929 ///
930 /// [`dynamic`]: kube_client::core::dynamic
931 #[cfg(feature = "unstable-runtime-subscribe")]
932 pub fn for_shared_stream_with(
933 trigger: impl Stream<Item = Arc<K>> + Send + 'static,
934 reader: Store<K>,
935 dyntype: K::DynamicType,
936 ) -> Self {
937 let mut trigger_selector = stream::SelectAll::new();
938 let self_watcher = trigger_self_shared(trigger.map(Ok), dyntype.clone()).boxed();
939 trigger_selector.push(self_watcher);
940 Self {
941 trigger_selector,
942 trigger_backoff: Box::<DefaultBackoff>::default(),
943 graceful_shutdown_selector: vec![
944 // Fallback future, ensuring that we never terminate if no additional futures are added to the selector
945 future::pending().boxed(),
946 ],
947 forceful_shutdown_selector: vec![
948 // Fallback future, ensuring that we never terminate if no additional futures are added to the selector
949 future::pending().boxed(),
950 ],
951 dyntype,
952 reader,
953 config: Default::default(),
954 }
955 }
956
957 /// Specify the configuration for the controller's behavior.
958 #[must_use]
959 pub fn with_config(mut self, config: Config) -> Self {
960 self.config = config;
961 self
962 }
963
964 /// Specify the backoff policy for "trigger" watches
965 ///
966 /// This includes the core watch, as well as auxiliary watches introduced by [`Self::owns`] and [`Self::watches`].
967 ///
968 /// The [`default_backoff`](crate::watcher::default_backoff) follows client-go conventions,
969 /// but can be overridden by calling this method.
970 #[must_use]
971 pub fn trigger_backoff(mut self, backoff: impl Backoff + 'static) -> Self {
972 self.trigger_backoff = Box::new(backoff);
973 self
974 }
975
976 /// Retrieve a copy of the reader before starting the controller
977 pub fn store(&self) -> Store<K> {
978 self.reader.clone()
979 }
980
981 /// Specify `Child` objects which `K` owns and should be watched
982 ///
983 /// Takes an [`Api`] object that determines how the `Controller` listens for changes to the `Child`.
984 /// All owned `Child` objects **must** contain an [`OwnerReference`] pointing back to a `K`.
985 ///
986 /// The [`watcher::Config`] controls the subset of `Child` objects that you want the [`Api`]
987 /// to watch - in the Api's configured scope - and receive reconcile events for.
988 /// To watch the full set of `Child` objects in the given `Api` scope, you can use [`watcher::Config::default`].
989 ///
990 /// [`OwnerReference`]: k8s_openapi::apimachinery::pkg::apis::meta::v1::OwnerReference
991 #[must_use]
992 pub fn owns<Child: Clone + Resource<DynamicType = ()> + DeserializeOwned + Debug + Send + 'static>(
993 self,
994 api: Api<Child>,
995 wc: watcher::Config,
996 ) -> Self {
997 self.owns_with(api, (), wc)
998 }
999
1000 /// Specify `Child` objects which `K` owns and should be watched
1001 ///
1002 /// Same as [`Controller::owns`], but accepts a `DynamicType` so it can be used with dynamic resources.
1003 #[must_use]
1004 pub fn owns_with<Child: Clone + Resource + DeserializeOwned + Debug + Send + 'static>(
1005 mut self,
1006 api: Api<Child>,
1007 dyntype: Child::DynamicType,
1008 wc: watcher::Config,
1009 ) -> Self
1010 where
1011 Child::DynamicType: Debug + Eq + Hash + Clone,
1012 {
1013 // TODO: call owns_stream_with when it's stable
1014 #[allow(deprecated)]
1015 let child_watcher = trigger_owners(
1016 metadata_watcher(api, wc).touched_objects(),
1017 self.dyntype.clone(),
1018 dyntype,
1019 );
1020 self.trigger_selector.push(child_watcher.boxed());
1021 self
1022 }
1023
1024 /// Trigger the reconciliation process for a stream of `Child` objects of the owner `K`
1025 ///
1026 /// Same as [`Controller::owns`], but instead of an `Api`, a stream of resources is used.
1027 /// This allows for customized and pre-filtered watch streams to be used as a trigger,
1028 /// as well as sharing input streams between multiple controllers.
1029 ///
1030 /// Watcher streams passed in here should be filtered first through `touched_objects`.
1031 ///
1032 /// # Example:
1033 ///
1034 /// ```no_run
1035 /// # use futures::StreamExt;
1036 /// # use k8s_openapi::api::core::v1::ConfigMap;
1037 /// # use k8s_openapi::api::apps::v1::StatefulSet;
1038 /// # use kube::runtime::controller::Action;
1039 /// # use kube::runtime::{predicates, metadata_watcher, watcher, Controller, WatchStreamExt};
1040 /// # use kube::{Api, Client, Error, ResourceExt};
1041 /// # use std::sync::Arc;
1042 /// # type CustomResource = ConfigMap;
1043 /// # async fn reconcile(_: Arc<CustomResource>, _: Arc<()>) -> Result<Action, Error> { Ok(Action::await_change()) }
1044 /// # fn error_policy(_: Arc<CustomResource>, _: &kube::Error, _: Arc<()>) -> Action { Action::await_change() }
1045 /// # async fn doc(client: kube::Client) {
1046 /// let sts_stream = metadata_watcher(Api::<StatefulSet>::all(client.clone()), watcher::Config::default())
1047 /// .touched_objects()
1048 /// .predicate_filter(predicates::generation, Default::default());
1049 ///
1050 /// Controller::new(Api::<CustomResource>::all(client), watcher::Config::default())
1051 /// .owns_stream(sts_stream)
1052 /// .run(reconcile, error_policy, Arc::new(()))
1053 /// .for_each(|_| std::future::ready(()))
1054 /// .await;
1055 /// # }
1056 /// ```
1057 #[cfg(feature = "unstable-runtime-stream-control")]
1058 #[must_use]
1059 pub fn owns_stream<Child: Resource<DynamicType = ()> + Send + 'static>(
1060 self,
1061 trigger: impl Stream<Item = Result<Child, watcher::Error>> + Send + 'static,
1062 ) -> Self {
1063 self.owns_stream_with(trigger, ())
1064 }
1065
1066 /// Trigger the reconciliation process for a stream of `Child` objects of the owner `K`
1067 ///
1068 /// Same as [`Controller::owns`], but instead of an `Api`, a stream of resources is used.
1069 /// This allows for customized and pre-filtered watch streams to be used as a trigger,
1070 /// as well as sharing input streams between multiple controllers.
1071 ///
1072 /// Same as [`Controller::owns_stream`], but accepts a `DynamicType` so it can be used with dynamic resources.
1073 #[cfg(feature = "unstable-runtime-stream-control")]
1074 #[must_use]
1075 pub fn owns_stream_with<Child: Resource + Send + 'static>(
1076 mut self,
1077 trigger: impl Stream<Item = Result<Child, watcher::Error>> + Send + 'static,
1078 dyntype: Child::DynamicType,
1079 ) -> Self
1080 where
1081 Child::DynamicType: Debug + Eq + Hash + Clone,
1082 {
1083 let child_watcher = trigger_owners(trigger, self.dyntype.clone(), dyntype);
1084 self.trigger_selector.push(child_watcher.boxed());
1085 self
1086 }
1087
1088 /// This is the same as [`Controller::for_stream`]. Instead of taking an
1089 /// `Api` (e.g. [`Controller::new`]), a stream of resources is used. Shared
1090 /// streams can be created out-of-band by subscribing on a store `Writer`.
1091 /// Through this interface, multiple controllers can use the same root
1092 /// (shared) input stream of resources to keep memory overheads smaller.
1093 ///
1094 /// Prefer [`Controller::new`] or [`Controller::for_stream`] if you do not
1095 /// need to share the stream.
1096 ///
1097 /// ## Warning:
1098 ///
1099 /// You **must** ensure the root stream (i.e. stream created through a `reflector()`)
1100 /// is driven to readiness independently of this controller to ensure the
1101 /// watcher never deadlocks.
1102 ///
1103 ///
1104 /// Trigger the reconciliation process for a shared stream of `Child`
1105 /// objects of the owner `K`
1106 ///
1107 /// Conceptually the same as [`Controller::owns`], but a stream is used
1108 /// instead of an `Api`. This interface behaves similarly to its non-shared
1109 /// counterpart [`Controller::owns_stream`].
1110 ///
1111 /// # Example:
1112 ///
1113 /// ```no_run
1114 /// # use futures::StreamExt;
1115 /// # use k8s_openapi::api::{apps::v1::Deployment, core::v1::Pod};
1116 /// # use kube::runtime::controller::{Action, Controller};
1117 /// # use kube::runtime::{predicates, watcher, reflector, WatchStreamExt};
1118 /// # use kube::{Api, Client, Error, ResourceExt};
1119 /// # use std::sync::Arc;
1120 /// # async fn reconcile(_: Arc<Deployment>, _: Arc<()>) -> Result<Action, Error> { Ok(Action::await_change()) }
1121 /// # fn error_policy(_: Arc<Deployment>, _: &kube::Error, _: Arc<()>) -> Action { Action::await_change() }
1122 /// # async fn doc(client: kube::Client) {
1123 /// let deploys: Api<Deployment> = Api::default_namespaced(client.clone());
1124 /// let pod_api: Api<Pod> = Api::default_namespaced(client);
1125 ///
1126 /// let (reader, writer) = reflector::store_shared(128);
1127 /// let subscriber = writer
1128 /// .subscribe()
1129 /// .expect("subscribers can only be created from shared stores");
1130 /// let pods = watcher(pod_api, watcher::Config::default())
1131 /// .default_backoff()
1132 /// .reflect(writer)
1133 /// .applied_objects()
1134 /// .for_each(|ev| async move {
1135 /// match ev {
1136 /// Ok(obj) => tracing::info!("got obj {obj:?}"),
1137 /// Err(error) => tracing::error!(%error, "received error")
1138 /// }
1139 /// });
1140 ///
1141 /// let controller = Controller::new(deploys, Default::default())
1142 /// .owns_shared_stream(subscriber)
1143 /// .run(reconcile, error_policy, Arc::new(()))
1144 /// .for_each(|ev| async move {
1145 /// tracing::info!("reconciled {ev:?}")
1146 /// });
1147 ///
1148 /// // Drive streams using a select statement
1149 /// tokio::select! {
1150 /// _ = pods => {},
1151 /// _ = controller => {},
1152 /// }
1153 /// # }
1154 #[cfg(feature = "unstable-runtime-subscribe")]
1155 #[must_use]
1156 pub fn owns_shared_stream<Child: Resource<DynamicType = ()> + Send + 'static>(
1157 self,
1158 trigger: impl Stream<Item = Arc<Child>> + Send + 'static,
1159 ) -> Self {
1160 self.owns_shared_stream_with(trigger, ())
1161 }
1162
1163 /// Trigger the reconciliation process for a shared stream of `Child` objects of the owner `K`
1164 ///
1165 /// Same as [`Controller::owns`], but instead of an `Api`, a shared stream of resources is used.
1166 /// The source stream can be shared between multiple controllers, optimising
1167 /// resource usage.
1168 ///
1169 /// Same as [`Controller::owns_shared_stream`], but accepts a `DynamicType` so it can be used with dynamic resources.
1170 #[cfg(feature = "unstable-runtime-subscribe")]
1171 #[must_use]
1172 pub fn owns_shared_stream_with<Child: Resource<DynamicType = ()> + Send + 'static>(
1173 mut self,
1174 trigger: impl Stream<Item = Arc<Child>> + Send + 'static,
1175 dyntype: Child::DynamicType,
1176 ) -> Self
1177 where
1178 Child::DynamicType: Debug + Eq + Hash + Clone,
1179 {
1180 let child_watcher = trigger_owners_shared(trigger.map(Ok), self.dyntype.clone(), dyntype);
1181 self.trigger_selector.push(child_watcher.boxed());
1182 self
1183 }
1184
1185 /// Specify `Watched` object which `K` has a custom relation to and should be watched
1186 ///
1187 /// To define the `Watched` relation with `K`, you **must** define a custom relation mapper, which,
1188 /// when given a `Watched` object, returns an option or iterator of relevant `ObjectRef<K>` to reconcile.
1189 ///
1190 /// If the relation `K` has to `Watched` is that `K` owns `Watched`, consider using [`Controller::owns`].
1191 ///
1192 /// Takes an [`Api`] object that determines how the `Controller` listens for changes to the `Watched`.
1193 ///
1194 /// The [`watcher::Config`] controls the subset of `Watched` objects that you want the [`Api`]
1195 /// to watch - in the Api's configured scope - and run through the custom mapper.
1196 /// To watch the full set of `Watched` objects in given the `Api` scope, you can use [`watcher::Config::default`].
1197 ///
1198 /// # Example
1199 ///
1200 /// Tracking cross cluster references using the [Operator-SDK] annotations.
1201 ///
1202 /// ```
1203 /// # use kube::runtime::{Controller, controller::Action, reflector::ObjectRef, watcher};
1204 /// # use kube::{Api, ResourceExt};
1205 /// # use k8s_openapi::api::core::v1::{ConfigMap, Namespace};
1206 /// # use futures::StreamExt;
1207 /// # use std::sync::Arc;
1208 /// # type WatchedResource = Namespace;
1209 /// # struct Context;
1210 /// # async fn reconcile(_: Arc<ConfigMap>, _: Arc<Context>) -> Result<Action, kube::Error> {
1211 /// # Ok(Action::await_change())
1212 /// # };
1213 /// # fn error_policy(_: Arc<ConfigMap>, _: &kube::Error, _: Arc<Context>) -> Action {
1214 /// # Action::await_change()
1215 /// # }
1216 /// # async fn doc(client: kube::Client) -> Result<(), Box<dyn std::error::Error>> {
1217 /// # let memcached = Api::<ConfigMap>::all(client.clone());
1218 /// # let context = Arc::new(Context);
1219 /// Controller::new(memcached, watcher::Config::default())
1220 /// .watches(
1221 /// Api::<WatchedResource>::all(client.clone()),
1222 /// watcher::Config::default(),
1223 /// |ar| {
1224 /// let prt = ar
1225 /// .annotations()
1226 /// .get("operator-sdk/primary-resource-type")
1227 /// .map(String::as_str);
1228 ///
1229 /// if prt != Some("Memcached.cache.example.com") {
1230 /// return None;
1231 /// }
1232 ///
1233 /// let (namespace, name) = ar
1234 /// .annotations()
1235 /// .get("operator-sdk/primary-resource")?
1236 /// .split_once('/')?;
1237 ///
1238 /// Some(ObjectRef::new(name).within(namespace))
1239 /// }
1240 /// )
1241 /// .run(reconcile, error_policy, context)
1242 /// .for_each(|_| futures::future::ready(()))
1243 /// .await;
1244 /// # Ok(())
1245 /// # }
1246 /// ```
1247 ///
1248 /// [Operator-SDK]: https://sdk.operatorframework.io/docs/building-operators/ansible/reference/retroactively-owned-resources/
1249 #[must_use]
1250 pub fn watches<Other, I>(
1251 self,
1252 api: Api<Other>,
1253 wc: watcher::Config,
1254 mapper: impl Fn(Other) -> I + Sync + Send + 'static,
1255 ) -> Self
1256 where
1257 Other: Clone + Resource + DeserializeOwned + Debug + Send + 'static,
1258 Other::DynamicType: Default + Debug + Clone + Eq + Hash,
1259 I: 'static + IntoIterator<Item = ObjectRef<K>>,
1260 I::IntoIter: Send,
1261 {
1262 self.watches_with(api, Default::default(), wc, mapper)
1263 }
1264
1265 /// Specify `Watched` object which `K` has a custom relation to and should be watched
1266 ///
1267 /// Same as [`Controller::watches`], but accepts a `DynamicType` so it can be used with dynamic resources.
1268 #[must_use]
1269 pub fn watches_with<Other, I>(
1270 mut self,
1271 api: Api<Other>,
1272 dyntype: Other::DynamicType,
1273 wc: watcher::Config,
1274 mapper: impl Fn(Other) -> I + Sync + Send + 'static,
1275 ) -> Self
1276 where
1277 Other: Clone + Resource + DeserializeOwned + Debug + Send + 'static,
1278 I: 'static + IntoIterator<Item = ObjectRef<K>>,
1279 I::IntoIter: Send,
1280 Other::DynamicType: Debug + Clone + Eq + Hash,
1281 {
1282 let other_watcher = trigger_others(watcher(api, wc).touched_objects(), mapper, dyntype);
1283 self.trigger_selector.push(other_watcher.boxed());
1284 self
1285 }
1286
1287 /// Trigger the reconciliation process for a stream of `Other` objects related to a `K`
1288 ///
1289 /// Same as [`Controller::watches`], but instead of an `Api`, a stream of resources is used.
1290 /// This allows for customized and pre-filtered watch streams to be used as a trigger,
1291 /// as well as sharing input streams between multiple controllers.
1292 ///
1293 /// Watcher streams passed in here should be filtered first through `touched_objects`.
1294 ///
1295 /// # Example:
1296 ///
1297 /// ```no_run
1298 /// # use futures::StreamExt;
1299 /// # use k8s_openapi::api::core::v1::ConfigMap;
1300 /// # use k8s_openapi::api::apps::v1::DaemonSet;
1301 /// # use kube::runtime::controller::Action;
1302 /// # use kube::runtime::{predicates, reflector::ObjectRef, watcher, Controller, WatchStreamExt};
1303 /// # use kube::{Api, Client, Error, ResourceExt};
1304 /// # use std::sync::Arc;
1305 /// # type CustomResource = ConfigMap;
1306 /// # async fn reconcile(_: Arc<CustomResource>, _: Arc<()>) -> Result<Action, Error> { Ok(Action::await_change()) }
1307 /// # fn error_policy(_: Arc<CustomResource>, _: &kube::Error, _: Arc<()>) -> Action { Action::await_change() }
1308 /// fn mapper(_: DaemonSet) -> Option<ObjectRef<CustomResource>> { todo!() }
1309 /// # async fn doc(client: kube::Client) {
1310 /// let api: Api<DaemonSet> = Api::all(client.clone());
1311 /// let cr: Api<CustomResource> = Api::all(client.clone());
1312 /// let daemons = watcher(api, watcher::Config::default())
1313 /// .touched_objects()
1314 /// .predicate_filter(predicates::generation, Default::default());
1315 ///
1316 /// Controller::new(cr, watcher::Config::default())
1317 /// .watches_stream(daemons, mapper)
1318 /// .run(reconcile, error_policy, Arc::new(()))
1319 /// .for_each(|_| std::future::ready(()))
1320 /// .await;
1321 /// # }
1322 /// ```
1323 #[cfg(feature = "unstable-runtime-stream-control")]
1324 #[must_use]
1325 pub fn watches_stream<Other, I>(
1326 self,
1327 trigger: impl Stream<Item = Result<Other, watcher::Error>> + Send + 'static,
1328 mapper: impl Fn(Other) -> I + Sync + Send + 'static,
1329 ) -> Self
1330 where
1331 Other: Clone + Resource + DeserializeOwned + Debug + Send + 'static,
1332 Other::DynamicType: Default + Debug + Clone,
1333 I: 'static + IntoIterator<Item = ObjectRef<K>>,
1334 I::IntoIter: Send,
1335 {
1336 self.watches_stream_with(trigger, mapper, Default::default())
1337 }
1338
1339 /// Trigger the reconciliation process for a stream of `Other` objects related to a `K`
1340 ///
1341 /// Same as [`Controller::watches`], but instead of an `Api`, a stream of resources is used.
1342 /// This allows for customized and pre-filtered watch streams to be used as a trigger,
1343 /// as well as sharing input streams between multiple controllers.
1344 ///
1345 /// Same as [`Controller::watches_stream`], but accepts a `DynamicType` so it can be used with dynamic resources.
1346 #[cfg(feature = "unstable-runtime-stream-control")]
1347 #[must_use]
1348 pub fn watches_stream_with<Other, I>(
1349 mut self,
1350 trigger: impl Stream<Item = Result<Other, watcher::Error>> + Send + 'static,
1351 mapper: impl Fn(Other) -> I + Sync + Send + 'static,
1352 dyntype: Other::DynamicType,
1353 ) -> Self
1354 where
1355 Other: Clone + Resource + DeserializeOwned + Debug + Send + 'static,
1356 Other::DynamicType: Debug + Clone,
1357 I: 'static + IntoIterator<Item = ObjectRef<K>>,
1358 I::IntoIter: Send,
1359 {
1360 let other_watcher = trigger_others(trigger, mapper, dyntype);
1361 self.trigger_selector.push(other_watcher.boxed());
1362 self
1363 }
1364
1365 /// Trigger the reconciliation process for a shared stream of `Other`
1366 /// objects related to a `K`
1367 ///
1368 /// Same as [`Controller::watches`], but instead of an `Api`, a shared
1369 /// stream of resources is used. This allows for sharing input streams
1370 /// between multiple controllers.
1371 ///
1372 /// Watcher streams passed in here should be filtered first through `touched_objects`.
1373 ///
1374 /// # Example:
1375 ///
1376 /// ```no_run
1377 /// # use futures::StreamExt;
1378 /// # use k8s_openapi::api::core::v1::ConfigMap;
1379 /// # use k8s_openapi::api::apps::v1::DaemonSet;
1380 /// # use kube::runtime::controller::Action;
1381 /// # use kube::runtime::{predicates, reflector::ObjectRef, watcher, Controller, WatchStreamExt};
1382 /// # use kube::{Api, Client, Error, ResourceExt};
1383 /// # use std::sync::Arc;
1384 /// # type CustomResource = ConfigMap;
1385 /// # async fn reconcile(_: Arc<CustomResource>, _: Arc<()>) -> Result<Action, Error> { Ok(Action::await_change()) }
1386 /// # fn error_policy(_: Arc<CustomResource>, _: &kube::Error, _: Arc<()>) -> Action { Action::await_change() }
1387 /// fn mapper(_: Arc<DaemonSet>) -> Option<ObjectRef<CustomResource>> { todo!() }
1388 /// # async fn doc(client: kube::Client) {
1389 /// let api: Api<DaemonSet> = Api::all(client.clone());
1390 /// let cr: Api<CustomResource> = Api::all(client.clone());
1391 /// let (reader, writer) = kube_runtime::reflector::store_shared(128);
1392 /// let subscriber = writer
1393 /// .subscribe()
1394 /// .expect("subscribers can only be created from shared stores");
1395 /// let daemons = watcher(api, watcher::Config::default())
1396 /// .reflect(writer)
1397 /// .touched_objects()
1398 /// .for_each(|ev| async move {
1399 /// match ev {
1400 /// Ok(obj) => {},
1401 /// Err(error) => tracing::error!(%error, "received err")
1402 /// }
1403 /// });
1404 ///
1405 /// let controller = Controller::new(cr, watcher::Config::default())
1406 /// .watches_shared_stream(subscriber, mapper)
1407 /// .run(reconcile, error_policy, Arc::new(()))
1408 /// .for_each(|_| std::future::ready(()));
1409 ///
1410 /// // Drive streams using a select statement
1411 /// tokio::select! {
1412 /// _ = daemons => {},
1413 /// _ = controller => {},
1414 /// }
1415 /// # }
1416 /// ```
1417 #[cfg(feature = "unstable-runtime-subscribe")]
1418 #[must_use]
1419 pub fn watches_shared_stream<Other, I>(
1420 self,
1421 trigger: impl Stream<Item = Arc<Other>> + Send + 'static,
1422 mapper: impl Fn(Arc<Other>) -> I + Sync + Send + 'static,
1423 ) -> Self
1424 where
1425 Other: Clone + Resource + DeserializeOwned + Debug + Send + 'static,
1426 Other::DynamicType: Default + Debug + Clone,
1427 I: 'static + IntoIterator<Item = ObjectRef<K>>,
1428 I::IntoIter: Send,
1429 {
1430 self.watches_shared_stream_with(trigger, mapper, Default::default())
1431 }
1432
1433 /// Trigger the reconciliation process for a shared stream of `Other` objects related to a `K`
1434 ///
1435 /// Same as [`Controller::watches`], but instead of an `Api`, a shared
1436 /// stream of resources is used. This allows for sharing of streams between
1437 /// multiple controllers.
1438 ///
1439 /// Same as [`Controller::watches_shared_stream`], but accepts a `DynamicType` so it can be used with dynamic resources.
1440 #[cfg(feature = "unstable-runtime-subscribe")]
1441 #[must_use]
1442 pub fn watches_shared_stream_with<Other, I>(
1443 mut self,
1444 trigger: impl Stream<Item = Arc<Other>> + Send + 'static,
1445 mapper: impl Fn(Arc<Other>) -> I + Sync + Send + 'static,
1446 dyntype: Other::DynamicType,
1447 ) -> Self
1448 where
1449 Other: Clone + Resource + DeserializeOwned + Debug + Send + 'static,
1450 Other::DynamicType: Debug + Clone,
1451 I: 'static + IntoIterator<Item = ObjectRef<K>>,
1452 I::IntoIter: Send,
1453 {
1454 let other_watcher = trigger_others_shared(trigger.map(Ok), mapper, dyntype);
1455 self.trigger_selector.push(other_watcher.boxed());
1456 self
1457 }
1458
1459 /// Trigger a reconciliation for all managed objects whenever `trigger` emits a value
1460 ///
1461 /// For example, this can be used to reconcile all objects whenever the controller's configuration changes.
1462 ///
1463 /// To reconcile all objects when a new line is entered:
1464 ///
1465 /// ```
1466 /// # async {
1467 /// use futures::stream::StreamExt;
1468 /// use k8s_openapi::api::core::v1::ConfigMap;
1469 /// use kube::{
1470 /// Client,
1471 /// api::{Api, ResourceExt},
1472 /// runtime::{
1473 /// controller::{Controller, Action},
1474 /// watcher,
1475 /// },
1476 /// };
1477 /// use std::{convert::Infallible, io::BufRead, sync::Arc};
1478 /// let (mut reload_tx, reload_rx) = futures::channel::mpsc::channel(0);
1479 /// // Using a regular background thread since tokio::io::stdin() doesn't allow aborting reads,
1480 /// // and its worker prevents the Tokio runtime from shutting down.
1481 /// std::thread::spawn(move || {
1482 /// for _ in std::io::BufReader::new(std::io::stdin()).lines() {
1483 /// let _ = reload_tx.try_send(());
1484 /// }
1485 /// });
1486 /// Controller::new(
1487 /// Api::<ConfigMap>::all(Client::try_default().await.unwrap()),
1488 /// watcher::Config::default(),
1489 /// )
1490 /// .reconcile_all_on(reload_rx.map(|_| ()))
1491 /// .run(
1492 /// |o, _| async move {
1493 /// println!("Reconciling {}", o.name_any());
1494 /// Ok(Action::await_change())
1495 /// },
1496 /// |_object: Arc<ConfigMap>, err: &Infallible, _| Err(err).unwrap(),
1497 /// Arc::new(()),
1498 /// );
1499 /// # };
1500 /// ```
1501 ///
1502 /// This can be called multiple times, in which case they are additive; reconciles are scheduled whenever *any* [`Stream`] emits a new item.
1503 ///
1504 /// If a [`Stream`] is terminated (by emitting [`None`]) then the [`Controller`] keeps running, but the [`Stream`] stops being polled.
1505 #[must_use]
1506 pub fn reconcile_all_on(mut self, trigger: impl Stream<Item = ()> + Send + 'static) -> Self {
1507 let store = self.store();
1508 let dyntype = self.dyntype.clone();
1509 self.trigger_selector.push(
1510 trigger
1511 .flat_map(move |()| {
1512 let dyntype = dyntype.clone();
1513 stream::iter(store.state().into_iter().map(move |obj| {
1514 Ok(ReconcileRequest {
1515 obj_ref: ObjectRef::from_obj_with(&*obj, dyntype.clone()),
1516 reason: ReconcileReason::BulkReconcile,
1517 })
1518 }))
1519 })
1520 .boxed(),
1521 );
1522 self
1523 }
1524
1525 /// Trigger the reconciliation process for a managed object `ObjectRef<K>` whenever `trigger` emits a value
1526 ///
1527 /// This can be used to inject reconciliations for specific objects from an external resource.
1528 ///
1529 /// # Example:
1530 ///
1531 /// ```no_run
1532 /// # async {
1533 /// # use futures::{StreamExt, Stream, stream, TryStreamExt};
1534 /// # use k8s_openapi::api::core::v1::{ConfigMap};
1535 /// # use kube::api::Api;
1536 /// # use kube::runtime::controller::Action;
1537 /// # use kube::runtime::reflector::{ObjectRef, Store};
1538 /// # use kube::runtime::{reflector, watcher, Controller, WatchStreamExt};
1539 /// # use kube::runtime::watcher::Config;
1540 /// # use kube::{Client, Error, ResourceExt};
1541 /// # use std::future;
1542 /// # use std::sync::Arc;
1543 /// #
1544 /// # let client: Client = todo!();
1545 /// # async fn reconcile(_: Arc<ConfigMap>, _: Arc<()>) -> Result<Action, Error> { Ok(Action::await_change()) }
1546 /// # fn error_policy(_: Arc<ConfigMap>, _: &kube::Error, _: Arc<()>) -> Action { Action::await_change() }
1547 /// # fn watch_external_objects() -> impl Stream<Item = ExternalObject> { stream::iter(vec![]) }
1548 /// # let ns = "controller-ns".to_string();
1549 /// struct ExternalObject {
1550 /// name: String,
1551 /// }
1552 /// let external_stream = watch_external_objects().map(|ext| {
1553 /// ObjectRef::new(&format!("{}-cm", ext.name)).within(&ns)
1554 /// });
1555 ///
1556 /// Controller::new(Api::<ConfigMap>::namespaced(client, &ns), Config::default())
1557 /// .reconcile_on(external_stream)
1558 /// .run(reconcile, error_policy, Arc::new(()))
1559 /// .for_each(|_| future::ready(()))
1560 /// .await;
1561 /// # };
1562 /// ```
1563 #[cfg(feature = "unstable-runtime-reconcile-on")]
1564 #[must_use]
1565 pub fn reconcile_on(mut self, trigger: impl Stream<Item = ObjectRef<K>> + Send + 'static) -> Self {
1566 self.trigger_selector.push(
1567 trigger
1568 .map(move |obj| {
1569 Ok(ReconcileRequest {
1570 obj_ref: obj,
1571 reason: ReconcileReason::Unknown,
1572 })
1573 })
1574 .boxed(),
1575 );
1576 self
1577 }
1578
1579 /// Start a graceful shutdown when `trigger` resolves. Once a graceful shutdown has been initiated:
1580 ///
1581 /// - No new reconciliations are started from the scheduler
1582 /// - The underlying Kubernetes watch is terminated
1583 /// - All running reconciliations are allowed to finish
1584 /// - [`Controller::run`]'s [`Stream`] terminates once all running reconciliations are done.
1585 ///
1586 /// For example, to stop the reconciler whenever the user presses Ctrl+C:
1587 ///
1588 /// ```rust
1589 /// # async {
1590 /// use futures::future::FutureExt;
1591 /// use k8s_openapi::api::core::v1::ConfigMap;
1592 /// use kube::{Api, Client, ResourceExt};
1593 /// use kube_runtime::{
1594 /// controller::{Controller, Action},
1595 /// watcher,
1596 /// };
1597 /// use std::{convert::Infallible, sync::Arc};
1598 /// Controller::new(
1599 /// Api::<ConfigMap>::all(Client::try_default().await.unwrap()),
1600 /// watcher::Config::default(),
1601 /// )
1602 /// .graceful_shutdown_on(tokio::signal::ctrl_c().map(|_| ()))
1603 /// .run(
1604 /// |o, _| async move {
1605 /// println!("Reconciling {}", o.name_any());
1606 /// Ok(Action::await_change())
1607 /// },
1608 /// |_, err: &Infallible, _| Err(err).unwrap(),
1609 /// Arc::new(()),
1610 /// );
1611 /// # };
1612 /// ```
1613 ///
1614 /// This can be called multiple times, in which case they are additive; the [`Controller`] starts to terminate
1615 /// as soon as *any* [`Future`] resolves.
1616 #[must_use]
1617 pub fn graceful_shutdown_on(mut self, trigger: impl Future<Output = ()> + Send + Sync + 'static) -> Self {
1618 self.graceful_shutdown_selector.push(trigger.boxed());
1619 self
1620 }
1621
1622 /// Initiate graceful shutdown on Ctrl+C or SIGTERM (on Unix), waiting for all reconcilers to finish.
1623 ///
1624 /// Once a graceful shutdown has been initiated, Ctrl+C (or SIGTERM) can be sent again
1625 /// to request a forceful shutdown (requesting that all reconcilers abort on the next yield point).
1626 ///
1627 /// NOTE: On Unix this leaves the default handlers for SIGINT and SIGTERM disabled after the [`Controller`] has
1628 /// terminated. If you run this in a process containing more tasks than just the [`Controller`], ensure that
1629 /// all other tasks either terminate when the [`Controller`] does, that they have their own signal handlers,
1630 /// or use [`Controller::graceful_shutdown_on`] to manage your own shutdown strategy.
1631 ///
1632 /// NOTE: If developing a Windows service then you need to listen to its lifecycle events instead, and hook that into
1633 /// [`Controller::graceful_shutdown_on`].
1634 ///
1635 /// NOTE: [`Controller::run`] terminates as soon as a forceful shutdown is requested, but leaves the reconcilers running
1636 /// in the background while they terminate. This will block [`tokio::runtime::Runtime`] termination until they actually terminate,
1637 /// unless you run [`std::process::exit`] afterwards.
1638 #[must_use]
1639 pub fn shutdown_on_signal(mut self) -> Self {
1640 async fn shutdown_signal() {
1641 futures::future::select(
1642 tokio::signal::ctrl_c().map(|_| ()).boxed(),
1643 #[cfg(unix)]
1644 tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
1645 .unwrap()
1646 .recv()
1647 .map(|_| ())
1648 .boxed(),
1649 // Assume that ctrl_c is enough on non-Unix platforms (such as Windows)
1650 #[cfg(not(unix))]
1651 futures::future::pending::<()>(),
1652 )
1653 .await;
1654 }
1655
1656 let (graceful_tx, graceful_rx) = channel::oneshot::channel();
1657 self.graceful_shutdown_selector
1658 .push(graceful_rx.map(|_| ()).boxed());
1659 self.forceful_shutdown_selector.push(
1660 async {
1661 tracing::info!("press ctrl+c to shut down gracefully");
1662 shutdown_signal().await;
1663 if let Ok(()) = graceful_tx.send(()) {
1664 tracing::info!("graceful shutdown requested, press ctrl+c again to force shutdown");
1665 } else {
1666 tracing::info!(
1667 "graceful shutdown already requested, press ctrl+c again to force shutdown"
1668 );
1669 }
1670 shutdown_signal().await;
1671 tracing::info!("forced shutdown requested");
1672 }
1673 .boxed(),
1674 );
1675 self
1676 }
1677
1678 /// Consume all the parameters of the Controller and start the applier stream
1679 ///
1680 /// This creates a stream from all builder calls and starts an applier with
1681 /// a specified `reconciler` and `error_policy` callbacks. Each of these will be called
1682 /// with a configurable `context`.
1683 pub fn run<ReconcilerFut, Ctx>(
1684 self,
1685 mut reconciler: impl FnMut(Arc<K>, Arc<Ctx>) -> ReconcilerFut,
1686 error_policy: impl Fn(Arc<K>, &ReconcilerFut::Error, Arc<Ctx>) -> Action,
1687 context: Arc<Ctx>,
1688 ) -> impl Stream<Item = Result<(ObjectRef<K>, Action), Error<ReconcilerFut::Error, watcher::Error>>>
1689 where
1690 K::DynamicType: Debug + Unpin,
1691 ReconcilerFut: TryFuture<Ok = Action> + Send + 'static,
1692 ReconcilerFut::Error: std::error::Error + Send + 'static,
1693 {
1694 applier(
1695 move |obj, ctx| {
1696 CancelableJoinHandle::spawn(
1697 TryFutureExt::into_future(reconciler(obj, ctx)).in_current_span(),
1698 &Handle::current(),
1699 )
1700 },
1701 error_policy,
1702 context,
1703 self.reader,
1704 StreamBackoff::new(self.trigger_selector, self.trigger_backoff)
1705 .take_until(future::select_all(self.graceful_shutdown_selector)),
1706 self.config,
1707 )
1708 .take_until(futures::future::select_all(self.forceful_shutdown_selector))
1709 }
1710}
1711
1712#[cfg(test)]
1713mod tests {
1714 use std::{convert::Infallible, pin::pin, sync::Arc, time::Duration};
1715
1716 use super::{APPLIER_REQUEUE_BUF_SIZE, Action};
1717 use crate::{
1718 Config, Controller, applier,
1719 reflector::{self, ObjectRef},
1720 watcher::{self, Event, watcher},
1721 };
1722 use futures::{Stream, StreamExt, TryStreamExt};
1723 use k8s_openapi::api::core::v1::ConfigMap;
1724 use kube_client::{
1725 Api, Resource,
1726 core::{ObjectMeta, PartialObjectMeta},
1727 };
1728 use serde::de::DeserializeOwned;
1729 use tokio::time::timeout;
1730
1731 fn assert_send<T: Send>(x: T) -> T {
1732 x
1733 }
1734
1735 // Used to typecheck that a type T is a generic type that implements Stream
1736 // and returns a WatchEvent generic over a resource `K`
1737 fn assert_stream<T, K>(x: T) -> T
1738 where
1739 T: Stream<Item = watcher::Result<Event<K>>> + Send,
1740 K: Resource + Clone + DeserializeOwned + std::fmt::Debug + Send + 'static,
1741 {
1742 x
1743 }
1744
1745 fn mock_type<T>() -> T {
1746 unimplemented!(
1747 "mock_type is not supposed to be called, only used for filling holes in type assertions"
1748 )
1749 }
1750
1751 // not #[test] because we don't want to actually run it, we just want to assert that it typechecks
1752 #[allow(dead_code, unused_must_use)]
1753 fn test_controller_should_be_send() {
1754 assert_send(
1755 Controller::new(mock_type::<Api<ConfigMap>>(), Default::default()).run(
1756 |_, _| async { Ok(mock_type::<Action>()) },
1757 |_: Arc<ConfigMap>, _: &std::io::Error, _| mock_type::<Action>(),
1758 Arc::new(()),
1759 ),
1760 );
1761 }
1762
1763 // not #[test] because we don't want to actually run it, we just want to
1764 // assert that it typechecks
1765 //
1766 // will check return types for `watcher` and `watcher with PartialObjectMeta` do not drift
1767 // given an arbitrary K that implements `Resource` (e.g ConfigMap)
1768 #[allow(dead_code, unused_must_use)]
1769 fn test_watcher_stream_type_drift() {
1770 assert_stream(watcher(mock_type::<Api<ConfigMap>>(), Default::default()));
1771 assert_stream(watcher(
1772 mock_type::<Api<PartialObjectMeta<ConfigMap>>>(),
1773 Default::default(),
1774 ));
1775 }
1776
1777 #[tokio::test]
1778 async fn applier_must_not_deadlock_if_reschedule_buffer_fills() {
1779 // This tests that `applier` handles reschedule queue backpressure correctly, by trying to flood it with no-op reconciles
1780 // This is intended to avoid regressing on https://github.com/kube-rs/kube/issues/926
1781
1782 // Assume that we can keep APPLIER_REQUEUE_BUF_SIZE flooded if we have 100x the number of objects "in rotation"
1783 // On my (@nightkr)'s 3900X I can reliably trigger this with 10x, but let's have some safety margin to avoid false negatives
1784 let items = APPLIER_REQUEUE_BUF_SIZE * 50;
1785 // Assume that everything's OK if we can reconcile every object 3 times on average
1786 let reconciles = items * 3;
1787
1788 let (queue_tx, queue_rx) = futures::channel::mpsc::unbounded::<ObjectRef<ConfigMap>>();
1789 let (store_rx, mut store_tx) = reflector::store();
1790 let mut applier = pin!(applier(
1791 |_obj, _| {
1792 Box::pin(async move {
1793 // Try to flood the rescheduling buffer buffer by just putting it back in the queue immediately
1794 //println!("reconciling {:?}", obj.metadata.name);
1795 Ok(Action::requeue(Duration::ZERO))
1796 })
1797 },
1798 |_: Arc<ConfigMap>, _: &Infallible, _| todo!(),
1799 Arc::new(()),
1800 store_rx,
1801 queue_rx.map(Result::<_, Infallible>::Ok),
1802 Config::default(),
1803 ));
1804 store_tx.apply_watcher_event(&watcher::Event::InitDone);
1805 for i in 0..items {
1806 let obj = ConfigMap {
1807 metadata: ObjectMeta {
1808 name: Some(format!("cm-{i}")),
1809 namespace: Some("default".to_string()),
1810 ..Default::default()
1811 },
1812 ..Default::default()
1813 };
1814 store_tx.apply_watcher_event(&watcher::Event::Apply(obj.clone()));
1815 queue_tx.unbounded_send(ObjectRef::from_obj(&obj)).unwrap();
1816 }
1817
1818 timeout(
1819 Duration::from_secs(10),
1820 applier
1821 .as_mut()
1822 .take(reconciles)
1823 .try_for_each(|_| async { Ok(()) }),
1824 )
1825 .await
1826 .expect("test timeout expired, applier likely deadlocked")
1827 .unwrap();
1828
1829 // Do an orderly shutdown to ensure that no individual reconcilers are stuck
1830 drop(queue_tx);
1831 timeout(
1832 Duration::from_secs(10),
1833 applier.try_for_each(|_| async { Ok(()) }),
1834 )
1835 .await
1836 .expect("applier cleanup timeout expired, individual reconciler likely deadlocked?")
1837 .unwrap();
1838 }
1839}