From 3ba59b9ac59a3e8b0a0399a74aed38acb9a39400 Mon Sep 17 00:00:00 2001 From: clux Date: Mon, 6 Dec 2021 06:44:14 +0000 Subject: [PATCH 01/21] more coverage experiments Signed-off-by: clux --- deny.toml | 1 + tarpaulin.toml | 1 + 2 files changed, 2 insertions(+) diff --git a/deny.toml b/deny.toml index 3a115a94a..ecfef67a9 100644 --- a/deny.toml +++ b/deny.toml @@ -29,6 +29,7 @@ copyleft = "deny" default = "deny" unlicensed = "deny" +# We are beholden to https://github.com/cncf/foundation/blob/master/allowed-third-party-license-policy.md#approved-licenses-for-allowlist allow = [ "MIT", "Apache-2.0", diff --git a/tarpaulin.toml b/tarpaulin.toml index cc3b002f8..24f66fbf1 100644 --- a/tarpaulin.toml +++ b/tarpaulin.toml @@ -10,3 +10,4 @@ exclude = ["integration"] # NB: skipping Doctests because they are slow to build and generally marked no_run run-types = ["Tests"] generate = ["Json"] +ignore_tests = true From 1899b3cd10e9b68215e753338f49dabd74a164b6 Mon Sep 17 00:00:00 2001 From: clux Date: Mon, 6 Dec 2021 06:54:37 +0000 Subject: [PATCH 02/21] ping hooks - 2021-12-06 Signed-off-by: clux From 40f98c2ce5e20b03b8b5d5b25550ed14198b6bc7 Mon Sep 17 00:00:00 2001 From: clux Date: Mon, 6 Dec 2021 07:10:13 +0000 Subject: [PATCH 03/21] custom client example into kube-client/lib might need to add this to actions, but testing first Signed-off-by: clux --- Makefile | 1 + kube-client/src/lib.rs | 41 +++++++++++++++++++++++++++++++++++++++++ tarpaulin.toml | 1 - 3 files changed, 42 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index a9975644f..9614fcfed 100644 --- a/Makefile +++ b/Makefile @@ -26,6 +26,7 @@ test: test-kubernetes: cargo test --lib --all -- --ignored # also run tests that fail on github actions cargo test -p kube --lib --features=derive,runtime -- --ignored + cargo test -p kube-client --lib --features=rustls-tls -- --ignored cargo run -p kube-examples --example crd_derive cargo run -p kube-examples --example crd_api diff --git a/kube-client/src/lib.rs b/kube-client/src/lib.rs index dacafe04e..79f7bd463 100644 --- a/kube-client/src/lib.rs +++ b/kube-client/src/lib.rs @@ -122,3 +122,44 @@ cfg_error! { pub use crate::core::{CustomResourceExt, Resource, ResourceExt}; /// Re-exports from kube_core pub use kube_core as core; + + +// Tests that require a cluster and the complete feature set +// Can be run with `cargo test -p kube-client --lib features=rustls-tls -- --ignored` +#[cfg(test)] +#[cfg(all(feature = "client", feature = "config"))] +mod test { + use crate::{Api, Client}; + + #[tokio::test] + #[ignore] // needs cluster (lists pods) + #[cfg(all(feature = "rustls-tls"))] + async fn cum_client_runtime_tls_configuration() -> Result<(), Box> { + use crate::{client::ConfigExt, Config}; + use k8s_openapi::api::core::v1::Pod; + use tower::ServiceBuilder; + + let config = Config::infer().await?; + + // Pick TLS at runtime + let use_rustls = std::env::var("USE_RUSTLS").map(|s| s == "1").unwrap_or(false); + let client = if use_rustls { + let https = config.rustls_https_connector()?; + let service = ServiceBuilder::new() + .layer(config.base_uri_layer()) + .service(hyper::Client::builder().build(https)); + Client::new(service, config.default_namespace) + } else { + let https = config.native_tls_https_connector()?; + let service = ServiceBuilder::new() + .layer(config.base_uri_layer()) + .service(hyper::Client::builder().build(https)); + Client::new(service, config.default_namespace) + }; + + let pods: Api = Api::default_namespaced(client); + pods.list(&Default::default()).await?; + + Ok(()) + } +} diff --git a/tarpaulin.toml b/tarpaulin.toml index 24f66fbf1..43c65efb3 100644 --- a/tarpaulin.toml +++ b/tarpaulin.toml @@ -9,5 +9,4 @@ timeout = "600s" exclude = ["integration"] # NB: skipping Doctests because they are slow to build and generally marked no_run run-types = ["Tests"] -generate = ["Json"] ignore_tests = true From a08f68e573ee1fc9915830e64e63a48f078f37eb Mon Sep 17 00:00:00 2001 From: clux Date: Mon, 6 Dec 2021 07:22:43 +0000 Subject: [PATCH 04/21] stick to native tls testing Signed-off-by: clux --- kube-client/src/lib.rs | 47 +++++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/kube-client/src/lib.rs b/kube-client/src/lib.rs index 79f7bd463..452d586ea 100644 --- a/kube-client/src/lib.rs +++ b/kube-client/src/lib.rs @@ -126,40 +126,45 @@ pub use kube_core as core; // Tests that require a cluster and the complete feature set // Can be run with `cargo test -p kube-client --lib features=rustls-tls -- --ignored` -#[cfg(test)] #[cfg(all(feature = "client", feature = "config"))] mod test { - use crate::{Api, Client}; + #[allow(unused_imports)] use crate::{Api, Client}; + // hard disabled test atm due to k3d rustls issues: https://github.com/kube-rs/kube-rs/issues?q=is%3Aopen+is%3Aissue+label%3Arustls + #[cfg(feature = "when_rustls_works_with_k3d")] #[tokio::test] #[ignore] // needs cluster (lists pods) #[cfg(all(feature = "rustls-tls"))] - async fn cum_client_runtime_tls_configuration() -> Result<(), Box> { + async fn custom_client_rustls_configuration() -> Result<(), Box> { use crate::{client::ConfigExt, Config}; use k8s_openapi::api::core::v1::Pod; use tower::ServiceBuilder; - let config = Config::infer().await?; - - // Pick TLS at runtime - let use_rustls = std::env::var("USE_RUSTLS").map(|s| s == "1").unwrap_or(false); - let client = if use_rustls { - let https = config.rustls_https_connector()?; - let service = ServiceBuilder::new() - .layer(config.base_uri_layer()) - .service(hyper::Client::builder().build(https)); - Client::new(service, config.default_namespace) - } else { - let https = config.native_tls_https_connector()?; - let service = ServiceBuilder::new() - .layer(config.base_uri_layer()) - .service(hyper::Client::builder().build(https)); - Client::new(service, config.default_namespace) - }; - + let https = config.rustls_https_connector()?; + let service = ServiceBuilder::new() + .layer(config.base_uri_layer()) + .service(hyper::Client::builder().build(https)); + let client = Client::new(service, config.default_namespace); let pods: Api = Api::default_namespaced(client); pods.list(&Default::default()).await?; + Ok(()) + } + #[tokio::test] + #[ignore] // needs cluster (lists pods) + #[cfg(all(feature = "native-tls"))] + async fn custom_client_native_tlss_configuration() -> Result<(), Box> { + use crate::{client::ConfigExt, Config}; + use k8s_openapi::api::core::v1::Pod; + use tower::ServiceBuilder; + let config = Config::infer().await?; + let https = config.native_tls_https_connector()?; + let service = ServiceBuilder::new() + .layer(config.base_uri_layer()) + .service(hyper::Client::builder().build(https)); + let client = Client::new(service, config.default_namespace); + let pods: Api = Api::default_namespaced(client); + pods.list(&Default::default()).await?; Ok(()) } } From f32e5a6ef69b1a6505085d524505da035fcc30d3 Mon Sep 17 00:00:00 2001 From: clux Date: Mon, 6 Dec 2021 07:51:24 +0000 Subject: [PATCH 05/21] port subresource tests from crd_api Signed-off-by: clux --- kube-client/src/lib.rs | 11 +++---- kube/src/lib.rs | 67 +++++++++++++++++++++++++++++------------- 2 files changed, 51 insertions(+), 27 deletions(-) diff --git a/kube-client/src/lib.rs b/kube-client/src/lib.rs index 452d586ea..9db7c6b77 100644 --- a/kube-client/src/lib.rs +++ b/kube-client/src/lib.rs @@ -128,7 +128,10 @@ pub use kube_core as core; // Can be run with `cargo test -p kube-client --lib features=rustls-tls -- --ignored` #[cfg(all(feature = "client", feature = "config"))] mod test { - #[allow(unused_imports)] use crate::{Api, Client}; + #![allow(unused_imports)] + use crate::{client::ConfigExt, Api, Client, Config}; + use k8s_openapi::api::core::v1::Pod; + use tower::ServiceBuilder; // hard disabled test atm due to k3d rustls issues: https://github.com/kube-rs/kube-rs/issues?q=is%3Aopen+is%3Aissue+label%3Arustls #[cfg(feature = "when_rustls_works_with_k3d")] @@ -136,9 +139,6 @@ mod test { #[ignore] // needs cluster (lists pods) #[cfg(all(feature = "rustls-tls"))] async fn custom_client_rustls_configuration() -> Result<(), Box> { - use crate::{client::ConfigExt, Config}; - use k8s_openapi::api::core::v1::Pod; - use tower::ServiceBuilder; let config = Config::infer().await?; let https = config.rustls_https_connector()?; let service = ServiceBuilder::new() @@ -154,9 +154,6 @@ mod test { #[ignore] // needs cluster (lists pods) #[cfg(all(feature = "native-tls"))] async fn custom_client_native_tlss_configuration() -> Result<(), Box> { - use crate::{client::ConfigExt, Config}; - use k8s_openapi::api::core::v1::Pod; - use tower::ServiceBuilder; let config = Config::infer().await?; let https = config.native_tls_https_connector()?; let service = ServiceBuilder::new() diff --git a/kube/src/lib.rs b/kube/src/lib.rs index c042b1e93..cef6d73a5 100644 --- a/kube/src/lib.rs +++ b/kube/src/lib.rs @@ -225,11 +225,12 @@ mod test { #[tokio::test] #[ignore] // needs cluster (creates + patches foo crd) #[cfg(all(feature = "derive", feature = "runtime"))] - async fn derived_resource_queriable() -> Result<(), Box> { + async fn derived_resource_queriable_and_has_subresources() -> Result<(), Box> { use crate::{ core::params::{DeleteParams, Patch, PatchParams}, runtime::wait::{await_condition, conditions}, }; + use serde_json::json; let client = Client::try_default().await?; let ssapply = PatchParams::apply("kube").force(); let crds: Api = Api::all(client.clone()); @@ -242,26 +243,52 @@ mod test { // Use it let foos: Api = Api::default_namespaced(client.clone()); // Apply from generated struct - let foo = Foo::new("baz", FooSpec { - name: "baz".into(), - info: Some("old baz".into()), - replicas: 3, - }); - let o = foos.patch("baz", &ssapply, &Patch::Apply(&foo)).await?; - assert_eq!(o.spec.name, "baz"); + { + let foo = Foo::new("baz", FooSpec { + name: "baz".into(), + info: Some("old baz".into()), + replicas: 1, + }); + let o = foos.patch("baz", &ssapply, &Patch::Apply(&foo)).await?; + assert_eq!(o.spec.name, "baz"); + } // Apply from partial json! - let patch = serde_json::json!({ - "apiVersion": "clux.dev/v1", - "kind": "Foo", - "spec": { - "name": "foo", - "replicas": 2 - } - }); - let o2 = foos.patch("baz", &ssapply, &Patch::Apply(patch)).await?; - assert_eq!(o2.spec.replicas, 2); - assert_eq!(foos.get_scale("baz").await?.spec.unwrap().replicas, Some(2)); - assert!(foos.get_status("baz").await?.status.is_none()); // nothing has set this + { + let patch = json!({ + "apiVersion": "clux.dev/v1", + "kind": "Foo", + "spec": { + "name": "foo", + "replicas": 2 + } + }); + let o = foos.patch("baz", &ssapply, &Patch::Apply(patch)).await?; + assert_eq!(o.spec.replicas, 2); + } + // check subresource + { + assert_eq!(foos.get_scale("baz").await?.spec.unwrap().replicas, Some(2)); + assert!(foos.get_status("baz").await?.status.is_none()); // nothing has set this + } + // set status subresource + { + let fs = serde_json::json!({"status": FooStatus { is_bad: false, replicas: 1 }}); + let o = foos + .patch_status("baz", &Default::default(), &Patch::Merge(&fs)) + .await?; + assert!(o.status.is_some()); + } + // set scale subresource + { + let fs = serde_json::json!({"spec": { "replicas": 3 }}); + let o = foos + .patch_scale("baz", &Default::default(), &Patch::Merge(&fs)) + .await?; + assert_eq!(o.status.unwrap().replicas, 1); // something needs to set the status for this + assert_eq!(o.spec.unwrap().replicas.unwrap(), 3); // what we asked for got updated + } + + // cleanup foos.delete("baz", &DeleteParams::default()).await?; Ok(()) } From 8c0295471100f1c8d7ddaca66ef84faf66ab3f40 Mon Sep 17 00:00:00 2001 From: clux Date: Mon, 6 Dec 2021 08:07:14 +0000 Subject: [PATCH 06/21] some coverage tests for discovery Signed-off-by: clux --- kube-client/src/lib.rs | 13 +++++++++++++ kube/src/lib.rs | 28 ++++++++++++++++++++++++++-- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/kube-client/src/lib.rs b/kube-client/src/lib.rs index 9db7c6b77..e235acfe6 100644 --- a/kube-client/src/lib.rs +++ b/kube-client/src/lib.rs @@ -164,4 +164,17 @@ mod test { pods.list(&Default::default()).await?; Ok(()) } + + #[tokio::test] + #[ignore] // needs cluster (lists api resources) + #[cfg(all(feature = "discovery"))] + async fn group_discovery_oneshot() -> Result<(), Box> { + use crate::{core::DynamicObject, discovery}; + let client = Client::try_default().await?; + let apigroup = discovery::group(&client, "apiregistration.k8s.io").await?; + let (ar, _caps) = apigroup.recommended_kind("APIService").unwrap(); + let api: Api = Api::all_with(client.clone(), &ar); + api.list(&Default::default()).await?; + Ok(()) + } } diff --git a/kube/src/lib.rs b/kube/src/lib.rs index cef6d73a5..d6ff96761 100644 --- a/kube/src/lib.rs +++ b/kube/src/lib.rs @@ -293,10 +293,34 @@ mod test { Ok(()) } + #[tokio::test] + #[ignore] // needs cluster (fetches api resources, and lists cr) + #[cfg(all(feature = "derive"))] + async fn derived_resources_discoverable() -> Result<(), Box> { + use crate::{ + core::{DynamicObject, GroupVersion, GroupVersionKind}, + discovery, + }; + let client = Client::try_default().await?; + let gvk = GroupVersionKind::gvk("clux.dev", "v1", "Foo"); + let gv = GroupVersion::gv("clux.dev", "v1"); + + // discover by both (recommended kind on groupversion) and (pinned gvk) and they should equal + let apigroup = discovery::oneshot::pinned_group(&client, &gv).await?; + let (ar1, caps1) = apigroup.recommended_kind("Foo").unwrap(); + let (ar2, caps2) = discovery::pinned_kind(&client, &gvk).await?; + assert_eq!(caps1.operations.len(), caps2.operations.len()); + assert_eq!(ar1, ar2); + + let api = Api::::all_with(client, &ar2); + api.list(&Default::default()).await?; + + Ok(()) + } + #[tokio::test] #[ignore] // needs cluster (fetches api resources, and lists all) - #[cfg(all(feature = "derive", feature = "runtime"))] - async fn dynamic_resources_discoverable() -> Result<(), Box> { + async fn resources_discoverable() -> Result<(), Box> { use crate::{ core::DynamicObject, discovery::{verbs, Discovery, Scope}, From d913d30d4e4015affb2e392097e88950a4dab0fd Mon Sep 17 00:00:00 2001 From: clux Date: Mon, 6 Dec 2021 09:27:18 +0000 Subject: [PATCH 07/21] add some more test for dynamic objects Signed-off-by: clux --- kube-client/src/lib.rs | 1 + kube-core/src/object.rs | 20 +++++++++++++++++--- kube/src/lib.rs | 26 ++++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 3 deletions(-) diff --git a/kube-client/src/lib.rs b/kube-client/src/lib.rs index e235acfe6..738090bdb 100644 --- a/kube-client/src/lib.rs +++ b/kube-client/src/lib.rs @@ -127,6 +127,7 @@ pub use kube_core as core; // Tests that require a cluster and the complete feature set // Can be run with `cargo test -p kube-client --lib features=rustls-tls -- --ignored` #[cfg(all(feature = "client", feature = "config"))] +#[cfg(test)] mod test { #![allow(unused_imports)] use crate::{client::ConfigExt, Api, Client, Config}; diff --git a/kube-core/src/object.rs b/kube-core/src/object.rs index 5a5ff7260..a848f8af4 100644 --- a/kube-core/src/object.rs +++ b/kube-core/src/object.rs @@ -286,7 +286,9 @@ pub struct NotUsed {} #[cfg(test)] mod test { - use super::{ApiResource, NotUsed, Object}; + use super::{ApiResource, NotUsed, Object, Resource}; + use crate::resource::ResourceExt; + #[test] fn simplified_k8s_object() { use k8s_openapi::api::core::v1::Pod; @@ -308,9 +310,21 @@ mod test { containers: vec![ContainerSimple { image: "blog".into() }], }; let mypod = PodSimple::new("blog", &ar, data).within("dev"); - assert_eq!(mypod.metadata.namespace.unwrap(), "dev"); - assert_eq!(mypod.metadata.name.unwrap(), "blog"); + + let meta = mypod.meta(); + assert_eq!(&mypod.metadata, meta); + assert_eq!(meta.namespace.as_ref().unwrap(), "dev"); + assert_eq!(meta.name.as_ref().unwrap(), "blog"); assert_eq!(mypod.types.as_ref().unwrap().kind, "Pod"); assert_eq!(mypod.types.as_ref().unwrap().api_version, "v1"); + + assert_eq!(mypod.namespace().unwrap(), "dev"); + assert_eq!(mypod.name(), "blog"); + + assert_eq!(PodSimple::api_version(&ar), "v1"); + assert_eq!(PodSimple::version(&ar), "v1"); + assert_eq!(PodSimple::plural(&ar), "pods"); + assert_eq!(PodSimple::kind(&ar), "Pod"); + assert_eq!(PodSimple::group(&ar), ""); } } diff --git a/kube/src/lib.rs b/kube/src/lib.rs index d6ff96761..2dad6dd8f 100644 --- a/kube/src/lib.rs +++ b/kube/src/lib.rs @@ -293,6 +293,32 @@ mod test { Ok(()) } + #[tokio::test] + #[ignore] // needs cluster (lists pods) + async fn custom_objects_are_usable() -> Result<(), Box> { + use crate::core::{ApiResource, NotUsed, Object}; + use k8s_openapi::api::core::v1::Pod; + #[derive(Clone, Deserialize, Debug)] + struct PodSpecSimple { + containers: Vec, + } + #[derive(Clone, Deserialize, Debug)] + struct ContainerSimple { + image: String, + } + type PodSimple = Object; + // use known type information from pod (can also use discovery for this) + let ar = ApiResource::erase::(&()); + + let client = Client::try_default().await?; + let api: Api = Api::default_namespaced_with(client, &ar); + for _p in api.list(&Default::default()).await? { + // can iterate over dynamic objects" + } + + Ok(()) + } + #[tokio::test] #[ignore] // needs cluster (fetches api resources, and lists cr) #[cfg(all(feature = "derive"))] From 0d8e379e37d88161fb685c37aadccb3229430587 Mon Sep 17 00:00:00 2001 From: clux Date: Mon, 6 Dec 2021 09:39:06 +0000 Subject: [PATCH 08/21] do iteration Signed-off-by: clux --- kube/src/lib.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/kube/src/lib.rs b/kube/src/lib.rs index 2dad6dd8f..796e4409c 100644 --- a/kube/src/lib.rs +++ b/kube/src/lib.rs @@ -312,8 +312,9 @@ mod test { let client = Client::try_default().await?; let api: Api = Api::default_namespaced_with(client, &ar); - for _p in api.list(&Default::default()).await? { - // can iterate over dynamic objects" + for p in api.list(&Default::default()).await? { + // run loop to cover ObjectList::iter + println!("found pod {} with containers: {:?}", p.name(), p.spec.containers); } Ok(()) From be26f2f28ce9daad286e5a500646a3ff66a72987 Mon Sep 17 00:00:00 2001 From: clux Date: Mon, 6 Dec 2021 09:55:56 +0000 Subject: [PATCH 09/21] do a few more discovery bits Signed-off-by: clux --- kube/src/lib.rs | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/kube/src/lib.rs b/kube/src/lib.rs index 796e4409c..12e91f1e3 100644 --- a/kube/src/lib.rs +++ b/kube/src/lib.rs @@ -349,12 +349,25 @@ mod test { #[ignore] // needs cluster (fetches api resources, and lists all) async fn resources_discoverable() -> Result<(), Box> { use crate::{ - core::DynamicObject, + core::{DynamicObject, GroupVersionKind}, discovery::{verbs, Discovery, Scope}, }; let client = Client::try_default().await?; - let discovery = Discovery::new(client.clone()).run().await?; + let discovery = Discovery::new(client.clone()) + .exclude(&[""]) // skip core resources + .run() + .await?; + // check our custom resource first by resolving within groups + { + assert!(discovery.has_group("clux.dev")); + let gvk = GroupVersionKind::gvk("clux.dev", "v1", "Foo"); + let (ar, _caps) = discovery.resolve_gvk(&gvk).unwrap(); + assert_eq!(ar.group, gvk.group); + assert_eq!(ar.version, gvk.version); + assert_eq!(ar.kind, gvk.kind); + } + for group in discovery.groups() { for (ar, caps) in group.recommended_resources() { if !caps.supports_operation(verbs::LIST) { @@ -365,13 +378,7 @@ mod test { } else { Api::all_with(client.clone(), &ar) }; - println!("{}/{} : {}", group.name(), ar.version, ar.kind); - let list = api.list(&Default::default()).await?; - for item in list.items { - let name = item.name(); - let ns = item.metadata.namespace.map(|s| s + "/").unwrap_or_default(); - println!("\t\t{}{}", ns, name); - } + api.list(&Default::default()).await?; } } Ok(()) From a4c8b3231d90c6502b7024fdb2180541f5989727 Mon Sep 17 00:00:00 2001 From: clux Date: Mon, 6 Dec 2021 10:07:16 +0000 Subject: [PATCH 10/21] Has traits Signed-off-by: clux --- kube-client/src/lib.rs | 2 ++ kube-core/src/object.rs | 8 ++++++-- kube/src/lib.rs | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/kube-client/src/lib.rs b/kube-client/src/lib.rs index 738090bdb..8ac37a897 100644 --- a/kube-client/src/lib.rs +++ b/kube-client/src/lib.rs @@ -178,4 +178,6 @@ mod test { api.list(&Default::default()).await?; Ok(()) } + + // TODO: ws test } diff --git a/kube-core/src/object.rs b/kube-core/src/object.rs index a848f8af4..a29905a42 100644 --- a/kube-core/src/object.rs +++ b/kube-core/src/object.rs @@ -286,7 +286,7 @@ pub struct NotUsed {} #[cfg(test)] mod test { - use super::{ApiResource, NotUsed, Object, Resource}; + use super::{ApiResource, HasSpec, HasStatus, NotUsed, Object, Resource}; use crate::resource::ResourceExt; #[test] @@ -297,7 +297,7 @@ mod test { struct PodSpecSimple { containers: Vec, } - #[derive(Clone)] + #[derive(Clone, Debug, PartialEq)] struct ContainerSimple { image: String, } @@ -320,6 +320,10 @@ mod test { assert_eq!(mypod.namespace().unwrap(), "dev"); assert_eq!(mypod.name(), "blog"); + assert!(mypod.status().is_none()); + assert_eq!(mypod.spec().containers[0], ContainerSimple { + image: "blog".into() + }); assert_eq!(PodSimple::api_version(&ar), "v1"); assert_eq!(PodSimple::version(&ar), "v1"); diff --git a/kube/src/lib.rs b/kube/src/lib.rs index 12e91f1e3..5a930f4c9 100644 --- a/kube/src/lib.rs +++ b/kube/src/lib.rs @@ -367,7 +367,7 @@ mod test { assert_eq!(ar.version, gvk.version); assert_eq!(ar.kind, gvk.kind); } - + // check all non-excluded groups that are iterable for group in discovery.groups() { for (ar, caps) in group.recommended_resources() { if !caps.supports_operation(verbs::LIST) { From 79a0220c7b1b17830ff1c31c7aef2223a56370df Mon Sep 17 00:00:00 2001 From: clux Date: Mon, 6 Dec 2021 10:10:20 +0000 Subject: [PATCH 11/21] dont skip core group, has special casings Signed-off-by: clux --- kube/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kube/src/lib.rs b/kube/src/lib.rs index 5a930f4c9..56c286487 100644 --- a/kube/src/lib.rs +++ b/kube/src/lib.rs @@ -355,7 +355,7 @@ mod test { let client = Client::try_default().await?; let discovery = Discovery::new(client.clone()) - .exclude(&[""]) // skip core resources + .exclude(&["rbac.authorization.k8s.io"]) // skip something .run() .await?; // check our custom resource first by resolving within groups From 3db3fc77a8b0cbe743392c056967e710611b7e87 Mon Sep 17 00:00:00 2001 From: clux Date: Mon, 6 Dec 2021 18:20:57 +0000 Subject: [PATCH 12/21] more core cases Signed-off-by: clux --- kube-client/src/lib.rs | 2 +- kube-core/src/subresource.rs | 31 +++++++++++++++++++++++++++++++ kube-core/src/util.rs | 19 +++++++++++++++++++ kube/src/lib.rs | 6 +++++- tarpaulin.toml | 1 + 5 files changed, 57 insertions(+), 2 deletions(-) diff --git a/kube-client/src/lib.rs b/kube-client/src/lib.rs index 8ac37a897..ba334d2e5 100644 --- a/kube-client/src/lib.rs +++ b/kube-client/src/lib.rs @@ -154,7 +154,7 @@ mod test { #[tokio::test] #[ignore] // needs cluster (lists pods) #[cfg(all(feature = "native-tls"))] - async fn custom_client_native_tlss_configuration() -> Result<(), Box> { + async fn custom_client_native_tls_configuration() -> Result<(), Box> { let config = Config::infer().await?; let https = config.native_tls_https_connector()?; let service = ServiceBuilder::new() diff --git a/kube-core/src/subresource.rs b/kube-core/src/subresource.rs index 86326a4f0..6432fda5e 100644 --- a/kube-core/src/subresource.rs +++ b/kube-core/src/subresource.rs @@ -331,3 +331,34 @@ impl Request { req.body(vec![]).map_err(Error::BuildRequest) } } + +// ---------------------------------------------------------------------------- +// tests +// ---------------------------------------------------------------------------- + +/// Cheap sanity check to ensure type maps work as expected +#[cfg(test)] +mod test { + use crate::{request::Request, resource::Resource}; + use k8s::{apps::v1 as appsv1, core::v1 as corev1}; + use k8s_openapi::api as k8s; + + use crate::subresource::LogParams; + + #[test] + fn logs_all_params() { + let url = corev1::Pod::url_path(&(), Some("ns")); + let lp = LogParams { + container: Some("nginx".into()), + follow: true, + limit_bytes: Some(10 * 1024 * 1024), + pretty: true, + previous: true, + since_seconds: Some(3600), + tail_lines: Some(4096), + timestamps: true, + }; + let req = Request::new(url).logs("mypod", &lp).unwrap(); + assert_eq!(req.uri(), "/api/v1/namespaces/ns/pods/mypod/log?&container=nginx&follow=true&limitBytes=10485760&pretty=true&previous=true&sinceSeconds=3600&tailLines=4096×tamps=true"); + } +} diff --git a/kube-core/src/util.rs b/kube-core/src/util.rs index 02d260c60..fe024ac59 100644 --- a/kube-core/src/util.rs +++ b/kube-core/src/util.rs @@ -34,3 +34,22 @@ impl Request { self.patch(name, &pparams, &Patch::Merge(patch)) } } + + +#[cfg(test)] +mod test { + #[test] + fn restart_patch_is_correct() { + use crate::{params::Patch, request::Request, resource::Resource}; + use k8s_openapi::api::apps::v1 as appsv1; + + let url = appsv1::Deployment::url_path(&(), Some("ns")); + let req = Request::new(url).restart("mydeploy").unwrap(); + assert_eq!(req.uri(), "/apis/apps/v1/namespaces/ns/deployments/mydeploy?"); + assert_eq!(req.method(), "PATCH"); + assert_eq!( + req.headers().get("Content-Type").unwrap().to_str().unwrap(), + Patch::Merge(()).content_type() + ); + } +} diff --git a/kube/src/lib.rs b/kube/src/lib.rs index 56c286487..709ddaf0e 100644 --- a/kube/src/lib.rs +++ b/kube/src/lib.rs @@ -184,7 +184,7 @@ pub use kube_core as core; #[cfg(test)] #[cfg(all(feature = "derive", feature = "client"))] mod test { - use crate::{Api, Client, CustomResourceExt, ResourceExt}; + use crate::{Api, Client, CustomResourceExt, Resource, ResourceExt}; use kube_derive::CustomResource; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -251,6 +251,9 @@ mod test { }); let o = foos.patch("baz", &ssapply, &Patch::Apply(&foo)).await?; assert_eq!(o.spec.name, "baz"); + let oref = o.object_ref(&()); + assert_eq!(oref.name.unwrap(), "baz"); + assert_eq!(oref.uid, o.uid()); } // Apply from partial json! { @@ -338,6 +341,7 @@ mod test { let (ar2, caps2) = discovery::pinned_kind(&client, &gvk).await?; assert_eq!(caps1.operations.len(), caps2.operations.len()); assert_eq!(ar1, ar2); + assert_eq!(DynamicObject::api_version(&ar2), "clux.dev/v1"); let api = Api::::all_with(client, &ar2); api.list(&Default::default()).await?; diff --git a/tarpaulin.toml b/tarpaulin.toml index 43c65efb3..63c63d2eb 100644 --- a/tarpaulin.toml +++ b/tarpaulin.toml @@ -7,6 +7,7 @@ color = "Always" ignored = true timeout = "600s" exclude = ["integration"] +excluded_files = ["kube-derive/tests"] # NB: skipping Doctests because they are slow to build and generally marked no_run run-types = ["Tests"] ignore_tests = true From ce17f846a41756cb245f133b11724a6d102391a6 Mon Sep 17 00:00:00 2001 From: clux Date: Tue, 7 Dec 2021 19:35:27 +0000 Subject: [PATCH 13/21] add parallel integration tests for pods, ws, evict, eventrecorder should push it to 70% Signed-off-by: clux --- Makefile | 6 +- examples/pod_api.rs | 28 +--- examples/pod_evict.rs | 2 +- kube-client/src/lib.rs | 277 ++++++++++++++++++++++++++++++++++++- kube-runtime/src/events.rs | 37 +++++ kube-runtime/src/wait.rs | 19 ++- kube/src/lib.rs | 29 ++-- tarpaulin.toml | 18 ++- 8 files changed, 375 insertions(+), 41 deletions(-) diff --git a/Makefile b/Makefile index 9614fcfed..ffcb21591 100644 --- a/Makefile +++ b/Makefile @@ -23,17 +23,17 @@ test: cargo test -p kube --lib --no-default-features cargo test -p kube-examples --example crd_api --no-default-features --features=deprecated,kubederive,native-tls -test-kubernetes: +test-integration: cargo test --lib --all -- --ignored # also run tests that fail on github actions cargo test -p kube --lib --features=derive,runtime -- --ignored - cargo test -p kube-client --lib --features=rustls-tls -- --ignored + cargo test -p kube-client --lib --features=rustls-tls,ws -- --ignored cargo run -p kube-examples --example crd_derive cargo run -p kube-examples --example crd_api readme: rustdoc README.md --test --edition=2021 -integration: dapp +e2e: dapp ls -lah integration/ docker build -t clux/kube-dapp:$(VERSION) integration/ k3d image import clux/kube-dapp:$(VERSION) --cluster main diff --git a/examples/pod_api.rs b/examples/pod_api.rs index bb52b05f6..a16ab0054 100644 --- a/examples/pod_api.rs +++ b/examples/pod_api.rs @@ -1,9 +1,9 @@ -use futures::{StreamExt, TryStreamExt}; use k8s_openapi::api::core::v1::Pod; use serde_json::json; use kube::{ - api::{Api, DeleteParams, ListParams, Patch, PatchParams, PostParams, ResourceExt, WatchEvent}, + api::{Api, DeleteParams, ListParams, Patch, PatchParams, PostParams, ResourceExt}, + runtime::wait::{await_condition, conditions::is_pod_running}, Client, }; @@ -12,10 +12,9 @@ async fn main() -> anyhow::Result<()> { std::env::set_var("RUST_LOG", "info,kube=debug"); tracing_subscriber::fmt::init(); let client = Client::try_default().await?; - let namespace = std::env::var("NAMESPACE").unwrap_or_else(|_| "default".into()); // Manage pods - let pods: Api = Api::namespaced(client, &namespace); + let pods: Api = Api::default_namespaced(client); // Create Pod blog tracing::info!("Creating Pod instance blog"); @@ -37,31 +36,14 @@ async fn main() -> anyhow::Result<()> { let name = o.name(); assert_eq!(p.name(), name); tracing::info!("Created {}", name); - // wait for it.. - std::thread::sleep(std::time::Duration::from_millis(5_000)); } Err(kube::Error::Api(ae)) => assert_eq!(ae.code, 409), // if you skipped delete, for instance Err(e) => return Err(e.into()), // any other case is probably bad } // Watch it phase for a few seconds - let lp = ListParams::default() - .fields(&format!("metadata.name={}", "blog")) - .timeout(10); - let mut stream = pods.watch(&lp, "0").await?.boxed(); - while let Some(status) = stream.try_next().await? { - match status { - WatchEvent::Added(o) => tracing::info!("Added {}", o.name()), - WatchEvent::Modified(o) => { - let s = o.status.as_ref().expect("status exists on pod"); - let phase = s.phase.clone().unwrap_or_default(); - tracing::info!("Modified: {} with phase: {}", o.name(), phase); - } - WatchEvent::Deleted(o) => tracing::info!("Deleted {}", o.name()), - WatchEvent::Error(e) => tracing::error!("Error {}", e), - _ => {} - } - } + let establish = await_condition(pods.clone(), "blog", is_pod_running()); + let _ = tokio::time::timeout(std::time::Duration::from_secs(15), establish).await?; // Verify we can get it tracing::info!("Get Pod blog"); diff --git a/examples/pod_evict.rs b/examples/pod_evict.rs index 18d18107c..62609e831 100644 --- a/examples/pod_evict.rs +++ b/examples/pod_evict.rs @@ -57,7 +57,7 @@ async fn main() -> anyhow::Result<()> { } } - // Clean up the old job record.. + // Evict the pod let ep = EvictParams::default(); let eres = pods.evict(pod_name, &ep).await?; println!("{:?}", eres); diff --git a/kube-client/src/lib.rs b/kube-client/src/lib.rs index ba334d2e5..d5bd580ad 100644 --- a/kube-client/src/lib.rs +++ b/kube-client/src/lib.rs @@ -125,13 +125,19 @@ pub use kube_core as core; // Tests that require a cluster and the complete feature set -// Can be run with `cargo test -p kube-client --lib features=rustls-tls -- --ignored` +// Can be run with `cargo test -p kube-client --lib features=rustls-tls,ws -- --ignored` #[cfg(all(feature = "client", feature = "config"))] #[cfg(test)] mod test { #![allow(unused_imports)] - use crate::{client::ConfigExt, Api, Client, Config}; + use crate::{ + api::{AttachParams, AttachedProcess}, + client::ConfigExt, + Api, Client, Config, ResourceExt, + }; + use futures::{StreamExt, TryStreamExt}; use k8s_openapi::api::core::v1::Pod; + use serde_json::json; use tower::ServiceBuilder; // hard disabled test atm due to k3d rustls issues: https://github.com/kube-rs/kube-rs/issues?q=is%3Aopen+is%3Aissue+label%3Arustls @@ -179,5 +185,270 @@ mod test { Ok(()) } - // TODO: ws test + #[tokio::test] + #[ignore] // needs cluster (will create and edit a pod) + async fn pod_can_use_manual_apis() -> Result<(), Box> { + use kube::api::{DeleteParams, ListParams, Patch, PatchParams, PostParams, WatchEvent}; + + let client = Client::try_default().await?; + let pods: Api = Api::default_namespaced(client); + + // create busybox pod that's alive for at most 30s + let p: Pod = serde_json::from_value(json!({ + "apiVersion": "v1", + "kind": "Pod", + "metadata": { "name": "busybox-kube1" }, + "spec": { + "containers": [{ + "name": "busybox", + "image": "busybox:1.34.1", + "command": ["sh", "-c", "sleep 30"], + }], + } + }))?; + + let pp = PostParams::default(); + match pods.create(&pp, &p).await { + Ok(o) => assert_eq!(p.name(), o.name()), + Err(crate::Error::Api(ae)) => assert_eq!(ae.code, 409), // if we failed to clean-up + Err(e) => return Err(e.into()), // any other case if a failure + } + + // Manual watch-api for it to become ready + // NB: don't do this; using conditions (see pod_api example) is easier and less error prone + let lp = ListParams::default() + .fields(&format!("metadata.name={}", "busybox-kube1")) + .timeout(15); + let mut stream = pods.watch(&lp, "0").await?.boxed(); + while let Some(status) = stream.try_next().await? { + match status { + WatchEvent::Modified(o) => { + let s = o.status.as_ref().expect("status exists on pod"); + let phase = s.phase.clone().unwrap_or_default(); + if phase == "Running" { + break; + } + } + WatchEvent::Error(e) => assert!(false, "watch error: {}", e), + _ => {} + } + } + + // Verify we can get it + let mut pod = pods.get("busybox-kube1").await?; + assert_eq!(p.spec.as_ref().unwrap().containers[0].name, "busybox"); + + // verify replace with explicit resource version + // NB: don't do this; use server side apply + { + assert!(pod.resource_version().is_some()); + pod.spec.as_mut().unwrap().active_deadline_seconds = Some(5); + + let pp = PostParams::default(); + let patched_pod = pods.replace("busybox-kube1", &pp, &pod).await?; + assert_eq!(patched_pod.spec.unwrap().active_deadline_seconds, Some(5)); + } + + // Delete it + let dp = DeleteParams::default(); + pods.delete("busybox-kube1", &dp).await?.map_left(|pdel| { + assert_eq!(pdel.name(), "busybox-kube1"); + }); + + Ok(()) + } + + #[tokio::test] + #[ignore] // needs cluster (will create and attach to a pod) + #[cfg(all(feature = "ws"))] + async fn pod_can_exec() -> Result<(), Box> { + use crate::api::{DeleteParams, EvictParams, ListParams, Patch, PatchParams, WatchEvent}; + + let client = Client::try_default().await?; + let pods: Api = Api::default_namespaced(client); + + // create busybox pod that's alive for at most 30s + let p: Pod = serde_json::from_value(json!({ + "apiVersion": "v1", + "kind": "Pod", + "metadata": { "name": "busybox-kube2" }, + "spec": { + "containers": [{ + "name": "busybox", + "image": "busybox:1.34.1", + "command": ["sh", "-c", "sleep 30"], + }], + } + }))?; + + match pods.create(&Default::default(), &p).await { + Ok(o) => assert_eq!(p.name(), o.name()), + Err(crate::Error::Api(ae)) => assert_eq!(ae.code, 409), // if we failed to clean-up + Err(e) => return Err(e.into()), // any other case if a failure + } + + // Manual watch-api for it to become ready + // NB: don't do this; using conditions (see pod_api example) is easier and less error prone + let lp = ListParams::default() + .fields(&format!("metadata.name={}", "busybox-kube2")) + .timeout(15); + let mut stream = pods.watch(&lp, "0").await?.boxed(); + while let Some(status) = stream.try_next().await? { + match status { + WatchEvent::Modified(o) => { + let s = o.status.as_ref().expect("status exists on pod"); + let phase = s.phase.clone().unwrap_or_default(); + if phase == "Running" { + break; + } + } + WatchEvent::Error(e) => assert!(false, "watch error: {}", e), + _ => {} + } + } + + // Verify exec works and we can get the output + { + let mut attached = pods + .exec( + "busybox-kube2", + vec!["sh", "-c", "for i in $(seq 1 3); do echo $i; done"], + &AttachParams::default().stderr(false), + ) + .await?; + let stdout = tokio_util::io::ReaderStream::new(attached.stdout().unwrap()); + let out = stdout + .filter_map(|r| async { r.ok().and_then(|v| String::from_utf8(v.to_vec()).ok()) }) + .collect::>() + .await + .join(""); + attached.await; + assert_eq!(out.lines().count(), 3); + assert_eq!(out, "1\n2\n3\n"); + } + + // Verify we can write to Stdin + { + use tokio::io::AsyncWriteExt; + let mut attached = pods + .exec( + "busybox-kube2", + vec!["sh"], + &AttachParams::default().stdin(true).stderr(false), + ) + .await?; + let mut stdin_writer = attached.stdin().unwrap(); + let mut stdout_stream = tokio_util::io::ReaderStream::new(attached.stdout().unwrap()); + let next_stdout = stdout_stream.next(); + stdin_writer.write(b"echo test string 1\n").await?; + let stdout = String::from_utf8(next_stdout.await.unwrap().unwrap().to_vec()).unwrap(); + println!("{}", stdout); + assert_eq!(stdout, "test string 1\n"); + + // AttachedProcess resolves with status object. + // Send `exit 1` to get a failure status. + stdin_writer.write(b"exit 1\n").await?; + if let Some(status) = attached.await { + println!("{:?}", status); + assert_eq!(status.status, Some("Failure".to_owned())); + assert_eq!(status.reason, Some("NonZeroExitCode".to_owned())); + } + } + + // evict the pod + let ep = EvictParams { + delete_options: Some(DeleteParams { + grace_period_seconds: Some(0), + ..DeleteParams::default() + }), + ..EvictParams::default() + }; + let eres = pods.evict("busybox-kube2", &ep).await?; + assert_eq!(eres.code, 201); // created + assert_eq!(eres.status, "Success"); + + Ok(()) + } + + #[tokio::test] + #[ignore] // needs cluster (will create and tail logs from a pod) + #[cfg(all(feature = "ws"))] + async fn can_get_pod_logs_and_evict_pod() -> Result<(), Box> { + use crate::{ + api::{DeleteParams, ListParams, Patch, PatchParams, WatchEvent}, + core::subresource::LogParams, + }; + + let client = Client::try_default().await?; + let pods: Api = Api::default_namespaced(client); + + // create busybox pod that's alive for at most 30s + let p: Pod = serde_json::from_value(json!({ + "apiVersion": "v1", + "kind": "Pod", + "metadata": { "name": "busybox-kube3" }, + "spec": { + "containers": [{ + "name": "busybox", + "image": "busybox:1.34.1", + "command": ["sh", "-c", "for i in $(seq 1 5); do echo kube $i; sleep 0.1; done"], + }], + } + }))?; + + match pods.create(&Default::default(), &p).await { + Ok(o) => assert_eq!(p.name(), o.name()), + Err(crate::Error::Api(ae)) => assert_eq!(ae.code, 409), // if we failed to clean-up + Err(e) => return Err(e.into()), // any other case if a failure + } + + // Manual watch-api for it to become ready + // NB: don't do this; using conditions (see pod_api example) is easier and less error prone + let lp = ListParams::default() + .fields(&format!("metadata.name={}", "busybox-kube3")) + .timeout(15); + let mut stream = pods.watch(&lp, "0").await?.boxed(); + while let Some(status) = stream.try_next().await? { + match status { + WatchEvent::Modified(o) => { + let s = o.status.as_ref().expect("status exists on pod"); + let phase = s.phase.clone().unwrap_or_default(); + if phase == "Running" { + break; + } + } + WatchEvent::Error(e) => assert!(false, "watch error: {}", e), + _ => {} + } + } + + // Get current list of logs + let lp = LogParams { + follow: true, + ..LogParams::default() + }; + let mut logs_stream = pods.log_stream("busybox-kube3", &lp).await?.boxed(); + let log_line = logs_stream.try_next().await?.unwrap(); + assert_eq!(log_line, "kube 1\n"); + + // wait for container to finish + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + + let all_logs = pods.logs("busybox-kube3", &Default::default()).await?; + assert_eq!(all_logs, "kube 1\nkube 2\nkube 3\nkube 4\nkube 5\n"); + + // remaining logs should have been buffered internally + assert_eq!(logs_stream.try_next().await?.unwrap(), "kube 2\n"); + assert_eq!(logs_stream.try_next().await?.unwrap(), "kube 3\n"); + assert_eq!(logs_stream.try_next().await?.unwrap(), "kube 4\n"); + assert_eq!(logs_stream.try_next().await?.unwrap(), "kube 5\n"); + + // Delete it + let dp = DeleteParams::default(); + pods.delete("busybox-kube3", &dp).await?.map_left(|pdel| { + assert_eq!(pdel.name(), "busybox-kube3"); + }); + + Ok(()) + } } diff --git a/kube-runtime/src/events.rs b/kube-runtime/src/events.rs index 9f103bb7e..807e23ea8 100644 --- a/kube-runtime/src/events.rs +++ b/kube-runtime/src/events.rs @@ -234,3 +234,40 @@ impl Recorder { Ok(()) } } + +#[cfg(test)] +mod test { + #![allow(unused_imports)] + use super::{Event, EventType, Recorder}; + use k8s_openapi::api::core::v1::{Event as CoreEvent, Service}; + use kube_client::{Api, Client, Resource}; + + #[tokio::test] + #[ignore] // needs cluster (creates a pointless event on the kubernetes main service) + async fn event_recorder_attaches_events() -> Result<(), Box> { + let client = Client::try_default().await?; + + let svcs: Api = Api::namespaced(client.clone(), "default"); + let s = svcs.get("kubernetes").await?; // always a kubernetes service in default + let recorder = Recorder::new(client.clone(), "kube".into(), s.object_ref(&())); + recorder + .publish(Event { + type_: EventType::Normal, + reason: "VeryCoolService".into(), + note: Some("Sending kubernetes to detention".into()), + action: "Test event - plz ignore".into(), + secondary: None, + }) + .await?; + let events: Api = Api::namespaced(client, "default"); + + let event_list = events.list(&Default::default()).await?; + let found_event = event_list + .into_iter() + .find(|e| std::matches!(e.reason.as_deref(), Some("VeryCoolService"))) + .unwrap(); + assert_eq!(found_event.message.unwrap(), "Sending kubernetes to detention"); + + Ok(()) + } +} diff --git a/kube-runtime/src/wait.rs b/kube-runtime/src/wait.rs index 363715a71..1ed616b84 100644 --- a/kube-runtime/src/wait.rs +++ b/kube-runtime/src/wait.rs @@ -152,7 +152,9 @@ impl) -> bool> Condition for F { /// Common conditions to wait for pub mod conditions { pub use super::Condition; - use k8s_openapi::apiextensions_apiserver::pkg::apis::apiextensions::v1::CustomResourceDefinition; + use k8s_openapi::{ + api::core::v1::Pod, apiextensions_apiserver::pkg::apis::apiextensions::v1::CustomResourceDefinition, + }; use kube_client::Resource; /// An await condition that returns `true` once the object has been deleted. @@ -189,6 +191,21 @@ pub mod conditions { } } + /// An await condition for `Pod` that returns `true` once it is running + #[must_use] + pub fn is_pod_running() -> impl Condition { + |obj: Option<&Pod>| { + if let Some(pod) = &obj { + if let Some(status) = &pod.status { + if let Some(phase) = &status.phase { + return phase == "Running"; + } + } + } + false + } + } + /// See [`Condition::not`] #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct Not(pub(super) A); diff --git a/kube/src/lib.rs b/kube/src/lib.rs index 709ddaf0e..694541d0f 100644 --- a/kube/src/lib.rs +++ b/kube/src/lib.rs @@ -184,7 +184,10 @@ pub use kube_core as core; #[cfg(test)] #[cfg(all(feature = "derive", feature = "client"))] mod test { - use crate::{Api, Client, CustomResourceExt, Resource, ResourceExt}; + use crate::{ + api::{DeleteParams, Patch, PatchParams}, + Api, Client, CustomResourceExt, Resource, ResourceExt, + }; use kube_derive::CustomResource; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -226,20 +229,18 @@ mod test { #[ignore] // needs cluster (creates + patches foo crd) #[cfg(all(feature = "derive", feature = "runtime"))] async fn derived_resource_queriable_and_has_subresources() -> Result<(), Box> { - use crate::{ - core::params::{DeleteParams, Patch, PatchParams}, - runtime::wait::{await_condition, conditions}, - }; + use crate::runtime::wait::{await_condition, conditions}; + use serde_json::json; let client = Client::try_default().await?; let ssapply = PatchParams::apply("kube").force(); let crds: Api = Api::all(client.clone()); - // Server-side apply CRD + // Server-side apply CRD and wait for it to get ready crds.patch("foos.clux.dev", &ssapply, &Patch::Apply(Foo::crd())) .await?; - // Wait for it to be ready: let establish = await_condition(crds, "foos.clux.dev", conditions::is_crd_established()); let _ = tokio::time::timeout(std::time::Duration::from_secs(10), establish).await?; + // Use it let foos: Api = Api::default_namespaced(client.clone()); // Apply from generated struct @@ -292,7 +293,8 @@ mod test { } // cleanup - foos.delete("baz", &DeleteParams::default()).await?; + foos.delete_collection(&DeleteParams::default(), &Default::default()) + .await?; Ok(()) } @@ -355,9 +357,20 @@ mod test { use crate::{ core::{DynamicObject, GroupVersionKind}, discovery::{verbs, Discovery, Scope}, + runtime::wait::{await_condition, conditions}, }; + let client = Client::try_default().await?; + // ensure crd is installed + let crds: Api = Api::all(client.clone()); + let ssapply = PatchParams::apply("kube").force(); + crds.patch("foos.clux.dev", &ssapply, &Patch::Apply(Foo::crd())) + .await?; + let establish = await_condition(crds, "foos.clux.dev", conditions::is_crd_established()); + let _ = tokio::time::timeout(std::time::Duration::from_secs(10), establish).await?; + + // run discovery let discovery = Discovery::new(client.clone()) .exclude(&["rbac.authorization.k8s.io"]) // skip something .run() diff --git a/tarpaulin.toml b/tarpaulin.toml index 63c63d2eb..8cc7084fd 100644 --- a/tarpaulin.toml +++ b/tarpaulin.toml @@ -1,13 +1,27 @@ -# Usage cargo tarpaulin -- --test-threads 1 +# Usage: configure your kubernetes context and run cargo tarpaulin +# Runs integration tests (--ignored) plus --lib tests on main crates +# +# NB: cargo tarpaulin -- --test-threads 1 (can help diagnose test interdependencies) +# NB: Tests should complete in about 30-60s after the compile +# NB: full recompiles happen every time: https://github.com/xd009642/tarpaulin/issues/777 even with --skip-clean [one_pass_coverage] workspace = true -features = "kube/derive kube/runtime" +features = "kube/derive kube/runtime kube/ws" color = "Always" ignored = true timeout = "600s" exclude = ["integration"] +# NB: proc macro code is not picked up by tarpaulin - so could maybe skip kube-derive completely excluded_files = ["kube-derive/tests"] # NB: skipping Doctests because they are slow to build and generally marked no_run run-types = ["Tests"] ignore_tests = true + +# We could potentially pass in examples here +# but: they don't help in covering kube-derive, and they force a full recompile +#[example_pass] +#features = "default" +#packages = ["kube-examples"] +#excluded_files = ["examples/"] +#example = ["crd_derive_schema"] From 9102527ea386c5774ebe539f60db4799c73e8a8c Mon Sep 17 00:00:00 2001 From: clux Date: Tue, 7 Dec 2021 19:39:28 +0000 Subject: [PATCH 14/21] write a policy on testing, and classify into 3 types this forces our integration folder to be moved to e2e as it is the heaviest type of test. but crucially it leaves a path for us to run our integration tests in a matrix against various clusters. Signed-off-by: clux --- .github/workflows/ci.yml | 14 +++---- CONTRIBUTING.md | 63 ++++++++++++++++++++++++---- Makefile | 18 ++++---- {integration => e2e}/Cargo.toml | 0 {integration => e2e}/Dockerfile | 0 {integration => e2e}/README.md | 14 +++++-- {integration => e2e}/dapp.rs | 0 {integration => e2e}/deployment.yaml | 0 8 files changed, 81 insertions(+), 28 deletions(-) rename {integration => e2e}/Cargo.toml (100%) rename {integration => e2e}/Dockerfile (100%) rename {integration => e2e}/README.md (60%) rename {integration => e2e}/dapp.rs (100%) rename {integration => e2e}/deployment.yaml (100%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 68c21a36f..145768a75 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -81,8 +81,8 @@ jobs: command: check args: --all - integration: - # Integration tests are linux only + e2e: + # e2e tests are docker on linux runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -113,17 +113,17 @@ jobs: --mount type=bind,source=$HOME/.cargo/registry,target=/root/.cargo/registry \ --mount type=bind,source=$HOME/.cargo/git,target=/root/.cargo/git \ clux/muslrust:stable \ - cargo build -p integration --release -v - cp target/x86_64-unknown-linux-musl/release/dapp integration/ + cargo build -p e2e --release -v + cp target/x86_64-unknown-linux-musl/release/dapp e2e/ - name: Build image - run: "docker build -t clux/kube-dapp:${{ github.sha }} integration/" + run: "docker build -t clux/kube-dapp:${{ github.sha }} e2e/" - name: Import image run: "k3d image import clux/kube-dapp:${{ github.sha }} --cluster kube" - - run: sed -i 's/latest/${{ github.sha }}/g' integration/deployment.yaml + - run: sed -i 's/latest/${{ github.sha }}/g' e2e/deployment.yaml - name: Create resource - run: kubectl apply -f integration/deployment.yaml -n apps + run: kubectl apply -f e2e/deployment.yaml -n apps - run: kubectl get all -n apps - run: kubectl describe jobs/dapp -n apps - name: Wait for job to complete diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index be7bcd5c0..1f4374c90 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -32,20 +32,67 @@ Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md). - **Channel**: Code is built and tested using the **stable** channel of Rust, but documented and formatted with **nightly** [*](https://github.com/kube-rs/kube-rs/issues/707) - **Formatting**: To format the codebase, run `make fmt` - **Documentation** To check documentation, run `make doc` -- **Testing**: To run tests, run `make test`. +- **Testing**: To run tests, run `make test` and see below. ## Testing -Most tests can be run with `cargo test --all`, but because of features, some tests must be run a little more precisely. -For the complete variations see the `make test` target in the `Makefile`. +We have 3 classes of tests. -Some tests and examples require an accessible kubernetes cluster via a `KUBECONFIG` environment variable. +- Unit tests +- Integration tests (requires Kubernetes) +- End to End tests (requires Kubernetes) -- unit tests marked as `#[ignore]` run via `cargo test --all --lib -- --ignored` -- examples run with `cargo run --example=...` -- [integration tests](https://github.com/kube-rs/kube-rs/tree/master/integration) +The last two will try to access the Kubernetes cluster that is your `current-context`; i.e. via your local `KUBECONFIG` evar or `~/.kube/config` file. + +The easiest way set up a minimal Kubernetes cluster for these is with [`k3d`](https://k3d.io/). + +### Unit Tests + +**Most** unit tests are run with `cargo test --lib --doc --all` + +but because of feature-sets, doc tests, and examples, you will need a couple of extra invocations to replicate our CI. + +For the complete variations, run the `make test` target in the `Makefile`. + +### Integration Tests + +These are tests within the crates marked with an *`#[ignore]`* decoration. + +These are slower as they will generally :warn: try to create/modify/delete resources on your `current-context` :warn:. + +Most integration tests are run with `cargo test --all --lib -- --ignored`, but because of feature-sets, you will need a few invocations of these to replicate our CI. See `make test-integration` + +#### End to End Tests + +We have a small set of [e2e tests](https://github.com/kube-rs/kube-rs/tree/master/integration) that tests difference between in-cluster and local configuration. + +These tests are the heaviest tests we have because they require a full `docker build`, image import (or push/pull flow), yaml construction, and `kubectl` usage to verify that the outcome was sufficient. + +To run E2E tests, use (or follow) `make e2e` as appropriate. + +### Test Guidelines + +#### When to add a test + +When adding new non-trivial pieces of logic that results in a drop in coverage you should add a test. + +Cross-reference with the coverage build [![coverage build](https://codecov.io/gh/kube-rs/kube-rs/branch/master/graph/badge.svg?token=9FCqEcyDTZ)](https://codecov.io/gh/kube-rs/kube-rs) and go to your branch. Coverage can also be run locally with [`cargo tarpaulin`](https://github.com/xd009642/tarpaulin) at project root. This will use our [tarpaulin.toml](./tarpaulin.toml) config, and **will run both unit and integration** tests. + +#### What type of test + +- Unit tests **MUST NOT** try to contact a Kubernetes cluster +- Integration tests **MUST NOT** be used when a unit test is sufficient +- Integration tests **MUST NOT** assume existence of non-standard objects in the cluster +- Integration tests **MUST NOT** cross-depend on other unit tests completing (and installing what you need) +- E2E tests **MUST NOT** be used where an integration test is sufficient + +In general: **use the least powerful method** of testing available to you: + +- use unit tests in `kube-core` +- use unit tests in `kube-client` (and in rare cases integration tests) +- use unit tests in `kube-runtime` (and occassionally integration tests) +- use e2e tests when testing differences between in-cluster and local configuration -The easiest way set up a minimal kubernetes cluster for these is with [`k3d`](https://k3d.io/). ## Support ### Documentation diff --git a/Makefile b/Makefile index ffcb21591..37e9093f5 100644 --- a/Makefile +++ b/Makefile @@ -34,12 +34,12 @@ readme: rustdoc README.md --test --edition=2021 e2e: dapp - ls -lah integration/ - docker build -t clux/kube-dapp:$(VERSION) integration/ + ls -lah e2e/ + docker build -t clux/kube-dapp:$(VERSION) e2e/ k3d image import clux/kube-dapp:$(VERSION) --cluster main - sed -i 's/latest/$(VERSION)/g' integration/deployment.yaml - kubectl apply -f integration/deployment.yaml - sed -i 's/$(VERSION)/latest/g' integration/deployment.yaml + sed -i 's/latest/$(VERSION)/g' e2e/deployment.yaml + kubectl apply -f e2e/deployment.yaml + sed -i 's/$(VERSION)/latest/g' e2e/deployment.yaml kubectl get all -n apps kubectl describe jobs/dapp -n apps kubectl wait --for=condition=complete job/dapp -n apps --timeout=50s || kubectl logs -f job/dapp -n apps @@ -50,13 +50,13 @@ dapp: docker run \ -v cargo-cache:/root/.cargo/registry \ -v "$$PWD:/volume" -w /volume \ - --rm -it clux/muslrust:stable cargo build --release -p integration - cp target/x86_64-unknown-linux-musl/release/dapp integration/dapp - chmod +x integration/dapp + --rm -it clux/muslrust:stable cargo build --release -p e2e + cp target/x86_64-unknown-linux-musl/release/dapp e2e/dapp + chmod +x e2e/dapp k3d: k3d cluster create --servers 1 --agents 1 main \ --k3s-agent-arg '--kubelet-arg=eviction-hard=imagefs.available<1%,nodefs.available<1%' \ --k3s-agent-arg '--kubelet-arg=eviction-minimum-reclaim=imagefs.available=1%,nodefs.available=1%' -.PHONY: doc build fmt clippy test readme k3d integration +.PHONY: doc build fmt clippy test readme k3d e2e diff --git a/integration/Cargo.toml b/e2e/Cargo.toml similarity index 100% rename from integration/Cargo.toml rename to e2e/Cargo.toml diff --git a/integration/Dockerfile b/e2e/Dockerfile similarity index 100% rename from integration/Dockerfile rename to e2e/Dockerfile diff --git a/integration/README.md b/e2e/README.md similarity index 60% rename from integration/README.md rename to e2e/README.md index 36e6cff73..feccfe938 100644 --- a/integration/README.md +++ b/e2e/README.md @@ -1,11 +1,17 @@ -# integration tests +# E2E tests -This is a working example of a kubernetes application `dapp`, that is deployed on CI during the `integration` job via [our ci workflow](https://github.com/kube-rs/kube-rs/blob/2b5e4ad788366125448ad40eadaf68cf9ceeaf31/.github/workflows/ci.yml#L58-L107). It is here to ensure in-cluster configuration is working. +Small set of tests to verify differences between local and in-cluster development. -## Behavior +In general, [you do not want to run or develop E2E tests](../CONTRIBUTING.md#test-guidelines). + +## dapp + +A working example of a kubernetes application `dapp` deployed on CI during the `e2e` job via [our ci workflow](https://github.com/kube-rs/kube-rs/blob/2b5e4ad788366125448ad40eadaf68cf9ceeaf31/.github/workflows/ci.yml#L58-L107). It is here to ensure in-cluster configuration is working. + +### Behavior The app currently only does what the `job_api` example does, but from within the cluster, so it needs the rbac permissions to `create` a `job` in `batch`. -## Github Actions +### Github Actions General process, optimized for time. - compile the image with [muslrust](https://github.com/clux/muslrust) diff --git a/integration/dapp.rs b/e2e/dapp.rs similarity index 100% rename from integration/dapp.rs rename to e2e/dapp.rs diff --git a/integration/deployment.yaml b/e2e/deployment.yaml similarity index 100% rename from integration/deployment.yaml rename to e2e/deployment.yaml From 97e6c478c180da6aeff1d5e954176db399320d5f Mon Sep 17 00:00:00 2001 From: clux Date: Tue, 7 Dec 2021 19:42:22 +0000 Subject: [PATCH 15/21] missing rename in root toml Signed-off-by: clux --- Cargo.toml | 2 +- tarpaulin.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index cab7d7780..f93c3463b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,6 @@ members = [ "kube-runtime", # internal - "integration", + "e2e", "examples", ] diff --git a/tarpaulin.toml b/tarpaulin.toml index 8cc7084fd..30bf94c4a 100644 --- a/tarpaulin.toml +++ b/tarpaulin.toml @@ -11,7 +11,7 @@ features = "kube/derive kube/runtime kube/ws" color = "Always" ignored = true timeout = "600s" -exclude = ["integration"] +exclude = ["e2e"] # NB: proc macro code is not picked up by tarpaulin - so could maybe skip kube-derive completely excluded_files = ["kube-derive/tests"] # NB: skipping Doctests because they are slow to build and generally marked no_run From b32a036718f5bba064c6e5aa3ed6b356b0e5fba0 Mon Sep 17 00:00:00 2001 From: clux Date: Tue, 7 Dec 2021 19:50:30 +0000 Subject: [PATCH 16/21] final rename piece Signed-off-by: clux --- e2e/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/Cargo.toml b/e2e/Cargo.toml index b5c787772..f2f73f745 100644 --- a/e2e/Cargo.toml +++ b/e2e/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "integration" +name = "e2e" version = "0.1.0" authors = ["clux "] publish = false From 2bdd9b3cf483b71d9f8583397c750c7e4a38d17a Mon Sep 17 00:00:00 2001 From: clux Date: Tue, 7 Dec 2021 20:08:14 +0000 Subject: [PATCH 17/21] missing rename Signed-off-by: clux --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1f4374c90..bd75dd5ac 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -64,7 +64,7 @@ Most integration tests are run with `cargo test --all --lib -- --ignored`, but b #### End to End Tests -We have a small set of [e2e tests](https://github.com/kube-rs/kube-rs/tree/master/integration) that tests difference between in-cluster and local configuration. +We have a small set of [e2e tests](https://github.com/kube-rs/kube-rs/tree/master/e2e) that tests difference between in-cluster and local configuration. These tests are the heaviest tests we have because they require a full `docker build`, image import (or push/pull flow), yaml construction, and `kubectl` usage to verify that the outcome was sufficient. From 227323d24cf9f77a0ed2cad3eb6c0d7ff773e8b6 Mon Sep 17 00:00:00 2001 From: clux Date: Tue, 7 Dec 2021 20:16:31 +0000 Subject: [PATCH 18/21] markdown fixes plus review feedback on contributing Signed-off-by: clux --- CONTRIBUTING.md | 12 +++++------- e2e/README.md | 2 +- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bd75dd5ac..ec0b5877e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -44,25 +44,23 @@ We have 3 classes of tests. The last two will try to access the Kubernetes cluster that is your `current-context`; i.e. via your local `KUBECONFIG` evar or `~/.kube/config` file. -The easiest way set up a minimal Kubernetes cluster for these is with [`k3d`](https://k3d.io/). +The easiest way set up a minimal Kubernetes cluster for these is with [`k3d`](https://k3d.io/) (`make k3d`). ### Unit Tests -**Most** unit tests are run with `cargo test --lib --doc --all` - -but because of feature-sets, doc tests, and examples, you will need a couple of extra invocations to replicate our CI. +**Most** unit tests are run with `cargo test --lib --doc --all`, but because of feature-sets, doc tests, and examples, you will need a couple of extra invocations to replicate our CI. For the complete variations, run the `make test` target in the `Makefile`. ### Integration Tests -These are tests within the crates marked with an *`#[ignore]`* decoration. +Slower set of tests within the crates marked with an **`#[ignore]`** attribute. -These are slower as they will generally :warn: try to create/modify/delete resources on your `current-context` :warn:. +:warning: These **WILL** try to modify resources in your current cluster :warning: Most integration tests are run with `cargo test --all --lib -- --ignored`, but because of feature-sets, you will need a few invocations of these to replicate our CI. See `make test-integration` -#### End to End Tests +### End to End Tests We have a small set of [e2e tests](https://github.com/kube-rs/kube-rs/tree/master/e2e) that tests difference between in-cluster and local configuration. diff --git a/e2e/README.md b/e2e/README.md index feccfe938..b5cebbe39 100644 --- a/e2e/README.md +++ b/e2e/README.md @@ -2,7 +2,7 @@ Small set of tests to verify differences between local and in-cluster development. -In general, [you do not want to run or develop E2E tests](../CONTRIBUTING.md#test-guidelines). +**[You probably do not want to make a new E2E test](../CONTRIBUTING.md#test-guidelines)**. ## dapp From d6ed19093ce516f393491f7edec165ce8b1a8128 Mon Sep 17 00:00:00 2001 From: clux Date: Tue, 7 Dec 2021 21:17:24 +0000 Subject: [PATCH 19/21] fix test names and wrong feature requirements on them Signed-off-by: clux --- Makefile | 3 +++ kube-client/src/lib.rs | 17 ++++++++--------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index 37e9093f5..d91d0e580 100644 --- a/Makefile +++ b/Makefile @@ -30,6 +30,9 @@ test-integration: cargo run -p kube-examples --example crd_derive cargo run -p kube-examples --example crd_api +coverage: + cargo tarpaulin --out=Html --output-dir=covout + readme: rustdoc README.md --test --edition=2021 diff --git a/kube-client/src/lib.rs b/kube-client/src/lib.rs index d5bd580ad..77dacfbbc 100644 --- a/kube-client/src/lib.rs +++ b/kube-client/src/lib.rs @@ -220,8 +220,8 @@ mod test { .fields(&format!("metadata.name={}", "busybox-kube1")) .timeout(15); let mut stream = pods.watch(&lp, "0").await?.boxed(); - while let Some(status) = stream.try_next().await? { - match status { + while let Some(ev) = stream.try_next().await? { + match ev { WatchEvent::Modified(o) => { let s = o.status.as_ref().expect("status exists on pod"); let phase = s.phase.clone().unwrap_or_default(); @@ -261,7 +261,7 @@ mod test { #[tokio::test] #[ignore] // needs cluster (will create and attach to a pod) #[cfg(all(feature = "ws"))] - async fn pod_can_exec() -> Result<(), Box> { + async fn pod_can_exec_and_evict() -> Result<(), Box> { use crate::api::{DeleteParams, EvictParams, ListParams, Patch, PatchParams, WatchEvent}; let client = Client::try_default().await?; @@ -293,8 +293,8 @@ mod test { .fields(&format!("metadata.name={}", "busybox-kube2")) .timeout(15); let mut stream = pods.watch(&lp, "0").await?.boxed(); - while let Some(status) = stream.try_next().await? { - match status { + while let Some(ev) = stream.try_next().await? { + match ev { WatchEvent::Modified(o) => { let s = o.status.as_ref().expect("status exists on pod"); let phase = s.phase.clone().unwrap_or_default(); @@ -372,8 +372,7 @@ mod test { #[tokio::test] #[ignore] // needs cluster (will create and tail logs from a pod) - #[cfg(all(feature = "ws"))] - async fn can_get_pod_logs_and_evict_pod() -> Result<(), Box> { + async fn can_get_and_stream_pod_logs() -> Result<(), Box> { use crate::{ api::{DeleteParams, ListParams, Patch, PatchParams, WatchEvent}, core::subresource::LogParams, @@ -408,8 +407,8 @@ mod test { .fields(&format!("metadata.name={}", "busybox-kube3")) .timeout(15); let mut stream = pods.watch(&lp, "0").await?.boxed(); - while let Some(status) = stream.try_next().await? { - match status { + while let Some(ev) = stream.try_next().await? { + match ev { WatchEvent::Modified(o) => { let s = o.status.as_ref().expect("status exists on pod"); let phase = s.phase.clone().unwrap_or_default(); From e2c6201e29e74b615b44acd4aa82585743aa3229 Mon Sep 17 00:00:00 2001 From: clux Date: Tue, 7 Dec 2021 21:20:40 +0000 Subject: [PATCH 20/21] keep log + evict in same test Signed-off-by: clux --- kube-client/src/lib.rs | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/kube-client/src/lib.rs b/kube-client/src/lib.rs index 77dacfbbc..3bf938ad7 100644 --- a/kube-client/src/lib.rs +++ b/kube-client/src/lib.rs @@ -187,7 +187,7 @@ mod test { #[tokio::test] #[ignore] // needs cluster (will create and edit a pod) - async fn pod_can_use_manual_apis() -> Result<(), Box> { + async fn pod_can_use_core_apis() -> Result<(), Box> { use kube::api::{DeleteParams, ListParams, Patch, PatchParams, PostParams, WatchEvent}; let client = Client::try_default().await?; @@ -261,7 +261,7 @@ mod test { #[tokio::test] #[ignore] // needs cluster (will create and attach to a pod) #[cfg(all(feature = "ws"))] - async fn pod_can_exec_and_evict() -> Result<(), Box> { + async fn pod_can_exec_and_write_to_stdin() -> Result<(), Box> { use crate::api::{DeleteParams, EvictParams, ListParams, Patch, PatchParams, WatchEvent}; let client = Client::try_default().await?; @@ -355,24 +355,18 @@ mod test { } } - // evict the pod - let ep = EvictParams { - delete_options: Some(DeleteParams { - grace_period_seconds: Some(0), - ..DeleteParams::default() - }), - ..EvictParams::default() - }; - let eres = pods.evict("busybox-kube2", &ep).await?; - assert_eq!(eres.code, 201); // created - assert_eq!(eres.status, "Success"); + // Delete it + let dp = DeleteParams::default(); + pods.delete("busybox-kube2", &dp).await?.map_left(|pdel| { + assert_eq!(pdel.name(), "busybox-kube2"); + }); Ok(()) } #[tokio::test] #[ignore] // needs cluster (will create and tail logs from a pod) - async fn can_get_and_stream_pod_logs() -> Result<(), Box> { + async fn can_get_pod_logs_and_evict() -> Result<(), Box> { use crate::{ api::{DeleteParams, ListParams, Patch, PatchParams, WatchEvent}, core::subresource::LogParams, @@ -442,11 +436,17 @@ mod test { assert_eq!(logs_stream.try_next().await?.unwrap(), "kube 4\n"); assert_eq!(logs_stream.try_next().await?.unwrap(), "kube 5\n"); - // Delete it - let dp = DeleteParams::default(); - pods.delete("busybox-kube3", &dp).await?.map_left(|pdel| { - assert_eq!(pdel.name(), "busybox-kube3"); - }); + // evict the pod + let ep = EvictParams { + delete_options: Some(DeleteParams { + grace_period_seconds: Some(0), + ..DeleteParams::default() + }), + ..EvictParams::default() + }; + let eres = pods.evict("busybox-kube3", &ep).await?; + assert_eq!(eres.code, 201); // created + assert_eq!(eres.status, "Success"); Ok(()) } From 87030368dc3486c28460626896df4889a138e278 Mon Sep 17 00:00:00 2001 From: clux Date: Tue, 7 Dec 2021 21:25:20 +0000 Subject: [PATCH 21/21] move import around.. Signed-off-by: clux --- kube-client/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kube-client/src/lib.rs b/kube-client/src/lib.rs index 3bf938ad7..0de60eded 100644 --- a/kube-client/src/lib.rs +++ b/kube-client/src/lib.rs @@ -262,7 +262,7 @@ mod test { #[ignore] // needs cluster (will create and attach to a pod) #[cfg(all(feature = "ws"))] async fn pod_can_exec_and_write_to_stdin() -> Result<(), Box> { - use crate::api::{DeleteParams, EvictParams, ListParams, Patch, PatchParams, WatchEvent}; + use crate::api::{DeleteParams, ListParams, Patch, PatchParams, WatchEvent}; let client = Client::try_default().await?; let pods: Api = Api::default_namespaced(client); @@ -368,7 +368,7 @@ mod test { #[ignore] // needs cluster (will create and tail logs from a pod) async fn can_get_pod_logs_and_evict() -> Result<(), Box> { use crate::{ - api::{DeleteParams, ListParams, Patch, PatchParams, WatchEvent}, + api::{DeleteParams, EvictParams, ListParams, Patch, PatchParams, WatchEvent}, core::subresource::LogParams, };