@@ -4,6 +4,12 @@ use snafu::{ResultExt, Snafu};
44use 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::{
2026use strum:: { EnumDiscriminants , IntoStaticStr } ;
2127
2228use 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
3331pub mod build;
3432pub mod dereference;
@@ -38,57 +36,11 @@ pub mod validate;
3836#[ strum_discriminants( derive( IntoStaticStr ) ) ]
3937#[ allow( clippy:: enum_variant_names) ]
4038pub 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+
152114pub 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