@@ -9,6 +9,12 @@ 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:: {
1319 ResourceExt ,
1420 core:: { DeserializeGuard , error_boundary} ,
@@ -29,11 +35,7 @@ use stackable_operator::{
2935use strum:: { EnumDiscriminants , IntoStaticStr } ;
3036
3137use crate :: {
32- controller:: build:: resource:: {
33- listener:: { build_group_listener, group_listener_name} ,
34- pdb:: build_pdb,
35- service:: { build_rolegroup_headless_service, build_rolegroup_metrics_service} ,
36- } ,
38+ controller:: build:: resource:: listener:: { build_group_listener, group_listener_name} ,
3739 crd:: { APP_NAME , DruidClusterStatus , DruidRole , OPERATOR_NAME , v1alpha1} ,
3840 internal_secret:: create_shared_internal_secret,
3941} ;
@@ -70,9 +72,30 @@ pub struct Ctx {
7072 pub operator_environment : OperatorEnvironmentOptions ,
7173}
7274
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+
7388#[ derive( Snafu , Debug , EnumDiscriminants ) ]
7489#[ strum_discriminants( derive( IntoStaticStr ) ) ]
7590pub enum Error {
91+ #[ snafu( display( "failed to build the Kubernetes resources" ) ) ]
92+ BuildResources { source : build:: Error } ,
93+
94+ #[ snafu( display( "failed to apply Kubernetes resource" ) ) ]
95+ ApplyResource {
96+ source : stackable_operator:: cluster_resources:: Error ,
97+ } ,
98+
7699 #[ snafu( display( "failed to apply Service for role group {role_group}" ) ) ]
77100 ApplyRoleGroupService {
78101 source : stackable_operator:: cluster_resources:: Error ,
@@ -234,99 +257,76 @@ pub async fn reconcile_druid(
234257 . await
235258 . context ( FailedInternalSecretCreationSnafu ) ?;
236259
237- let mut ss_cond_builder = StatefulSetConditionBuilder :: default ( ) ;
260+ let resources =
261+ build:: build ( & validated_cluster, & service_account_name) . context ( BuildResourcesSnafu ) ?;
238262
239- for ( druid_role, groups) in validated_cluster. role_group_configs . iter ( ) {
240- for ( rolegroup_name, rg) in groups. iter ( ) {
241- let rg_headless_service =
242- build_rolegroup_headless_service ( & validated_cluster, druid_role, rolegroup_name) ;
243- let rg_metrics_service =
244- build_rolegroup_metrics_service ( & validated_cluster, druid_role, rolegroup_name) ;
245-
246- let rg_configmap = build:: resource:: config_map:: build_rolegroup_config_map (
247- & validated_cluster,
248- druid_role,
249- rolegroup_name,
250- rg,
251- )
252- . context ( BuildConfigMapSnafu ) ?;
253- let rg_statefulset = build:: resource:: statefulset:: build_rolegroup_statefulset (
254- & validated_cluster,
255- druid_role,
256- rolegroup_name,
257- rg,
258- & service_account_name,
259- )
260- . context ( BuildRoleGroupStatefulSetSnafu ) ?;
263+ let mut ss_cond_builder = StatefulSetConditionBuilder :: default ( ) ;
261264
265+ // Apply order: everything a Pod mounts (ConfigMaps) must exist before the StatefulSets, so the
266+ // StatefulSets are applied last to prevent unnecessary Pod restarts.
267+ // See https://github.com/stackabletech/commons-operator/issues/111 for details.
268+ for service in resources. services {
269+ cluster_resources
270+ . add ( client, service)
271+ . await
272+ . context ( ApplyResourceSnafu ) ?;
273+ }
274+ for listener in resources. listeners {
275+ cluster_resources
276+ . add ( client, listener)
277+ . await
278+ . context ( ApplyResourceSnafu ) ?;
279+ }
280+ for config_map in resources. config_maps {
281+ cluster_resources
282+ . add ( client, config_map)
283+ . await
284+ . context ( ApplyResourceSnafu ) ?;
285+ }
286+ for pdb in resources. pod_disruption_budgets {
287+ cluster_resources
288+ . add ( client, pdb)
289+ . await
290+ . context ( ApplyResourceSnafu ) ?;
291+ }
292+ for stateful_set in resources. stateful_sets {
293+ ss_cond_builder. add (
262294 cluster_resources
263- . add ( client, rg_headless_service)
264- . await
265- . with_context ( |_| ApplyRoleGroupServiceSnafu {
266- role_group : rolegroup_name. to_string ( ) ,
267- } ) ?;
268- cluster_resources
269- . add ( client, rg_metrics_service)
270- . await
271- . with_context ( |_| ApplyRoleGroupServiceSnafu {
272- role_group : rolegroup_name. to_string ( ) ,
273- } ) ?;
274- cluster_resources
275- . add ( client, rg_configmap)
295+ . add ( client, stateful_set)
276296 . await
277- . with_context ( |_| ApplyRoleGroupConfigSnafu {
278- role_group : rolegroup_name. to_string ( ) ,
279- } ) ?;
280-
281- // Note: The StatefulSet needs to be applied after all ConfigMaps and Secrets it mounts
282- // to prevent unnecessary Pod restarts.
283- // See https://github.com/stackabletech/commons-operator/issues/111 for details.
284- ss_cond_builder. add (
285- cluster_resources
286- . add ( client, rg_statefulset)
287- . await
288- . with_context ( |_| ApplyRoleGroupStatefulSetSnafu {
289- role_group : rolegroup_name. to_string ( ) ,
290- } ) ?,
291- ) ;
292- }
297+ . context ( ApplyResourceSnafu ) ?,
298+ ) ;
299+ }
293300
294- if let Some ( listener_class) = & validated_cluster. role_config ( druid_role) . listener_class
295- && let Some ( listener_group_name) = group_listener_name ( & validated_cluster, druid_role)
296- {
297- let role_group_listener = build_group_listener (
298- & validated_cluster,
299- listener_class,
300- listener_group_name,
301- druid_role,
302- ) ;
303-
304- let listener = cluster_resources
305- . add ( client, role_group_listener)
306- . await
307- . context ( ApplyGroupListenerSnafu ) ?;
308-
309- if * druid_role == DruidRole :: Router {
310- // discovery
311- for discovery_cm in build_discovery_configmaps ( & validated_cluster, listener)
312- . await
313- . context ( BuildDiscoveryConfigSnafu ) ?
314- {
315- cluster_resources
316- . add ( client, discovery_cm)
317- . await
318- . context ( ApplyDiscoveryConfigSnafu ) ?;
319- }
320- }
321- }
301+ // The Router group Listener and its discovery ConfigMaps are applied here rather than in the
302+ // build step: the discovery ConfigMap derives from the *applied* Router listener's ingress
303+ // address, which is only known after the Listener has been applied.
304+ if let Some ( listener_class) = & validated_cluster
305+ . role_config ( & DruidRole :: Router )
306+ . listener_class
307+ && let Some ( listener_group_name) =
308+ group_listener_name ( & validated_cluster, & DruidRole :: Router )
309+ {
310+ let router_listener = build_group_listener (
311+ & validated_cluster,
312+ listener_class,
313+ listener_group_name,
314+ & DruidRole :: Router ,
315+ ) ;
322316
323- let role_config = validated_cluster. role_config ( druid_role) ;
317+ let listener = cluster_resources
318+ . add ( client, router_listener)
319+ . await
320+ . context ( ApplyGroupListenerSnafu ) ?;
324321
325- if let Some ( pdb) = build_pdb ( & role_config. pdb , & validated_cluster, druid_role) {
322+ for discovery_cm in build_discovery_configmaps ( & validated_cluster, listener)
323+ . await
324+ . context ( BuildDiscoveryConfigSnafu ) ?
325+ {
326326 cluster_resources
327- . add ( client, pdb )
327+ . add ( client, discovery_cm )
328328 . await
329- . context ( ApplyPdbSnafu ) ?;
329+ . context ( ApplyDiscoveryConfigSnafu ) ?;
330330 }
331331 }
332332
0 commit comments