Skip to content

Commit 8ce6f51

Browse files
committed
fix: add tests & cleanup
1 parent 1c5a145 commit 8ce6f51

4 files changed

Lines changed: 226 additions & 19 deletions

File tree

rust/operator-binary/src/controller/build/resource/pdb.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,54 @@ pub fn build_pdb(
3636
fn max_unavailable_nodes() -> u16 {
3737
1
3838
}
39+
40+
#[cfg(test)]
41+
mod tests {
42+
use pretty_assertions::assert_eq;
43+
use stackable_operator::{
44+
commons::pdb::PdbConfig, k8s_openapi::apimachinery::pkg::util::intstr::IntOrString,
45+
};
46+
47+
use super::*;
48+
use crate::controller::build::properties::test_support::minimal_validated_cluster;
49+
50+
#[test]
51+
fn build_pdb_returns_none_when_disabled() {
52+
let cluster = minimal_validated_cluster();
53+
let pdb = PdbConfig {
54+
enabled: false,
55+
max_unavailable: None,
56+
};
57+
assert!(build_pdb(&pdb, &cluster, &NifiRole::Node).is_none());
58+
}
59+
60+
#[test]
61+
fn build_pdb_uses_explicit_max_unavailable() {
62+
let cluster = minimal_validated_cluster();
63+
let pdb = PdbConfig {
64+
enabled: true,
65+
max_unavailable: Some(2),
66+
};
67+
68+
let spec = build_pdb(&pdb, &cluster, &NifiRole::Node)
69+
.expect("an enabled PDB must be built")
70+
.spec
71+
.expect("the PDB must have a spec");
72+
assert_eq!(Some(IntOrString::Int(2)), spec.max_unavailable);
73+
}
74+
75+
#[test]
76+
fn build_pdb_defaults_max_unavailable_to_one() {
77+
let cluster = minimal_validated_cluster();
78+
let pdb = PdbConfig {
79+
enabled: true,
80+
max_unavailable: None,
81+
};
82+
83+
let spec = build_pdb(&pdb, &cluster, &NifiRole::Node)
84+
.expect("an enabled PDB must be built")
85+
.spec
86+
.expect("the PDB must have a spec");
87+
assert_eq!(Some(IntOrString::Int(1)), spec.max_unavailable);
88+
}
89+
}

rust/operator-binary/src/controller/build/resource/service.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,3 +139,47 @@ fn prometheus_annotations(product_version: &str) -> Annotations {
139139
])
140140
.expect("should be valid annotations")
141141
}
142+
143+
#[cfg(test)]
144+
mod tests {
145+
use std::str::FromStr as _;
146+
147+
use pretty_assertions::assert_eq;
148+
use rstest::rstest;
149+
150+
use super::*;
151+
use crate::controller::build::properties::test_support::minimal_validated_cluster;
152+
153+
#[rstest]
154+
// NiFi 1.x exposes metrics on a dedicated JMX-exporter port ...
155+
#[case("1.28.1", METRICS_PORT_NAME, METRICS_PORT)]
156+
// ... while NiFi 2.x serves them on the HTTPS port.
157+
#[case("2.9.0", HTTPS_PORT_NAME, HTTPS_PORT)]
158+
fn metrics_service_port_depends_on_version(
159+
#[case] product_version: &str,
160+
#[case] expected_name: &str,
161+
#[case] expected_port: u16,
162+
) {
163+
let port = metrics_service_port(product_version);
164+
assert_eq!(Some(expected_name.to_string()), port.name);
165+
assert_eq!(i32::from(expected_port), port.port);
166+
}
167+
168+
#[test]
169+
fn headless_service_is_cluster_ip_none_with_https_port() {
170+
let cluster = minimal_validated_cluster();
171+
let rg = RoleGroupName::from_str("default").expect("valid role-group name");
172+
173+
let spec = build_rolegroup_headless_service(&cluster, &rg)
174+
.spec
175+
.expect("headless service must have a spec");
176+
177+
assert_eq!(Some("ClusterIP".to_string()), spec.type_);
178+
assert_eq!(Some("None".to_string()), spec.cluster_ip);
179+
180+
let ports = spec.ports.expect("headless service must expose ports");
181+
assert_eq!(1, ports.len());
182+
assert_eq!(Some(HTTPS_PORT_NAME.to_string()), ports[0].name);
183+
assert_eq!(i32::from(HTTPS_PORT), ports[0].port);
184+
}
185+
}

rust/operator-binary/src/controller/build/resource/statefulset.rs

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ use crate::{
5757
ValidatedCluster, ValidatedRoleGroupConfig,
5858
build::{
5959
graceful_shutdown::add_graceful_shutdown_config,
60+
properties::ConfigFileName,
6061
resource::{
6162
listener::{
6263
LISTENER_VOLUME_DIR, LISTENER_VOLUME_NAME, build_group_listener_pvc,
@@ -291,8 +292,11 @@ pub(crate) async fn build_node_rolegroup_statefulset(
291292
format!("keytool -importkeystore -srckeystore {KEYSTORE_NIFI_CONTAINER_MOUNT}/truststore.p12 -destkeystore {STACKABLE_SERVER_TLS_DIR}/truststore.p12 -srcstorepass {STACKABLE_TLS_STORE_PASSWORD} -deststorepass {STACKABLE_TLS_STORE_PASSWORD}"),
292293

293294
"echo Replacing config directory".to_string(),
294-
"cp /conf/* /stackable/nifi/conf".to_string(),
295-
"test -L /stackable/nifi/conf/logback.xml || ln -sf /stackable/log_config/logback.xml /stackable/nifi/conf/logback.xml".to_string(),
295+
format!("cp {CONFIG_VOLUME_MOUNT}/* {NIFI_CONFIG_DIRECTORY}"),
296+
format!(
297+
"test -L {NIFI_CONFIG_DIRECTORY}/{logback} || ln -sf {STACKABLE_LOG_CONFIG_DIR}/{logback} {NIFI_CONFIG_DIRECTORY}/{logback}",
298+
logback = ConfigFileName::Logback
299+
),
296300
format!(r#"export NODE_ADDRESS="{node_address}""#),
297301
]);
298302

@@ -311,23 +315,27 @@ pub(crate) async fn build_node_rolegroup_statefulset(
311315
]);
312316
}
313317

314-
prepare_args.extend(vec![
315-
"export LISTENER_DEFAULT_ADDRESS=$(cat /stackable/listener/default-address/address)"
316-
.to_string(),
317-
]);
318-
prepare_args.extend(vec![
319-
"export LISTENER_DEFAULT_PORT_HTTPS=$(cat /stackable/listener/default-address/ports/https)"
320-
.to_string(),
321-
]);
322-
323-
prepare_args.extend(vec![
324-
"echo Templating config files".to_string(),
325-
"config-utils template /stackable/nifi/conf/nifi.properties".to_string(),
326-
"config-utils template /stackable/nifi/conf/state-management.xml".to_string(),
327-
"config-utils template /stackable/nifi/conf/login-identity-providers.xml".to_string(),
328-
"config-utils template /stackable/nifi/conf/authorizers.xml".to_string(),
329-
"config-utils template /stackable/nifi/conf/security.properties".to_string(),
330-
]);
318+
prepare_args.push(format!(
319+
"export LISTENER_DEFAULT_ADDRESS=$(cat {LISTENER_VOLUME_DIR}/default-address/address)"
320+
));
321+
prepare_args.push(format!(
322+
"export LISTENER_DEFAULT_PORT_HTTPS=$(cat {LISTENER_VOLUME_DIR}/default-address/ports/https)"
323+
));
324+
325+
// Template the config files that contain `${env:...}`/`${file:...}` placeholders, in a fixed
326+
// order. Sourced from the `ConfigFileName` enum so the file names stay in sync with the
327+
// ConfigMap builder; `bootstrap.conf` and `logback.xml` are intentionally not templated.
328+
prepare_args.push("echo Templating config files".to_string());
329+
prepare_args.extend(
330+
[
331+
ConfigFileName::NifiProperties,
332+
ConfigFileName::StateManagementXml,
333+
ConfigFileName::LoginIdentityProviders,
334+
ConfigFileName::Authorizers,
335+
ConfigFileName::SecurityProperties,
336+
]
337+
.map(|file| format!("config-utils template {NIFI_CONFIG_DIRECTORY}/{file}")),
338+
);
331339

332340
let mut container_prepare = new_container_builder(&PREPARE_CONTAINER_NAME);
333341

rust/operator-binary/src/controller/validate.rs

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,3 +246,107 @@ pub(crate) fn build_role_group_configs(
246246
role_group_configs.insert(NifiRole::Node, groups);
247247
Ok(role_group_configs)
248248
}
249+
250+
#[cfg(test)]
251+
mod tests {
252+
use pretty_assertions::assert_eq;
253+
use stackable_operator::v2::types::kubernetes::ConfigMapName;
254+
255+
use super::*;
256+
257+
/// A NiFi cluster with the Vector agent enabled at the Node role level.
258+
const NIFI_VECTOR_ENABLED_YAML: &str = r#"
259+
apiVersion: nifi.stackable.tech/v1alpha1
260+
kind: NifiCluster
261+
metadata:
262+
name: simple-nifi
263+
namespace: default
264+
spec:
265+
image:
266+
productVersion: 2.9.0
267+
clusterConfig:
268+
authentication:
269+
- authenticationClass: nifi-admin-credentials-simple
270+
sensitiveProperties:
271+
keySecret: simple-nifi-sensitive-property-key
272+
autoGenerate: true
273+
nodes:
274+
config:
275+
logging:
276+
enableVectorAgent: true
277+
roleGroups:
278+
default:
279+
replicas: 1
280+
"#;
281+
282+
/// A minimal NiFi cluster with the Vector agent disabled (the default).
283+
const NIFI_VECTOR_DISABLED_YAML: &str = r#"
284+
apiVersion: nifi.stackable.tech/v1alpha1
285+
kind: NifiCluster
286+
metadata:
287+
name: simple-nifi
288+
namespace: default
289+
spec:
290+
image:
291+
productVersion: 2.9.0
292+
clusterConfig:
293+
authentication:
294+
- authenticationClass: nifi-admin-credentials-simple
295+
sensitiveProperties:
296+
keySecret: simple-nifi-sensitive-property-key
297+
autoGenerate: true
298+
nodes:
299+
roleGroups:
300+
default:
301+
replicas: 1
302+
"#;
303+
304+
fn default_rg(
305+
configs: &BTreeMap<NifiRole, BTreeMap<RoleGroupName, ValidatedRoleGroupConfig>>,
306+
) -> &ValidatedRoleGroupConfig {
307+
configs[&NifiRole::Node]
308+
.get(&RoleGroupName::from_str("default").expect("valid role-group name"))
309+
.expect("the 'default' role group must exist")
310+
}
311+
312+
#[test]
313+
fn vector_container_is_validated_when_agent_enabled() {
314+
let nifi: v1alpha1::NifiCluster =
315+
serde_yaml::from_str(NIFI_VECTOR_ENABLED_YAML).expect("invalid test YAML");
316+
let aggregator = Some(ConfigMapName::from_str("nifi-vector-aggregator-discovery").unwrap());
317+
318+
let configs = build_role_group_configs(&nifi, &aggregator)
319+
.expect("role group configs should validate");
320+
321+
let vector = default_rg(&configs)
322+
.vector_container
323+
.as_ref()
324+
.expect("the Vector container config should be present when the agent is enabled");
325+
assert_eq!(
326+
"nifi-vector-aggregator-discovery",
327+
vector.vector_aggregator_config_map_name.to_string()
328+
);
329+
}
330+
331+
#[test]
332+
fn vector_agent_enabled_without_aggregator_name_fails() {
333+
let nifi: v1alpha1::NifiCluster =
334+
serde_yaml::from_str(NIFI_VECTOR_ENABLED_YAML).expect("invalid test YAML");
335+
336+
let error = build_role_group_configs(&nifi, &None)
337+
.expect_err("a missing aggregator ConfigMap name must fail when Vector is enabled");
338+
assert!(matches!(error, Error::MissingVectorAggregatorConfigMapName));
339+
}
340+
341+
#[test]
342+
fn no_vector_container_when_agent_disabled() {
343+
let nifi: v1alpha1::NifiCluster =
344+
serde_yaml::from_str(NIFI_VECTOR_DISABLED_YAML).expect("invalid test YAML");
345+
346+
// The aggregator name is not required when the Vector agent is disabled.
347+
let configs =
348+
build_role_group_configs(&nifi, &None).expect("role group configs should validate");
349+
350+
assert!(default_rg(&configs).vector_container.is_none());
351+
}
352+
}

0 commit comments

Comments
 (0)