Skip to content
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

testsys: Add support for k8s workloads #2830

Merged
merged 1 commit into from
Mar 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,26 @@ cargo make -e TESTSYS_TEST=migration test

To see the state of the tests as they run use `cargo make watch-test`.

### Testing Workloads

Workload tests are tests designed to run as an orchestrated container.
A workload test is defined in `Test.toml` with a map named `workloads`.

```toml
[aws-nvidia]
workloads = { <WORKLOAD-NAME> = "<WORKLOAD-IMAGE-URI>" }
```

To run the workload test set `TESTSYS_TEST=workload` in the `cargo make test` call.

```shell
cargo make -e TESTSYS_TEST=workload test
```

To see the state of the tests as they run use `cargo make watch-test`.

For more information can be found in the [TestSys workload documentation](https://github.com/bottlerocket-os/bottlerocket-test-system/tree/develop/bottlerocket/tests/workload).

### Custom Test Types

Custom tests can be run with TestSys by calling `cargo make -e TESTSYS_TEST=<CUSTOM-TEST-NAME> test -f <PATH-TO-TEMPLATED-YAML>`.
Expand Down
15 changes: 15 additions & 0 deletions tools/testsys-config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,9 @@ pub struct GenericVariantConfig {
pub control_plane_endpoint: Option<String>,
/// The path to userdata that should be used for Bottlerocket launch
pub userdata: Option<String>,
/// The workload tests that should be run
#[serde(default)]
pub workloads: BTreeMap<String, String>,
#[serde(default)]
pub dev: DeveloperConfig,
}
Expand All @@ -280,6 +283,12 @@ impl GenericVariantConfig {
self.secrets
};

let workloads = if self.workloads.is_empty() {
other.workloads
} else {
self.workloads
};

Self {
cluster_names,
instance_type: self.instance_type.or(other.instance_type),
Expand All @@ -289,6 +298,7 @@ impl GenericVariantConfig {
conformance_registry: self.conformance_registry.or(other.conformance_registry),
control_plane_endpoint: self.control_plane_endpoint.or(other.control_plane_endpoint),
userdata: self.userdata.or(other.userdata),
workloads,
dev: self.dev.merge(other.dev),
}
}
Expand Down Expand Up @@ -353,6 +363,7 @@ pub struct TestsysImages {
pub sonobuoy_test_agent_image: Option<String>,
pub ecs_test_agent_image: Option<String>,
pub migration_test_agent_image: Option<String>,
pub k8s_workload_agent_image: Option<String>,
pub controller_image: Option<String>,
pub testsys_agent_pull_secret: Option<String>,
}
Expand Down Expand Up @@ -380,6 +391,7 @@ impl TestsysImages {
sonobuoy_test_agent_image: Some(format!("{}/sonobuoy-test-agent:{tag}", registry)),
ecs_test_agent_image: Some(format!("{}/ecs-test-agent:{tag}", registry)),
migration_test_agent_image: Some(format!("{}/migration-test-agent:{tag}", registry)),
k8s_workload_agent_image: Some(format!("{}/k8s-workload-agent:{tag}", registry)),
controller_image: Some(format!("{}/controller:{tag}", registry)),
testsys_agent_pull_secret: None,
}
Expand Down Expand Up @@ -409,6 +421,9 @@ impl TestsysImages {
migration_test_agent_image: self
.migration_test_agent_image
.or(other.migration_test_agent_image),
k8s_workload_agent_image: self
.k8s_workload_agent_image
.or(other.k8s_workload_agent_image),
controller_image: self.controller_image.or(other.controller_image),
testsys_agent_pull_secret: self
.testsys_agent_pull_secret
Expand Down
6 changes: 6 additions & 0 deletions tools/testsys/src/aws_ecs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,12 @@ impl CrdCreator for AwsEcsCreator {
Ok(CreateCrdOutput::NewCrd(Box::new(Crd::Test(test_crd))))
}

async fn workload_crd<'a>(&self, _test_input: TestInput<'a>) -> Result<CreateCrdOutput> {
Err(error::Error::Invalid {
what: "Workload testing is not supported for non-k8s variants".to_string(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious, will adding ECS be a follow on from this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Ecs workload testing will be available after TestSys 0.0.6 is out.

})
}

fn additional_fields(&self, _test_type: &str) -> BTreeMap<String, String> {
btreemap! {"region".to_string() => self.region.clone()}
}
Expand Down
8 changes: 7 additions & 1 deletion tools/testsys/src/aws_k8s.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::crds::{
};
use crate::error::{self, Result};
use crate::migration::migration_crd;
use crate::sonobuoy::sonobuoy_crd;
use crate::sonobuoy::{sonobuoy_crd, workload_crd};
use bottlerocket_types::agent_config::{
ClusterType, CreationPolicy, EksClusterConfig, EksctlConfig, K8sVersion,
};
Expand Down Expand Up @@ -169,6 +169,12 @@ impl CrdCreator for AwsK8sCreator {
)?))))
}

async fn workload_crd<'a>(&self, test_input: TestInput<'a>) -> Result<CreateCrdOutput> {
Ok(CreateCrdOutput::NewCrd(Box::new(Crd::Test(workload_crd(
test_input,
)?))))
}

fn additional_fields(&self, _test_type: &str) -> BTreeMap<String, String> {
btreemap! {"region".to_string() => self.region.clone()}
}
Expand Down
31 changes: 31 additions & 0 deletions tools/testsys/src/crds.rs
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,9 @@ pub(crate) trait CrdCreator: Sync {
/// Create a testing CRD for this variant of Bottlerocket.
async fn test_crd<'a>(&self, test_input: TestInput<'a>) -> Result<CreateCrdOutput>;

/// Create a workload testing CRD for this variant of Bottlerocket.
async fn workload_crd<'a>(&self, test_input: TestInput<'a>) -> Result<CreateCrdOutput>;

/// Create a set of additional fields that may be used by an externally defined agent on top of
/// the ones in `CrdInput`
fn additional_fields(&self, _test_type: &str) -> BTreeMap<String, String> {
Expand Down Expand Up @@ -411,6 +414,34 @@ pub(crate) trait CrdCreator: Sync {
crds.push(crd)
}
}
KnownTestType::Workload => {
let bottlerocket_output = self
.bottlerocket_crd(BottlerocketInput {
cluster_crd_name: &cluster_crd_name,
image_id: self.image_id(crd_input)?,
test_type,
crd_input,
})
.await?;
let bottlerocket_crd_name = bottlerocket_output.crd_name();
if let Some(crd) = bottlerocket_output.crd() {
debug!("Bottlerocket crd was created for '{}'", cluster_name);
crds.push(crd)
}
let test_output = self
.workload_crd(TestInput {
cluster_crd_name: &cluster_crd_name,
bottlerocket_crd_name: &bottlerocket_crd_name,
test_type,
crd_input,
prev_tests: Default::default(),
name_suffix: None,
})
.await?;
if let Some(crd) = test_output.crd() {
crds.push(crd)
}
}
KnownTestType::Migration => {
let image_id = if let Some(image_id) = &crd_input.starting_image_id {
debug!(
Expand Down
26 changes: 26 additions & 0 deletions tools/testsys/src/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,10 @@ struct CliConfig {
/// Specify the path to the userdata that should be added for Bottlerocket launch
#[clap(long, env = "TESTSYS_USERDATA")]
pub userdata: Option<String>,

/// A set of workloads that should be run for a workload test (--workload my-workload=<WORKLOAD-IMAGE>)
#[clap(long = "workload", parse(try_from_str = parse_workloads), number_of_values = 1)]
pub workloads: Vec<(String, String)>,
}

impl From<CliConfig> for GenericVariantConfig {
Expand All @@ -165,6 +169,7 @@ impl From<CliConfig> for GenericVariantConfig {
control_plane_endpoint: val.control_plane_endpoint,
userdata: val.userdata,
dev: Default::default(),
workloads: val.workloads.into_iter().collect(),
}
}
}
Expand Down Expand Up @@ -400,6 +405,17 @@ fn parse_key_val(s: &str) -> Result<(String, SecretName)> {
))
}

fn parse_workloads(s: &str) -> Result<(String, String)> {
let mut iter = s.splitn(2, '=');
let key = iter.next().context(error::InvalidSnafu {
what: "Key is missing",
})?;
let value = iter.next().context(error::InvalidSnafu {
what: "Value is missing",
})?;
Ok((key.to_string(), value.to_string()))
}

#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "lowercase")]
pub enum KnownTestType {
Expand All @@ -415,6 +431,8 @@ pub enum KnownTestType {
/// be created at the starting version, migrated to the target version and back to the starting
/// version with validation testing.
Migration,
/// Workload testing is used to test specific workloads on a set of Bottlerocket nodes.
Workload,
}

/// If a test type is one that is supported by TestSys it will be created as `Known(KnownTestType)`.
Expand Down Expand Up @@ -486,6 +504,13 @@ pub(crate) struct TestsysImages {
)]
pub(crate) migration_test: Option<String>,

/// K8s workload agent URI. If not provided the latest released test agent will be used.
#[clap(
long = "k8s-workload-agent-image",
env = "TESTSYS_K8S_WORKLOAD_AGENT_IMAGE"
)]
pub(crate) k8s_workload: Option<String>,

/// TestSys controller URI. If not provided the latest released controller will be used.
#[clap(long = "controller-image", env = "TESTSYS_CONTROLLER_IMAGE")]
pub(crate) controller_uri: Option<String>,
Expand All @@ -509,6 +534,7 @@ impl From<TestsysImages> for testsys_config::TestsysImages {
sonobuoy_test_agent_image: val.sonobuoy_test,
ecs_test_agent_image: val.ecs_test,
migration_test_agent_image: val.migration_test,
k8s_workload_agent_image: val.k8s_workload,
controller_image: val.controller_uri,
testsys_agent_pull_secret: val.secret,
}
Expand Down
76 changes: 74 additions & 2 deletions tools/testsys/src/sonobuoy.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use crate::crds::TestInput;
use crate::error::{self, Result};
use crate::run::KnownTestType;
use bottlerocket_types::agent_config::{SonobuoyConfig, SonobuoyMode};
use bottlerocket_types::agent_config::{
SonobuoyConfig, SonobuoyMode, WorkloadConfig, WorkloadTest,
};
use maplit::btreemap;
use model::Test;
use snafu::ResultExt;
Expand All @@ -19,7 +21,9 @@ pub(crate) fn sonobuoy_crd(test_input: TestInput) -> Result<Test> {
.expect("A cluster name is required for migrations");
let sonobuoy_mode = match test_input.test_type {
KnownTestType::Conformance => SonobuoyMode::CertifiedConformance,
KnownTestType::Quick | KnownTestType::Migration => SonobuoyMode::Quick,
KnownTestType::Quick | KnownTestType::Migration | KnownTestType::Workload => {
SonobuoyMode::Quick
}
};

let labels = test_input.crd_input.labels(btreemap! {
Expand Down Expand Up @@ -83,6 +87,74 @@ pub(crate) fn sonobuoy_crd(test_input: TestInput) -> Result<Test> {
})
}

/// Create a workload CRD for K8s testing.
pub(crate) fn workload_crd(test_input: TestInput) -> Result<Test> {
let cluster_resource_name = test_input
.cluster_crd_name
.as_ref()
.expect("A cluster name is required for migrations");
let bottlerocket_resource_name = test_input
.bottlerocket_crd_name
.as_ref()
.expect("A cluster name is required for migrations");

let labels = test_input.crd_input.labels(btreemap! {
"testsys/type".to_string() => test_input.test_type.to_string(),
"testsys/cluster".to_string() => cluster_resource_name.to_string(),
});
let plugins: Vec<_> = test_input
.crd_input
.config
.workloads
.iter()
.map(|(name, image)| WorkloadTest {
name: name.to_string(),
image: image.to_string(),
})
.collect();
if plugins.is_empty() {
return Err(error::Error::Invalid {
what: "There were no plugins specified in the workload test.
Workloads can be specified in `Test.toml` or via the command line."
.to_string(),
});
}

WorkloadConfig::builder()
.resources(bottlerocket_resource_name)
.resources(cluster_resource_name)
.set_depends_on(Some(test_input.prev_tests))
.set_retries(Some(5))
.image(
test_input
.crd_input
.images
.k8s_workload_agent_image
.to_owned()
.expect("The default K8s workload testing image is missing"),
)
.set_image_pull_secret(
test_input
.crd_input
.images
.testsys_agent_pull_secret
.to_owned(),
)
.keep_running(true)
.kubeconfig_base64_template(cluster_resource_name, "encodedKubeconfig")
.plugins(plugins)
.set_secrets(Some(test_input.crd_input.config.secrets.to_owned()))
.set_labels(Some(labels))
.build(format!(
"{}{}",
cluster_resource_name,
test_input.name_suffix.unwrap_or("-test")
))
.context(error::BuildSnafu {
what: "Workload CRD",
})
}

fn e2e_repo_config_base64<S>(e2e_registry: S) -> String
where
S: Display,
Expand Down
8 changes: 7 additions & 1 deletion tools/testsys/src/vmware_k8s.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::crds::{
};
use crate::error::{self, Result};
use crate::migration::migration_crd;
use crate::sonobuoy::sonobuoy_crd;
use crate::sonobuoy::{sonobuoy_crd, workload_crd};
use bottlerocket_types::agent_config::{
CreationPolicy, CustomUserData, K8sVersion, VSphereK8sClusterConfig, VSphereK8sClusterInfo,
VSphereVmConfig,
Expand Down Expand Up @@ -279,6 +279,12 @@ impl CrdCreator for VmwareK8sCreator {
)?))))
}

async fn workload_crd<'a>(&self, test_input: TestInput<'a>) -> Result<CreateCrdOutput> {
Ok(CreateCrdOutput::NewCrd(Box::new(Crd::Test(workload_crd(
test_input,
)?))))
}

fn additional_fields(&self, _test_type: &str) -> BTreeMap<String, String> {
btreemap! {"region".to_string() => self.region.clone()}
}
Expand Down