From 4a85800e137c52ed07955ff46665a8e8c612c8c5 Mon Sep 17 00:00:00 2001 From: clux Date: Sat, 6 Feb 2021 09:55:43 +0000 Subject: [PATCH] add eviction subresource - for #127 --- examples/Cargo.toml | 4 ++ examples/pod_evict.rs | 65 +++++++++++++++++++++++++++++ kube/src/api/mod.rs | 2 +- kube/src/api/subresource.rs | 83 ++++++++++++++++++++++++++++++++++++- 4 files changed, 151 insertions(+), 3 deletions(-) create mode 100644 examples/pod_evict.rs diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 49dd609e5..786d0ca02 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -111,6 +111,10 @@ path = "pod_attach.rs" name = "pod_exec" path = "pod_exec.rs" +[[example]] +name = "pod_evict" +path = "pod_evict.rs" + [[example]] name = "pod_shell" path = "pod_shell.rs" diff --git a/examples/pod_evict.rs b/examples/pod_evict.rs new file mode 100644 index 000000000..3d1f52294 --- /dev/null +++ b/examples/pod_evict.rs @@ -0,0 +1,65 @@ +#[macro_use] extern crate log; +use futures::{StreamExt, TryStreamExt}; +use k8s_openapi::api::core::v1::Pod; +use serde_json::json; + +use kube::{ + api::{Api, Eviction, ListParams, Meta, PostParams, WatchEvent}, + Client, +}; + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + std::env::set_var("RUST_LOG", "info,kube=debug"); + env_logger::init(); + let client = Client::try_default().await?; + let namespace = std::env::var("NAMESPACE").unwrap_or("default".into()); + + // Create a Job + let pod_name = "empty-pod"; + let empty_pod = serde_json::from_value(json!({ + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": pod_name, + }, + "spec": { + "containers": [{ + "name": "empty", + "image": "alpine:latest", + "command": ["tail", "-f", "/dev/null"] + }], + } + }))?; + + let pods: Api = Api::namespaced(client, &namespace); + let pp = PostParams::default(); + pods.create(&pp, &empty_pod).await?; + + // Wait until the pod is running, although it's not necessary + let lp = ListParams::default() + .fields("metadata.name=empty-pod") + .timeout(10); + let mut stream = pods.watch(&lp, "0").await?.boxed(); + while let Some(status) = stream.try_next().await? { + match status { + WatchEvent::Added(o) => { + info!("Added {}", Meta::name(&o)); + } + WatchEvent::Modified(o) => { + let s = o.status.as_ref().expect("status exists on pod"); + if s.phase.clone().unwrap_or_default() == "Running" { + info!("Ready to evict to {}", Meta::name(&o)); + break; + } + } + _ => {} + } + } + + // Clean up the old job record.. + let ev = Eviction::new(pod_name); + let eres = pods.evict(pod_name, &pp, &ev).await?; + println!("{:?}", eres); + Ok(()) +} diff --git a/kube/src/api/mod.rs b/kube/src/api/mod.rs index 45b673bd1..ce142b3dd 100644 --- a/kube/src/api/mod.rs +++ b/kube/src/api/mod.rs @@ -26,7 +26,7 @@ pub use dynamic::DynamicResource; mod subresource; #[cfg(feature = "ws")] pub use subresource::{AttachParams, AttachableObject, ExecutingObject}; -pub use subresource::{LogParams, LoggingObject, ScaleSpec, ScaleStatus}; +pub use subresource::{Eviction, EvictionObject, LogParams, LoggingObject, ScaleSpec, ScaleStatus}; pub(crate) mod object; pub use self::object::{Object, ObjectList, WatchEvent}; diff --git a/kube/src/api/subresource.rs b/kube/src/api/subresource.rs index acfc94bf5..75c2bc30f 100644 --- a/kube/src/api/subresource.rs +++ b/kube/src/api/subresource.rs @@ -1,9 +1,10 @@ use bytes::Bytes; use futures::Stream; -use serde::de::DeserializeOwned; +use serde::{de::DeserializeOwned, Serialize}; use crate::{ - api::{Api, Patch, PatchParams, PostParams, Resource}, + api::{Api, DeleteParams, Patch, PatchParams, PostParams, Resource}, + client::Status, Error, Result, }; @@ -221,6 +222,84 @@ where } } +// ---------------------------------------------------------------------------- +// Eviction subresource +// ---------------------------------------------------------------------------- + +use k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta; +/// Eviction body +#[derive(Default, Clone, Serialize)] +pub struct Eviction { + /// How the eviction should occur + pub delete_options: Option, + /// Metadata describing the pod being evicted + /// we somehow need this despite providing the name of the pod.. + pub metadata: ObjectMeta, +} + +impl Eviction { + /// Create an eviction object for a named resource + pub fn new(name: &str) -> Self { + Self { + metadata: ObjectMeta { + name: Some(name.to_string()), + ..ObjectMeta::default() + }, + delete_options: None, + } + } + + /// Attach DeleteParams to the eviction object + pub fn with_options(mut self, dp: DeleteParams) -> Self { + self.delete_options = Some(dp); + self + } +} + +impl Resource { + /// Create an eviction + pub fn evict(&self, name: &str, pp: &PostParams, data: Vec) -> Result>> { + let base_url = self.make_url() + "/" + name + "/" + "eviction?"; + // This is technically identical to Resource::create, but different url + pp.validate()?; + let mut qp = url::form_urlencoded::Serializer::new(base_url); + if pp.dry_run { + qp.append_pair("dryRun", "All"); + } + let urlstr = qp.finish(); + let req = http::Request::post(urlstr); + req.body(data).map_err(Error::HttpError) + } +} + +#[test] +fn evict_path() { + use crate::api::Resource; + use k8s_openapi::api::core::v1 as corev1; + let r = Resource::namespaced::("ns"); + let mut pp = PostParams::default(); + pp.dry_run = true; + let req = r.evict("foo", &pp, vec![]).unwrap(); + assert_eq!(req.uri(), "/api/v1/namespaces/ns/pods/foo/eviction?&dryRun=All"); +} + +/// Marker trait for objects that can be evicted +pub trait EvictionObject {} + +impl EvictionObject for k8s_openapi::api::core::v1::Pod {} + +impl Api +where + K: Clone + DeserializeOwned + EvictionObject, +{ + /// Create an eviction + pub async fn evict(&self, name: &str, pp: &PostParams, data: &Eviction) -> Result { + let bytes = serde_json::to_vec(data)?; + let req = self.resource.evict(name, pp, bytes)?; + self.client.request::(req).await + } +} + // ---------------------------------------------------------------------------- // Attach subresource // ----------------------------------------------------------------------------