diff --git a/tests/contest/contest/src/main.rs b/tests/contest/contest/src/main.rs index bb1019ca8..e2eda3671 100644 --- a/tests/contest/contest/src/main.rs +++ b/tests/contest/contest/src/main.rs @@ -16,6 +16,7 @@ use crate::tests::hooks::get_hooks_tests; use crate::tests::hostname::get_hostname_test; use crate::tests::intel_rdt::get_intel_rdt_test; use crate::tests::io_priority::get_io_priority_test; +use crate::tests::kill::get_kill_test; use crate::tests::lifecycle::{ContainerCreate, ContainerLifecycle}; use crate::tests::linux_ns_itype::get_ns_itype_tests; use crate::tests::mounts_recursive::get_mounts_recursive_test; @@ -125,6 +126,7 @@ fn main() -> Result<()> { let process_rlimtis = get_process_rlimits_test(); let no_pivot = get_no_pivot_test(); let process_oom_score_adj = get_process_oom_score_adj_test(); + let kill = get_kill_test(); tm.add_test_group(Box::new(cl)); tm.add_test_group(Box::new(cc)); @@ -154,6 +156,7 @@ fn main() -> Result<()> { tm.add_test_group(Box::new(process_rlimtis)); tm.add_test_group(Box::new(no_pivot)); tm.add_test_group(Box::new(process_oom_score_adj)); + tm.add_test_group(Box::new(kill)); tm.add_test_group(Box::new(io_priority_test)); tm.add_cleanup(Box::new(cgroups::cleanup_v1)); diff --git a/tests/contest/contest/src/tests/kill/kill_test.rs b/tests/contest/contest/src/tests/kill/kill_test.rs new file mode 100644 index 000000000..4ffcb31da --- /dev/null +++ b/tests/contest/contest/src/tests/kill/kill_test.rs @@ -0,0 +1,146 @@ +use std::time::Duration; + +use anyhow::{anyhow, Context, Result}; +use oci_spec::runtime::{ProcessBuilder, Spec, SpecBuilder}; +use test_framework::{Test, TestGroup, TestResult}; + +use crate::tests::lifecycle::ContainerLifecycle; + +fn create_spec(args: &[&str]) -> Result { + let args_vec: Vec = args.iter().map(|&a| a.to_string()).collect(); + let spec = SpecBuilder::default() + .process( + ProcessBuilder::default() + .args(args_vec) + .build() + .context("failed to build process spec")?, + ) + .build() + .context("failed to build spec")?; + Ok(spec) +} + +fn kill_with_empty_id_test() -> TestResult { + let mut container = ContainerLifecycle::new(); + + // kill with empty id + container.set_id(""); + let result = match container.kill() { + TestResult::Failed(_) => TestResult::Passed, + TestResult::Passed => TestResult::Failed(anyhow!( + "Expected killing container with empty id to fail, but was successful" + )), + _ => TestResult::Failed(anyhow!( + "Unexpected killing container with empty id test result" + )), + }; + container.delete(); + result +} + +fn kill_non_existed_container() -> TestResult { + let mut container = ContainerLifecycle::new(); + + // kill for non existed container + container.set_id("non-existent-container-id"); + let result = match container.kill() { + TestResult::Failed(_) => TestResult::Passed, + TestResult::Passed => TestResult::Failed(anyhow!( + "Expected killing non existed container to fail, but was successful" + )), + _ => TestResult::Failed(anyhow!( + "Unexpected killing non existed container test result" + )), + }; + container.delete(); + result +} +fn kill_created_container_test() -> TestResult { + let container = ContainerLifecycle::new(); + + // kill created container + match container.create() { + TestResult::Passed => {} + _ => return TestResult::Failed(anyhow!("Failed to create container")), + } + let result = container.kill(); + container.delete(); + result +} + +fn kill_stopped_container_test() -> TestResult { + let container = ContainerLifecycle::new(); + let spec = create_spec(&["true"]).unwrap(); + + // kill stopped container + match container.create_with_spec(spec) { + TestResult::Passed => {} + _ => return TestResult::Failed(anyhow!("Failed to create container")), + } + match container.start() { + TestResult::Passed => {} + _ => return TestResult::Failed(anyhow!("Failed to start container")), + } + container.waiting_for_status(Duration::from_secs(10), Duration::from_secs(1), "stopped"); + let result = match container.kill() { + TestResult::Failed(_) => TestResult::Passed, + TestResult::Passed => TestResult::Failed(anyhow!("Expected failure but got success")), + _ => TestResult::Failed(anyhow!("Unexpected test result")), + }; + container.delete(); + result +} + +fn kill_start_container_test() -> TestResult { + let container = ContainerLifecycle::new(); + let spec = create_spec(&["sleep", "30"]).unwrap(); + + // kill start container + match container.create_with_spec(spec) { + TestResult::Passed => {} + _ => return TestResult::Failed(anyhow!("Failed to recreate container")), + } + + match container.start() { + TestResult::Passed => {} + TestResult::Failed(err) => { + return TestResult::Failed(anyhow!("Failed to start container: {:?}", err)); + } + _ => unreachable!(), + } + container.waiting_for_status(Duration::from_secs(10), Duration::from_secs(1), "running"); + let result = container.kill(); + container.delete(); + result +} + +pub fn get_kill_test() -> TestGroup { + let mut test_group = TestGroup::new("kill_container"); + + let kill_with_empty_id_test = + Test::new("kill_with_empty_id_test", Box::new(kill_with_empty_id_test)); + let kill_non_existed_container = Test::new( + "kill_non_existed_container", + Box::new(kill_non_existed_container), + ); + let kill_created_container_test = Test::new( + "kill_created_container_test", + Box::new(kill_created_container_test), + ); + let kill_stopped_container_test = Test::new( + "kill_stopped_container_test", + Box::new(kill_stopped_container_test), + ); + let kill_start_container_test = Test::new( + "kill_start_container_test", + Box::new(kill_start_container_test), + ); + test_group.add(vec![ + Box::new(kill_with_empty_id_test), + Box::new(kill_non_existed_container), + Box::new(kill_created_container_test), + Box::new(kill_stopped_container_test), + Box::new(kill_start_container_test), + ]); + test_group +} diff --git a/tests/contest/contest/src/tests/kill/mod.rs b/tests/contest/contest/src/tests/kill/mod.rs new file mode 100644 index 000000000..ff6cfe9fd --- /dev/null +++ b/tests/contest/contest/src/tests/kill/mod.rs @@ -0,0 +1,3 @@ +mod kill_test; + +pub use kill_test::get_kill_test; diff --git a/tests/contest/contest/src/tests/lifecycle/container_lifecycle.rs b/tests/contest/contest/src/tests/lifecycle/container_lifecycle.rs index 6b925feea..c4cdcfd88 100644 --- a/tests/contest/contest/src/tests/lifecycle/container_lifecycle.rs +++ b/tests/contest/contest/src/tests/lifecycle/container_lifecycle.rs @@ -1,11 +1,13 @@ use std::thread::sleep; -use std::time::Duration; +use std::time::{Duration, Instant}; +use anyhow::anyhow; +use oci_spec::runtime::Spec; use test_framework::{TestResult, TestableGroup}; use super::util::criu_installed; use super::{checkpoint, create, delete, exec, kill, start, state}; -use crate::utils::{generate_uuid, prepare_bundle}; +use crate::utils::{generate_uuid, get_state, prepare_bundle, set_config, State}; // By experimenting, somewhere around 50 is enough for youki process // to get the kill signal and shut down @@ -33,10 +35,23 @@ impl ContainerLifecycle { } } + pub fn set_id(&mut self, id: &str) { + self.container_id = id.to_string(); + } + + pub fn get_id(&self) -> &str { + &self.container_id + } + pub fn create(&self) -> TestResult { create::create(self.project_path.path(), &self.container_id).into() } + pub fn create_with_spec(&self, spec: Spec) -> TestResult { + set_config(&self.project_path, &spec).unwrap(); + create::create(self.project_path.path(), &self.container_id).into() + } + #[allow(dead_code)] pub fn exec(&self, cmd: Vec<&str>, expected_output: Option<&str>) -> TestResult { exec::exec( @@ -86,6 +101,31 @@ impl ContainerLifecycle { &self.container_id, ) } + + pub fn waiting_for_status( + &self, + retry_timeout: Duration, + poll_interval: Duration, + target_status: &str, + ) -> TestResult { + let start = Instant::now(); + while start.elapsed() < retry_timeout { + let (out, err) = get_state(&self.container_id, &self.project_path).unwrap(); + if !err.is_empty() { + self.kill(); + self.delete(); + return TestResult::Failed(anyhow!("error in state : {}", err)); + } + + let state: State = serde_json::from_str(&out).unwrap(); + + if state.status == target_status { + return TestResult::Passed; + } + sleep(poll_interval); + } + TestResult::Failed(anyhow!("error pod status is not update")) + } } impl TestableGroup for ContainerLifecycle { diff --git a/tests/contest/contest/src/tests/mod.rs b/tests/contest/contest/src/tests/mod.rs index 6e8e39be8..a741016f3 100644 --- a/tests/contest/contest/src/tests/mod.rs +++ b/tests/contest/contest/src/tests/mod.rs @@ -6,6 +6,7 @@ pub mod hooks; pub mod hostname; pub mod intel_rdt; pub mod io_priority; +pub mod kill; pub mod lifecycle; pub mod linux_ns_itype; pub mod mounts_recursive;