Skip to content

Commit 397471b

Browse files
committed
refactor: Introduce build aggregator for SparkHistoryServer
1 parent 64cac02 commit 397471b

2 files changed

Lines changed: 110 additions & 60 deletions

File tree

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

Lines changed: 53 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@ use stackable_operator::{
55
builder::{self},
66
cluster_resources::ClusterResourceApplyStrategy,
77
commons::rbac::build_rbac_resources,
8+
crd::listener,
9+
k8s_openapi::api::{
10+
apps::v1::StatefulSet,
11+
core::v1::{ConfigMap, Service, ServiceAccount},
12+
policy::v1::PodDisruptionBudget,
13+
rbac::v1::RoleBinding,
14+
},
815
kube::{
916
core::{DeserializeGuard, error_boundary},
1017
runtime::controller::Action,
@@ -18,13 +25,9 @@ use strum::{EnumDiscriminants, IntoStaticStr};
1825
use crate::{
1926
Ctx,
2027
crd::{
21-
constants::{HISTORY_APP_NAME, HISTORY_ROLE_NAME, JVM_SECURITY_PROPERTIES_FILE},
28+
constants::{HISTORY_APP_NAME, JVM_SECURITY_PROPERTIES_FILE},
2229
history::v1alpha1,
2330
},
24-
history::controller::build::resource::{
25-
config_map::build_config_map, listener::build_group_listener, pdb::build_pdb,
26-
service::build_rolegroup_metrics_service, statefulset::build_stateful_set,
27-
},
2831
};
2932

3033
pub mod build;
@@ -49,18 +52,8 @@ pub enum Error {
4952
name: String,
5053
},
5154

52-
#[snafu(display("failed to update the history server stateful set"))]
53-
ApplyStatefulSet {
54-
source: stackable_operator::cluster_resources::Error,
55-
},
56-
57-
#[snafu(display("failed to update history server config map"))]
58-
ApplyConfigMap {
59-
source: stackable_operator::cluster_resources::Error,
60-
},
61-
62-
#[snafu(display("failed to update history server metrics service"))]
63-
ApplyMetricsService {
55+
#[snafu(display("failed to apply Kubernetes resource"))]
56+
ApplyResource {
6457
source: stackable_operator::cluster_resources::Error,
6558
},
6659

@@ -94,11 +87,6 @@ pub enum Error {
9487
rolegroup: String,
9588
},
9689

97-
#[snafu(display("failed to apply PodDisruptionBudget"))]
98-
ApplyPdb {
99-
source: stackable_operator::cluster_resources::Error,
100-
},
101-
10290
#[snafu(display("failed to get required Labels"))]
10391
GetRequiredLabels {
10492
source:
@@ -123,11 +111,6 @@ pub enum Error {
123111
source: Box<error_boundary::InvalidObject>,
124112
},
125113

126-
#[snafu(display("failed to apply group listener"))]
127-
ApplyGroupListener {
128-
source: stackable_operator::cluster_resources::Error,
129-
},
130-
131114
#[snafu(display("failed to serialize Spark default properties"))]
132115
InvalidSparkDefaults { source: PropertiesWriterError },
133116
}
@@ -137,7 +120,22 @@ impl ReconcilerError for Error {
137120
ErrorDiscriminants::from(self).into()
138121
}
139122
}
140-
/// Updates the status of the SparkApplication that started the pod.
123+
124+
/// Every Kubernetes resource produced by the build step for a SparkHistoryServer.
125+
///
126+
/// Built without a Kubernetes client: all references are already dereferenced and validated by
127+
/// this point, so the only errors possible during assembly are resource-construction failures.
128+
pub struct SparkHistoryResources {
129+
pub service_account: ServiceAccount,
130+
pub role_binding: RoleBinding,
131+
/// One ConfigMap, metrics Service and StatefulSet per role group.
132+
pub config_maps: Vec<ConfigMap>,
133+
pub metrics_services: Vec<Service>,
134+
pub stateful_sets: Vec<StatefulSet>,
135+
pub listener: listener::v1alpha1::Listener,
136+
pub pod_disruption_budget: Option<PodDisruptionBudget>,
137+
}
138+
141139
pub async fn reconcile(
142140
shs: Arc<DeserializeGuard<v1alpha1::SparkHistoryServer>>,
143141
ctx: Arc<Ctx>,
@@ -170,9 +168,9 @@ pub async fn reconcile(
170168
&shs.spec.object_overrides,
171169
);
172170

173-
let log_dir = &validated.cluster_config.log_dir;
174-
175-
// Use a dedicated service account for history server pods.
171+
// Use a dedicated service account for history server pods. Building the RBAC resources needs
172+
// the cluster-resource labels, so it stays in the reconcile step; the built objects (whose
173+
// names are deterministic) are handed to the client-free build step.
176174
let (service_account, role_binding) = build_rbac_resources(
177175
shs,
178176
HISTORY_APP_NAME,
@@ -181,52 +179,47 @@ pub async fn reconcile(
181179
.context(GetRequiredLabelsSnafu)?,
182180
)
183181
.context(BuildRbacResourcesSnafu)?;
184-
let service_account = cluster_resources
185-
.add(client, service_account)
182+
183+
let resources = build::build(&validated, service_account, role_binding)?;
184+
185+
// Apply order: ServiceAccount and RoleBinding first, then the ConfigMaps, metrics Services,
186+
// Listener and PodDisruptionBudget, and finally the StatefulSets (they mount the ConfigMaps
187+
// and run under the SA, so those must exist first).
188+
cluster_resources
189+
.add(client, resources.service_account)
186190
.await
187191
.context(ApplyServiceAccountSnafu)?;
188192
cluster_resources
189-
.add(client, role_binding)
193+
.add(client, resources.role_binding)
190194
.await
191195
.context(ApplyRoleBindingSnafu)?;
192-
193-
for (role_group_name, rg) in &validated.role_groups {
194-
let config_map = build_config_map(&validated, role_group_name, rg)?;
195-
196-
let metrics_service = build_rolegroup_metrics_service(&validated, role_group_name);
197-
198-
let sts = build_stateful_set(&validated, role_group_name, rg, log_dir, &service_account)?;
199-
196+
for config_map in resources.config_maps {
200197
cluster_resources
201198
.add(client, config_map)
202199
.await
203-
.context(ApplyConfigMapSnafu)?;
200+
.context(ApplyResourceSnafu)?;
201+
}
202+
for metrics_service in resources.metrics_services {
204203
cluster_resources
205204
.add(client, metrics_service)
206205
.await
207-
.context(ApplyMetricsServiceSnafu)?;
208-
cluster_resources
209-
.add(client, sts)
210-
.await
211-
.context(ApplyStatefulSetSnafu)?;
206+
.context(ApplyResourceSnafu)?;
212207
}
213-
214-
let rg_group_listener = build_group_listener(
215-
&validated,
216-
HISTORY_ROLE_NAME,
217-
validated.role_config.listener_class.clone(),
218-
);
219-
220208
cluster_resources
221-
.add(client, rg_group_listener)
209+
.add(client, resources.listener)
222210
.await
223-
.context(ApplyGroupListenerSnafu)?;
224-
225-
if let Some(pdb) = build_pdb(&validated.role_config.pdb, &validated) {
211+
.context(ApplyResourceSnafu)?;
212+
if let Some(pdb) = resources.pod_disruption_budget {
226213
cluster_resources
227214
.add(client, pdb)
228215
.await
229-
.context(ApplyPdbSnafu)?;
216+
.context(ApplyResourceSnafu)?;
217+
}
218+
for stateful_set in resources.stateful_sets {
219+
cluster_resources
220+
.add(client, stateful_set)
221+
.await
222+
.context(ApplyResourceSnafu)?;
230223
}
231224

232225
cluster_resources
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,58 @@
11
pub mod resource;
2+
3+
use stackable_operator::k8s_openapi::api::{core::v1::ServiceAccount, rbac::v1::RoleBinding};
4+
5+
use crate::{
6+
crd::constants::HISTORY_ROLE_NAME,
7+
history::controller::{
8+
Error, SparkHistoryResources,
9+
build::resource::{
10+
config_map::build_config_map, listener::build_group_listener, pdb::build_pdb,
11+
service::build_rolegroup_metrics_service, statefulset::build_stateful_set,
12+
},
13+
validate::ValidatedSparkHistoryServer,
14+
},
15+
};
16+
17+
/// Builds every Kubernetes resource for the given validated SparkHistoryServer.
18+
pub fn build(
19+
validated: &ValidatedSparkHistoryServer,
20+
service_account: ServiceAccount,
21+
role_binding: RoleBinding,
22+
) -> Result<SparkHistoryResources, Error> {
23+
let log_dir = &validated.cluster_config.log_dir;
24+
25+
let mut config_maps = vec![];
26+
let mut metrics_services = vec![];
27+
let mut stateful_sets = vec![];
28+
29+
for (role_group_name, rg) in &validated.role_groups {
30+
config_maps.push(build_config_map(validated, role_group_name, rg)?);
31+
metrics_services.push(build_rolegroup_metrics_service(validated, role_group_name));
32+
stateful_sets.push(build_stateful_set(
33+
validated,
34+
role_group_name,
35+
rg,
36+
log_dir,
37+
&service_account,
38+
)?);
39+
}
40+
41+
let listener = build_group_listener(
42+
validated,
43+
HISTORY_ROLE_NAME,
44+
validated.role_config.listener_class.clone(),
45+
);
46+
47+
let pod_disruption_budget = build_pdb(&validated.role_config.pdb, validated);
48+
49+
Ok(SparkHistoryResources {
50+
service_account,
51+
role_binding,
52+
config_maps,
53+
metrics_services,
54+
stateful_sets,
55+
listener,
56+
pod_disruption_budget,
57+
})
58+
}

0 commit comments

Comments
 (0)