Skip to content

Commit 1b31a10

Browse files
committed
fix: consolidate executor build merging
1 parent 9d92a57 commit 1b31a10

6 files changed

Lines changed: 89 additions & 66 deletions

File tree

rust/operator-binary/src/airflow_controller.rs

Lines changed: 14 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ use strum::{EnumDiscriminants, IntoStaticStr};
3030

3131
use crate::{
3232
controller::{
33-
ValidatedCluster,
33+
ValidatedCluster, ValidatedExecutorTemplate,
3434
build::{
3535
config_map,
3636
resource::{
@@ -44,8 +44,7 @@ use crate::{
4444
},
4545
controller_commons::LOG_VOLUME_NAME,
4646
crd::{
47-
self, APP_NAME, AirflowClusterStatus, AirflowConfigOverrides, AirflowExecutor,
48-
AirflowExecutorCommonConfiguration, Container, OPERATOR_NAME,
47+
APP_NAME, AirflowClusterStatus, AirflowConfigOverrides, Container, OPERATOR_NAME,
4948
internal_secret::{
5049
FERNET_KEY_SECRET_KEY, INTERNAL_SECRET_SECRET_KEY, JWT_SECRET_SECRET_KEY,
5150
},
@@ -132,9 +131,6 @@ pub enum Error {
132131
source: crate::controller::build::config_map::Error,
133132
},
134133

135-
#[snafu(display("failed to resolve and merge config for role and role group"))]
136-
FailedToResolveConfig { source: crd::Error },
137-
138134
#[snafu(display("invalid git-sync specification"))]
139135
InvalidGitSyncSpec { source: git_sync::v1alpha2::Error },
140136

@@ -303,13 +299,9 @@ pub async fn reconcile_airflow(
303299

304300
// if the kubernetes executor is specified, in place of a worker role that will be in the role
305301
// collection there will be a pod template created to be used for pod provisioning
306-
if let AirflowExecutor::KubernetesExecutors {
307-
common_configuration,
308-
} = &validated_cluster.cluster_config.executor
309-
{
302+
if let Some(executor_template) = &validated_cluster.cluster_config.executor_template {
310303
build_executor_template(
311-
airflow,
312-
common_configuration,
304+
executor_template,
313305
&validated_cluster,
314306
&mut cluster_resources,
315307
client,
@@ -352,7 +344,7 @@ pub async fn reconcile_airflow(
352344
&validated_cluster.cluster_config.dags_git_sync,
353345
&validated_cluster.image,
354346
&Vec::<EnvVar>::from(validated_rg_config.env_overrides.clone()),
355-
&airflow.volume_mounts(),
347+
&validated_cluster.volume_mounts(),
356348
LOG_VOLUME_NAME.as_ref(),
357349
&logging.git_sync_container,
358350
)
@@ -436,35 +428,21 @@ pub async fn reconcile_airflow(
436428
}
437429

438430
async fn build_executor_template(
439-
airflow: &v1alpha2::AirflowCluster,
440-
common_config: &AirflowExecutorCommonConfiguration,
431+
executor_template: &ValidatedExecutorTemplate,
441432
validated_cluster: &ValidatedCluster,
442433
cluster_resources: &mut ClusterResources<'_>,
443434
client: &stackable_operator::client::Client,
444435
rbac_sa: &stackable_operator::k8s_openapi::api::core::v1::ServiceAccount,
445436
) -> Result<(), Error> {
446-
let merged_executor_config = airflow
447-
.merged_executor_config(&common_config.config)
448-
.context(FailedToResolveConfigSnafu)?;
449-
// The Kubernetes-executor pod template is not an `AirflowRole` with role groups, so its logging
450-
// is validated here (at build time) via the shared `validate_logging`, mirroring the role-group
451-
// path in `validate`. `Container::Base` is the executor's product container.
452-
let executor_logging = crate::controller::validate::validate_logging(
453-
&merged_executor_config.logging,
454-
&Container::Base,
455-
&validated_cluster
456-
.cluster_config
457-
.vector_aggregator_config_map_name,
458-
)
459-
.context(ValidateSnafu)?;
437+
let executor_config = &executor_template.config;
460438
let rg_configmap = config_map::build_rolegroup_config_map(
461439
validated_cluster,
462440
&executor_role_name(),
463441
&executor_role_group_name(),
464442
// The kubernetes-executor pod template does not apply webserver_config.py overrides
465443
// (preserves prior behaviour, which passed an empty map here).
466444
&AirflowConfigOverrides::default(),
467-
&executor_logging,
445+
&executor_config.logging,
468446
&Container::Base,
469447
)
470448
.context(BuildConfigMapSnafu)?;
@@ -478,22 +456,19 @@ async fn build_executor_template(
478456
let git_sync_resources = git_sync::v1alpha2::GitSyncResources::new(
479457
&validated_cluster.cluster_config.dags_git_sync,
480458
&validated_cluster.image,
481-
&env_vars_from_overrides(&common_config.env_overrides),
482-
&airflow.volume_mounts(),
459+
&env_vars_from_overrides(&executor_template.env_overrides),
460+
&validated_cluster.volume_mounts(),
483461
LOG_VOLUME_NAME.as_ref(),
484-
&merged_executor_config
485-
.logging
486-
.for_container(&Container::GitSync),
462+
&executor_config.logging.git_sync_container,
487463
)
488464
.context(InvalidGitSyncSpecSnafu)?;
489465

490466
let worker_pod_template_config_map = build_executor_template_config_map(
491467
validated_cluster,
492468
&rbac_sa.name_unchecked(),
493-
&merged_executor_config,
494-
&executor_logging,
495-
&common_config.env_overrides,
496-
&common_config.pod_overrides,
469+
executor_config,
470+
&executor_template.env_overrides,
471+
&executor_template.pod_overrides,
497472
&git_sync_resources,
498473
)
499474
.context(BuildExecutorTemplateSnafu)?;

rust/operator-binary/src/controller/build/properties/env_vars.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ use stackable_operator::{
1313
};
1414

1515
use crate::{
16-
controller::ValidatedCluster,
16+
controller::{ValidatedCluster, ValidatedLogging},
1717
crd::{
18-
AirflowExecutor, AirflowRole, ExecutorConfig, HTTP_PORT, LOG_CONFIG_DIR, STACKABLE_LOG_DIR,
18+
AirflowExecutor, AirflowRole, HTTP_PORT, LOG_CONFIG_DIR, STACKABLE_LOG_DIR,
1919
TEMPLATE_LOCATION, TEMPLATE_NAME,
2020
authentication::{
2121
AirflowAuthenticationClassResolved, AirflowClientAuthenticationDetailsResolved,
@@ -360,7 +360,7 @@ fn static_envs(
360360
pub fn build_airflow_template_envs(
361361
cluster: &ValidatedCluster,
362362
env_overrides: &HashMap<String, String>,
363-
config: &ExecutorConfig,
363+
logging: &ValidatedLogging,
364364
git_sync_resources: &git_sync::v1alpha2::GitSyncResources,
365365
) -> Vec<EnvVar> {
366366
let mut env: BTreeMap<String, EnvVar> = BTreeMap::new();
@@ -417,7 +417,7 @@ pub fn build_airflow_template_envs(
417417
// _STACKABLE_POST_HOOK will contain a command to create a shutdown hook that will be
418418
// evaluated in the wrapper for each stackable spark container: this is necessary for pods
419419
// that are created and then terminated (we do a similar thing for spark-k8s).
420-
if config.logging.enable_vector_agent {
420+
if logging.enable_vector_agent {
421421
env.insert(
422422
"_STACKABLE_POST_HOOK".into(),
423423
EnvVar {

rust/operator-binary/src/controller/build/resource/executor.rs

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use crate::{
2424
executor_role_group_name, executor_role_name, executor_template_role_group_name,
2525
},
2626
controller::{
27-
ValidatedCluster, ValidatedLogging,
27+
ValidatedAirflowConfig, ValidatedCluster,
2828
build::{
2929
graceful_shutdown::add_graceful_shutdown_config,
3030
properties::env_vars::build_airflow_template_envs,
@@ -35,9 +35,7 @@ use crate::{
3535
},
3636
},
3737
controller_commons::{self, CONFIG_VOLUME_NAME, LOG_CONFIG_VOLUME_NAME, LOG_VOLUME_NAME},
38-
crd::{
39-
CONFIG_PATH, Container, ExecutorConfig, LOG_CONFIG_DIR, STACKABLE_LOG_DIR, TEMPLATE_NAME,
40-
},
38+
crd::{CONFIG_PATH, Container, LOG_CONFIG_DIR, STACKABLE_LOG_DIR, TEMPLATE_NAME},
4139
};
4240

4341
#[derive(Snafu, Debug)]
@@ -79,8 +77,7 @@ type Result<T, E = Error> = std::result::Result<T, E>;
7977
pub fn build_executor_template_config_map(
8078
cluster: &ValidatedCluster,
8179
sa_name: &str,
82-
merged_executor_config: &ExecutorConfig,
83-
executor_logging: &ValidatedLogging,
80+
executor_config: &ValidatedAirflowConfig,
8481
env_overrides: &HashMap<String, String>,
8582
pod_overrides: &PodTemplateSpec,
8683
git_sync_resources: &git_sync::v1alpha2::GitSyncResources,
@@ -99,12 +96,12 @@ pub fn build_executor_template_config_map(
9996

10097
pb.metadata(pb_metadata)
10198
.image_pull_secrets_from_product_image(resolved_product_image)
102-
.affinity(&merged_executor_config.affinity)
99+
.affinity(&executor_config.affinity)
103100
.service_account_name(sa_name)
104101
.restart_policy("Never")
105102
.security_context(PodSecurityContextBuilder::new().fs_group(1000).build());
106103

107-
add_graceful_shutdown_config(merged_executor_config.graceful_shutdown_timeout, &mut pb)
104+
add_graceful_shutdown_config(executor_config.graceful_shutdown_timeout, &mut pb)
108105
.context(GracefulShutdownSnafu)?;
109106

110107
// N.B. this "base" name is an airflow requirement and should not be changed!
@@ -119,11 +116,11 @@ pub fn build_executor_template_config_map(
119116
.context(PodSnafu)?;
120117
airflow_container
121118
.image_from_product_image(resolved_product_image)
122-
.resources(merged_executor_config.resources.clone().into())
119+
.resources(executor_config.resources.clone().into())
123120
.add_env_vars(build_airflow_template_envs(
124121
cluster,
125122
env_overrides,
126-
merged_executor_config,
123+
&executor_config.logging,
127124
git_sync_resources,
128125
))
129126
.add_volume_mounts(cluster.volume_mounts())
@@ -157,11 +154,11 @@ pub fn build_executor_template_config_map(
157154
.resource_names(&executor_role_name(), &executor_role_group_name())
158155
.role_group_config_map()
159156
.as_ref(),
160-
&executor_logging.product_container,
157+
&executor_config.logging.product_container,
161158
))
162159
.context(AddVolumeSnafu)?;
163160

164-
if let Some(vector_log_config) = &executor_logging.vector_container {
161+
if let Some(vector_log_config) = &executor_config.logging.vector_container {
165162
pb.add_container(build_logging_container(
166163
resolved_product_image,
167164
vector_log_config,

rust/operator-binary/src/controller/mod.rs

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
use std::{collections::BTreeMap, str::FromStr};
1+
use std::{
2+
collections::{BTreeMap, HashMap},
3+
str::FromStr,
4+
};
25

36
use stackable_operator::{
47
builder::meta::ObjectMetaBuilder,
@@ -11,7 +14,7 @@ use stackable_operator::{
1114
database_connections::drivers::{
1215
celery::CeleryDatabaseConnectionDetails, sqlalchemy::SqlAlchemyDatabaseConnectionDetails,
1316
},
14-
k8s_openapi::api::core::v1::{Volume, VolumeMount},
17+
k8s_openapi::api::core::v1::{PodTemplateSpec, Volume, VolumeMount},
1518
kube::{Resource, ResourceExt, api::ObjectMeta},
1619
kvp::Labels,
1720
product_logging::spec::ContainerLogConfig,
@@ -38,7 +41,7 @@ use crate::{
3841
airflow_controller::AIRFLOW_CONTROLLER_NAME,
3942
crd::{
4043
APP_NAME, AirflowConfig, AirflowConfigOverrides, AirflowExecutor, AirflowRole,
41-
AirflowStorageConfig, OPERATOR_NAME,
44+
AirflowStorageConfig, ExecutorConfig, OPERATOR_NAME,
4245
authentication::AirflowClientAuthenticationDetailsResolved,
4346
authorization::AirflowAuthorizationResolved, v1alpha2,
4447
},
@@ -90,6 +93,29 @@ impl ValidatedAirflowConfig {
9093
graceful_shutdown_timeout: merged.graceful_shutdown_timeout,
9194
}
9295
}
96+
97+
/// Builds the validated config from the merged [`ExecutorConfig`] (Kubernetes-executor pod
98+
/// template), swapping in the already-validated logging. [`ExecutorConfig`] is field-identical
99+
/// to [`AirflowConfig`].
100+
pub(crate) fn from_merged_executor(merged: ExecutorConfig, logging: ValidatedLogging) -> Self {
101+
Self {
102+
resources: merged.resources,
103+
logging,
104+
affinity: merged.affinity,
105+
graceful_shutdown_timeout: merged.graceful_shutdown_timeout,
106+
}
107+
}
108+
}
109+
110+
/// The validated Kubernetes-executor pod-template config, computed during validation so the build
111+
/// step never merges or validates the raw cluster's executor config itself.
112+
pub struct ValidatedExecutorTemplate {
113+
/// The merged + validated executor config (resources, affinity, logging, …).
114+
pub config: ValidatedAirflowConfig,
115+
/// Env-var overrides for the executor pod template (`spec.kubernetesExecutors.envOverrides`).
116+
pub env_overrides: HashMap<String, String>,
117+
/// Pod overrides for the executor pod template (`spec.kubernetesExecutors.podOverrides`).
118+
pub pod_overrides: PodTemplateSpec,
93119
}
94120

95121
/// Validated logging configuration for the containers of a role-group (or Kubernetes-executor) Pod.
@@ -111,6 +137,9 @@ pub struct ValidatedLogging {
111137
/// here rather than from the raw cluster object.
112138
pub struct ValidatedClusterConfig {
113139
pub executor: AirflowExecutor,
140+
/// The validated Kubernetes-executor pod-template config (`None` for the Celery executor),
141+
/// merged and logging-validated up-front so the build step does not touch the raw cluster.
142+
pub executor_template: Option<ValidatedExecutorTemplate>,
114143
pub authentication_config: AirflowClientAuthenticationDetailsResolved,
115144
pub authorization_config: AirflowAuthorizationResolved,
116145
/// The Git-sync definitions for the DAGs (`spec.clusterConfig.dagsGitSync`).
@@ -130,8 +159,6 @@ pub struct ValidatedClusterConfig {
130159
pub volumes: Vec<Volume>,
131160
/// User-supplied extra VolumeMounts (`spec.clusterConfig.volumeMounts`).
132161
pub volume_mounts: Vec<VolumeMount>,
133-
/// The validated Vector aggregator discovery ConfigMap name (`None` when no aggregator is set).
134-
pub vector_aggregator_config_map_name: Option<ConfigMapName>,
135162
}
136163

137164
/// The validated cluster: proves that config merging succeeded for every role and

rust/operator-binary/src/controller/validate.rs

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ use strum::IntoEnumIterator;
3131

3232
use super::{
3333
AirflowRoleGroupConfig, ValidatedAirflowConfig, ValidatedCluster, ValidatedClusterConfig,
34-
ValidatedLogging, ValidatedRoleConfig, dereference::DereferencedObjects,
34+
ValidatedExecutorTemplate, ValidatedLogging, ValidatedRoleConfig,
35+
dereference::DereferencedObjects,
3536
};
3637
use crate::{
3738
airflow_controller::CONTAINER_IMAGE_BASE_NAME,
@@ -64,6 +65,9 @@ pub enum Error {
6465
source: stackable_operator::v2::controller_utils::Error,
6566
},
6667

68+
#[snafu(display("failed to merge the Kubernetes-executor pod-template config"))]
69+
MergeExecutorConfig { source: crate::crd::Error },
70+
6771
#[snafu(display("invalid role group name {role_group}"))]
6872
ParseRoleGroupName {
6973
source: stackable_operator::v2::macros::attributed_string_type::Error,
@@ -174,13 +178,38 @@ pub fn validate_cluster(
174178
let (metadata_database_connection_details, celery_database_connection_details) =
175179
database_connection_details(airflow);
176180

181+
// The Kubernetes-executor pod template is not an `AirflowRole` with role groups, so its config
182+
// is merged and its logging validated here, up-front, rather than in the build step.
183+
// `Container::Base` is the executor's product container.
184+
let executor_template = match &airflow.spec.executor {
185+
AirflowExecutor::KubernetesExecutors {
186+
common_configuration,
187+
} => {
188+
let merged = airflow
189+
.merged_executor_config(&common_configuration.config)
190+
.context(MergeExecutorConfigSnafu)?;
191+
let logging = validate_logging(
192+
&merged.logging,
193+
&Container::Base,
194+
&vector_aggregator_config_map_name,
195+
)?;
196+
Some(ValidatedExecutorTemplate {
197+
config: ValidatedAirflowConfig::from_merged_executor(merged, logging),
198+
env_overrides: common_configuration.env_overrides.clone(),
199+
pod_overrides: common_configuration.pod_overrides.clone(),
200+
})
201+
}
202+
AirflowExecutor::CeleryExecutors { .. } => None,
203+
};
204+
177205
Ok(ValidatedCluster::new(
178206
cluster_name,
179207
namespace,
180208
uid,
181209
resolved_product_image,
182210
ValidatedClusterConfig {
183211
executor: airflow.spec.executor.clone(),
212+
executor_template,
184213
authentication_config,
185214
authorization_config,
186215
dags_git_sync: airflow.spec.cluster_config.dags_git_sync.clone(),
@@ -196,7 +225,6 @@ pub fn validate_cluster(
196225
.enabled,
197226
volumes: airflow.spec.cluster_config.volumes.clone(),
198227
volume_mounts: airflow.spec.cluster_config.volume_mounts.clone(),
199-
vector_aggregator_config_map_name: vector_aggregator_config_map_name.clone(),
200228
},
201229
role_groups,
202230
role_configs,

rust/operator-binary/src/crd/mod.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -429,10 +429,6 @@ impl v1alpha2::AirflowCluster {
429429
self.get_role(role).map(|r| r.role_config)
430430
}
431431

432-
pub fn volume_mounts(&self) -> Vec<VolumeMount> {
433-
self.spec.cluster_config.volume_mounts.clone()
434-
}
435-
436432
/// Retrieve and merge resource configs for the executor template
437433
pub fn merged_executor_config(
438434
&self,

0 commit comments

Comments
 (0)