Skip to content

Commit 6c06c70

Browse files
committed
refactor: introduce build() aggregator for Kubernetes resources
1 parent 3d321ba commit 6c06c70

2 files changed

Lines changed: 220 additions & 160 deletions

File tree

rust/operator-binary/src/controller.rs

Lines changed: 70 additions & 160 deletions
Original file line numberDiff line numberDiff line change
@@ -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

5661
use 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
}
475452
type 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

Comments
 (0)