Skip to content

Commit fc5daf4

Browse files
committed
refactor: introduce the build aggregator
1 parent 37dcb30 commit fc5daf4

3 files changed

Lines changed: 153 additions & 103 deletions

File tree

rust/operator-binary/src/controller/build/mod.rs

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,107 @@
33
44
use std::str::FromStr;
55

6-
use stackable_operator::v2::types::operator::RoleGroupName;
6+
use snafu::{ResultExt, Snafu};
7+
use stackable_operator::{
8+
utils::cluster_info::KubernetesClusterInfo, v2::types::operator::RoleGroupName,
9+
};
10+
11+
use crate::controller::{
12+
KubernetesResources, ValidatedCluster,
13+
build::resource::{
14+
config_map::{self, build_rolegroup_config_map},
15+
pdb::build_pdb,
16+
service::{build_rolegroup_metrics_service, build_rolegroup_service},
17+
statefulset::{self, build_rolegroup_statefulset},
18+
},
19+
};
720

821
// Placeholder role-group name used for the recommended labels of the role-level discovery
922
// `ConfigMap` (which is not tied to a single role group).
1023
stackable_operator::constant!(pub(crate) PLACEHOLDER_DISCOVERY_ROLE_GROUP: RoleGroupName = "discovery");
1124

25+
#[derive(Snafu, Debug)]
26+
pub enum Error {
27+
#[snafu(display("failed to build ConfigMap for role group {role_group}"))]
28+
ConfigMap {
29+
source: config_map::Error,
30+
role_group: RoleGroupName,
31+
},
32+
33+
#[snafu(display("failed to build StatefulSet for role group {role_group}"))]
34+
StatefulSet {
35+
source: statefulset::Error,
36+
role_group: RoleGroupName,
37+
},
38+
}
39+
40+
/// Builds every Kubernetes resource for the given validated cluster.
41+
///
42+
/// Does not need a Kubernetes client: every reference to another Kubernetes resource is already
43+
/// dereferenced and validated by this point, so the errors returned here are resource-assembly
44+
/// failures only. `cluster_info` is static cluster metadata (not a client call), and
45+
/// `service_account_name` is the name of the RBAC `ServiceAccount` the role-group Pods run under
46+
/// (RBAC resources are built and applied separately, in the reconcile step).
47+
///
48+
/// The role-level discovery `ConfigMap` is applied separately in the reconcile step and is not
49+
/// part of this bundle.
50+
pub fn build(
51+
cluster: &ValidatedCluster,
52+
cluster_info: &KubernetesClusterInfo,
53+
service_account_name: &str,
54+
) -> Result<KubernetesResources, Error> {
55+
let mut stateful_sets = vec![];
56+
let mut services = vec![];
57+
let mut config_maps = vec![];
58+
let mut pod_disruption_budgets = vec![];
59+
60+
for (hbase_role, role_group_configs) in &cluster.role_group_configs {
61+
for (role_group_name, rg_config) in role_group_configs {
62+
services.push(build_rolegroup_service(
63+
cluster,
64+
hbase_role,
65+
role_group_name,
66+
));
67+
services.push(build_rolegroup_metrics_service(
68+
cluster,
69+
hbase_role,
70+
role_group_name,
71+
));
72+
config_maps.push(
73+
build_rolegroup_config_map(cluster, cluster_info, hbase_role, role_group_name)
74+
.context(ConfigMapSnafu {
75+
role_group: role_group_name.clone(),
76+
})?,
77+
);
78+
stateful_sets.push(
79+
build_rolegroup_statefulset(
80+
cluster,
81+
hbase_role,
82+
role_group_name,
83+
rg_config,
84+
service_account_name,
85+
)
86+
.context(StatefulSetSnafu {
87+
role_group: role_group_name.clone(),
88+
})?,
89+
);
90+
}
91+
92+
if let Some(role_config) = cluster.role_configs.get(hbase_role)
93+
&& let Some(pdb) = build_pdb(&role_config.pdb, cluster, hbase_role)
94+
{
95+
pod_disruption_budgets.push(pdb);
96+
}
97+
}
98+
99+
Ok(KubernetesResources {
100+
stateful_sets,
101+
services,
102+
config_maps,
103+
pod_disruption_budgets,
104+
})
105+
}
106+
12107
pub mod graceful_shutdown;
13108
pub mod jvm;
14109
pub mod kerberos;

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

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,14 @@ pub use stackable_operator::v2::types::operator::RoleGroupName;
1010
use stackable_operator::{
1111
builder::meta::ObjectMetaBuilder,
1212
commons::product_image_selection::ResolvedProductImage,
13-
k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta,
13+
k8s_openapi::{
14+
api::{
15+
apps::v1::StatefulSet,
16+
core::v1::{ConfigMap, Service},
17+
policy::v1::PodDisruptionBudget,
18+
},
19+
apimachinery::pkg::apis::meta::v1::ObjectMeta,
20+
},
1421
kube::Resource,
1522
kvp::Labels,
1623
v2::{
@@ -51,6 +58,17 @@ pub(crate) fn controller_name() -> ControllerName {
5158
.expect("the controller name is a valid label value")
5259
}
5360

61+
/// The complete set of Kubernetes resources built for a [`ValidatedCluster`], ready to be applied.
62+
///
63+
/// hbase exposes its listeners as volume/PVC sources inside the `StatefulSet` rather than as
64+
/// top-level `Listener` objects, so (unlike some sibling operators) there is no `listeners` field.
65+
pub struct KubernetesResources {
66+
pub stateful_sets: Vec<StatefulSet>,
67+
pub services: Vec<Service>,
68+
pub config_maps: Vec<ConfigMap>,
69+
pub pod_disruption_budgets: Vec<PodDisruptionBudget>,
70+
}
71+
5472
/// The validated cluster: proves that config merging and validation succeeded for
5573
/// every role and role group before any resources are created.
5674
#[derive(Clone, Debug)]

rust/operator-binary/src/hbase_controller.rs

Lines changed: 38 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,7 @@ use strum::{EnumDiscriminants, IntoStaticStr};
3030

3131
use crate::{
3232
controller::{
33-
RoleGroupName,
34-
build::resource::{
35-
config_map::build_rolegroup_config_map,
36-
discovery::build_discovery_config_map,
37-
pdb::build_pdb,
38-
service::{build_rolegroup_metrics_service, build_rolegroup_service},
39-
statefulset::build_rolegroup_statefulset,
40-
},
33+
build::{self, resource::discovery::build_discovery_config_map},
4134
controller_name, operator_name, product_name,
4235
},
4336
crd::{APP_NAME, HbaseClusterStatus, OPERATOR_NAME, v1alpha1},
@@ -74,37 +67,11 @@ pub enum Error {
7467
source: stackable_operator::cluster_resources::Error,
7568
},
7669

77-
#[snafu(display("failed to apply Service for role group {role_group}"))]
78-
ApplyRoleGroupService {
79-
source: stackable_operator::cluster_resources::Error,
80-
role_group: RoleGroupName,
81-
},
82-
83-
#[snafu(display("failed to build rolegroup ConfigMap"))]
84-
BuildRolegroupConfigMap {
85-
source: crate::controller::build::resource::config_map::Error,
86-
},
70+
#[snafu(display("failed to build cluster resources"))]
71+
BuildResources { source: build::Error },
8772

88-
#[snafu(display("failed to apply ConfigMap for role group {role_group}"))]
89-
ApplyRoleGroupConfig {
90-
source: stackable_operator::cluster_resources::Error,
91-
role_group: RoleGroupName,
92-
},
93-
94-
#[snafu(display("failed to build StatefulSet for role group {role_group}"))]
95-
BuildRoleGroupStatefulSet {
96-
source: crate::controller::build::resource::statefulset::Error,
97-
role_group: RoleGroupName,
98-
},
99-
100-
#[snafu(display("failed to apply StatefulSet for role group {role_group}"))]
101-
ApplyRoleGroupStatefulSet {
102-
source: stackable_operator::cluster_resources::Error,
103-
role_group: RoleGroupName,
104-
},
105-
106-
#[snafu(display("failed to apply PodDisruptionBudget"))]
107-
ApplyPdb {
73+
#[snafu(display("failed to apply cluster resource"))]
74+
ApplyResource {
10875
source: stackable_operator::cluster_resources::Error,
10976
},
11077

@@ -204,73 +171,43 @@ pub async fn reconcile_hbase(
204171
// depend on the applied ServiceAccount.
205172
let service_account_name = rbac_sa.name_any();
206173

174+
let resources = build::build(
175+
&validated_cluster,
176+
&client.kubernetes_cluster_info,
177+
&service_account_name,
178+
)
179+
.context(BuildResourcesSnafu)?;
180+
207181
let mut ss_cond_builder = StatefulSetConditionBuilder::default();
208182

209-
for (hbase_role, role_group_configs) in &validated_cluster.role_group_configs {
210-
for (role_group_name, validated_rg_config) in role_group_configs {
211-
let rg_service =
212-
build_rolegroup_service(&validated_cluster, hbase_role, role_group_name);
213-
214-
let rg_metrics_service =
215-
build_rolegroup_metrics_service(&validated_cluster, hbase_role, role_group_name);
216-
217-
let rg_configmap = build_rolegroup_config_map(
218-
&validated_cluster,
219-
&client.kubernetes_cluster_info,
220-
hbase_role,
221-
role_group_name,
222-
)
223-
.context(BuildRolegroupConfigMapSnafu)?;
224-
let rg_statefulset = build_rolegroup_statefulset(
225-
&validated_cluster,
226-
hbase_role,
227-
role_group_name,
228-
validated_rg_config,
229-
&service_account_name,
230-
)
231-
.with_context(|_| BuildRoleGroupStatefulSetSnafu {
232-
role_group: role_group_name.clone(),
233-
})?;
234-
cluster_resources
235-
.add(client, rg_service)
236-
.await
237-
.with_context(|_| ApplyRoleGroupServiceSnafu {
238-
role_group: role_group_name.clone(),
239-
})?;
240-
cluster_resources
241-
.add(client, rg_metrics_service)
242-
.await
243-
.with_context(|_| ApplyRoleGroupServiceSnafu {
244-
role_group: role_group_name.clone(),
245-
})?;
246-
cluster_resources
247-
.add(client, rg_configmap)
248-
.await
249-
.with_context(|_| ApplyRoleGroupConfigSnafu {
250-
role_group: role_group_name.clone(),
251-
})?;
252-
253-
// Note: The StatefulSet needs to be applied after all ConfigMaps and Secrets it mounts
254-
// to prevent unnecessary Pod restarts.
255-
// See https://github.com/stackabletech/commons-operator/issues/111 for details.
256-
ss_cond_builder.add(
257-
cluster_resources
258-
.add(client, rg_statefulset)
259-
.await
260-
.with_context(|_| ApplyRoleGroupStatefulSetSnafu {
261-
role_group: role_group_name.clone(),
262-
})?,
263-
);
264-
}
265-
266-
if let Some(role_config) = validated_cluster.role_configs.get(hbase_role)
267-
&& let Some(pdb) = build_pdb(&role_config.pdb, &validated_cluster, hbase_role)
268-
{
183+
// Apply order: everything before the StatefulSets, StatefulSets last. A changed ConfigMap or
184+
// Secret a Pod mounts must exist before the Pod restarts, otherwise the Pod restarts again
185+
// unnecessarily. See https://github.com/stackabletech/commons-operator/issues/111 for details.
186+
for service in resources.services {
187+
cluster_resources
188+
.add(client, service)
189+
.await
190+
.context(ApplyResourceSnafu)?;
191+
}
192+
for config_map in resources.config_maps {
193+
cluster_resources
194+
.add(client, config_map)
195+
.await
196+
.context(ApplyResourceSnafu)?;
197+
}
198+
for pdb in resources.pod_disruption_budgets {
199+
cluster_resources
200+
.add(client, pdb)
201+
.await
202+
.context(ApplyResourceSnafu)?;
203+
}
204+
for statefulset in resources.stateful_sets {
205+
ss_cond_builder.add(
269206
cluster_resources
270-
.add(client, pdb)
207+
.add(client, statefulset)
271208
.await
272-
.context(ApplyPdbSnafu)?;
273-
}
209+
.context(ApplyResourceSnafu)?,
210+
);
274211
}
275212

276213
// Discovery CM will fail to build until the rest of the cluster has been deployed, so do it last

0 commit comments

Comments
 (0)