@@ -19,6 +19,11 @@ use stackable_operator::{
1919 cluster_resources:: ClusterResourceApplyStrategy ,
2020 commons:: { networking:: DomainName , product_image_selection:: ResolvedProductImage } ,
2121 crd:: listener,
22+ k8s_openapi:: api:: {
23+ apps:: v1:: StatefulSet ,
24+ core:: v1:: { ConfigMap , Service } ,
25+ policy:: v1:: PodDisruptionBudget ,
26+ } ,
2227 kube:: {
2328 Resource , ResourceExt ,
2429 api:: { DynamicObject , ObjectMeta } ,
@@ -55,18 +60,7 @@ pub use stackable_operator::v2::types::operator::{RoleGroupName, RoleName};
5560
5661use crate :: {
5762 controller:: {
58- build:: {
59- properties:: listener:: get_kafka_listener_config,
60- resource:: {
61- listener:: build_broker_rolegroup_bootstrap_listener,
62- pdb:: build_pdb,
63- rbac:: { build_rbac_role_binding, build_rbac_service_account} ,
64- service:: { build_rolegroup_headless_service, build_rolegroup_metrics_service} ,
65- statefulset:: {
66- build_broker_rolegroup_statefulset, build_controller_rolegroup_statefulset,
67- } ,
68- } ,
69- } ,
63+ build:: resource:: rbac:: { build_rbac_role_binding, build_rbac_service_account} ,
7064 node_id_hasher:: node_id_hash32_offset,
7165 security:: ValidatedKafkaSecurity ,
7266 } ,
@@ -94,6 +88,19 @@ pub enum PodDescriptorsError {
9488 } ,
9589}
9690
91+ /// Every Kubernetes resource produced by the [`build`] step.
92+ ///
93+ /// The discovery `ConfigMap` is not part of this: it depends on the applied bootstrap
94+ /// [`Listener`](listener)s' status and is therefore built in [`reconcile_kafka`] after they are
95+ /// applied.
96+ pub struct KubernetesResources {
97+ pub stateful_sets : Vec < StatefulSet > ,
98+ pub services : Vec < Service > ,
99+ pub listeners : Vec < listener:: v1alpha1:: Listener > ,
100+ pub config_maps : Vec < ConfigMap > ,
101+ pub pod_disruption_budgets : Vec < PodDisruptionBudget > ,
102+ }
103+
97104/// The validated cluster. Carries everything the build steps need, resolved once
98105/// here so downstream code never re-derives it or touches the raw spec.
99106///
@@ -393,27 +400,12 @@ pub enum Error {
393400 #[ snafu( display( "failed to validate cluster" ) ) ]
394401 ValidateCluster { source : validate:: Error } ,
395402
396- #[ snafu( display( "failed to apply bootstrap Listener" ) ) ]
397- ApplyBootstrapListener {
398- source : stackable_operator:: cluster_resources:: Error ,
399- } ,
403+ #[ snafu( display( "failed to build the Kubernetes resources" ) ) ]
404+ BuildResources { source : build:: Error } ,
400405
401- #[ snafu( display( "failed to apply Service for role group {role_group} " ) ) ]
402- ApplyRoleGroupService {
406+ #[ snafu( display( "failed to apply Kubernetes resource " ) ) ]
407+ ApplyResource {
403408 source : stackable_operator:: cluster_resources:: Error ,
404- role_group : RoleGroupName ,
405- } ,
406-
407- #[ snafu( display( "failed to apply ConfigMap for role group {role_group}" ) ) ]
408- ApplyRoleGroupConfig {
409- source : stackable_operator:: cluster_resources:: Error ,
410- role_group : RoleGroupName ,
411- } ,
412-
413- #[ snafu( display( "failed to apply StatefulSet for role group {role_group}" ) ) ]
414- ApplyRoleGroupStatefulSet {
415- source : stackable_operator:: cluster_resources:: Error ,
416- role_group : RoleGroupName ,
417409 } ,
418410
419411 #[ snafu( display( "failed to build discovery ConfigMap" ) ) ]
@@ -446,11 +438,6 @@ pub enum Error {
446438 source : stackable_operator:: client:: Error ,
447439 } ,
448440
449- #[ snafu( display( "failed to apply PodDisruptionBudget" ) ) ]
450- ApplyPdb {
451- source : stackable_operator:: cluster_resources:: Error ,
452- } ,
453-
454441 #[ snafu( display( "failed to get required Labels" ) ) ]
455442 GetRequiredLabels {
456443 source :
@@ -461,16 +448,6 @@ pub enum Error {
461448 InvalidKafkaCluster {
462449 source : error_boundary:: InvalidObject ,
463450 } ,
464-
465- #[ snafu( display( "failed to build statefulset" ) ) ]
466- BuildStatefulset {
467- source : crate :: controller:: build:: resource:: statefulset:: Error ,
468- } ,
469-
470- #[ snafu( display( "failed to build configmap" ) ) ]
471- BuildConfigMap {
472- source : crate :: controller:: build:: resource:: config_map:: Error ,
473- } ,
474451}
475452type Result < T , E = Error > = std:: result:: Result < T , E > ;
476453
@@ -483,21 +460,16 @@ impl ReconcilerError for Error {
483460 match self {
484461 Error :: Dereference { .. } => None ,
485462 Error :: ValidateCluster { .. } => None ,
486- Error :: ApplyBootstrapListener { .. } => None ,
487- Error :: ApplyRoleGroupService { .. } => None ,
488- Error :: ApplyRoleGroupConfig { .. } => None ,
489- Error :: ApplyRoleGroupStatefulSet { .. } => None ,
463+ Error :: BuildResources { .. } => None ,
464+ Error :: ApplyResource { .. } => None ,
490465 Error :: BuildDiscoveryConfig { .. } => None ,
491466 Error :: ApplyDiscoveryConfig { .. } => None ,
492467 Error :: DeleteOrphans { .. } => None ,
493468 Error :: ApplyServiceAccount { .. } => None ,
494469 Error :: ApplyRoleBinding { .. } => None ,
495470 Error :: ApplyStatus { .. } => None ,
496- Error :: ApplyPdb { .. } => None ,
497471 Error :: GetRequiredLabels { .. } => None ,
498472 Error :: InvalidKafkaCluster { .. } => None ,
499- Error :: BuildStatefulset { .. } => None ,
500- Error :: BuildConfigMap { .. } => None ,
501473 }
502474 }
503475}
@@ -565,121 +537,59 @@ pub async fn reconcile_kafka(
565537 . await
566538 . context ( ApplyRoleBindingSnafu ) ?;
567539
568- let mut bootstrap_listeners = Vec :: < listener:: v1alpha1:: Listener > :: new ( ) ;
569-
570- for ( kafka_role, rg_map) in & validated_cluster. role_group_configs {
571- for ( rolegroup_name, validated_rg) in rg_map {
572- // The Vector agent config is the static `vector.yaml`, added to the rolegroup
573- // ConfigMap only when the Vector agent is enabled (resolved during validation).
574- let vector_config = validated_rg
575- . config
576- . logging
577- . vector_container
578- . is_some ( )
579- . then ( build:: properties:: product_logging:: vector_config_file_content) ;
580-
581- let rg_headless_service = build_rolegroup_headless_service (
582- & validated_cluster,
583- kafka_role,
584- rolegroup_name,
585- & validated_cluster. cluster_config . kafka_security ,
586- ) ;
587-
588- let rg_metrics_service =
589- build_rolegroup_metrics_service ( & validated_cluster, kafka_role, rolegroup_name) ;
590-
591- let kafka_listeners = get_kafka_listener_config (
592- & validated_cluster,
593- & validated_cluster. cluster_config . kafka_security ,
594- kafka_role,
595- rolegroup_name,
596- ) ;
597-
598- let rg_configmap = build:: resource:: config_map:: build_rolegroup_config_map (
599- & validated_cluster,
600- rolegroup_name,
601- validated_rg,
602- & kafka_listeners,
603- vector_config,
604- )
605- . context ( BuildConfigMapSnafu ) ?;
606-
607- let rg_statefulset = match kafka_role {
608- KafkaRole :: Broker => build_broker_rolegroup_statefulset (
609- kafka_role,
610- rolegroup_name,
611- & validated_cluster,
612- validated_rg,
613- & service_account_name,
614- )
615- . context ( BuildStatefulsetSnafu ) ?,
616- KafkaRole :: Controller => build_controller_rolegroup_statefulset (
617- kafka_role,
618- rolegroup_name,
619- & validated_cluster,
620- validated_rg,
621- & service_account_name,
622- )
623- . context ( BuildStatefulsetSnafu ) ?,
624- } ;
625-
626- if let AnyConfig :: Broker ( broker_config) = & validated_rg. config . config {
627- let rg_bootstrap_listener = build_broker_rolegroup_bootstrap_listener (
628- & validated_cluster,
629- kafka_role,
630- rolegroup_name,
631- broker_config,
632- ) ;
633- bootstrap_listeners. push (
634- cluster_resources
635- . add ( client, rg_bootstrap_listener)
636- . await
637- . context ( ApplyBootstrapListenerSnafu ) ?,
638- ) ;
639- }
540+ // Build every Kubernetes resource up front (client-free). The discovery ConfigMap is not part
541+ // of this, as it depends on the applied bootstrap Listeners' status (see below).
542+ let resources =
543+ build:: build ( & validated_cluster, & service_account_name) . context ( BuildResourcesSnafu ) ?;
544+
545+ // Apply order: Services, then Listeners (collecting the applied bootstrap Listeners for the
546+ // discovery ConfigMap), then ConfigMaps, then PodDisruptionBudgets, and finally the
547+ // StatefulSets. The StatefulSets must be applied after all ConfigMaps and Secrets they mount to
548+ // prevent unnecessary Pod restarts.
549+ // See https://github.com/stackabletech/commons-operator/issues/111 for details.
550+ for service in resources. services {
551+ cluster_resources
552+ . add ( client, service)
553+ . await
554+ . context ( ApplyResourceSnafu ) ?;
555+ }
640556
557+ let mut bootstrap_listeners = Vec :: < listener:: v1alpha1:: Listener > :: new ( ) ;
558+ for rg_listener in resources. listeners {
559+ bootstrap_listeners. push (
641560 cluster_resources
642- . add ( client, rg_headless_service)
643- . await
644- . with_context ( |_| ApplyRoleGroupServiceSnafu {
645- role_group : rolegroup_name. clone ( ) ,
646- } ) ?;
647- cluster_resources
648- . add ( client, rg_metrics_service)
649- . await
650- . with_context ( |_| ApplyRoleGroupServiceSnafu {
651- role_group : rolegroup_name. clone ( ) ,
652- } ) ?;
653- cluster_resources
654- . add ( client, rg_configmap)
561+ . add ( client, rg_listener)
655562 . await
656- . with_context ( |_| ApplyRoleGroupConfigSnafu {
657- role_group : rolegroup_name. clone ( ) ,
658- } ) ?;
659-
660- // Note: The StatefulSet needs to be applied after all ConfigMaps and Secrets it mounts
661- // to prevent unnecessary Pod restarts.
662- // See https://github.com/stackabletech/commons-operator/issues/111 for details.
663- ss_cond_builder. add (
664- cluster_resources
665- . add ( client, rg_statefulset)
666- . await
667- . with_context ( |_| ApplyRoleGroupStatefulSetSnafu {
668- role_group : rolegroup_name. clone ( ) ,
669- } ) ?,
670- ) ;
671- }
563+ . context ( ApplyResourceSnafu ) ?,
564+ ) ;
565+ }
566+
567+ for config_map in resources. config_maps {
568+ cluster_resources
569+ . add ( client, config_map)
570+ . await
571+ . context ( ApplyResourceSnafu ) ?;
572+ }
573+
574+ for pdb in resources. pod_disruption_budgets {
575+ cluster_resources
576+ . add ( client, pdb)
577+ . await
578+ . context ( ApplyResourceSnafu ) ?;
579+ }
672580
673- if let Some ( role_config) = validated_cluster. role_configs . get ( kafka_role)
674- && let Some ( pdb) = build_pdb ( & role_config. pdb , & validated_cluster, kafka_role)
675- {
581+ for stateful_set in resources. stateful_sets {
582+ ss_cond_builder. add (
676583 cluster_resources
677- . add ( client, pdb )
584+ . add ( client, stateful_set )
678585 . await
679- . context ( ApplyPdbSnafu ) ? ;
680- }
586+ . context ( ApplyResourceSnafu ) ? ,
587+ ) ;
681588 }
682589
590+ // The discovery ConfigMap reports the bootstrap Listeners' ingress addresses, which are only
591+ // populated on the applied Listener objects (by the Listener operator), so it is built here
592+ // rather than in the client-free build() step.
683593 let discovery_cm = build:: resource:: discovery:: build_discovery_configmap (
684594 & validated_cluster,
685595 & bootstrap_listeners,
0 commit comments