Skip to content

Commit bdb6aff

Browse files
committed
refactor: Introduce build aggregator
1 parent d6c0c26 commit bdb6aff

2 files changed

Lines changed: 268 additions & 137 deletions

File tree

rust/operator-binary/src/zk_controller.rs

Lines changed: 79 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,17 @@ use std::{hash::Hasher, str::FromStr, sync::Arc};
33

44
use const_format::concatcp;
55
use fnv::FnvHasher;
6-
use snafu::{ResultExt, Snafu};
6+
use snafu::{OptionExt, ResultExt, Snafu};
77
use stackable_operator::{
88
cli::OperatorEnvironmentOptions,
99
cluster_resources::ClusterResourceApplyStrategy,
1010
commons::rbac::build_rbac_resources,
11+
crd::listener::v1alpha1::Listener,
12+
k8s_openapi::api::{
13+
apps::v1::StatefulSet,
14+
core::v1::{ConfigMap, Service},
15+
policy::v1::PodDisruptionBudget,
16+
},
1117
kube::{
1218
api::DynamicObject,
1319
core::{DeserializeGuard, error_boundary},
@@ -20,26 +26,15 @@ use stackable_operator::{
2026
compute_conditions, operations::ClusterOperationsConditionBuilder,
2127
statefulset::StatefulSetConditionBuilder,
2228
},
23-
v2::{
24-
cluster_resources::cluster_resources_new,
25-
types::operator::{ControllerName, RoleGroupName},
26-
},
29+
v2::{cluster_resources::cluster_resources_new, types::operator::ControllerName},
2730
};
2831
use strum::{EnumDiscriminants, IntoStaticStr};
2932

3033
use crate::{
3134
APP_NAME, OPERATOR_NAME, ObjectRef,
32-
crd::{ZookeeperRole, v1alpha1},
35+
crd::v1alpha1,
3336
zk_controller::{
34-
build::resource::{
35-
config_map, discovery,
36-
listener::build_role_listener,
37-
pdb::build_pdb,
38-
service::{
39-
build_server_rolegroup_headless_service, build_server_rolegroup_metrics_service,
40-
},
41-
statefulset::build_server_rolegroup_statefulset,
42-
},
37+
build::resource::discovery,
4338
validate::{operator_name, product_name},
4439
},
4540
};
@@ -62,7 +57,6 @@ type Result<T, E = Error> = std::result::Result<T, E>;
6257

6358
#[derive(Snafu, Debug, EnumDiscriminants)]
6459
#[strum_discriminants(derive(IntoStaticStr))]
65-
#[allow(clippy::enum_variant_names)]
6660
pub enum Error {
6761
#[snafu(display("ZookeeperCluster object is invalid"))]
6862
InvalidZookeeperCluster {
@@ -81,41 +75,24 @@ pub enum Error {
8175
#[snafu(display("internal operator failure"))]
8276
InternalOperatorFailure { source: crate::crd::Error },
8377

84-
#[snafu(display("failed to apply Service for role group {rolegroup}"))]
85-
ApplyRoleGroupService {
86-
source: stackable_operator::cluster_resources::Error,
87-
rolegroup: RoleGroupName,
88-
},
89-
90-
#[snafu(display("failed to build ConfigMap for role group {rolegroup}"))]
91-
BuildRoleGroupConfigMap {
92-
source: config_map::Error,
93-
rolegroup: RoleGroupName,
94-
},
78+
#[snafu(display("failed to build the Kubernetes resources"))]
79+
BuildResources { source: build::Error },
9580

96-
#[snafu(display("failed to apply ConfigMap for role group {rolegroup}"))]
97-
ApplyRoleGroupConfig {
81+
#[snafu(display("failed to apply Kubernetes resource"))]
82+
ApplyResource {
9883
source: stackable_operator::cluster_resources::Error,
99-
rolegroup: RoleGroupName,
100-
},
101-
102-
#[snafu(display("failed to build StatefulSet for role group {rolegroup}"))]
103-
BuildRoleGroupStatefulSet {
104-
source: build::resource::statefulset::Error,
105-
rolegroup: RoleGroupName,
106-
},
107-
108-
#[snafu(display("failed to apply StatefulSet for role group {rolegroup}"))]
109-
ApplyRoleGroupStatefulSet {
110-
source: stackable_operator::cluster_resources::Error,
111-
rolegroup: RoleGroupName,
11284
},
11385

11486
#[snafu(display("object is missing metadata to build owner reference"))]
11587
ObjectMissingMetadataForOwnerRef {
11688
source: stackable_operator::builder::meta::Error,
11789
},
11890

91+
#[snafu(display(
92+
"no role Listener was applied; the discovery ConfigMap is derived from the applied role Listener"
93+
))]
94+
NoRoleListener,
95+
11996
#[snafu(display("failed to build discovery ConfigMap"))]
12097
BuildDiscoveryConfig { source: discovery::Error },
12198

@@ -149,23 +126,13 @@ pub enum Error {
149126
source: stackable_operator::cluster_resources::Error,
150127
},
151128

152-
#[snafu(display("failed to apply PodDisruptionBudget"))]
153-
ApplyPdb {
154-
source: stackable_operator::cluster_resources::Error,
155-
},
156-
157129
#[snafu(display("failed to build label"))]
158130
BuildLabel { source: LabelError },
159131

160132
#[snafu(display("failed to build object meta data"))]
161133
ObjectMeta {
162134
source: stackable_operator::builder::meta::Error,
163135
},
164-
165-
#[snafu(display("failed to apply group listener"))]
166-
ApplyGroupListener {
167-
source: stackable_operator::cluster_resources::Error,
168-
},
169136
}
170137

171138
impl ReconcilerError for Error {
@@ -180,27 +147,34 @@ impl ReconcilerError for Error {
180147
Error::ValidateCluster { .. } => None,
181148
Error::CrdValidationFailure { .. } => None,
182149
Error::InternalOperatorFailure { .. } => None,
183-
Error::ApplyRoleGroupService { .. } => None,
184-
Error::BuildRoleGroupConfigMap { .. } => None,
185-
Error::ApplyRoleGroupConfig { .. } => None,
186-
Error::BuildRoleGroupStatefulSet { .. } => None,
187-
Error::ApplyRoleGroupStatefulSet { .. } => None,
150+
Error::BuildResources { .. } => None,
151+
Error::ApplyResource { .. } => None,
188152
Error::ObjectMissingMetadataForOwnerRef { .. } => None,
153+
Error::NoRoleListener => None,
189154
Error::BuildDiscoveryConfig { .. } => None,
190155
Error::ApplyDiscoveryConfig { .. } => None,
191156
Error::ApplyStatus { .. } => None,
192157
Error::ApplyServiceAccount { .. } => None,
193158
Error::ApplyRoleBinding { .. } => None,
194159
Error::BuildRbacResources { .. } => None,
195160
Error::DeleteOrphans { .. } => None,
196-
Error::ApplyPdb { .. } => None,
197161
Error::BuildLabel { .. } => None,
198162
Error::ObjectMeta { .. } => None,
199-
Error::ApplyGroupListener { .. } => None,
200163
}
201164
}
202165
}
203166

167+
/// Every Kubernetes resource produced by the client-free [`build()`](build::build) step.
168+
///
169+
/// The discovery `ConfigMap` is deliberately absent — see [`build()`](build::build).
170+
pub struct KubernetesResources {
171+
pub stateful_sets: Vec<StatefulSet>,
172+
pub services: Vec<Service>,
173+
pub listeners: Vec<Listener>,
174+
pub config_maps: Vec<ConfigMap>,
175+
pub pod_disruption_budgets: Vec<PodDisruptionBudget>,
176+
}
177+
204178
pub async fn reconcile_zk(
205179
zk: Arc<DeserializeGuard<v1alpha1::ZookeeperCluster>>,
206180
ctx: Arc<Ctx>,
@@ -245,7 +219,7 @@ pub async fn reconcile_zk(
245219
.context(BuildRbacResourcesSnafu)?;
246220

247221
cluster_resources
248-
.add(client, rbac_sa.clone())
222+
.add(client, rbac_sa)
249223
.await
250224
.context(ApplyServiceAccountSnafu)?;
251225

@@ -254,98 +228,63 @@ pub async fn reconcile_zk(
254228
.await
255229
.context(ApplyRoleBindingSnafu)?;
256230

257-
let mut ss_cond_builder = StatefulSetConditionBuilder::default();
231+
let resources = build::build(&validated_cluster, &client.kubernetes_cluster_info)
232+
.context(BuildResourcesSnafu)?;
258233

259-
let zk_role = ZookeeperRole::Server;
260-
let server_role_group_configs = validated_cluster
261-
.role_group_configs
262-
.get(&zk_role)
263-
.into_iter()
264-
.flatten();
265-
for (rolegroup_name, rolegroup_config) in server_role_group_configs {
266-
// Resource naming, labels and owner references are derived from the `ValidatedCluster` and
267-
// the type-safe `RoleGroupName`.
268-
let rg_headless_service =
269-
build_server_rolegroup_headless_service(&validated_cluster, rolegroup_name);
270-
let rg_metrics_service = build_server_rolegroup_metrics_service(
271-
&validated_cluster,
272-
rolegroup_name,
273-
rolegroup_config,
274-
);
275-
let rg_configmap = config_map::build_server_rolegroup_config_map(
276-
&validated_cluster,
277-
&client.kubernetes_cluster_info,
278-
rolegroup_name,
279-
rolegroup_config,
280-
)
281-
.context(BuildRoleGroupConfigMapSnafu {
282-
rolegroup: rolegroup_name.clone(),
283-
})?;
284-
let rg_statefulset = build_server_rolegroup_statefulset(
285-
&validated_cluster,
286-
rolegroup_name,
287-
rolegroup_config,
288-
)
289-
.with_context(|_| BuildRoleGroupStatefulSetSnafu {
290-
rolegroup: rolegroup_name.clone(),
291-
})?;
234+
let mut ss_cond_builder = StatefulSetConditionBuilder::default();
292235

236+
for service in resources.services {
293237
cluster_resources
294-
.add(client, rg_headless_service)
295-
.await
296-
.with_context(|_| ApplyRoleGroupServiceSnafu {
297-
rolegroup: rolegroup_name.clone(),
298-
})?;
299-
cluster_resources
300-
.add(client, rg_metrics_service)
238+
.add(client, service)
301239
.await
302-
.with_context(|_| ApplyRoleGroupServiceSnafu {
303-
rolegroup: rolegroup_name.clone(),
304-
})?;
305-
cluster_resources
306-
.add(client, rg_configmap)
307-
.await
308-
.with_context(|_| ApplyRoleGroupConfigSnafu {
309-
rolegroup: rolegroup_name.clone(),
310-
})?;
240+
.context(ApplyResourceSnafu)?;
241+
}
311242

312-
// Note: The StatefulSet needs to be applied after all ConfigMaps and Secrets it mounts
313-
// to prevent unnecessary Pod restarts.
314-
// See https://github.com/stackabletech/commons-operator/issues/111 for details.
315-
ss_cond_builder.add(
243+
// ZooKeeper has a single role Listener; the applied object feeds the discovery ConfigMap.
244+
let mut applied_role_listener: Option<Listener> = None;
245+
for listener in resources.listeners {
246+
applied_role_listener = Some(
316247
cluster_resources
317-
.add(client, rg_statefulset)
248+
.add(client, listener)
318249
.await
319-
.with_context(|_| ApplyRoleGroupStatefulSetSnafu {
320-
rolegroup: rolegroup_name.clone(),
321-
})?,
250+
.context(ApplyResourceSnafu)?,
322251
);
323252
}
253+
let role_listener = applied_role_listener.context(NoRoleListenerSnafu)?;
324254

325-
if let Some(role_config) = &validated_cluster.role_config
326-
&& let Some(pdb) = build_pdb(&role_config.pdb, &validated_cluster, &zk_role)
327-
{
255+
for config_map in resources.config_maps {
256+
cluster_resources
257+
.add(client, config_map)
258+
.await
259+
.context(ApplyResourceSnafu)?;
260+
}
261+
262+
for pdb in resources.pod_disruption_budgets {
328263
cluster_resources
329264
.add(client, pdb)
330265
.await
331-
.context(ApplyPdbSnafu)?;
266+
.context(ApplyResourceSnafu)?;
332267
}
333268

334-
let listener = build_role_listener(&validated_cluster, &zk_role);
335-
let applied_listener = cluster_resources
336-
.add(client, listener)
337-
.await
338-
.context(ApplyGroupListenerSnafu)?;
269+
// Note: The StatefulSet needs to be applied after all ConfigMaps and Secrets it mounts
270+
// to prevent unnecessary Pod restarts.
271+
// See https://github.com/stackabletech/commons-operator/issues/111 for details.
272+
for statefulset in resources.stateful_sets {
273+
ss_cond_builder.add(
274+
cluster_resources
275+
.add(client, statefulset)
276+
.await
277+
.context(ApplyResourceSnafu)?,
278+
);
279+
}
339280

340281
// std's SipHasher is deprecated, and DefaultHasher is unstable across Rust releases.
341282
// We don't /need/ stability, but it's still nice to avoid spurious changes where possible.
342283
let mut discovery_hash = FnvHasher::with_key(0);
343-
let discovery_cm = discovery::build_discovery_configmap(
344-
&validated_cluster,
345-
ZK_CONTROLLER_NAME,
346-
applied_listener,
347-
)
348-
.context(BuildDiscoveryConfigSnafu)?;
284+
285+
let discovery_cm =
286+
discovery::build_discovery_configmap(&validated_cluster, ZK_CONTROLLER_NAME, role_listener)
287+
.context(BuildDiscoveryConfigSnafu)?;
349288

350289
let discovery_cm = cluster_resources
351290
.add(client, discovery_cm)
@@ -462,8 +401,13 @@ mod tests {
462401
k8s_openapi::api::core::v1::ConfigMap, v2::types::operator::RoleGroupName,
463402
};
464403

465-
use super::*;
466-
use crate::zk_controller::test_support::{cluster_info, minimal_zk, validated_cluster};
404+
use crate::{
405+
crd::ZookeeperRole,
406+
zk_controller::{
407+
build::resource::config_map,
408+
test_support::{cluster_info, minimal_zk, validated_cluster},
409+
},
410+
};
467411

468412
#[test]
469413
fn test_default_config() {

0 commit comments

Comments
 (0)