Skip to content

Commit a85f623

Browse files
committed
test: add unit tests for conditional settings
1 parent 2426b77 commit a85f623

6 files changed

Lines changed: 526 additions & 8 deletions

File tree

rust/operator-binary/src/crd/security.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,20 +61,19 @@ impl ZookeeperSecurity {
6161
// ports
6262
pub const CLIENT_PORT: Port = Port(2181);
6363
pub const CLIENT_PORT_NAME: &'static str = "clientPort";
64+
// TLS store file names (relative to the respective TLS dir)
65+
pub const KEYSTORE_FILE: &'static str = "keystore.p12";
6466
// directories
6567
pub const QUORUM_TLS_DIR: &'static str = "/stackable/quorum_tls";
6668
pub const QUORUM_TLS_MOUNT_DIR: &'static str = "/stackable/quorum_tls_mount";
69+
pub const QUORUM_TLS_VOLUME_NAME: &'static str = "quorum-tls";
6770
pub const SECURE_CLIENT_PORT: Port = Port(2282);
6871
pub const SECURE_CLIENT_PORT_NAME: &'static str = "secureClientPort";
6972
pub const SERVER_CNXN_FACTORY: &'static str = "serverCnxnFactory";
7073
pub const SERVER_TLS_DIR: &'static str = "/stackable/server_tls";
7174
pub const SERVER_TLS_MOUNT_DIR: &'static str = "/stackable/server_tls_mount";
7275
// TLS volume names (the mount name must match the volume name)
7376
pub const SERVER_TLS_VOLUME_NAME: &'static str = "server-tls";
74-
pub const QUORUM_TLS_VOLUME_NAME: &'static str = "quorum-tls";
75-
// TLS store file names (relative to the respective TLS dir)
76-
pub const KEYSTORE_FILE: &'static str = "keystore.p12";
77-
pub const TRUSTSTORE_FILE: &'static str = "truststore.p12";
7877
// Common TLS
7978
pub const SSL_AUTH_PROVIDER_X509: &'static str = "authProvider.x509";
8079
// Client TLS
@@ -95,6 +94,7 @@ impl ZookeeperSecurity {
9594
// Mis
9695
pub const STORE_PASSWORD_ENV: &'static str = "STORE_PASSWORD";
9796
pub const SYSTEM_TRUST_STORE_DIR: &'static str = "/etc/pki/java/cacerts";
97+
pub const TRUSTSTORE_FILE: &'static str = "truststore.p12";
9898

9999
/// Build a `ZookeeperSecurity` from a [`v1alpha1::ZookeeperCluster`] and already-resolved
100100
/// [`DereferencedAuthenticationClasses`]. Synchronous; intended to be called from the validate

rust/operator-binary/src/zk_controller.rs

Lines changed: 135 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -434,16 +434,23 @@ pub(crate) mod test_support {
434434
}
435435
}
436436

437-
/// Runs the real validate step against a minimal (auth-free) fixture.
438-
pub fn validated_cluster(zk: &v1alpha1::ZookeeperCluster) -> ValidatedCluster {
437+
/// Runs the real validate step against a minimal (auth-free) fixture, returning the result so
438+
/// tests can assert on validation errors.
439+
pub fn try_validate(
440+
zk: &v1alpha1::ZookeeperCluster,
441+
) -> Result<ValidatedCluster, super::validate::Error> {
439442
validate(
440443
zk,
441444
&DereferencedObjects {
442445
authentication_classes: DereferencedAuthenticationClasses::new_for_tests(),
443446
},
444447
&operator_environment(),
445448
)
446-
.expect("validate should succeed for the test fixture")
449+
}
450+
451+
/// Runs the real validate step against a minimal (auth-free) fixture.
452+
pub fn validated_cluster(zk: &v1alpha1::ZookeeperCluster) -> ValidatedCluster {
453+
try_validate(zk).expect("validate should succeed for the test fixture")
447454
}
448455
}
449456

@@ -599,6 +606,131 @@ mod tests {
599606
assert!(zoo_cfg.contains("syncLimit=2"), "{zoo_cfg}");
600607
}
601608

609+
#[test]
610+
fn test_non_tls_uses_insecure_client_port() {
611+
// With server TLS disabled (`serverSecretClass: null`) the insecure client port is used and
612+
// none of the server-TLS settings (keystore, port unification) are emitted. This exercises
613+
// the non-TLS branch of `ZookeeperSecurity::{client_port, config_settings}`.
614+
let zookeeper_yaml = r#"
615+
apiVersion: zookeeper.stackable.tech/v1alpha1
616+
kind: ZookeeperCluster
617+
metadata:
618+
name: simple-zookeeper
619+
spec:
620+
image:
621+
productVersion: "3.9.5"
622+
clusterConfig:
623+
tls:
624+
serverSecretClass: null
625+
servers:
626+
roleGroups:
627+
default:
628+
replicas: 3
629+
"#;
630+
let cm = build_config_map(zookeeper_yaml).data.unwrap();
631+
let zoo_cfg = cm.get("zoo.cfg").unwrap();
632+
assert!(zoo_cfg.contains("clientPort=2181"), "{zoo_cfg}");
633+
assert!(!zoo_cfg.contains("client.portUnification"), "{zoo_cfg}");
634+
// The server-TLS keystore line (distinct from the always-present quorum keystore).
635+
assert!(!zoo_cfg.contains("ssl.keyStore.location"), "{zoo_cfg}");
636+
}
637+
638+
#[test]
639+
fn test_config_override_wins_over_typed_config() {
640+
// The typed `config` sets `syncLimit`, but a `configOverride` for the same key must win
641+
// (configOverrides are layered last in `zoo.cfg` precedence).
642+
let zookeeper_yaml = r#"
643+
apiVersion: zookeeper.stackable.tech/v1alpha1
644+
kind: ZookeeperCluster
645+
metadata:
646+
name: simple-zookeeper
647+
spec:
648+
image:
649+
productVersion: "3.9.5"
650+
servers:
651+
roleGroups:
652+
default:
653+
replicas: 3
654+
config:
655+
syncLimit: 9
656+
configOverrides:
657+
zoo.cfg:
658+
syncLimit: "15"
659+
"#;
660+
let cm = build_config_map(zookeeper_yaml).data.unwrap();
661+
let zoo_cfg = cm.get("zoo.cfg").unwrap();
662+
assert!(zoo_cfg.contains("syncLimit=15"), "{zoo_cfg}");
663+
}
664+
665+
#[test]
666+
fn test_custom_log_config_omits_logback() {
667+
// Automatic logging renders `logback.xml` into the ConfigMap; a custom log ConfigMap
668+
// suppresses it (the `Custom` arm of `build_logback_config`).
669+
let automatic_yaml = r#"
670+
apiVersion: zookeeper.stackable.tech/v1alpha1
671+
kind: ZookeeperCluster
672+
metadata:
673+
name: simple-zookeeper
674+
spec:
675+
image:
676+
productVersion: "3.9.5"
677+
servers:
678+
roleGroups:
679+
default:
680+
replicas: 3
681+
"#;
682+
let automatic = build_config_map(automatic_yaml).data.unwrap();
683+
assert!(automatic.contains_key("logback.xml"));
684+
685+
let custom_yaml = r#"
686+
apiVersion: zookeeper.stackable.tech/v1alpha1
687+
kind: ZookeeperCluster
688+
metadata:
689+
name: simple-zookeeper
690+
spec:
691+
image:
692+
productVersion: "3.9.5"
693+
servers:
694+
roleGroups:
695+
default:
696+
replicas: 3
697+
config:
698+
logging:
699+
containers:
700+
zookeeper:
701+
custom:
702+
configMap: my-log-config
703+
"#;
704+
let custom = build_config_map(custom_yaml).data.unwrap();
705+
assert!(!custom.contains_key("logback.xml"));
706+
}
707+
708+
#[test]
709+
fn test_vector_agent_adds_vector_config() {
710+
// Enabling the Vector agent (with the required aggregator discovery ConfigMap) adds
711+
// `vector.yaml` to the rolegroup ConfigMap.
712+
let zookeeper_yaml = r#"
713+
apiVersion: zookeeper.stackable.tech/v1alpha1
714+
kind: ZookeeperCluster
715+
metadata:
716+
name: simple-zookeeper
717+
spec:
718+
image:
719+
productVersion: "3.9.5"
720+
clusterConfig:
721+
vectorAggregatorConfigMapName: vector-aggregator-discovery
722+
servers:
723+
roleGroups:
724+
default:
725+
replicas: 3
726+
config:
727+
logging:
728+
enableVectorAgent: true
729+
"#;
730+
let cm = build_config_map(zookeeper_yaml).data.unwrap();
731+
assert!(cm.contains_key("vector.yaml"));
732+
}
733+
602734
fn build_config_map(zookeeper_yaml: &str) -> ConfigMap {
603735
let zookeeper = minimal_zk(zookeeper_yaml);
604736
let validated_cluster = validated_cluster(&zookeeper);

rust/operator-binary/src/zk_controller/build/properties/zoo_cfg.rs

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,9 +177,190 @@ impl ValidatedCluster {
177177

178178
#[cfg(test)]
179179
mod tests {
180+
use std::str::FromStr;
181+
182+
use stackable_operator::v2::types::operator::RoleGroupName;
183+
180184
use super::*;
181185
use crate::zk_controller::test_support::{cluster_info, minimal_zk, validated_cluster};
182186

187+
/// Validates `yaml` into a [`ValidatedCluster`] and returns its `server` `default` role group.
188+
fn validated_with_default_rg(yaml: &str) -> (ValidatedCluster, ZookeeperRoleGroupConfig) {
189+
let validated = validated_cluster(&minimal_zk(yaml));
190+
let rg_name = RoleGroupName::from_str("default").expect("valid role group name");
191+
let rg = validated.role_group_configs[&ZookeeperRole::Server][&rg_name].clone();
192+
(validated, rg)
193+
}
194+
195+
#[test]
196+
fn server_addresses_append_secure_client_port_when_tls_enabled() {
197+
// TLS is enabled by default, so the quorum entries advertise the secure client port.
198+
let (validated, _) = validated_with_default_rg(
199+
r#"
200+
apiVersion: zookeeper.stackable.tech/v1alpha1
201+
kind: ZookeeperCluster
202+
metadata:
203+
name: test-zk
204+
spec:
205+
image:
206+
productVersion: "3.9.5"
207+
servers:
208+
roleGroups:
209+
default:
210+
replicas: 1
211+
"#,
212+
);
213+
let entry = server_addresses(&validated, &cluster_info())
214+
.remove("server.1")
215+
.expect("missing server.1");
216+
assert!(entry.ends_with(";2282"), "unexpected entry: {entry}");
217+
}
218+
219+
#[test]
220+
fn server_addresses_append_insecure_client_port_when_tls_disabled() {
221+
// With server TLS disabled the quorum entries advertise the insecure client port.
222+
let (validated, _) = validated_with_default_rg(
223+
r#"
224+
apiVersion: zookeeper.stackable.tech/v1alpha1
225+
kind: ZookeeperCluster
226+
metadata:
227+
name: test-zk
228+
spec:
229+
image:
230+
productVersion: "3.9.5"
231+
clusterConfig:
232+
tls:
233+
serverSecretClass: null
234+
servers:
235+
roleGroups:
236+
default:
237+
replicas: 1
238+
"#,
239+
);
240+
let entry = server_addresses(&validated, &cluster_info())
241+
.remove("server.1")
242+
.expect("missing server.1");
243+
assert!(entry.ends_with(";2181"), "unexpected entry: {entry}");
244+
}
245+
246+
#[test]
247+
fn server_addresses_unset_replicas_predicts_single_entry() {
248+
// An unset replica count (HPA-managed) predicts a single-server quorum entry.
249+
let validated = validated_cluster(&minimal_zk(
250+
r#"
251+
apiVersion: zookeeper.stackable.tech/v1alpha1
252+
kind: ZookeeperCluster
253+
metadata:
254+
name: test-zk
255+
spec:
256+
image:
257+
productVersion: "3.9.5"
258+
servers:
259+
roleGroups:
260+
default: {}
261+
"#,
262+
));
263+
let addresses = server_addresses(&validated, &cluster_info());
264+
assert_eq!(addresses.len(), 1);
265+
assert!(addresses.contains_key("server.1"), "{addresses:?}");
266+
}
267+
268+
#[test]
269+
fn server_addresses_honor_myid_offset_across_role_groups() {
270+
// The quorum keys are `server.<i + myidOffset>`, aggregated across all server role groups.
271+
let validated = validated_cluster(&minimal_zk(
272+
r#"
273+
apiVersion: zookeeper.stackable.tech/v1alpha1
274+
kind: ZookeeperCluster
275+
metadata:
276+
name: test-zk
277+
spec:
278+
image:
279+
productVersion: "3.9.5"
280+
servers:
281+
roleGroups:
282+
primary:
283+
replicas: 2
284+
secondary:
285+
replicas: 2
286+
config:
287+
myidOffset: 10
288+
"#,
289+
));
290+
let addresses = server_addresses(&validated, &cluster_info());
291+
// primary: offset 1 -> server.1, server.2; secondary: offset 10 -> server.10, server.11.
292+
for expected in ["server.1", "server.2", "server.10", "server.11"] {
293+
assert!(
294+
addresses.contains_key(expected),
295+
"missing {expected} in {addresses:?}"
296+
);
297+
}
298+
assert_eq!(addresses.len(), 4);
299+
}
300+
301+
#[test]
302+
fn metrics_http_port_defaults_and_honors_override() {
303+
// Default: the seeded `METRICS_PROVIDER_HTTP_PORT`.
304+
let (validated, rg) = validated_with_default_rg(
305+
r#"
306+
apiVersion: zookeeper.stackable.tech/v1alpha1
307+
kind: ZookeeperCluster
308+
metadata:
309+
name: test-zk
310+
spec:
311+
image:
312+
productVersion: "3.9.5"
313+
servers:
314+
roleGroups:
315+
default:
316+
replicas: 1
317+
"#,
318+
);
319+
assert_eq!(validated.metrics_http_port(&rg), METRICS_PROVIDER_HTTP_PORT);
320+
321+
// A numeric `configOverride` wins.
322+
let (validated, rg) = validated_with_default_rg(
323+
r#"
324+
apiVersion: zookeeper.stackable.tech/v1alpha1
325+
kind: ZookeeperCluster
326+
metadata:
327+
name: test-zk
328+
spec:
329+
image:
330+
productVersion: "3.9.5"
331+
servers:
332+
roleGroups:
333+
default:
334+
replicas: 1
335+
configOverrides:
336+
zoo.cfg:
337+
metricsProvider.httpPort: "9999"
338+
"#,
339+
);
340+
assert_eq!(validated.metrics_http_port(&rg), Port::from(9999));
341+
342+
// A non-numeric `configOverride` falls back to the default.
343+
let (validated, rg) = validated_with_default_rg(
344+
r#"
345+
apiVersion: zookeeper.stackable.tech/v1alpha1
346+
kind: ZookeeperCluster
347+
metadata:
348+
name: test-zk
349+
spec:
350+
image:
351+
productVersion: "3.9.5"
352+
servers:
353+
roleGroups:
354+
default:
355+
replicas: 1
356+
configOverrides:
357+
zoo.cfg:
358+
metricsProvider.httpPort: "not-a-port"
359+
"#,
360+
);
361+
assert_eq!(validated.metrics_http_port(&rg), METRICS_PROVIDER_HTTP_PORT);
362+
}
363+
183364
#[test]
184365
fn server_addresses_predicts_one_entry_per_replica() {
185366
let zk = minimal_zk(

0 commit comments

Comments
 (0)