Skip to content

Commit

Permalink
add eviction subresource - for #127
Browse files Browse the repository at this point in the history
  • Loading branch information
clux committed Feb 6, 2021
1 parent d450509 commit 4a85800
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 3 deletions.
4 changes: 4 additions & 0 deletions examples/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
65 changes: 65 additions & 0 deletions examples/pod_evict.rs
Original file line number Diff line number Diff line change
@@ -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<Pod> = 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(())
}
2 changes: 1 addition & 1 deletion kube/src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down
83 changes: 81 additions & 2 deletions kube/src/api/subresource.rs
Original file line number Diff line number Diff line change
@@ -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,
};

Expand Down Expand Up @@ -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<DeleteParams>,
/// 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<u8>) -> Result<http::Request<Vec<u8>>> {
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::<corev1::Pod>("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<K> Api<K>
where
K: Clone + DeserializeOwned + EvictionObject,
{
/// Create an eviction
pub async fn evict(&self, name: &str, pp: &PostParams, data: &Eviction) -> Result<Status> {
let bytes = serde_json::to_vec(data)?;
let req = self.resource.evict(name, pp, bytes)?;
self.client.request::<Status>(req).await
}
}

// ----------------------------------------------------------------------------
// Attach subresource
// ----------------------------------------------------------------------------
Expand Down

0 comments on commit 4a85800

Please sign in to comment.