-
-
Notifications
You must be signed in to change notification settings - Fork 324
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Controller<T> #184
Closed
+182
−3
Closed
Controller<T> #184
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
872f79e
controller skeleton
clux 56dd275
use pull based model here as well
clux cc03b89
ideas on how to do controller loop
clux 65f2493
make an event channel
clux b4595f8
poll might be problematic with lifetimes...
clux c62e14d
try using callbacks
clux 242b1b0
ugly compile
clux 832bb1b
complete rebase
clux File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
use crate::{ | ||
api::{ | ||
resource::{ListParams, Resource}, | ||
Meta, WatchEvent, | ||
}, | ||
runtime::informer::Informer, | ||
Client, Error, Result, | ||
}; | ||
use futures::{lock::Mutex, stream, Stream, StreamExt}; | ||
use serde::de::DeserializeOwned; | ||
use std::{convert::TryFrom, time::Duration}; | ||
use tokio::{sync::mpsc, time::delay_for}; | ||
|
||
/// An object to be reconciled | ||
/// | ||
/// The type that is pulled out of Controller::poll | ||
#[derive(Debug, Clone)] | ||
pub struct ReconcileEvent { | ||
pub name: String, | ||
pub namespace: Option<String>, | ||
} | ||
|
||
impl<K> From<K> for ReconcileEvent | ||
where | ||
K: Meta, | ||
{ | ||
fn from(k: K) -> ReconcileEvent { | ||
ReconcileEvent { | ||
name: Meta::name(&k), | ||
namespace: Meta::namespace(&k), | ||
} | ||
} | ||
} | ||
|
||
impl<K> TryFrom<WatchEvent<K>> for ReconcileEvent | ||
where | ||
K: Meta + Clone, | ||
{ | ||
type Error = crate::Error; | ||
|
||
/// Helper to convert the openapi ReplicaSet to the useful info | ||
fn try_from(w: WatchEvent<K>) -> Result<ReconcileEvent> { | ||
match w { | ||
WatchEvent::Added(o) => Ok(o.into()), | ||
WatchEvent::Modified(o) => Ok(o.into()), | ||
WatchEvent::Deleted(o) => Ok(o.into()), | ||
WatchEvent::Error(e) => Err(Error::Api(e)), | ||
} | ||
} | ||
} | ||
|
||
/// An Ok return value from a reconcile fn | ||
/// | ||
/// Designed so the Controller can decide whether to requeue the event | ||
/// Error cases are not encapsulated in this struct (they are handled by Result) | ||
#[derive(Debug)] | ||
pub enum ReconcileStatus { | ||
/// Successful reconcile | ||
Complete, | ||
/// Partial success, reque after some time | ||
RequeAfter(Duration), | ||
} | ||
|
||
/// A controller for a kubernetes object K | ||
#[derive(Clone)] | ||
pub struct Controller<K, F> | ||
where | ||
K: Clone + DeserializeOwned + Meta, | ||
F: Fn(ReconcileEvent) -> Result<ReconcileStatus>, | ||
{ | ||
client: Client, | ||
resource: Resource, | ||
informers: Vec<Informer<K>>, | ||
reconciler: Box<F>, | ||
} | ||
|
||
// TODO: is 'static limiting here? | ||
impl<K: 'static, F: 'static> Controller<K, F> | ||
where | ||
K: Clone + DeserializeOwned + Meta + Send + Sync, | ||
F: Fn(ReconcileEvent) -> Result<ReconcileStatus> + Send + Sync, | ||
{ | ||
/// Create a controller with a kube client on a kube resource | ||
pub fn new(client: Client, r: Resource, recfn: F) -> Self { | ||
Controller { | ||
client: client, | ||
resource: r, | ||
informers: vec![], | ||
reconciler: Box::new(recfn), | ||
} | ||
} | ||
|
||
/// Create internal informers for an associated kube resource | ||
/// | ||
/// TODO: this needs to only find resources with a property matching root resource | ||
pub fn owns(mut self, r: Resource, lp: ListParams) -> Self { | ||
self.informers.push(Informer::new(self.client.clone(), lp, r)); | ||
self | ||
} | ||
|
||
/// Initialize | ||
pub fn init(self) { | ||
info!("Starting Controller for {:?}", self.resource); | ||
let (tx, mut rx) = mpsc::unbounded_channel(); | ||
|
||
// 1. poll informers in parallel and push results to queue | ||
for inf in self.informers.clone() { | ||
// TODO: ownership move? | ||
//let queue = self.queue.clone(); | ||
let txi = tx.clone(); | ||
tokio::spawn(async move { | ||
let mut poll_i = inf.poll().await.expect("could talk to api server").boxed(); | ||
while let Some(ev) = poll_i.next().await { | ||
match ev { | ||
Ok(wi) => { | ||
let ri = ReconcileEvent::try_from(wi); | ||
//(*queue.lock().await).push_back(ri); | ||
txi.send(ri).expect("channel can receive"); | ||
} | ||
_ => unimplemented!(), | ||
//Err(e) => tx.unbounded_send(Err(e)), | ||
} | ||
} | ||
}); | ||
} | ||
// TODO: init main informer | ||
// TODO: queue up events | ||
// TODO: debounce events | ||
|
||
|
||
// Prepare a sender so we can stack things back on the channel | ||
let txa = tx.clone(); | ||
// Event loop that triggers the reconcile fn | ||
tokio::spawn(async move { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Makes sense while prototyping.. but we should try to avoid |
||
loop { | ||
match rx.recv().await { | ||
None => return, // tx dropped | ||
Some(wi) => { | ||
match wi { | ||
Err(e) => { | ||
// Got WatchEvent::Error | ||
// TODO: handle here? handle above? | ||
} | ||
Ok(wo) => { | ||
let ri = wo.clone(); | ||
let name = wo.name.clone(); | ||
match (self.reconciler)(wo) { | ||
Ok(status) => { | ||
// Reconcile cb completed with app decicion | ||
match status { | ||
ReconcileStatus::Complete => { | ||
info!("Reconciled {}", name); | ||
} | ||
ReconcileStatus::RequeAfter(dur) => { | ||
info!("Requeing {} in {:?}", name, dur); | ||
delay_for(dur).await; | ||
txa.send(Ok(ri)).expect("channel can receive"); | ||
} | ||
} | ||
} | ||
Err(e) => { | ||
// Reconcile cb failed (any unspecified error) | ||
let dur = Duration::from_secs(10); // TODO: expo decay | ||
warn!("Failed to reconcile {} - requining in {:?}", e, dur); | ||
delay_for(dur).await; | ||
txa.send(Ok(ri)).expect("channel can receive"); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is pretty limiting that
F
can't return animpl Future
.