Skip to content

Commit 64cac02

Browse files
committed
refactor: Introduce build aggregator for SparkConnectServer
1 parent 05a3440 commit 64cac02

2 files changed

Lines changed: 168 additions & 162 deletions

File tree

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

Lines changed: 61 additions & 162 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ use snafu::{ResultExt, Snafu};
44
use stackable_operator::{
55
cluster_resources::ClusterResourceApplyStrategy,
66
commons::rbac::build_rbac_resources,
7+
crd::listener,
8+
k8s_openapi::api::{
9+
apps::v1::StatefulSet,
10+
core::v1::{ConfigMap, Service, ServiceAccount},
11+
rbac::v1::RoleBinding,
12+
},
713
kube::{
814
ResourceExt,
915
core::{DeserializeGuard, error_boundary},
@@ -20,15 +26,7 @@ use stackable_operator::{
2026
use strum::{EnumDiscriminants, IntoStaticStr};
2127

2228
use super::crd::{CONNECT_APP_NAME, v1alpha1};
23-
use crate::{
24-
Ctx,
25-
connect::{
26-
common,
27-
controller::build::{executor, server, service},
28-
crd::SparkConnectServerStatus,
29-
},
30-
crd::constants::OPERATOR_NAME,
31-
};
29+
use crate::{Ctx, connect::crd::SparkConnectServerStatus, crd::constants::OPERATOR_NAME};
3230

3331
pub mod build;
3432
pub mod dereference;
@@ -38,57 +36,11 @@ pub mod validate;
3836
#[strum_discriminants(derive(IntoStaticStr))]
3937
#[allow(clippy::enum_variant_names)]
4038
pub enum Error {
41-
#[snafu(display("failed to apply spark connect listener"))]
42-
ApplyListener {
43-
source: stackable_operator::cluster_resources::Error,
44-
},
45-
46-
#[snafu(display("failed to serialize connect properties"))]
47-
SerializeProperties { source: common::Error },
48-
49-
#[snafu(display("failed to build connect executor properties"))]
50-
ExecutorProperties { source: executor::Error },
51-
52-
#[snafu(display("failed to build connect server properties"))]
53-
ServerProperties { source: server::Error },
54-
55-
#[snafu(display("failed to build spark connect executor config map for {name}"))]
56-
BuildExecutorConfigMap {
57-
source: executor::Error,
58-
name: String,
59-
},
60-
61-
#[snafu(display("failed to build spark connect server config map for {name}"))]
62-
BuildServerConfigMap { source: server::Error, name: String },
63-
64-
#[snafu(display("failed to build spark connect stateful set"))]
65-
BuildServerStatefulSet { source: server::Error },
66-
67-
#[snafu(display("failed to update status of spark connect server {name}"))]
68-
ApplyStatus {
69-
source: stackable_operator::client::Error,
70-
name: String,
71-
},
72-
73-
#[snafu(display("failed to update the connect server stateful set"))]
74-
ApplyStatefulSet {
75-
source: stackable_operator::cluster_resources::Error,
76-
},
77-
78-
#[snafu(display("failed to update connect executor config map for {name}"))]
79-
ApplyExecutorConfigMap {
80-
source: stackable_operator::cluster_resources::Error,
81-
name: String,
82-
},
39+
#[snafu(display("failed to build the Kubernetes resources"))]
40+
BuildResources { source: build::Error },
8341

84-
#[snafu(display("failed to update connect server config map for {name}"))]
85-
ApplyServerConfigMap {
86-
source: stackable_operator::cluster_resources::Error,
87-
name: String,
88-
},
89-
90-
#[snafu(display("failed to update connect server service"))]
91-
ApplyService {
42+
#[snafu(display("failed to apply Kubernetes resource"))]
43+
ApplyResource {
9244
source: stackable_operator::cluster_resources::Error,
9345
},
9446

@@ -102,6 +54,12 @@ pub enum Error {
10254
source: stackable_operator::cluster_resources::Error,
10355
},
10456

57+
#[snafu(display("failed to update status of spark connect server {name}"))]
58+
ApplyStatus {
59+
source: stackable_operator::client::Error,
60+
name: String,
61+
},
62+
10563
#[snafu(display("failed to delete orphaned resources"))]
10664
DeleteOrphanedResources {
10765
source: stackable_operator::cluster_resources::Error,
@@ -123,17 +81,6 @@ pub enum Error {
12381
source: stackable_operator::commons::rbac::Error,
12482
},
12583

126-
#[snafu(display("failed to build connect executor pod template"))]
127-
ExecutorPodTemplate {
128-
source: crate::connect::controller::build::executor::Error,
129-
},
130-
131-
#[snafu(display("failed to serialize executor pod template"))]
132-
ExecutorPodTemplateSerde { source: serde_yaml::Error },
133-
134-
#[snafu(display("failed to build connect server S3 properties"))]
135-
S3SparkProperties { source: crate::connect::s3::Error },
136-
13784
#[snafu(display("failed to dereference SparkConnectServer"))]
13885
DereferenceSparkConnectServer { source: dereference::Error },
13986

@@ -148,7 +95,22 @@ impl ReconcilerError for Error {
14895
ErrorDiscriminants::from(self).into()
14996
}
15097
}
151-
/// Updates the status of the SparkApplication that started the pod.
98+
99+
/// Every Kubernetes resource produced by the build step for a SparkConnectServer.
100+
///
101+
/// Built without a Kubernetes client: all references are already dereferenced and validated by
102+
/// this point, so the only errors possible during assembly are resource-construction failures.
103+
pub struct SparkConnectResources {
104+
pub service_account: ServiceAccount,
105+
pub role_binding: RoleBinding,
106+
/// The headless Service (for executors to reach the driver) and the metrics Service.
107+
pub services: Vec<Service>,
108+
/// The executor and server ConfigMaps.
109+
pub config_maps: Vec<ConfigMap>,
110+
pub listener: listener::v1alpha1::Listener,
111+
pub stateful_set: StatefulSet,
112+
}
113+
152114
pub async fn reconcile(
153115
scs: Arc<DeserializeGuard<v1alpha1::SparkConnectServer>>,
154116
ctx: Arc<Ctx>,
@@ -177,8 +139,6 @@ pub async fn reconcile(
177139
"Validated SparkConnectServer identity"
178140
);
179141

180-
let resolved_s3 = &validated.cluster_config.resolved_s3;
181-
182142
let mut cluster_resources = cluster_resources_new(
183143
&validate::product_name(),
184144
&validate::operator_name(),
@@ -190,7 +150,9 @@ pub async fn reconcile(
190150
&scs.spec.object_overrides,
191151
);
192152

193-
// Use a dedicated service account for connect server pods.
153+
// Use a dedicated service account for connect server pods. Building the RBAC resources needs
154+
// the cluster-resource labels, so it stays in the reconcile step; the built objects (whose
155+
// names are deterministic) are handed to the client-free build step.
194156
let (service_account, role_binding) = build_rbac_resources(
195157
scs,
196158
CONNECT_APP_NAME,
@@ -200,106 +162,43 @@ pub async fn reconcile(
200162
)
201163
.context(BuildRbacResourcesSnafu)?;
202164

203-
let service_account = cluster_resources
204-
.add(client, service_account)
205-
.await
206-
.context(ApplyServiceAccountSnafu)?;
207-
cluster_resources
208-
.add(client, role_binding)
209-
.await
210-
.context(ApplyRoleBindingSnafu)?;
211-
212-
// Headless service used by executors connect back to the driver
213-
let headless_service = service::build_headless_service(&validated);
214-
215-
let applied_headless_service = cluster_resources
216-
.add(client, headless_service.clone())
217-
.await
218-
.context(ApplyServiceSnafu)?;
219-
220-
// Metrics service used for scraping
221-
let metrics_service = service::build_metrics_service(&validated);
165+
let resources = build::build(&validated, service_account, role_binding, &scs.spec.args)
166+
.context(BuildResourcesSnafu)?;
222167

168+
// Apply order: ServiceAccount and RoleBinding first, then the Services, ConfigMaps and
169+
// Listener, and finally the StatefulSet (it mounts the ConfigMaps and runs under the SA, so
170+
// they must exist first).
223171
cluster_resources
224-
.add(client, metrics_service.clone())
172+
.add(client, resources.service_account)
225173
.await
226-
.context(ApplyServiceSnafu)?;
227-
228-
// ========================================
229-
// Server config map
230-
231-
let spark_props = common::spark_properties(&[
232-
resolved_s3
233-
.spark_properties()
234-
.context(S3SparkPropertiesSnafu)?,
235-
server::server_properties(&validated, &applied_headless_service, &service_account)
236-
.context(ServerPropertiesSnafu)?,
237-
executor::executor_properties(&validated).context(ExecutorPropertiesSnafu)?,
238-
])
239-
.context(SerializePropertiesSnafu)?;
240-
241-
// ========================================
242-
// Executor config map and pod template
243-
let executor_config_map =
244-
executor::executor_config_map(&validated).context(BuildExecutorConfigMapSnafu {
245-
name: validated.name_any(),
246-
})?;
174+
.context(ApplyServiceAccountSnafu)?;
247175
cluster_resources
248-
.add(client, executor_config_map.clone())
176+
.add(client, resources.role_binding)
249177
.await
250-
.context(ApplyExecutorConfigMapSnafu {
251-
name: validated.name_any(),
252-
})?;
253-
254-
let executor_pod_template = serde_yaml::to_string(
255-
&executor::executor_pod_template(&validated, &executor_config_map)
256-
.context(ExecutorPodTemplateSnafu)?,
257-
)
258-
.context(ExecutorPodTemplateSerdeSnafu)?;
259-
260-
// ========================================
261-
// Server config map
262-
let server_config_map =
263-
server::server_config_map(&validated, &spark_props, &executor_pod_template).context(
264-
BuildServerConfigMapSnafu {
265-
name: validated.name_any(),
266-
},
267-
)?;
178+
.context(ApplyRoleBindingSnafu)?;
179+
for service in resources.services {
180+
cluster_resources
181+
.add(client, service)
182+
.await
183+
.context(ApplyResourceSnafu)?;
184+
}
185+
for config_map in resources.config_maps {
186+
cluster_resources
187+
.add(client, config_map)
188+
.await
189+
.context(ApplyResourceSnafu)?;
190+
}
268191
cluster_resources
269-
.add(client, server_config_map.clone())
192+
.add(client, resources.listener)
270193
.await
271-
.context(ApplyServerConfigMapSnafu {
272-
name: validated.name_any(),
273-
})?;
274-
275-
// ========================================
276-
// Server listener
277-
let listener = server::build_listener(&validated);
278-
279-
let applied_listener = cluster_resources
280-
.add(client, listener)
281-
.await
282-
.context(ApplyListenerSnafu)?;
283-
284-
// ========================================
285-
// Server stateful set
286-
let args = server::command_args(&scs.spec.args);
287-
let stateful_set = server::build_stateful_set(
288-
&validated,
289-
&service_account,
290-
&server_config_map,
291-
&applied_listener.name_any(),
292-
args,
293-
)
294-
.context(BuildServerStatefulSetSnafu)?;
194+
.context(ApplyResourceSnafu)?;
295195

296196
let mut ss_cond_builder = StatefulSetConditionBuilder::default();
297-
298197
ss_cond_builder.add(
299198
cluster_resources
300-
.add(client, stateful_set)
199+
.add(client, resources.stateful_set)
301200
.await
302-
.context(ApplyStatefulSetSnafu)?,
201+
.context(ApplyResourceSnafu)?,
303202
);
304203

305204
cluster_resources

0 commit comments

Comments
 (0)