Skip to content

Commit 122cbdf

Browse files
committed
refactor(zk_controller): extract dereference and validate steps
1 parent 54ff1ae commit 122cbdf

3 files changed

Lines changed: 203 additions & 68 deletions

File tree

rust/operator-binary/src/zk_controller.rs

Lines changed: 38 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ use stackable_operator::{
3131
cli::OperatorEnvironmentOptions,
3232
cluster_resources::{ClusterResourceApplyStrategy, ClusterResources},
3333
commons::{
34-
product_image_selection::{self, ResolvedProductImage},
34+
product_image_selection::ResolvedProductImage,
3535
rbac::build_rbac_resources,
3636
},
3737
constants::RESTART_CONTROLLER_ENABLED_LABEL,
@@ -55,7 +55,6 @@ use stackable_operator::{
5555
},
5656
kvp::{LabelError, Labels},
5757
logging::controller::ReconcilerError,
58-
product_config_utils::{transform_all_roles_to_config, validate_all_roles_and_groups_config},
5958
product_logging::{
6059
self,
6160
framework::{
@@ -81,7 +80,7 @@ use crate::{
8180
command::create_init_container_command_args,
8281
config::jvm::{construct_non_heap_jvm_args, construct_zk_server_heap_env},
8382
crd::{
84-
CONTAINER_IMAGE_BASE_NAME, JMX_METRICS_PORT_NAME, JVM_SECURITY_PROPERTIES_FILE,
83+
JMX_METRICS_PORT_NAME, JVM_SECURITY_PROPERTIES_FILE,
8584
MAX_PREPARE_LOG_FILE_SIZE, MAX_ZK_LOG_FILES_SIZE, METRICS_PROVIDER_HTTP_PORT_NAME,
8685
STACKABLE_CONFIG_DIR, STACKABLE_DATA_DIR, STACKABLE_LOG_CONFIG_DIR, STACKABLE_LOG_DIR,
8786
STACKABLE_RW_CONFIG_DIR, ZOOKEEPER_ELECTION_PORT, ZOOKEEPER_ELECTION_PORT_NAME,
@@ -101,6 +100,9 @@ use crate::{
101100
utils::build_recommended_labels,
102101
};
103102

103+
mod dereference;
104+
mod validate;
105+
104106
pub const ZK_CONTROLLER_NAME: &str = "zookeepercluster";
105107
pub const ZK_FULL_CONTROLLER_NAME: &str = concatcp!(ZK_CONTROLLER_NAME, '.', OPERATOR_NAME);
106108
pub const LISTENER_VOLUME_NAME: &str = "listener";
@@ -126,12 +128,15 @@ pub enum Error {
126128
source: error_boundary::InvalidObject,
127129
},
128130

131+
#[snafu(display("failed to dereference resources"))]
132+
Dereference { source: dereference::Error },
133+
134+
#[snafu(display("failed to validate cluster"))]
135+
ValidateCluster { source: validate::Error },
136+
129137
#[snafu(display("crd validation failure"))]
130138
CrdValidationFailure { source: crate::crd::Error },
131139

132-
#[snafu(display("object defines no server role"))]
133-
NoServerRole,
134-
135140
#[snafu(display("could not parse role [{role}]"))]
136141
RoleParseFailure {
137142
source: strum::ParseError,
@@ -165,16 +170,6 @@ pub enum Error {
165170
rolegroup: RoleGroupRef<v1alpha1::ZookeeperCluster>,
166171
},
167172

168-
#[snafu(display("failed to generate product config"))]
169-
GenerateProductConfig {
170-
source: stackable_operator::product_config_utils::Error,
171-
},
172-
173-
#[snafu(display("invalid product config"))]
174-
InvalidProductConfig {
175-
source: stackable_operator::product_config_utils::Error,
176-
},
177-
178173
#[snafu(display("failed to serialize [{ZOOKEEPER_PROPERTIES_FILE}] for {}", rolegroup))]
179174
SerializeZooCfg {
180175
source: PropertiesWriterError,
@@ -228,9 +223,6 @@ pub enum Error {
228223
cm_name: String,
229224
},
230225

231-
#[snafu(display("failed to initialize security context"))]
232-
FailedToInitializeSecurityContext { source: crate::crd::security::Error },
233-
234226
#[snafu(display("failed to resolve and merge config for role and role group"))]
235227
FailedToResolveConfig { source: crate::crd::Error },
236228

@@ -289,11 +281,6 @@ pub enum Error {
289281
source: stackable_operator::builder::pod::volume::ListenerOperatorVolumeSourceBuilderError,
290282
},
291283

292-
#[snafu(display("failed to resolve product image"))]
293-
ResolveProductImage {
294-
source: product_image_selection::Error,
295-
},
296-
297284
#[snafu(display("failed to build service"))]
298285
BuildService { source: service::Error },
299286

@@ -310,16 +297,15 @@ impl ReconcilerError for Error {
310297
match self {
311298
Error::MissingSecretLifetime => None,
312299
Error::InvalidZookeeperCluster { .. } => None,
300+
Error::Dereference { .. } => None,
301+
Error::ValidateCluster { .. } => None,
313302
Error::CrdValidationFailure { .. } => None,
314-
Error::NoServerRole => None,
315303
Error::RoleParseFailure { .. } => None,
316304
Error::InternalOperatorFailure { .. } => None,
317305
Error::ApplyRoleGroupService { .. } => None,
318306
Error::BuildRoleGroupConfig { .. } => None,
319307
Error::ApplyRoleGroupConfig { .. } => None,
320308
Error::ApplyRoleGroupStatefulSet { .. } => None,
321-
Error::GenerateProductConfig { .. } => None,
322-
Error::InvalidProductConfig { .. } => None,
323309
Error::SerializeZooCfg { .. } => None,
324310
Error::ObjectMissingMetadataForOwnerRef { .. } => None,
325311
Error::BuildDiscoveryConfig { .. } => None,
@@ -331,7 +317,6 @@ impl ReconcilerError for Error {
331317
Error::DeleteOrphans { .. } => None,
332318
Error::VectorAggregatorConfigMapMissing => None,
333319
Error::InvalidLoggingConfig { .. } => None,
334-
Error::FailedToInitializeSecurityContext { .. } => None,
335320
Error::FailedToResolveConfig { .. } => None,
336321
Error::FailedToCreatePdb { .. } => None,
337322
Error::GracefulShutdown { .. } => None,
@@ -346,7 +331,6 @@ impl ReconcilerError for Error {
346331
Error::ApplyGroupListener { .. } => None,
347332
Error::BuildListenerPersistentVolume { .. } => None,
348333
Error::ListenerConfiguration { .. } => None,
349-
Error::ResolveProductImage { .. } => None,
350334
Error::BuildService { .. } => None,
351335
Error::RetrieveMetricsPortFromConfig { .. } => None,
352336
}
@@ -364,15 +348,23 @@ pub async fn reconcile_zk(
364348
.context(InvalidZookeeperClusterSnafu)?;
365349
let client = &ctx.client;
366350

367-
let resolved_product_image = zk
368-
.spec
369-
.image
370-
.resolve(
371-
CONTAINER_IMAGE_BASE_NAME,
372-
&ctx.operator_environment.image_repository,
373-
crate::built_info::PKG_VERSION,
374-
)
375-
.context(ResolveProductImageSnafu)?;
351+
// dereference (client required)
352+
let dereferenced_objects = dereference::dereference(client, zk)
353+
.await
354+
.context(DereferenceSnafu)?;
355+
356+
// validate (no client required)
357+
let validate::ValidatedInputs {
358+
resolved_product_image,
359+
zookeeper_security,
360+
validated_role_config,
361+
} = validate::validate(
362+
zk,
363+
&dereferenced_objects,
364+
&ctx.operator_environment,
365+
&ctx.product_config,
366+
)
367+
.context(ValidateClusterSnafu)?;
376368

377369
let mut cluster_resources = ClusterResources::new(
378370
APP_NAME,
@@ -384,39 +376,11 @@ pub async fn reconcile_zk(
384376
)
385377
.context(CreateClusterResourcesSnafu)?;
386378

387-
let validated_config = validate_all_roles_and_groups_config(
388-
&resolved_product_image.product_version,
389-
&transform_all_roles_to_config(
390-
zk,
391-
&[(
392-
ZookeeperRole::Server.to_string(),
393-
(
394-
vec![
395-
PropertyNameKind::Env,
396-
PropertyNameKind::File(ZOOKEEPER_PROPERTIES_FILE.to_string()),
397-
PropertyNameKind::File(JVM_SECURITY_PROPERTIES_FILE.to_string()),
398-
],
399-
zk.spec.servers.clone().context(NoServerRoleSnafu)?,
400-
),
401-
)]
402-
.into(),
403-
)
404-
.context(GenerateProductConfigSnafu)?,
405-
&ctx.product_config,
406-
false,
407-
false,
408-
)
409-
.context(InvalidProductConfigSnafu)?;
410-
411-
let role_server_config = validated_config
379+
let role_server_config = validated_role_config
412380
.get(&ZookeeperRole::Server.to_string())
413381
.map(Cow::Borrowed)
414382
.unwrap_or_default();
415383

416-
let zookeeper_security = ZookeeperSecurity::new_from_zookeeper_cluster(client, zk)
417-
.await
418-
.context(FailedToInitializeSecurityContextSnafu)?;
419-
420384
let (rbac_sa, rbac_rolebinding) = build_rbac_resources(
421385
zk,
422386
APP_NAME,
@@ -1072,9 +1036,15 @@ pub fn error_policy(
10721036

10731037
#[cfg(test)]
10741038
mod tests {
1075-
use stackable_operator::commons::networking::DomainName;
1039+
use stackable_operator::{
1040+
commons::networking::DomainName,
1041+
product_config_utils::{
1042+
transform_all_roles_to_config, validate_all_roles_and_groups_config,
1043+
},
1044+
};
10761045

10771046
use super::*;
1047+
use crate::crd::CONTAINER_IMAGE_BASE_NAME;
10781048

10791049
#[test]
10801050
fn test_default_config() {
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
//! The dereference step in the ZookeeperCluster controller.
2+
//!
3+
//! Fetches all Kubernetes objects referenced by the [`v1alpha1::ZookeeperCluster`] spec and
4+
//! returns them in [`DereferencedObjects`]. Synchronous validation of the fetched objects
5+
//! (image resolution, product-config validation, security struct assembly) happens in the
6+
//! validate step.
7+
8+
use snafu::{ResultExt, Snafu};
9+
use stackable_operator::client::Client;
10+
11+
use crate::crd::{
12+
authentication::{self, ResolvedAuthenticationClasses},
13+
v1alpha1,
14+
};
15+
16+
#[derive(Snafu, Debug)]
17+
pub enum Error {
18+
#[snafu(display("failed to resolve authentication classes"))]
19+
ResolveAuthenticationClasses { source: authentication::Error },
20+
}
21+
22+
type Result<T, E = Error> = std::result::Result<T, E>;
23+
24+
/// Kubernetes objects referenced from the [`v1alpha1::ZookeeperCluster`] spec, already fetched.
25+
pub struct DereferencedObjects {
26+
pub resolved_authentication_classes: ResolvedAuthenticationClasses,
27+
}
28+
29+
/// Fetches all Kubernetes objects referenced from the [`v1alpha1::ZookeeperCluster`] spec.
30+
pub async fn dereference(
31+
client: &Client,
32+
zk: &v1alpha1::ZookeeperCluster,
33+
) -> Result<DereferencedObjects> {
34+
let resolved_authentication_classes = authentication::resolve_authentication_classes(
35+
client,
36+
&zk.spec.cluster_config.authentication,
37+
)
38+
.await
39+
.context(ResolveAuthenticationClassesSnafu)?;
40+
41+
Ok(DereferencedObjects {
42+
resolved_authentication_classes,
43+
})
44+
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
//! The validate step in the ZookeeperCluster controller.
2+
//!
3+
//! Synchronously validates inputs that don't require a Kubernetes client. Produces
4+
//! [`ValidatedInputs`], consumed by the rest of `reconcile_zk`.
5+
6+
use product_config::{ProductConfigManager, types::PropertyNameKind};
7+
use snafu::{OptionExt, ResultExt, Snafu};
8+
use stackable_operator::{
9+
cli::OperatorEnvironmentOptions,
10+
commons::product_image_selection::{self, ResolvedProductImage},
11+
product_config_utils::{
12+
ValidatedRoleConfigByPropertyKind, transform_all_roles_to_config,
13+
validate_all_roles_and_groups_config,
14+
},
15+
};
16+
17+
use crate::{
18+
crd::{
19+
CONTAINER_IMAGE_BASE_NAME, JVM_SECURITY_PROPERTIES_FILE, ZOOKEEPER_PROPERTIES_FILE,
20+
ZookeeperRole,
21+
security::ZookeeperSecurity,
22+
v1alpha1,
23+
},
24+
zk_controller::dereference::DereferencedObjects,
25+
};
26+
27+
#[derive(Snafu, Debug)]
28+
pub enum Error {
29+
#[snafu(display("failed to resolve product image"))]
30+
ResolveProductImage {
31+
source: product_image_selection::Error,
32+
},
33+
34+
#[snafu(display("object defines no server role"))]
35+
NoServerRole,
36+
37+
#[snafu(display("failed to generate product config"))]
38+
GenerateProductConfig {
39+
source: stackable_operator::product_config_utils::Error,
40+
},
41+
42+
#[snafu(display("invalid product config"))]
43+
InvalidProductConfig {
44+
source: stackable_operator::product_config_utils::Error,
45+
},
46+
}
47+
48+
type Result<T, E = Error> = std::result::Result<T, E>;
49+
50+
/// Synchronous inputs the rest of `reconcile_zk` needs after dereferencing.
51+
pub struct ValidatedInputs {
52+
pub resolved_product_image: ResolvedProductImage,
53+
pub zookeeper_security: ZookeeperSecurity,
54+
pub validated_role_config: ValidatedRoleConfigByPropertyKind,
55+
}
56+
57+
/// Validates the cluster spec and the dereferenced inputs.
58+
pub fn validate(
59+
zk: &v1alpha1::ZookeeperCluster,
60+
dereferenced_objects: &DereferencedObjects,
61+
operator_environment: &OperatorEnvironmentOptions,
62+
product_config: &ProductConfigManager,
63+
) -> Result<ValidatedInputs> {
64+
let resolved_product_image = zk
65+
.spec
66+
.image
67+
.resolve(
68+
CONTAINER_IMAGE_BASE_NAME,
69+
&operator_environment.image_repository,
70+
crate::built_info::PKG_VERSION,
71+
)
72+
.context(ResolveProductImageSnafu)?;
73+
74+
let zookeeper_security = ZookeeperSecurity::new(
75+
zk,
76+
dereferenced_objects.resolved_authentication_classes.clone(),
77+
);
78+
79+
let validated_role_config =
80+
validated_product_config(zk, &resolved_product_image.product_version, product_config)?;
81+
82+
Ok(ValidatedInputs {
83+
resolved_product_image,
84+
zookeeper_security,
85+
validated_role_config,
86+
})
87+
}
88+
89+
fn validated_product_config(
90+
zk: &v1alpha1::ZookeeperCluster,
91+
product_version: &str,
92+
product_config: &ProductConfigManager,
93+
) -> Result<ValidatedRoleConfigByPropertyKind> {
94+
let server_role = zk.spec.servers.clone().context(NoServerRoleSnafu)?;
95+
96+
let role_config = transform_all_roles_to_config(
97+
zk,
98+
&[(
99+
ZookeeperRole::Server.to_string(),
100+
(
101+
vec![
102+
PropertyNameKind::Env,
103+
PropertyNameKind::File(ZOOKEEPER_PROPERTIES_FILE.to_string()),
104+
PropertyNameKind::File(JVM_SECURITY_PROPERTIES_FILE.to_string()),
105+
],
106+
server_role,
107+
),
108+
)]
109+
.into(),
110+
)
111+
.context(GenerateProductConfigSnafu)?;
112+
113+
validate_all_roles_and_groups_config(
114+
product_version,
115+
&role_config,
116+
product_config,
117+
false,
118+
false,
119+
)
120+
.context(InvalidProductConfigSnafu)
121+
}

0 commit comments

Comments
 (0)