@@ -9,7 +9,14 @@ use stackable_operator::{
99 cli:: OperatorEnvironmentOptions ,
1010 cluster_resources:: ClusterResourceApplyStrategy ,
1111 commons:: rbac:: build_rbac_resources,
12+ crd:: listener:: v1alpha1:: Listener ,
13+ k8s_openapi:: api:: {
14+ apps:: v1:: StatefulSet ,
15+ core:: v1:: { ConfigMap , Service } ,
16+ policy:: v1:: PodDisruptionBudget ,
17+ } ,
1218 kube:: {
19+ ResourceExt ,
1320 core:: { DeserializeGuard , error_boundary} ,
1421 runtime:: controller:: Action ,
1522 } ,
@@ -28,11 +35,7 @@ use stackable_operator::{
2835use strum:: { EnumDiscriminants , IntoStaticStr } ;
2936
3037use crate :: {
31- controller:: build:: resource:: {
32- listener:: { build_group_listener, group_listener_name} ,
33- pdb:: build_pdb,
34- service:: { build_rolegroup_headless_service, build_rolegroup_metrics_service} ,
35- } ,
38+ controller:: build:: resource:: listener:: { build_group_listener, group_listener_name} ,
3639 crd:: { APP_NAME , DruidClusterStatus , DruidRole , OPERATOR_NAME , v1alpha1} ,
3740 internal_secret:: create_shared_internal_secret,
3841} ;
@@ -69,30 +72,28 @@ pub struct Ctx {
6972 pub operator_environment : OperatorEnvironmentOptions ,
7073}
7174
75+ /// Every Kubernetes resource produced by the client-free [`build`](build::build) step.
76+ ///
77+ /// The Router group `Listener` and the discovery `ConfigMap`s are intentionally *not* yet part of this
78+ /// bundle: the discovery `ConfigMap` derives from the *applied* Router listener's ingress address,
79+ /// so both are built and applied in the reconcile step instead.
80+ pub struct KubernetesResources {
81+ pub stateful_sets : Vec < StatefulSet > ,
82+ pub services : Vec < Service > ,
83+ pub listeners : Vec < Listener > ,
84+ pub config_maps : Vec < ConfigMap > ,
85+ pub pod_disruption_budgets : Vec < PodDisruptionBudget > ,
86+ }
87+
7288#[ derive( Snafu , Debug , EnumDiscriminants ) ]
7389#[ strum_discriminants( derive( IntoStaticStr ) ) ]
7490pub enum Error {
75- #[ snafu( display( "failed to apply Service for role group {role_group}" ) ) ]
76- ApplyRoleGroupService {
77- source : stackable_operator:: cluster_resources:: Error ,
78- role_group : String ,
79- } ,
80-
81- #[ snafu( display( "failed to apply ConfigMap for role group {role_group}" ) ) ]
82- ApplyRoleGroupConfig {
83- source : stackable_operator:: cluster_resources:: Error ,
84- role_group : String ,
85- } ,
86-
87- #[ snafu( display( "failed to build StatefulSet" ) ) ]
88- BuildRoleGroupStatefulSet {
89- source : build:: resource:: statefulset:: Error ,
90- } ,
91+ #[ snafu( display( "failed to build the Kubernetes resources" ) ) ]
92+ BuildResources { source : build:: Error } ,
9193
92- #[ snafu( display( "failed to apply StatefulSet for role group {role_group} " ) ) ]
93- ApplyRoleGroupStatefulSet {
94+ #[ snafu( display( "failed to apply Kubernetes resource " ) ) ]
95+ ApplyResource {
9496 source : stackable_operator:: cluster_resources:: Error ,
95- role_group : String ,
9697 } ,
9798
9899 #[ snafu( display( "failed to dereference cluster objects" ) ) ]
@@ -136,11 +137,6 @@ pub enum Error {
136137 source : stackable_operator:: commons:: rbac:: Error ,
137138 } ,
138139
139- #[ snafu( display( "failed to apply PodDisruptionBudget" ) ) ]
140- ApplyPdb {
141- source : stackable_operator:: cluster_resources:: Error ,
142- } ,
143-
144140 #[ snafu( display( "failed to get required labels" ) ) ]
145141 GetRequiredLabels {
146142 source : KeyValuePairError < LabelValueError > ,
@@ -158,11 +154,6 @@ pub enum Error {
158154
159155 #[ snafu( display( "failed to validate cluster" ) ) ]
160156 ValidateCluster { source : validate:: Error } ,
161-
162- #[ snafu( display( "failed to build rolegroup ConfigMap" ) ) ]
163- BuildConfigMap {
164- source : build:: resource:: config_map:: Error ,
165- } ,
166157}
167158
168159type Result < T , E = Error > = std:: result:: Result < T , E > ;
@@ -213,9 +204,13 @@ pub async fn reconcile_druid(
213204 . context ( GetRequiredLabelsSnafu ) ?,
214205 )
215206 . context ( BuildRbacResourcesSnafu ) ?;
207+
208+ // The ServiceAccount name is deterministic on the built object, so the StatefulSet builder only
209+ // needs the name and does not depend on the applied ServiceAccount.
210+ let service_account_name = rbac_sa. name_any ( ) ;
211+
216212 cluster_resources
217- // We clone rbac_sa because we need to reuse it below
218- . add ( client, rbac_sa. clone ( ) )
213+ . add ( client, rbac_sa)
219214 . await
220215 . context ( ApplyServiceAccountSnafu ) ?;
221216 cluster_resources
@@ -229,99 +224,76 @@ pub async fn reconcile_druid(
229224 . await
230225 . context ( FailedInternalSecretCreationSnafu ) ?;
231226
232- let mut ss_cond_builder = StatefulSetConditionBuilder :: default ( ) ;
227+ let resources =
228+ build:: build ( & validated_cluster, & service_account_name) . context ( BuildResourcesSnafu ) ?;
233229
234- for ( druid_role, groups) in validated_cluster. role_group_configs . iter ( ) {
235- for ( rolegroup_name, rg) in groups. iter ( ) {
236- let rg_headless_service =
237- build_rolegroup_headless_service ( & validated_cluster, druid_role, rolegroup_name) ;
238- let rg_metrics_service =
239- build_rolegroup_metrics_service ( & validated_cluster, druid_role, rolegroup_name) ;
240-
241- let rg_configmap = build:: resource:: config_map:: build_rolegroup_config_map (
242- & validated_cluster,
243- druid_role,
244- rolegroup_name,
245- rg,
246- )
247- . context ( BuildConfigMapSnafu ) ?;
248- let rg_statefulset = build:: resource:: statefulset:: build_rolegroup_statefulset (
249- & validated_cluster,
250- druid_role,
251- rolegroup_name,
252- rg,
253- & rbac_sa,
254- )
255- . context ( BuildRoleGroupStatefulSetSnafu ) ?;
230+ let mut ss_cond_builder = StatefulSetConditionBuilder :: default ( ) ;
256231
232+ // Apply order: everything a Pod mounts (ConfigMaps) must exist before the StatefulSets, so the
233+ // StatefulSets are applied last to prevent unnecessary Pod restarts.
234+ // See https://github.com/stackabletech/commons-operator/issues/111 for details.
235+ for service in resources. services {
236+ cluster_resources
237+ . add ( client, service)
238+ . await
239+ . context ( ApplyResourceSnafu ) ?;
240+ }
241+ for listener in resources. listeners {
242+ cluster_resources
243+ . add ( client, listener)
244+ . await
245+ . context ( ApplyResourceSnafu ) ?;
246+ }
247+ for config_map in resources. config_maps {
248+ cluster_resources
249+ . add ( client, config_map)
250+ . await
251+ . context ( ApplyResourceSnafu ) ?;
252+ }
253+ for pdb in resources. pod_disruption_budgets {
254+ cluster_resources
255+ . add ( client, pdb)
256+ . await
257+ . context ( ApplyResourceSnafu ) ?;
258+ }
259+ for stateful_set in resources. stateful_sets {
260+ ss_cond_builder. add (
257261 cluster_resources
258- . add ( client, rg_headless_service)
259- . await
260- . with_context ( |_| ApplyRoleGroupServiceSnafu {
261- role_group : rolegroup_name. to_string ( ) ,
262- } ) ?;
263- cluster_resources
264- . add ( client, rg_metrics_service)
265- . await
266- . with_context ( |_| ApplyRoleGroupServiceSnafu {
267- role_group : rolegroup_name. to_string ( ) ,
268- } ) ?;
269- cluster_resources
270- . add ( client, rg_configmap)
262+ . add ( client, stateful_set)
271263 . await
272- . with_context ( |_| ApplyRoleGroupConfigSnafu {
273- role_group : rolegroup_name. to_string ( ) ,
274- } ) ?;
275-
276- // Note: The StatefulSet needs to be applied after all ConfigMaps and Secrets it mounts
277- // to prevent unnecessary Pod restarts.
278- // See https://github.com/stackabletech/commons-operator/issues/111 for details.
279- ss_cond_builder. add (
280- cluster_resources
281- . add ( client, rg_statefulset)
282- . await
283- . with_context ( |_| ApplyRoleGroupStatefulSetSnafu {
284- role_group : rolegroup_name. to_string ( ) ,
285- } ) ?,
286- ) ;
287- }
264+ . context ( ApplyResourceSnafu ) ?,
265+ ) ;
266+ }
288267
289- if let Some ( listener_class) = & validated_cluster. role_config ( druid_role) . listener_class
290- && let Some ( listener_group_name) = group_listener_name ( & validated_cluster, druid_role)
291- {
292- let role_group_listener = build_group_listener (
293- & validated_cluster,
294- listener_class,
295- listener_group_name,
296- druid_role,
297- ) ;
298-
299- let listener = cluster_resources
300- . add ( client, role_group_listener)
301- . await
302- . context ( ApplyGroupListenerSnafu ) ?;
303-
304- if * druid_role == DruidRole :: Router {
305- // discovery
306- for discovery_cm in build_discovery_configmaps ( & validated_cluster, listener)
307- . await
308- . context ( BuildDiscoveryConfigSnafu ) ?
309- {
310- cluster_resources
311- . add ( client, discovery_cm)
312- . await
313- . context ( ApplyDiscoveryConfigSnafu ) ?;
314- }
315- }
316- }
268+ // The Router group Listener and its discovery ConfigMaps are applied here rather than in the
269+ // build step: the discovery ConfigMap derives from the *applied* Router listener's ingress
270+ // address, which is only known after the Listener has been applied.
271+ if let Some ( listener_class) = & validated_cluster
272+ . role_config ( & DruidRole :: Router )
273+ . listener_class
274+ && let Some ( listener_group_name) =
275+ group_listener_name ( & validated_cluster, & DruidRole :: Router )
276+ {
277+ let router_listener = build_group_listener (
278+ & validated_cluster,
279+ listener_class,
280+ listener_group_name,
281+ & DruidRole :: Router ,
282+ ) ;
317283
318- let role_config = validated_cluster. role_config ( druid_role) ;
284+ let listener = cluster_resources
285+ . add ( client, router_listener)
286+ . await
287+ . context ( ApplyGroupListenerSnafu ) ?;
319288
320- if let Some ( pdb) = build_pdb ( & role_config. pdb , & validated_cluster, druid_role) {
289+ for discovery_cm in build_discovery_configmaps ( & validated_cluster, listener)
290+ . await
291+ . context ( BuildDiscoveryConfigSnafu ) ?
292+ {
321293 cluster_resources
322- . add ( client, pdb )
294+ . add ( client, discovery_cm )
323295 . await
324- . context ( ApplyPdbSnafu ) ?;
296+ . context ( ApplyDiscoveryConfigSnafu ) ?;
325297 }
326298 }
327299
0 commit comments