Skip to content
Draft
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
14 changes: 5 additions & 9 deletions rust/operator-binary/src/crd/affinity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,17 @@ pub fn get_affinity(cluster_name: &str, role: &ZookeeperRole) -> StackableAffini
#[cfg(test)]
mod tests {

use std::{collections::BTreeMap, str::FromStr};
use std::collections::BTreeMap;

use stackable_operator::{
commons::affinity::StackableAffinity,
k8s_openapi::{
api::core::v1::{PodAffinityTerm, PodAntiAffinity, WeightedPodAffinityTerm},
apimachinery::pkg::apis::meta::v1::LabelSelector,
},
v2::types::operator::RoleGroupName,
};

use crate::{
crd::affinity::ZookeeperRole,
zk_controller::test_support::{minimal_zk, validated_cluster},
};
use crate::test_support::{minimal_zk, server_rolegroup_config, validated_cluster};

#[test]
fn test_affinity_defaults() {
Expand Down Expand Up @@ -99,9 +95,9 @@ mod tests {
node_selector: None,
};

let default_group = RoleGroupName::from_str("default").expect("valid role group name");
let affinity = validated_cluster(&zk).role_group_configs[&ZookeeperRole::Server]
[&default_group]
let validated = validated_cluster(&zk);
let affinity = server_rolegroup_config(&validated, "default")
.1
.config
.affinity
.clone();
Expand Down
23 changes: 23 additions & 0 deletions rust/operator-binary/src/crd/authentication.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,4 +131,27 @@ impl DereferencedAuthenticationClasses {
dereferenced_authentication_classes: vec![],
}
}

/// Builds a [`DereferencedAuthenticationClasses`] holding a single TLS `AuthenticationClass`.
/// Exercises the client-mTLS branches of [`crate::crd::security::ZookeeperSecurity`] (secure
/// client port and `ssl.clientAuth=need`), including the case where server TLS is off and the
/// auth class alone turns TLS on.
#[cfg(test)]
pub fn new_for_tests_with_tls_client_auth() -> Self {
use stackable_operator::crd::authentication::tls;

let auth_class = core::v1alpha1::AuthenticationClass::new(
"zk-client-auth-tls",
core::v1alpha1::AuthenticationClassSpec {
provider: core::v1alpha1::AuthenticationClassProvider::Tls(
tls::v1alpha1::AuthenticationProvider {
client_cert_secret_class: Some("zk-client-auth-secret".to_owned()),
},
),
},
);
DereferencedAuthenticationClasses {
dereferenced_authentication_classes: vec![auth_class],
}
}
}
2 changes: 2 additions & 0 deletions rust/operator-binary/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ use crate::{
};

pub mod crd;
#[cfg(test)]
mod test_support;
mod webhooks;
mod zk_controller;
mod znode_controller;
Expand Down
128 changes: 128 additions & 0 deletions rust/operator-binary/src/test_support.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
//! Shared helpers for building validated test clusters from minimal YAML fixtures.
//!
//! Lives at the crate root because it is used across `zk_controller`, `znode_controller` and
//! `crd` test modules, not just one of them.

use std::str::FromStr;

use stackable_operator::{
cli::OperatorEnvironmentOptions, commons::networking::DomainName,
utils::cluster_info::KubernetesClusterInfo, v2::types::operator::RoleGroupName,
};

use crate::{
crd::{ZookeeperRole, authentication::DereferencedAuthenticationClasses, v1alpha1},
zk_controller::{
dereference::DereferencedObjects,
validate::{ValidatedCluster, ZookeeperRoleGroupConfig, validate},
},
};

/// Parses a minimal `ZookeeperCluster` test fixture, defaulting `namespace`/`uid` so the
/// validate step can build a [`ValidatedCluster`].
pub fn minimal_zk(yaml: &str) -> v1alpha1::ZookeeperCluster {
let mut zk: v1alpha1::ZookeeperCluster =
serde_yaml::from_str(yaml).expect("invalid test ZookeeperCluster YAML");
zk.metadata
.namespace
.get_or_insert_with(|| "default".to_owned());
zk.metadata
.uid
.get_or_insert_with(|| "c27b3971-ca72-42c1-80a4-abdfc1db0ddd".to_owned());
zk
}

/// Minimal valid `ZookeeperCluster` YAML: a single `default` server role group with `replicas`
/// servers and nothing else.
///
/// Use this (or [`minimal_zk_default`]) for tests that just need *a* valid cluster and don't
/// care about the spec. Keep an inline fixture when the spec detail is the thing under test.
pub fn minimal_zk_yaml(replicas: u16) -> String {
format!(
r#"
apiVersion: zookeeper.stackable.tech/v1alpha1
kind: ZookeeperCluster
metadata:
name: simple-zookeeper
spec:
image:
productVersion: "3.9.5"
servers:
roleGroups:
default:
replicas: {replicas}
"#
)
}

/// Parsed counterpart of [`minimal_zk_yaml`].
pub fn minimal_zk_default(replicas: u16) -> v1alpha1::ZookeeperCluster {
minimal_zk(&minimal_zk_yaml(replicas))
}

pub fn cluster_info() -> KubernetesClusterInfo {
KubernetesClusterInfo {
cluster_domain: DomainName::try_from("cluster.local").expect("valid domain"),
}
}

pub(crate) fn operator_environment() -> OperatorEnvironmentOptions {
OperatorEnvironmentOptions {
operator_namespace: "stackable-operators".to_owned(),
operator_service_name: "zookeeper-operator".to_owned(),
image_repository: "oci.example.org".to_owned(),
}
}

/// Runs the real validate step against a minimal (auth-free) fixture, returning the result so
/// tests can assert on validation errors.
pub fn try_validate(
zk: &v1alpha1::ZookeeperCluster,
) -> Result<ValidatedCluster, crate::zk_controller::validate::Error> {
try_validate_with_auth(zk, DereferencedAuthenticationClasses::new_for_tests())
}

/// Runs the real validate step with caller-supplied (dereferenced) AuthenticationClasses, so
/// tests can exercise the client-mTLS matrix.
pub fn try_validate_with_auth(
zk: &v1alpha1::ZookeeperCluster,
authentication_classes: DereferencedAuthenticationClasses,
) -> Result<ValidatedCluster, crate::zk_controller::validate::Error> {
validate(
zk,
&DereferencedObjects {
authentication_classes,
},
&operator_environment(),
)
}

/// Runs the real validate step against a minimal (auth-free) fixture.
pub fn validated_cluster(zk: &v1alpha1::ZookeeperCluster) -> ValidatedCluster {
try_validate(zk).expect("validate should succeed for the test fixture")
}

/// Runs the real validate step with a single TLS client-auth `AuthenticationClass`.
pub fn validated_cluster_with_client_auth(zk: &v1alpha1::ZookeeperCluster) -> ValidatedCluster {
try_validate_with_auth(
zk,
DereferencedAuthenticationClasses::new_for_tests_with_tls_client_auth(),
)
.expect("validate should succeed for the test fixture")
}

/// Looks up the validated, merged config of the named `server` role group together with its
/// parsed [`RoleGroupName`] — the standard `(name, config)` inputs to the
/// `build_server_rolegroup_*` functions. Panics if the group does not exist.
pub fn server_rolegroup_config<'a>(
validated: &'a ValidatedCluster,
role_group: &str,
) -> (RoleGroupName, &'a ZookeeperRoleGroupConfig) {
let role_group_name = RoleGroupName::from_str(role_group).expect("valid role group name");
let config = validated
.role_group_configs
.get(&ZookeeperRole::Server)
.and_then(|groups| groups.get(&role_group_name))
.unwrap_or_else(|| panic!("server role group {role_group:?} should exist"));
(role_group_name, config)
}
Loading
Loading