Skip to content

Commit 2710992

Browse files
committed
refactor(znode_controller): extract dereference and validate steps
1 parent 122cbdf commit 2710992

3 files changed

Lines changed: 208 additions & 99 deletions

File tree

rust/operator-binary/src/znode_controller.rs

Lines changed: 53 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ use snafu::{OptionExt, ResultExt, Snafu};
88
use stackable_operator::{
99
cli::OperatorEnvironmentOptions,
1010
cluster_resources::{ClusterResourceApplyStrategy, ClusterResources},
11-
commons::product_image_selection::{self, ResolvedProductImage},
11+
commons::product_image_selection::ResolvedProductImage,
1212
crd::listener,
1313
k8s_openapi::api::core::v1::ConfigMap,
1414
kube::{
15-
self, Resource,
15+
Resource,
1616
api::ObjectMeta,
1717
core::{DeserializeGuard, DynamicObject, error_boundary},
1818
runtime::{controller, finalizer, reflector::ObjectRef},
@@ -26,11 +26,14 @@ use tracing::{debug, info};
2626

2727
use crate::{
2828
APP_NAME, OPERATOR_NAME,
29-
crd::{CONTAINER_IMAGE_BASE_NAME, ZookeeperRole, security::ZookeeperSecurity, v1alpha1},
29+
crd::{ZookeeperRole, security::ZookeeperSecurity, v1alpha1},
3030
discovery::{self, build_discovery_configmap},
3131
listener::role_listener_name,
3232
};
3333

34+
mod dereference;
35+
mod validate;
36+
3437
pub const ZNODE_CONTROLLER_NAME: &str = "znode";
3538
pub const ZNODE_FULL_CONTROLLER_NAME: &str = concatcp!(ZNODE_CONTROLLER_NAME, '.', OPERATOR_NAME);
3639

@@ -48,25 +51,17 @@ pub enum Error {
4851
source: error_boundary::InvalidObject,
4952
},
5053

54+
#[snafu(display("failed to dereference resources"))]
55+
Dereference { source: dereference::Error },
56+
57+
#[snafu(display("failed to validate cluster"))]
58+
ValidateCluster { source: validate::Error },
59+
5160
#[snafu(display(
5261
"object is missing metadata that should be created by the Kubernetes cluster",
5362
))]
5463
ObjectMissingMetadata,
5564

56-
#[snafu(display("object does not refer to ZookeeperCluster"))]
57-
InvalidZkReference,
58-
59-
#[snafu(display("could not find {zk:?}"))]
60-
FindZk {
61-
source: stackable_operator::client::Error,
62-
zk: ObjectRef<v1alpha1::ZookeeperCluster>,
63-
},
64-
65-
ZkDoesNotExist {
66-
source: stackable_operator::client::Error,
67-
zk: ObjectRef<v1alpha1::ZookeeperCluster>,
68-
},
69-
7065
#[snafu(display("could not find server role service name for {zk:?}"))]
7166
NoZkSvcName {
7267
zk: ObjectRef<v1alpha1::ZookeeperCluster>,
@@ -124,19 +119,11 @@ pub enum Error {
124119
#[snafu(display("object has no namespace"))]
125120
ObjectHasNoNamespace,
126121

127-
#[snafu(display("failed to initialize security context"))]
128-
FailedToInitializeSecurityContext { source: crate::crd::security::Error },
129-
130122
#[snafu(display("Znode {znode:?} missing expected keys (name and/or namespace)"))]
131123
ZnodeMissingExpectedKeys {
132124
source: stackable_operator::cluster_resources::Error,
133125
znode: ObjectRef<v1alpha1::ZookeeperZnode>,
134126
},
135-
136-
#[snafu(display("failed to resolve product image"))]
137-
ResolveProductImage {
138-
source: product_image_selection::Error,
139-
},
140127
}
141128
type Result<T, E = Error> = std::result::Result<T, E>;
142129

@@ -169,10 +156,9 @@ impl ReconcilerError for Error {
169156
fn secondary_object(&self) -> Option<ObjectRef<DynamicObject>> {
170157
match self {
171158
Error::InvalidZookeeperZnode { .. } => None,
159+
Error::Dereference { .. } => None,
160+
Error::ValidateCluster { .. } => None,
172161
Error::ObjectMissingMetadata => None,
173-
Error::InvalidZkReference => None,
174-
Error::FindZk { zk, .. } => Some(zk.clone().erase()),
175-
Error::ZkDoesNotExist { zk, .. } => Some(zk.clone().erase()),
176162
Error::NoZkSvcName { zk } => Some(zk.clone().erase()),
177163
Error::FindZkSvc { zk, .. } => Some(zk.clone().erase()),
178164
Error::NoZkFqdn { zk } => Some(zk.clone().erase()),
@@ -184,9 +170,7 @@ impl ReconcilerError for Error {
184170
Error::Finalizer { .. } => None,
185171
Error::DeleteOrphans { .. } => None,
186172
Error::ObjectHasNoNamespace => None,
187-
Error::FailedToInitializeSecurityContext { .. } => None,
188173
Error::ZnodeMissingExpectedKeys { .. } => None,
189-
Error::ResolveProductImage { .. } => None,
190174
}
191175
}
192176
}
@@ -213,7 +197,8 @@ pub async fn reconcile_znode(
213197
};
214198
let client = &ctx.client;
215199

216-
let zk = find_zk_of_znode(client, znode).await;
200+
// dereference (client required) — replaces find_zk_of_znode
201+
let dereferenced_objects = dereference::dereference(client, znode).await;
217202
let mut default_status_updates: Option<v1alpha1::ZookeeperZnodeStatus> = None;
218203
// Store the znode path in the status rather than the object itself, to ensure that only K8s administrators can override it
219204
let znode_path = match znode.status.as_ref().and_then(|s| s.znode_path.as_deref()) {
@@ -251,21 +236,37 @@ pub async fn reconcile_znode(
251236
|ev| async {
252237
match ev {
253238
finalizer::Event::Apply(znode) => {
254-
let zk = zk?;
255-
let resolved_product_image = zk
256-
.spec
257-
.image
258-
.resolve(
259-
CONTAINER_IMAGE_BASE_NAME,
260-
&ctx.operator_environment.image_repository,
261-
crate::built_info::PKG_VERSION,
262-
)
263-
.context(ResolveProductImageSnafu)?;
264-
reconcile_apply(client, &znode, Ok(zk), &znode_path, &resolved_product_image)
265-
.await
239+
let dereferenced = dereferenced_objects.context(DereferenceSnafu)?;
240+
let validate::ValidatedInputs {
241+
resolved_product_image,
242+
zookeeper_security,
243+
} = validate::validate(&znode, &dereferenced, &ctx.operator_environment)
244+
.context(ValidateClusterSnafu)?;
245+
reconcile_apply(
246+
client,
247+
&znode,
248+
dereferenced.zk,
249+
&zookeeper_security,
250+
&znode_path,
251+
&resolved_product_image,
252+
)
253+
.await
266254
}
267-
finalizer::Event::Cleanup(_znode) => {
268-
reconcile_cleanup(client, zk, &znode_path).await
255+
finalizer::Event::Cleanup(znode) => {
256+
let dereferenced = match dereferenced_objects {
257+
Ok(d) => d,
258+
Err(dereference::Error::ZkDoesNotExist { zk, .. }) => {
259+
tracing::info!(%zk, "Tried to clean up ZookeeperZnode bound to a ZookeeperCluster that does not exist, assuming it is already gone");
260+
return Ok(controller::Action::await_change());
261+
}
262+
Err(e) => return Err(e).context(DereferenceSnafu),
263+
};
264+
let validate::ValidatedInputs {
265+
zookeeper_security, ..
266+
} = validate::validate(&znode, &dereferenced, &ctx.operator_environment)
267+
.context(ValidateClusterSnafu)?;
268+
reconcile_cleanup(client, dereferenced.zk, &zookeeper_security, &znode_path)
269+
.await
269270
}
270271
}
271272
},
@@ -277,16 +278,11 @@ pub async fn reconcile_znode(
277278
async fn reconcile_apply(
278279
client: &stackable_operator::client::Client,
279280
znode: &v1alpha1::ZookeeperZnode,
280-
zk: Result<v1alpha1::ZookeeperCluster>,
281+
zk: v1alpha1::ZookeeperCluster,
282+
zookeeper_security: &ZookeeperSecurity,
281283
znode_path: &str,
282284
resolved_product_image: &ResolvedProductImage,
283285
) -> Result<controller::Action> {
284-
let zk = zk?;
285-
286-
let zookeeper_security = ZookeeperSecurity::new_from_zookeeper_cluster(client, &zk)
287-
.await
288-
.context(FailedToInitializeSecurityContextSnafu)?;
289-
290286
let mut cluster_resources = ClusterResources::new(
291287
APP_NAME,
292288
OPERATOR_NAME,
@@ -298,7 +294,7 @@ async fn reconcile_apply(
298294
.context(ZnodeMissingExpectedKeysSnafu { znode })?;
299295

300296
znode_mgmt::ensure_znode_exists(
301-
&zk_mgmt_addr(&zk, &zookeeper_security, &client.kubernetes_cluster_info)?,
297+
&zk_mgmt_addr(&zk, zookeeper_security, &client.kubernetes_cluster_info)?,
302298
znode_path,
303299
)
304300
.await
@@ -327,7 +323,7 @@ async fn reconcile_apply(
327323
listener,
328324
Some(znode_path),
329325
resolved_product_image,
330-
&zookeeper_security,
326+
zookeeper_security,
331327
)
332328
.context(BuildDiscoveryConfigMapSnafu)?;
333329

@@ -346,24 +342,13 @@ async fn reconcile_apply(
346342

347343
async fn reconcile_cleanup(
348344
client: &stackable_operator::client::Client,
349-
zk: Result<v1alpha1::ZookeeperCluster>,
345+
zk: v1alpha1::ZookeeperCluster,
346+
zookeeper_security: &ZookeeperSecurity,
350347
znode_path: &str,
351348
) -> Result<controller::Action> {
352-
let zk = match zk {
353-
Err(Error::ZkDoesNotExist { zk, .. }) => {
354-
tracing::info!(%zk, "Tried to clean up ZookeeperZnode bound to a ZookeeperCluster that does not exist, assuming it is already gone");
355-
return Ok(controller::Action::await_change());
356-
}
357-
res => res?,
358-
};
359-
360-
let zookeeper_security = ZookeeperSecurity::new_from_zookeeper_cluster(client, &zk)
361-
.await
362-
.context(FailedToInitializeSecurityContextSnafu)?;
363-
364349
// Clean up znode from the ZooKeeper cluster before letting Kubernetes delete the object
365350
znode_mgmt::ensure_znode_missing(
366-
&zk_mgmt_addr(&zk, &zookeeper_security, &client.kubernetes_cluster_info)?,
351+
&zk_mgmt_addr(&zk, zookeeper_security, &client.kubernetes_cluster_info)?,
367352
znode_path,
368353
)
369354
.await
@@ -403,37 +388,6 @@ fn zk_mgmt_addr(
403388
))
404389
}
405390

406-
async fn find_zk_of_znode(
407-
client: &stackable_operator::client::Client,
408-
znode: &v1alpha1::ZookeeperZnode,
409-
) -> Result<v1alpha1::ZookeeperCluster> {
410-
let zk_ref = &znode.spec.cluster_ref;
411-
if let (Some(zk_name), Some(zk_ns)) = (
412-
zk_ref.name.as_deref(),
413-
zk_ref.namespace_relative_from(znode),
414-
) {
415-
match client
416-
.get::<v1alpha1::ZookeeperCluster>(zk_name, zk_ns)
417-
.await
418-
{
419-
Ok(zk) => Ok(zk),
420-
Err(err) => match &err {
421-
stackable_operator::client::Error::GetResource {
422-
source: kube::Error::Api(s),
423-
..
424-
} if s.is_not_found() => Err(err).with_context(|_| ZkDoesNotExistSnafu {
425-
zk: ObjectRef::new(zk_name).within(zk_ns),
426-
}),
427-
_ => Err(err).with_context(|_| FindZkSnafu {
428-
zk: ObjectRef::new(zk_name).within(zk_ns),
429-
}),
430-
},
431-
}
432-
} else {
433-
InvalidZkReferenceSnafu.fail()
434-
}
435-
}
436-
437391
pub fn error_policy(
438392
_obj: Arc<DeserializeGuard<v1alpha1::ZookeeperZnode>>,
439393
_error: &Error,
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
//! The dereference step in the ZookeeperZnode controller.
2+
//!
3+
//! Fetches the parent [`v1alpha1::ZookeeperCluster`] referenced by the znode's
4+
//! `spec.clusterRef`, plus the [`ResolvedAuthenticationClasses`] of that cluster. Both Apply
5+
//! and Cleanup paths in `reconcile_znode` share this output.
6+
7+
use snafu::{ResultExt, Snafu};
8+
use stackable_operator::{
9+
client::Client,
10+
kube::{self, runtime::reflector::ObjectRef},
11+
};
12+
13+
use crate::crd::{
14+
authentication::{self, ResolvedAuthenticationClasses},
15+
v1alpha1,
16+
};
17+
18+
#[derive(Snafu, Debug)]
19+
pub enum Error {
20+
#[snafu(display("object does not refer to ZookeeperCluster"))]
21+
InvalidZkReference,
22+
23+
#[snafu(display("could not find {zk:?}"))]
24+
FindZk {
25+
source: stackable_operator::client::Error,
26+
zk: ObjectRef<v1alpha1::ZookeeperCluster>,
27+
},
28+
29+
#[snafu(display("could not find {zk:?}"))]
30+
ZkDoesNotExist {
31+
source: stackable_operator::client::Error,
32+
zk: ObjectRef<v1alpha1::ZookeeperCluster>,
33+
},
34+
35+
#[snafu(display("failed to resolve authentication classes"))]
36+
ResolveAuthenticationClasses { source: authentication::Error },
37+
}
38+
39+
type Result<T, E = Error> = std::result::Result<T, E>;
40+
41+
/// Kubernetes objects referenced from the [`v1alpha1::ZookeeperZnode`] spec, already fetched.
42+
pub struct DereferencedObjects {
43+
pub zk: v1alpha1::ZookeeperCluster,
44+
pub resolved_authentication_classes: ResolvedAuthenticationClasses,
45+
}
46+
47+
/// Fetches all Kubernetes objects referenced from the [`v1alpha1::ZookeeperZnode`] spec.
48+
pub async fn dereference(
49+
client: &Client,
50+
znode: &v1alpha1::ZookeeperZnode,
51+
) -> Result<DereferencedObjects> {
52+
let zk = find_zk_of_znode(client, znode).await?;
53+
54+
let resolved_authentication_classes = authentication::resolve_authentication_classes(
55+
client,
56+
&zk.spec.cluster_config.authentication,
57+
)
58+
.await
59+
.context(ResolveAuthenticationClassesSnafu)?;
60+
61+
Ok(DereferencedObjects {
62+
zk,
63+
resolved_authentication_classes,
64+
})
65+
}
66+
67+
async fn find_zk_of_znode(
68+
client: &Client,
69+
znode: &v1alpha1::ZookeeperZnode,
70+
) -> Result<v1alpha1::ZookeeperCluster> {
71+
let zk_ref = &znode.spec.cluster_ref;
72+
let (Some(zk_name), Some(zk_ns)) = (
73+
zk_ref.name.as_deref(),
74+
zk_ref.namespace_relative_from(znode),
75+
) else {
76+
return InvalidZkReferenceSnafu.fail();
77+
};
78+
79+
match client
80+
.get::<v1alpha1::ZookeeperCluster>(zk_name, zk_ns)
81+
.await
82+
{
83+
Ok(zk) => Ok(zk),
84+
Err(err) => match &err {
85+
stackable_operator::client::Error::GetResource {
86+
source: kube::Error::Api(s),
87+
..
88+
} if s.is_not_found() => Err(err).with_context(|_| ZkDoesNotExistSnafu {
89+
zk: ObjectRef::new(zk_name).within(zk_ns),
90+
}),
91+
_ => Err(err).with_context(|_| FindZkSnafu {
92+
zk: ObjectRef::new(zk_name).within(zk_ns),
93+
}),
94+
},
95+
}
96+
}

0 commit comments

Comments
 (0)