From 30acbebfb346fa3af47c1546adb5d0bb4c36166c Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Tue, 12 May 2026 16:51:51 +0200 Subject: [PATCH 01/13] refactor: extract dereference/validate pipeline from reconcile_hive Move external resource resolution (product image, S3 connection, metadata database, OPA config) into controller::dereference module with its own error enum. Extract config validation and merging into validate_cluster(), which produces a ValidatedHiveCluster proving all product-config validation succeeded before any Kubernetes resources are created. The validated struct owns the resolved product image and per-role/ per-rolegroup merged configs. Existing build functions are unchanged and receive their parameters from the validated structs. Co-Authored-By: Claude Opus 4.6 --- rust/operator-binary/src/controller.rs | 348 ++++++++++-------- .../src/controller/dereference.rs | 95 +++++ rust/operator-binary/src/crd/mod.rs | 2 +- 3 files changed, 290 insertions(+), 155 deletions(-) create mode 100644 rust/operator-binary/src/controller/dereference.rs diff --git a/rust/operator-binary/src/controller.rs b/rust/operator-binary/src/controller.rs index ade37ef0..a10b0397 100644 --- a/rust/operator-binary/src/controller.rs +++ b/rust/operator-binary/src/controller.rs @@ -1,5 +1,7 @@ //! Ensures that `Pod`s are configured and running for each [`v1alpha1::HiveCluster`] +pub mod dereference; + use std::{ borrow::Cow, collections::{BTreeMap, HashMap}, @@ -35,8 +37,7 @@ use stackable_operator::{ cli::OperatorEnvironmentOptions, cluster_resources::{ClusterResourceApplyStrategy, ClusterResources}, commons::{ - product_image_selection::{self, ResolvedProductImage}, - rbac::build_rbac_resources, + product_image_selection::ResolvedProductImage, rbac::build_rbac_resources, secret_class::SecretClassVolumeProvisionParts, }, constants::RESTART_CONTROLLER_ENABLED_LABEL, @@ -129,6 +130,31 @@ pub struct Ctx { pub operator_environment: OperatorEnvironmentOptions, } +/// Per-role configuration extracted during validation. +#[derive(Clone, Debug)] +pub struct ValidatedRoleConfig { + pub pdb: stackable_operator::commons::pdb::PdbConfig, + pub listener_class: String, +} + +/// Per-rolegroup configuration: the merged CRD config plus the product-config properties. +#[derive(Clone, Debug)] +pub struct ValidatedRoleGroupConfig { + pub merged_config: MetaStoreConfig, + pub product_config_properties: HashMap>, +} + +pub use crate::controller::dereference::DereferencedObjects; + +/// The validated cluster: proves that product-config validation and config merging +/// succeeded for every role and role group before any resources are created. +#[derive(Clone, Debug)] +pub struct ValidatedHiveCluster { + pub image: ResolvedProductImage, + pub role_groups: BTreeMap>, + pub role_configs: BTreeMap, +} + #[derive(Snafu, Debug, EnumDiscriminants)] #[strum_discriminants(derive(strum::IntoStaticStr))] #[allow(clippy::enum_variant_names)] @@ -320,25 +346,15 @@ pub enum Error { #[snafu(display("failed to configure service"))] ServiceConfiguration { source: crate::service::Error }, - #[snafu(display("failed to resolve product image"))] - ResolveProductImage { - source: product_image_selection::Error, - }, - - #[snafu(display("invalid OpaConfig"))] - InvalidOpaConfig { - source: stackable_operator::commons::opa::Error, + #[snafu(display("failed to dereference cluster resources"))] + Dereference { + source: crate::controller::dereference::Error, }, #[snafu(display("failed to build TLS certificate SecretClass Volume"))] TlsCertSecretClassVolumeBuild { source: stackable_operator::builder::pod::volume::SecretOperatorVolumeSourceBuilderError, }, - - #[snafu(display("invalid metadata database connection"))] - InvalidMetadataDatabaseConnection { - source: stackable_operator::database_connections::Error, - }, } type Result = std::result::Result; @@ -348,55 +364,15 @@ impl ReconcilerError for Error { } } -pub async fn reconcile_hive( - hive: Arc>, - ctx: Arc, -) -> Result { - tracing::info!("Starting reconcile"); - let hive = hive - .0 - .as_ref() - .map_err(error_boundary::InvalidObject::clone) - .context(InvalidHiveClusterSnafu)?; - let client = &ctx.client; - let hive_namespace = hive.namespace().context(ObjectHasNoNamespaceSnafu)?; - - let resolved_product_image = hive - .spec - .image - .resolve( - CONTAINER_IMAGE_BASE_NAME, - &ctx.operator_environment.image_repository, - crate::built_info::PKG_VERSION, - ) - .context(ResolveProductImageSnafu)?; +fn validate_cluster( + hive: &v1alpha1::HiveCluster, + dereferenced: &DereferencedObjects, + product_config_manager: &ProductConfigManager, +) -> Result { let role = hive.spec.metastore.as_ref().context(NoMetaStoreRoleSnafu)?; - let hive_role = HiveRole::MetaStore; - - let s3_connection_spec: Option = - if let Some(s3) = &hive.spec.cluster_config.s3 { - Some( - s3.clone() - .resolve( - client, - &hive.namespace().ok_or(Error::ObjectHasNoNamespace)?, - ) - .await - .context(ConfigureS3ConnectionSnafu)?, - ) - } else { - None - }; - - let metadata_database_connection_details = hive - .spec - .cluster_config - .metadata_database - .jdbc_connection_details("METADATA") - .context(InvalidMetadataDatabaseConnectionSnafu)?; let validated_config = validate_all_roles_and_groups_config( - &resolved_product_image.product_version, + &dereferenced.resolved_product_image.product_version, &transform_all_roles_to_config( hive, &[( @@ -414,17 +390,89 @@ pub async fn reconcile_hive( .into(), ) .context(GenerateProductConfigSnafu)?, - &ctx.product_config, + product_config_manager, false, false, ) .context(InvalidProductConfigSnafu)?; + let mut role_groups = BTreeMap::new(); + let mut role_configs = BTreeMap::new(); + let metastore_config = validated_config .get(&HiveRole::MetaStore.to_string()) .map(Cow::Borrowed) .unwrap_or_default(); + let hive_role = HiveRole::MetaStore; + + if let Some(HiveMetastoreRoleConfig { + common: GenericRoleConfig { + pod_disruption_budget: pdb, + }, + listener_class, + }) = hive.role_config(&hive_role) + { + role_configs.insert( + hive_role.clone(), + ValidatedRoleConfig { + pdb: pdb.clone(), + listener_class: listener_class.clone(), + }, + ); + } + + let mut group_configs = BTreeMap::new(); + for (rolegroup_name, rolegroup_config) in metastore_config.iter() { + let rolegroup = hive.metastore_rolegroup_ref(rolegroup_name); + + let merged_config = hive + .merged_config(&hive_role, &rolegroup) + .context(FailedToResolveResourceConfigSnafu)?; + + group_configs.insert( + rolegroup_name.clone(), + ValidatedRoleGroupConfig { + merged_config, + product_config_properties: rolegroup_config.clone(), + }, + ); + } + + role_groups.insert(hive_role, group_configs); + + Ok(ValidatedHiveCluster { + image: dereferenced.resolved_product_image.clone(), + role_groups, + role_configs, + }) +} + +pub async fn reconcile_hive( + hive: Arc>, + ctx: Arc, +) -> Result { + tracing::info!("Starting reconcile"); + let hive = hive + .0 + .as_ref() + .map_err(error_boundary::InvalidObject::clone) + .context(InvalidHiveClusterSnafu)?; + let client = &ctx.client; + let hive_namespace = hive.namespace().context(ObjectHasNoNamespaceSnafu)?; + + let dereferenced = crate::controller::dereference::dereference( + client, + hive, + CONTAINER_IMAGE_BASE_NAME, + &ctx.operator_environment.image_repository, + crate::built_info::PKG_VERSION, + ) + .await + .context(DereferenceSnafu)?; + + let validated = validate_cluster(hive, &dereferenced, &ctx.product_config)?; + let mut cluster_resources = ClusterResources::new( APP_NAME, OPERATOR_NAME, @@ -454,112 +502,104 @@ pub async fn reconcile_hive( .await .context(ApplyRoleBindingSnafu)?; - let hive_opa_config = match hive.get_opa_config() { - Some(opa_config) => Some( - HiveOpaConfig::from_opa_config(client, hive, opa_config) - .await - .context(InvalidOpaConfigSnafu)?, - ), - None => None, - }; - let mut ss_cond_builder = StatefulSetConditionBuilder::default(); - for (rolegroup_name, rolegroup_config) in metastore_config.iter() { - let rolegroup = hive.metastore_rolegroup_ref(rolegroup_name); - - let config = hive - .merged_config(&HiveRole::MetaStore, &rolegroup) - .context(FailedToResolveResourceConfigSnafu)?; - - let rg_metrics_service = - build_rolegroup_metrics_service(hive, &resolved_product_image, &rolegroup) - .context(ServiceConfigurationSnafu)?; - - let rg_headless_service = - build_rolegroup_headless_service(hive, &resolved_product_image, &rolegroup) - .context(ServiceConfigurationSnafu)?; - - let rg_configmap = build_metastore_rolegroup_config_map( - hive, - &hive_namespace, - &resolved_product_image, - &rolegroup, - rolegroup_config, - &metadata_database_connection_details, - s3_connection_spec.as_ref(), - &config, - &client.kubernetes_cluster_info, - hive_opa_config.as_ref(), - )?; - let rg_statefulset = build_metastore_rolegroup_statefulset( - hive, - &hive_role, - &resolved_product_image, - &rolegroup, - rolegroup_config, - &metadata_database_connection_details, - s3_connection_spec.as_ref(), - &config, - &rbac_sa.name_any(), - hive_opa_config.as_ref(), - )?; - - cluster_resources - .add(client, rg_metrics_service) + for (hive_role, role_group_configs) in &validated.role_groups { + if let Some(role_config) = validated.role_configs.get(hive_role) { + add_pdbs( + &role_config.pdb, + hive, + hive_role, + client, + &mut cluster_resources, + ) .await - .context(ApplyRoleGroupServiceSnafu { - rolegroup: rolegroup.clone(), - })?; + .context(FailedToCreatePdbSnafu)?; + } - cluster_resources - .add(client, rg_headless_service) - .await - .context(ApplyRoleGroupServiceSnafu { - rolegroup: rolegroup.clone(), - })?; + for (rolegroup_name, validated_rg_config) in role_group_configs { + let rolegroup = hive.metastore_rolegroup_ref(rolegroup_name); + + let rg_metrics_service = + build_rolegroup_metrics_service(hive, &validated.image, &rolegroup) + .context(ServiceConfigurationSnafu)?; + + let rg_headless_service = + build_rolegroup_headless_service(hive, &validated.image, &rolegroup) + .context(ServiceConfigurationSnafu)?; + + let rg_configmap = build_metastore_rolegroup_config_map( + hive, + &hive_namespace, + &validated.image, + &rolegroup, + &validated_rg_config.product_config_properties, + &dereferenced.metadata_database_connection_details, + dereferenced.s3_connection_spec.as_ref(), + &validated_rg_config.merged_config, + &client.kubernetes_cluster_info, + dereferenced.hive_opa_config.as_ref(), + )?; + let rg_statefulset = build_metastore_rolegroup_statefulset( + hive, + hive_role, + &validated.image, + &rolegroup, + &validated_rg_config.product_config_properties, + &dereferenced.metadata_database_connection_details, + dereferenced.s3_connection_spec.as_ref(), + &validated_rg_config.merged_config, + &rbac_sa.name_any(), + dereferenced.hive_opa_config.as_ref(), + )?; - cluster_resources - .add(client, rg_configmap) - .await - .context(ApplyRoleGroupConfigSnafu { - rolegroup: rolegroup.clone(), - })?; - - // Note: The StatefulSet needs to be applied after all ConfigMaps and Secrets it mounts - // to prevent unnecessary Pod restarts. - // See https://github.com/stackabletech/commons-operator/issues/111 for details. - ss_cond_builder.add( cluster_resources - .add(client, rg_statefulset) + .add(client, rg_metrics_service) .await - .context(ApplyRoleGroupStatefulSetSnafu { + .context(ApplyRoleGroupServiceSnafu { rolegroup: rolegroup.clone(), - })?, - ); - } + })?; - let role_config = hive.role_config(&hive_role); - if let Some(HiveMetastoreRoleConfig { - common: GenericRoleConfig { - pod_disruption_budget: pdb, - }, - .. - }) = role_config - { - add_pdbs(pdb, hive, &hive_role, client, &mut cluster_resources) - .await - .context(FailedToCreatePdbSnafu)?; + cluster_resources + .add(client, rg_headless_service) + .await + .context(ApplyRoleGroupServiceSnafu { + rolegroup: rolegroup.clone(), + })?; + + cluster_resources.add(client, rg_configmap).await.context( + ApplyRoleGroupConfigSnafu { + rolegroup: rolegroup.clone(), + }, + )?; + + // Note: The StatefulSet needs to be applied after all ConfigMaps and Secrets it mounts + // to prevent unnecessary Pod restarts. + // See https://github.com/stackabletech/commons-operator/issues/111 for details. + ss_cond_builder.add( + cluster_resources + .add(client, rg_statefulset) + .await + .context(ApplyRoleGroupStatefulSetSnafu { + rolegroup: rolegroup.clone(), + })?, + ); + } } // std's SipHasher is deprecated, and DefaultHasher is unstable across Rust releases. // We don't /need/ stability, but it's still nice to avoid spurious changes where possible. let mut discovery_hash = FnvHasher::with_key(0); - if let Some(HiveMetastoreRoleConfig { listener_class, .. }) = role_config { - let role_listener: Listener = - build_role_listener(hive, &resolved_product_image, &hive_role, listener_class) - .context(ListenerConfigurationSnafu)?; + let hive_role = HiveRole::MetaStore; + if let Some(role_config) = validated.role_configs.get(&hive_role) { + let role_listener: Listener = build_role_listener( + hive, + &validated.image, + &hive_role, + &role_config.listener_class, + ) + .context(ListenerConfigurationSnafu)?; let listener = cluster_resources.add(client, role_listener).await.context( ApplyGroupListenerSnafu { role: hive_role.to_string(), @@ -570,7 +610,7 @@ pub async fn reconcile_hive( hive, hive, hive_role, - &resolved_product_image, + &validated.image, None, listener, ) diff --git a/rust/operator-binary/src/controller/dereference.rs b/rust/operator-binary/src/controller/dereference.rs new file mode 100644 index 00000000..79bcfed7 --- /dev/null +++ b/rust/operator-binary/src/controller/dereference.rs @@ -0,0 +1,95 @@ +use snafu::{ResultExt, Snafu}; +use stackable_operator::{ + commons::product_image_selection::{self, ResolvedProductImage}, + crd::s3, + database_connections::drivers::jdbc::JdbcDatabaseConnectionDetails, + kube::ResourceExt, +}; + +use crate::{config::opa::HiveOpaConfig, crd::v1alpha1}; + +#[derive(Snafu, Debug)] +pub enum Error { + #[snafu(display("failed to resolve product image"))] + ResolveProductImage { + source: product_image_selection::Error, + }, + + #[snafu(display("object defines no namespace"))] + ObjectHasNoNamespace, + + #[snafu(display("failed to configure S3 connection"))] + ConfigureS3Connection { + source: s3::v1alpha1::ConnectionError, + }, + + #[snafu(display("invalid metadata database connection"))] + InvalidMetadataDatabaseConnection { + source: stackable_operator::database_connections::Error, + }, + + #[snafu(display("invalid OpaConfig"))] + InvalidOpaConfig { + source: stackable_operator::commons::opa::Error, + }, +} + +/// External references resolved during the dereference step. +pub struct DereferencedObjects { + pub resolved_product_image: ResolvedProductImage, + pub s3_connection_spec: Option, + pub metadata_database_connection_details: JdbcDatabaseConnectionDetails, + pub hive_opa_config: Option, +} + +pub async fn dereference( + client: &stackable_operator::client::Client, + hive: &v1alpha1::HiveCluster, + image_base_name: &str, + image_repository: &str, + pkg_version: &str, +) -> Result { + let resolved_product_image = hive + .spec + .image + .resolve(image_base_name, image_repository, pkg_version) + .context(ResolveProductImageSnafu)?; + + let s3_connection_spec: Option = + if let Some(s3) = &hive.spec.cluster_config.s3 { + Some( + s3.clone() + .resolve( + client, + &hive.namespace().ok_or(Error::ObjectHasNoNamespace)?, + ) + .await + .context(ConfigureS3ConnectionSnafu)?, + ) + } else { + None + }; + + let metadata_database_connection_details = hive + .spec + .cluster_config + .metadata_database + .jdbc_connection_details("METADATA") + .context(InvalidMetadataDatabaseConnectionSnafu)?; + + let hive_opa_config = match hive.get_opa_config() { + Some(opa_config) => Some( + HiveOpaConfig::from_opa_config(client, hive, opa_config) + .await + .context(InvalidOpaConfigSnafu)?, + ), + None => None, + }; + + Ok(DereferencedObjects { + resolved_product_image, + s3_connection_spec, + metadata_database_connection_details, + hive_opa_config, + }) +} diff --git a/rust/operator-binary/src/crd/mod.rs b/rust/operator-binary/src/crd/mod.rs index 7e4edc63..2b046f18 100644 --- a/rust/operator-binary/src/crd/mod.rs +++ b/rust/operator-binary/src/crd/mod.rs @@ -383,7 +383,7 @@ pub struct HdfsConnection { pub config_map: String, } -#[derive(Display, EnumString, EnumIter)] +#[derive(Clone, Debug, Display, EnumString, EnumIter, Eq, Hash, Ord, PartialEq, PartialOrd)] #[strum(serialize_all = "camelCase")] pub enum HiveRole { #[strum(serialize = "metastore")] From 6da580cae18b4f37df3afd50aa37aeb5a1ec21f5 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Tue, 12 May 2026 17:15:50 +0200 Subject: [PATCH 02/13] refactor: extract validate_cluster into controller::validate module Co-Authored-By: Claude Opus 4.6 --- rust/operator-binary/src/controller.rs | 138 +---------------- .../src/controller/validate.rs | 144 ++++++++++++++++++ 2 files changed, 152 insertions(+), 130 deletions(-) create mode 100644 rust/operator-binary/src/controller/validate.rs diff --git a/rust/operator-binary/src/controller.rs b/rust/operator-binary/src/controller.rs index a10b0397..eb1f9023 100644 --- a/rust/operator-binary/src/controller.rs +++ b/rust/operator-binary/src/controller.rs @@ -1,9 +1,9 @@ //! Ensures that `Pod`s are configured and running for each [`v1alpha1::HiveCluster`] pub mod dereference; +pub mod validate; use std::{ - borrow::Cow, collections::{BTreeMap, HashMap}, hash::Hasher, sync::Arc, @@ -64,7 +64,6 @@ use stackable_operator::{ kvp::{Labels, ObjectLabels}, logging::controller::ReconcilerError, memory::{BinaryMultiple, MemoryQuantity}, - product_config_utils::{transform_all_roles_to_config, validate_all_roles_and_groups_config}, product_logging::{ self, framework::{ @@ -75,7 +74,7 @@ use stackable_operator::{ CustomContainerLogConfig, }, }, - role_utils::{GenericRoleConfig, RoleGroupRef}, + role_utils::RoleGroupRef, shared::time::Duration, status::condition::{ compute_conditions, operations::ClusterOperationsConditionBuilder, @@ -101,7 +100,7 @@ use crate::{ STACKABLE_LOG_CONFIG_MOUNT_DIR, STACKABLE_LOG_CONFIG_MOUNT_DIR_NAME, STACKABLE_LOG_DIR, STACKABLE_LOG_DIR_NAME, databases::{MetadataDatabaseConnection, derby_driver_class}, - v1alpha1::{self, HiveMetastoreRoleConfig}, + v1alpha1, }, discovery::{self}, kerberos::{ @@ -130,31 +129,6 @@ pub struct Ctx { pub operator_environment: OperatorEnvironmentOptions, } -/// Per-role configuration extracted during validation. -#[derive(Clone, Debug)] -pub struct ValidatedRoleConfig { - pub pdb: stackable_operator::commons::pdb::PdbConfig, - pub listener_class: String, -} - -/// Per-rolegroup configuration: the merged CRD config plus the product-config properties. -#[derive(Clone, Debug)] -pub struct ValidatedRoleGroupConfig { - pub merged_config: MetaStoreConfig, - pub product_config_properties: HashMap>, -} - -pub use crate::controller::dereference::DereferencedObjects; - -/// The validated cluster: proves that product-config validation and config merging -/// succeeded for every role and role group before any resources are created. -#[derive(Clone, Debug)] -pub struct ValidatedHiveCluster { - pub image: ResolvedProductImage, - pub role_groups: BTreeMap>, - pub role_configs: BTreeMap, -} - #[derive(Snafu, Debug, EnumDiscriminants)] #[strum_discriminants(derive(strum::IntoStaticStr))] #[allow(clippy::enum_variant_names)] @@ -162,9 +136,6 @@ pub enum Error { #[snafu(display("object defines no namespace"))] ObjectHasNoNamespace, - #[snafu(display("object defines no metastore role"))] - NoMetaStoreRole, - #[snafu(display("failed to apply Service for {rolegroup}"))] ApplyRoleGroupService { source: stackable_operator::cluster_resources::Error, @@ -189,16 +160,6 @@ pub enum Error { rolegroup: RoleGroupRef, }, - #[snafu(display("failed to generate product config"))] - GenerateProductConfig { - source: stackable_operator::product_config_utils::Error, - }, - - #[snafu(display("invalid product config"))] - InvalidProductConfig { - source: stackable_operator::product_config_utils::Error, - }, - #[snafu(display("object is missing metadata to build owner reference"))] ObjectMissingMetadataForOwnerRef { source: stackable_operator::builder::meta::Error, @@ -227,9 +188,6 @@ pub enum Error { ))] S3TlsNoVerificationNotSupported, - #[snafu(display("failed to resolve and merge resource config for role and role group"))] - FailedToResolveResourceConfig { source: crate::crd::Error }, - #[snafu(display("failed to create hive container [{name}]"))] FailedToCreateHiveContainer { source: stackable_operator::builder::pod::container::Error, @@ -351,6 +309,9 @@ pub enum Error { source: crate::controller::dereference::Error, }, + #[snafu(display("failed to validate cluster configuration"))] + Validate { source: validate::Error }, + #[snafu(display("failed to build TLS certificate SecretClass Volume"))] TlsCertSecretClassVolumeBuild { source: stackable_operator::builder::pod::volume::SecretOperatorVolumeSourceBuilderError, @@ -364,90 +325,6 @@ impl ReconcilerError for Error { } } -fn validate_cluster( - hive: &v1alpha1::HiveCluster, - dereferenced: &DereferencedObjects, - product_config_manager: &ProductConfigManager, -) -> Result { - let role = hive.spec.metastore.as_ref().context(NoMetaStoreRoleSnafu)?; - - let validated_config = validate_all_roles_and_groups_config( - &dereferenced.resolved_product_image.product_version, - &transform_all_roles_to_config( - hive, - &[( - HiveRole::MetaStore.to_string(), - ( - vec![ - PropertyNameKind::Env, - PropertyNameKind::Cli, - PropertyNameKind::File(HIVE_SITE_XML.to_string()), - PropertyNameKind::File(JVM_SECURITY_PROPERTIES_FILE.to_string()), - ], - role.clone(), - ), - )] - .into(), - ) - .context(GenerateProductConfigSnafu)?, - product_config_manager, - false, - false, - ) - .context(InvalidProductConfigSnafu)?; - - let mut role_groups = BTreeMap::new(); - let mut role_configs = BTreeMap::new(); - - let metastore_config = validated_config - .get(&HiveRole::MetaStore.to_string()) - .map(Cow::Borrowed) - .unwrap_or_default(); - - let hive_role = HiveRole::MetaStore; - - if let Some(HiveMetastoreRoleConfig { - common: GenericRoleConfig { - pod_disruption_budget: pdb, - }, - listener_class, - }) = hive.role_config(&hive_role) - { - role_configs.insert( - hive_role.clone(), - ValidatedRoleConfig { - pdb: pdb.clone(), - listener_class: listener_class.clone(), - }, - ); - } - - let mut group_configs = BTreeMap::new(); - for (rolegroup_name, rolegroup_config) in metastore_config.iter() { - let rolegroup = hive.metastore_rolegroup_ref(rolegroup_name); - - let merged_config = hive - .merged_config(&hive_role, &rolegroup) - .context(FailedToResolveResourceConfigSnafu)?; - - group_configs.insert( - rolegroup_name.clone(), - ValidatedRoleGroupConfig { - merged_config, - product_config_properties: rolegroup_config.clone(), - }, - ); - } - - role_groups.insert(hive_role, group_configs); - - Ok(ValidatedHiveCluster { - image: dereferenced.resolved_product_image.clone(), - role_groups, - role_configs, - }) -} - pub async fn reconcile_hive( hive: Arc>, ctx: Arc, @@ -471,7 +348,8 @@ pub async fn reconcile_hive( .await .context(DereferenceSnafu)?; - let validated = validate_cluster(hive, &dereferenced, &ctx.product_config)?; + let validated = validate::validate_cluster(hive, &dereferenced, &ctx.product_config) + .context(ValidateSnafu)?; let mut cluster_resources = ClusterResources::new( APP_NAME, diff --git a/rust/operator-binary/src/controller/validate.rs b/rust/operator-binary/src/controller/validate.rs new file mode 100644 index 00000000..449742fb --- /dev/null +++ b/rust/operator-binary/src/controller/validate.rs @@ -0,0 +1,144 @@ +use std::{ + borrow::Cow, + collections::{BTreeMap, HashMap}, +}; + +use product_config::{ProductConfigManager, types::PropertyNameKind}; +use snafu::{OptionExt, ResultExt, Snafu}; +use stackable_operator::{ + commons::product_image_selection::ResolvedProductImage, + product_config_utils::{transform_all_roles_to_config, validate_all_roles_and_groups_config}, + role_utils::GenericRoleConfig, +}; + +use super::dereference::DereferencedObjects; +use crate::crd::{ + HIVE_SITE_XML, HiveRole, JVM_SECURITY_PROPERTIES_FILE, MetaStoreConfig, + v1alpha1::{self, HiveMetastoreRoleConfig}, +}; + +#[derive(Snafu, Debug)] +pub enum Error { + #[snafu(display("object defines no metastore role"))] + NoMetaStoreRole, + + #[snafu(display("failed to generate product config"))] + GenerateProductConfig { + source: stackable_operator::product_config_utils::Error, + }, + + #[snafu(display("invalid product config"))] + InvalidProductConfig { + source: stackable_operator::product_config_utils::Error, + }, + + #[snafu(display("failed to resolve and merge resource config for role and role group"))] + FailedToResolveResourceConfig { source: crate::crd::Error }, +} + +/// Per-role configuration extracted during validation. +#[derive(Clone, Debug)] +pub struct ValidatedRoleConfig { + pub pdb: stackable_operator::commons::pdb::PdbConfig, + pub listener_class: String, +} + +/// Per-rolegroup configuration: the merged CRD config plus the product-config properties. +#[derive(Clone, Debug)] +pub struct ValidatedRoleGroupConfig { + pub merged_config: MetaStoreConfig, + pub product_config_properties: HashMap>, +} + +/// The validated cluster: proves that product-config validation and config merging +/// succeeded for every role and role group before any resources are created. +#[derive(Clone, Debug)] +pub struct ValidatedHiveCluster { + pub image: ResolvedProductImage, + pub role_groups: BTreeMap>, + pub role_configs: BTreeMap, +} + +pub fn validate_cluster( + hive: &v1alpha1::HiveCluster, + dereferenced: &DereferencedObjects, + product_config_manager: &ProductConfigManager, +) -> Result { + let role = hive.spec.metastore.as_ref().context(NoMetaStoreRoleSnafu)?; + + let validated_config = validate_all_roles_and_groups_config( + &dereferenced.resolved_product_image.product_version, + &transform_all_roles_to_config( + hive, + &[( + HiveRole::MetaStore.to_string(), + ( + vec![ + PropertyNameKind::Env, + PropertyNameKind::Cli, + PropertyNameKind::File(HIVE_SITE_XML.to_string()), + PropertyNameKind::File(JVM_SECURITY_PROPERTIES_FILE.to_string()), + ], + role.clone(), + ), + )] + .into(), + ) + .context(GenerateProductConfigSnafu)?, + product_config_manager, + false, + false, + ) + .context(InvalidProductConfigSnafu)?; + + let mut role_groups = BTreeMap::new(); + let mut role_configs = BTreeMap::new(); + + let metastore_config = validated_config + .get(&HiveRole::MetaStore.to_string()) + .map(Cow::Borrowed) + .unwrap_or_default(); + + let hive_role = HiveRole::MetaStore; + + if let Some(HiveMetastoreRoleConfig { + common: GenericRoleConfig { + pod_disruption_budget: pdb, + }, + listener_class, + }) = hive.role_config(&hive_role) + { + role_configs.insert( + hive_role.clone(), + ValidatedRoleConfig { + pdb: pdb.clone(), + listener_class: listener_class.clone(), + }, + ); + } + + let mut group_configs = BTreeMap::new(); + for (rolegroup_name, rolegroup_config) in metastore_config.iter() { + let rolegroup = hive.metastore_rolegroup_ref(rolegroup_name); + + let merged_config = hive + .merged_config(&hive_role, &rolegroup) + .context(FailedToResolveResourceConfigSnafu)?; + + group_configs.insert( + rolegroup_name.clone(), + ValidatedRoleGroupConfig { + merged_config, + product_config_properties: rolegroup_config.clone(), + }, + ); + } + + role_groups.insert(hive_role, group_configs); + + Ok(ValidatedHiveCluster { + image: dereferenced.resolved_product_image.clone(), + role_groups, + role_configs, + }) +} From 63744a2f1d36c77c2e0ccd11c5845245dc14d487 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Wed, 13 May 2026 11:41:21 +0200 Subject: [PATCH 03/13] refactor: align error variant naming with airflow and hbase operators Rename FailedToResolveResourceConfig to FailedToResolveConfig and fix OPA error display string to match the convention used across all three dereference/validate extraction PRs. Co-Authored-By: Claude Opus 4.6 --- rust/operator-binary/src/controller/dereference.rs | 2 +- rust/operator-binary/src/controller/validate.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/rust/operator-binary/src/controller/dereference.rs b/rust/operator-binary/src/controller/dereference.rs index 79bcfed7..fb0bdf2b 100644 --- a/rust/operator-binary/src/controller/dereference.rs +++ b/rust/operator-binary/src/controller/dereference.rs @@ -28,7 +28,7 @@ pub enum Error { source: stackable_operator::database_connections::Error, }, - #[snafu(display("invalid OpaConfig"))] + #[snafu(display("invalid OPA configuration"))] InvalidOpaConfig { source: stackable_operator::commons::opa::Error, }, diff --git a/rust/operator-binary/src/controller/validate.rs b/rust/operator-binary/src/controller/validate.rs index 449742fb..7ceeaee8 100644 --- a/rust/operator-binary/src/controller/validate.rs +++ b/rust/operator-binary/src/controller/validate.rs @@ -32,8 +32,8 @@ pub enum Error { source: stackable_operator::product_config_utils::Error, }, - #[snafu(display("failed to resolve and merge resource config for role and role group"))] - FailedToResolveResourceConfig { source: crate::crd::Error }, + #[snafu(display("failed to resolve and merge config for role and role group"))] + FailedToResolveConfig { source: crate::crd::Error }, } /// Per-role configuration extracted during validation. @@ -123,7 +123,7 @@ pub fn validate_cluster( let merged_config = hive .merged_config(&hive_role, &rolegroup) - .context(FailedToResolveResourceConfigSnafu)?; + .context(FailedToResolveConfigSnafu)?; group_configs.insert( rolegroup_name.clone(), From 1b613089f5553af03037a06bd6da285570a526bc Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Wed, 13 May 2026 16:58:56 +0200 Subject: [PATCH 04/13] refactor: move product image resolution from dereference to validate Image resolution is a pure computation, not an I/O dereference, so it belongs in validate_cluster alongside the other config validation. This aligns with the pattern used by the trino and airflow operators. Co-Authored-By: Claude Opus 4.6 --- rust/operator-binary/src/controller.rs | 14 ++++++------ .../src/controller/dereference.rs | 21 +----------------- .../src/controller/validate.rs | 22 ++++++++++++++----- 3 files changed, 25 insertions(+), 32 deletions(-) diff --git a/rust/operator-binary/src/controller.rs b/rust/operator-binary/src/controller.rs index eb1f9023..678812c1 100644 --- a/rust/operator-binary/src/controller.rs +++ b/rust/operator-binary/src/controller.rs @@ -338,18 +338,18 @@ pub async fn reconcile_hive( let client = &ctx.client; let hive_namespace = hive.namespace().context(ObjectHasNoNamespaceSnafu)?; - let dereferenced = crate::controller::dereference::dereference( - client, + let dereferenced = crate::controller::dereference::dereference(client, hive) + .await + .context(DereferenceSnafu)?; + + let validated = validate::validate_cluster( hive, CONTAINER_IMAGE_BASE_NAME, &ctx.operator_environment.image_repository, crate::built_info::PKG_VERSION, + &ctx.product_config, ) - .await - .context(DereferenceSnafu)?; - - let validated = validate::validate_cluster(hive, &dereferenced, &ctx.product_config) - .context(ValidateSnafu)?; + .context(ValidateSnafu)?; let mut cluster_resources = ClusterResources::new( APP_NAME, diff --git a/rust/operator-binary/src/controller/dereference.rs b/rust/operator-binary/src/controller/dereference.rs index fb0bdf2b..77e331aa 100644 --- a/rust/operator-binary/src/controller/dereference.rs +++ b/rust/operator-binary/src/controller/dereference.rs @@ -1,20 +1,12 @@ use snafu::{ResultExt, Snafu}; use stackable_operator::{ - commons::product_image_selection::{self, ResolvedProductImage}, - crd::s3, - database_connections::drivers::jdbc::JdbcDatabaseConnectionDetails, - kube::ResourceExt, + crd::s3, database_connections::drivers::jdbc::JdbcDatabaseConnectionDetails, kube::ResourceExt, }; use crate::{config::opa::HiveOpaConfig, crd::v1alpha1}; #[derive(Snafu, Debug)] pub enum Error { - #[snafu(display("failed to resolve product image"))] - ResolveProductImage { - source: product_image_selection::Error, - }, - #[snafu(display("object defines no namespace"))] ObjectHasNoNamespace, @@ -36,7 +28,6 @@ pub enum Error { /// External references resolved during the dereference step. pub struct DereferencedObjects { - pub resolved_product_image: ResolvedProductImage, pub s3_connection_spec: Option, pub metadata_database_connection_details: JdbcDatabaseConnectionDetails, pub hive_opa_config: Option, @@ -45,16 +36,7 @@ pub struct DereferencedObjects { pub async fn dereference( client: &stackable_operator::client::Client, hive: &v1alpha1::HiveCluster, - image_base_name: &str, - image_repository: &str, - pkg_version: &str, ) -> Result { - let resolved_product_image = hive - .spec - .image - .resolve(image_base_name, image_repository, pkg_version) - .context(ResolveProductImageSnafu)?; - let s3_connection_spec: Option = if let Some(s3) = &hive.spec.cluster_config.s3 { Some( @@ -87,7 +69,6 @@ pub async fn dereference( }; Ok(DereferencedObjects { - resolved_product_image, s3_connection_spec, metadata_database_connection_details, hive_opa_config, diff --git a/rust/operator-binary/src/controller/validate.rs b/rust/operator-binary/src/controller/validate.rs index 7ceeaee8..728fce44 100644 --- a/rust/operator-binary/src/controller/validate.rs +++ b/rust/operator-binary/src/controller/validate.rs @@ -6,12 +6,11 @@ use std::{ use product_config::{ProductConfigManager, types::PropertyNameKind}; use snafu::{OptionExt, ResultExt, Snafu}; use stackable_operator::{ - commons::product_image_selection::ResolvedProductImage, + commons::product_image_selection::{self, ResolvedProductImage}, product_config_utils::{transform_all_roles_to_config, validate_all_roles_and_groups_config}, role_utils::GenericRoleConfig, }; -use super::dereference::DereferencedObjects; use crate::crd::{ HIVE_SITE_XML, HiveRole, JVM_SECURITY_PROPERTIES_FILE, MetaStoreConfig, v1alpha1::{self, HiveMetastoreRoleConfig}, @@ -19,6 +18,11 @@ use crate::crd::{ #[derive(Snafu, Debug)] pub enum Error { + #[snafu(display("failed to resolve product image"))] + ResolveProductImage { + source: product_image_selection::Error, + }, + #[snafu(display("object defines no metastore role"))] NoMetaStoreRole, @@ -61,13 +65,21 @@ pub struct ValidatedHiveCluster { pub fn validate_cluster( hive: &v1alpha1::HiveCluster, - dereferenced: &DereferencedObjects, + image_base_name: &str, + image_repository: &str, + pkg_version: &str, product_config_manager: &ProductConfigManager, ) -> Result { + let resolved_product_image = hive + .spec + .image + .resolve(image_base_name, image_repository, pkg_version) + .context(ResolveProductImageSnafu)?; + let role = hive.spec.metastore.as_ref().context(NoMetaStoreRoleSnafu)?; let validated_config = validate_all_roles_and_groups_config( - &dereferenced.resolved_product_image.product_version, + &resolved_product_image.product_version, &transform_all_roles_to_config( hive, &[( @@ -137,7 +149,7 @@ pub fn validate_cluster( role_groups.insert(hive_role, group_configs); Ok(ValidatedHiveCluster { - image: dereferenced.resolved_product_image.clone(), + image: resolved_product_image, role_groups, role_configs, }) From 213bac34d81a8e87436b5b563ca9bdba07229e83 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Wed, 13 May 2026 17:23:16 +0200 Subject: [PATCH 05/13] changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e08b815..982dc84b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,11 +17,13 @@ All notable changes to this project will be documented in this file. This means you need to replace your simple database connection string with a typed struct. This struct is consistent between different CRDs, so that you can easily copy/paste it between stacklets. Read on the [Hive database documentation](https://docs.stackable.tech/home/nightly/hive/usage-guide/database-driver) for details ([#674]). +- Internal operator refactoring: introduce dereference() and validate() steps in the reconciler ([#707]). [#674]: https://github.com/stackabletech/hive-operator/pull/674 [#693]: https://github.com/stackabletech/hive-operator/pull/693 [#695]: https://github.com/stackabletech/hive-operator/pull/695 [#702]: https://github.com/stackabletech/hive-operator/pull/702 +[#707]: https://github.com/stackabletech/hive-operator/pull/707 ## [26.3.0] - 2026-03-16 From 55ed469bbec60ef5365b1c8f45fec24d635471b0 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy <1712947+adwk67@users.noreply.github.com> Date: Fri, 15 May 2026 13:24:50 +0200 Subject: [PATCH 06/13] Update rust/operator-binary/src/controller.rs Co-authored-by: Siegfried Weber --- rust/operator-binary/src/controller.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rust/operator-binary/src/controller.rs b/rust/operator-binary/src/controller.rs index 678812c1..df2b7c48 100644 --- a/rust/operator-binary/src/controller.rs +++ b/rust/operator-binary/src/controller.rs @@ -1,7 +1,7 @@ //! Ensures that `Pod`s are configured and running for each [`v1alpha1::HiveCluster`] -pub mod dereference; -pub mod validate; +mod dereference; +mod validate; use std::{ collections::{BTreeMap, HashMap}, From 6db46c1d60b15f7c6c3d8da557f3d5bbebf7ac08 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Fri, 15 May 2026 14:59:08 +0200 Subject: [PATCH 07/13] assume single role in ValidatedHiveCluster --- rust/operator-binary/src/controller.rs | 156 +++++++++--------- .../src/controller/validate.rs | 30 ++-- 2 files changed, 88 insertions(+), 98 deletions(-) diff --git a/rust/operator-binary/src/controller.rs b/rust/operator-binary/src/controller.rs index df2b7c48..52728363 100644 --- a/rust/operator-binary/src/controller.rs +++ b/rust/operator-binary/src/controller.rs @@ -382,112 +382,108 @@ pub async fn reconcile_hive( let mut ss_cond_builder = StatefulSetConditionBuilder::default(); - for (hive_role, role_group_configs) in &validated.role_groups { - if let Some(role_config) = validated.role_configs.get(hive_role) { - add_pdbs( - &role_config.pdb, - hive, - hive_role, - client, - &mut cluster_resources, - ) - .await - .context(FailedToCreatePdbSnafu)?; - } + for (rolegroup_name, validated_rg_config) in &validated.role_groups { + let rolegroup = hive.metastore_rolegroup_ref(rolegroup_name); - for (rolegroup_name, validated_rg_config) in role_group_configs { - let rolegroup = hive.metastore_rolegroup_ref(rolegroup_name); - - let rg_metrics_service = - build_rolegroup_metrics_service(hive, &validated.image, &rolegroup) - .context(ServiceConfigurationSnafu)?; - - let rg_headless_service = - build_rolegroup_headless_service(hive, &validated.image, &rolegroup) - .context(ServiceConfigurationSnafu)?; - - let rg_configmap = build_metastore_rolegroup_config_map( - hive, - &hive_namespace, - &validated.image, - &rolegroup, - &validated_rg_config.product_config_properties, - &dereferenced.metadata_database_connection_details, - dereferenced.s3_connection_spec.as_ref(), - &validated_rg_config.merged_config, - &client.kubernetes_cluster_info, - dereferenced.hive_opa_config.as_ref(), - )?; - let rg_statefulset = build_metastore_rolegroup_statefulset( - hive, - hive_role, - &validated.image, - &rolegroup, - &validated_rg_config.product_config_properties, - &dereferenced.metadata_database_connection_details, - dereferenced.s3_connection_spec.as_ref(), - &validated_rg_config.merged_config, - &rbac_sa.name_any(), - dereferenced.hive_opa_config.as_ref(), - )?; + let rg_metrics_service = + build_rolegroup_metrics_service(hive, &validated.image, &rolegroup) + .context(ServiceConfigurationSnafu)?; - cluster_resources - .add(client, rg_metrics_service) - .await - .context(ApplyRoleGroupServiceSnafu { - rolegroup: rolegroup.clone(), - })?; + let rg_headless_service = + build_rolegroup_headless_service(hive, &validated.image, &rolegroup) + .context(ServiceConfigurationSnafu)?; + + let rg_configmap = build_metastore_rolegroup_config_map( + hive, + &hive_namespace, + &validated.image, + &rolegroup, + &validated_rg_config.product_config_properties, + &dereferenced.metadata_database_connection_details, + dereferenced.s3_connection_spec.as_ref(), + &validated_rg_config.merged_config, + &client.kubernetes_cluster_info, + dereferenced.hive_opa_config.as_ref(), + )?; + let rg_statefulset = build_metastore_rolegroup_statefulset( + hive, + &HiveRole::MetaStore, + &validated.image, + &rolegroup, + &validated_rg_config.product_config_properties, + &dereferenced.metadata_database_connection_details, + dereferenced.s3_connection_spec.as_ref(), + &validated_rg_config.merged_config, + &rbac_sa.name_any(), + dereferenced.hive_opa_config.as_ref(), + )?; + + cluster_resources + .add(client, rg_metrics_service) + .await + .context(ApplyRoleGroupServiceSnafu { + rolegroup: rolegroup.clone(), + })?; + + cluster_resources + .add(client, rg_headless_service) + .await + .context(ApplyRoleGroupServiceSnafu { + rolegroup: rolegroup.clone(), + })?; + cluster_resources + .add(client, rg_configmap) + .await + .context(ApplyRoleGroupConfigSnafu { + rolegroup: rolegroup.clone(), + })?; + + // Note: The StatefulSet needs to be applied after all ConfigMaps and Secrets it mounts + // to prevent unnecessary Pod restarts. + // See https://github.com/stackabletech/commons-operator/issues/111 for details. + ss_cond_builder.add( cluster_resources - .add(client, rg_headless_service) + .add(client, rg_statefulset) .await - .context(ApplyRoleGroupServiceSnafu { - rolegroup: rolegroup.clone(), - })?; - - cluster_resources.add(client, rg_configmap).await.context( - ApplyRoleGroupConfigSnafu { + .context(ApplyRoleGroupStatefulSetSnafu { rolegroup: rolegroup.clone(), - }, - )?; - - // Note: The StatefulSet needs to be applied after all ConfigMaps and Secrets it mounts - // to prevent unnecessary Pod restarts. - // See https://github.com/stackabletech/commons-operator/issues/111 for details. - ss_cond_builder.add( - cluster_resources - .add(client, rg_statefulset) - .await - .context(ApplyRoleGroupStatefulSetSnafu { - rolegroup: rolegroup.clone(), - })?, - ); - } + })?, + ); } // std's SipHasher is deprecated, and DefaultHasher is unstable across Rust releases. // We don't /need/ stability, but it's still nice to avoid spurious changes where possible. let mut discovery_hash = FnvHasher::with_key(0); - let hive_role = HiveRole::MetaStore; - if let Some(role_config) = validated.role_configs.get(&hive_role) { + if let Some(role_config) = validated.role_config { + add_pdbs( + &role_config.pdb, + hive, + &HiveRole::MetaStore, + client, + &mut cluster_resources, + ) + .await + .context(FailedToCreatePdbSnafu)?; + let role_listener: Listener = build_role_listener( hive, &validated.image, - &hive_role, + &HiveRole::MetaStore, &role_config.listener_class, ) .context(ListenerConfigurationSnafu)?; let listener = cluster_resources.add(client, role_listener).await.context( ApplyGroupListenerSnafu { - role: hive_role.to_string(), + role: HiveRole::MetaStore.to_string(), }, )?; for discovery_cm in discovery::build_discovery_configmaps( hive, hive, - hive_role, + HiveRole::MetaStore, &validated.image, None, listener, diff --git a/rust/operator-binary/src/controller/validate.rs b/rust/operator-binary/src/controller/validate.rs index 728fce44..9c55dbdf 100644 --- a/rust/operator-binary/src/controller/validate.rs +++ b/rust/operator-binary/src/controller/validate.rs @@ -59,8 +59,8 @@ pub struct ValidatedRoleGroupConfig { #[derive(Clone, Debug)] pub struct ValidatedHiveCluster { pub image: ResolvedProductImage, - pub role_groups: BTreeMap>, - pub role_configs: BTreeMap, + pub role_groups: BTreeMap, + pub role_config: Option, } pub fn validate_cluster( @@ -103,9 +103,6 @@ pub fn validate_cluster( ) .context(InvalidProductConfigSnafu)?; - let mut role_groups = BTreeMap::new(); - let mut role_configs = BTreeMap::new(); - let metastore_config = validated_config .get(&HiveRole::MetaStore.to_string()) .map(Cow::Borrowed) @@ -113,21 +110,20 @@ pub fn validate_cluster( let hive_role = HiveRole::MetaStore; - if let Some(HiveMetastoreRoleConfig { + let role_config = if let Some(HiveMetastoreRoleConfig { common: GenericRoleConfig { pod_disruption_budget: pdb, }, listener_class, }) = hive.role_config(&hive_role) { - role_configs.insert( - hive_role.clone(), - ValidatedRoleConfig { - pdb: pdb.clone(), - listener_class: listener_class.clone(), - }, - ); - } + Some(ValidatedRoleConfig { + pdb: pdb.clone(), + listener_class: listener_class.clone(), + }) + } else { + None + }; let mut group_configs = BTreeMap::new(); for (rolegroup_name, rolegroup_config) in metastore_config.iter() { @@ -146,11 +142,9 @@ pub fn validate_cluster( ); } - role_groups.insert(hive_role, group_configs); - Ok(ValidatedHiveCluster { image: resolved_product_image, - role_groups, - role_configs, + role_groups: group_configs, + role_config, }) } From bd9b277c891a1d6c987a6b509ccc5f7871111791 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Fri, 15 May 2026 15:36:43 +0200 Subject: [PATCH 08/13] move db connection details from dereferenced to the controller and remove derives where they do not exist on the upstream type --- rust/operator-binary/src/controller.rs | 4 ++-- .../src/controller/dereference.rs | 18 +----------------- .../operator-binary/src/controller/validate.rs | 16 +++++++++++++++- 3 files changed, 18 insertions(+), 20 deletions(-) diff --git a/rust/operator-binary/src/controller.rs b/rust/operator-binary/src/controller.rs index 52728363..e4d8128c 100644 --- a/rust/operator-binary/src/controller.rs +++ b/rust/operator-binary/src/controller.rs @@ -399,7 +399,7 @@ pub async fn reconcile_hive( &validated.image, &rolegroup, &validated_rg_config.product_config_properties, - &dereferenced.metadata_database_connection_details, + &validated.metadata_database_connection_details, dereferenced.s3_connection_spec.as_ref(), &validated_rg_config.merged_config, &client.kubernetes_cluster_info, @@ -411,7 +411,7 @@ pub async fn reconcile_hive( &validated.image, &rolegroup, &validated_rg_config.product_config_properties, - &dereferenced.metadata_database_connection_details, + &validated.metadata_database_connection_details, dereferenced.s3_connection_spec.as_ref(), &validated_rg_config.merged_config, &rbac_sa.name_any(), diff --git a/rust/operator-binary/src/controller/dereference.rs b/rust/operator-binary/src/controller/dereference.rs index 77e331aa..da0738b9 100644 --- a/rust/operator-binary/src/controller/dereference.rs +++ b/rust/operator-binary/src/controller/dereference.rs @@ -1,7 +1,5 @@ use snafu::{ResultExt, Snafu}; -use stackable_operator::{ - crd::s3, database_connections::drivers::jdbc::JdbcDatabaseConnectionDetails, kube::ResourceExt, -}; +use stackable_operator::{crd::s3, kube::ResourceExt}; use crate::{config::opa::HiveOpaConfig, crd::v1alpha1}; @@ -15,11 +13,6 @@ pub enum Error { source: s3::v1alpha1::ConnectionError, }, - #[snafu(display("invalid metadata database connection"))] - InvalidMetadataDatabaseConnection { - source: stackable_operator::database_connections::Error, - }, - #[snafu(display("invalid OPA configuration"))] InvalidOpaConfig { source: stackable_operator::commons::opa::Error, @@ -29,7 +22,6 @@ pub enum Error { /// External references resolved during the dereference step. pub struct DereferencedObjects { pub s3_connection_spec: Option, - pub metadata_database_connection_details: JdbcDatabaseConnectionDetails, pub hive_opa_config: Option, } @@ -52,13 +44,6 @@ pub async fn dereference( None }; - let metadata_database_connection_details = hive - .spec - .cluster_config - .metadata_database - .jdbc_connection_details("METADATA") - .context(InvalidMetadataDatabaseConnectionSnafu)?; - let hive_opa_config = match hive.get_opa_config() { Some(opa_config) => Some( HiveOpaConfig::from_opa_config(client, hive, opa_config) @@ -70,7 +55,6 @@ pub async fn dereference( Ok(DereferencedObjects { s3_connection_spec, - metadata_database_connection_details, hive_opa_config, }) } diff --git a/rust/operator-binary/src/controller/validate.rs b/rust/operator-binary/src/controller/validate.rs index 9c55dbdf..73bc9d90 100644 --- a/rust/operator-binary/src/controller/validate.rs +++ b/rust/operator-binary/src/controller/validate.rs @@ -7,6 +7,7 @@ use product_config::{ProductConfigManager, types::PropertyNameKind}; use snafu::{OptionExt, ResultExt, Snafu}; use stackable_operator::{ commons::product_image_selection::{self, ResolvedProductImage}, + database_connections::drivers::jdbc::JdbcDatabaseConnectionDetails, product_config_utils::{transform_all_roles_to_config, validate_all_roles_and_groups_config}, role_utils::GenericRoleConfig, }; @@ -38,6 +39,11 @@ pub enum Error { #[snafu(display("failed to resolve and merge config for role and role group"))] FailedToResolveConfig { source: crate::crd::Error }, + + #[snafu(display("invalid metadata database connection"))] + InvalidMetadataDatabaseConnection { + source: stackable_operator::database_connections::Error, + }, } /// Per-role configuration extracted during validation. @@ -56,11 +62,11 @@ pub struct ValidatedRoleGroupConfig { /// The validated cluster: proves that product-config validation and config merging /// succeeded for every role and role group before any resources are created. -#[derive(Clone, Debug)] pub struct ValidatedHiveCluster { pub image: ResolvedProductImage, pub role_groups: BTreeMap, pub role_config: Option, + pub metadata_database_connection_details: JdbcDatabaseConnectionDetails, } pub fn validate_cluster( @@ -142,9 +148,17 @@ pub fn validate_cluster( ); } + let metadata_database_connection_details = hive + .spec + .cluster_config + .metadata_database + .jdbc_connection_details("METADATA") + .context(InvalidMetadataDatabaseConnectionSnafu)?; + Ok(ValidatedHiveCluster { image: resolved_product_image, role_groups: group_configs, role_config, + metadata_database_connection_details, }) } From 1758748c9a0d030200496455462c02584ba5352d Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Fri, 15 May 2026 15:52:39 +0200 Subject: [PATCH 09/13] move validated cluster to the controller which will be the central dependency for subsequent steps --- rust/operator-binary/src/controller.rs | 12 +++++++++++ .../src/controller/validate.rs | 21 +++++++------------ 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/rust/operator-binary/src/controller.rs b/rust/operator-binary/src/controller.rs index e4d8128c..fae76ad2 100644 --- a/rust/operator-binary/src/controller.rs +++ b/rust/operator-binary/src/controller.rs @@ -92,6 +92,7 @@ use crate::{ jvm::{construct_hadoop_heapsize_env, construct_non_heap_jvm_args}, opa::{HiveOpaConfig, OPA_TLS_VOLUME_NAME}, }, + controller::validate::{ValidatedRoleConfig, ValidatedRoleGroupConfig}, crd::{ APP_NAME, CORE_SITE_XML, Container, HIVE_PORT, HIVE_PORT_NAME, HIVE_SITE_XML, HiveClusterStatus, HiveRole, JVM_SECURITY_PROPERTIES_FILE, METRICS_PORT, METRICS_PORT_NAME, @@ -325,6 +326,17 @@ impl ReconcilerError for Error { } } +/// The validated cluster: proves that product-config validation and config merging +/// succeeded for every role and role group before any resources are created. +/// Placed in the controller so that subsequent steps that reference this struct +/// only depend on the controller. +pub struct ValidatedHiveCluster { + pub image: ResolvedProductImage, + pub role_groups: BTreeMap, + pub role_config: Option, + pub metadata_database_connection_details: JdbcDatabaseConnectionDetails, +} + pub async fn reconcile_hive( hive: Arc>, ctx: Arc, diff --git a/rust/operator-binary/src/controller/validate.rs b/rust/operator-binary/src/controller/validate.rs index 73bc9d90..cd1de60e 100644 --- a/rust/operator-binary/src/controller/validate.rs +++ b/rust/operator-binary/src/controller/validate.rs @@ -6,15 +6,17 @@ use std::{ use product_config::{ProductConfigManager, types::PropertyNameKind}; use snafu::{OptionExt, ResultExt, Snafu}; use stackable_operator::{ - commons::product_image_selection::{self, ResolvedProductImage}, - database_connections::drivers::jdbc::JdbcDatabaseConnectionDetails, + commons::product_image_selection, product_config_utils::{transform_all_roles_to_config, validate_all_roles_and_groups_config}, role_utils::GenericRoleConfig, }; -use crate::crd::{ - HIVE_SITE_XML, HiveRole, JVM_SECURITY_PROPERTIES_FILE, MetaStoreConfig, - v1alpha1::{self, HiveMetastoreRoleConfig}, +use crate::{ + controller::ValidatedHiveCluster, + crd::{ + HIVE_SITE_XML, HiveRole, JVM_SECURITY_PROPERTIES_FILE, MetaStoreConfig, + v1alpha1::{self, HiveMetastoreRoleConfig}, + }, }; #[derive(Snafu, Debug)] @@ -60,15 +62,6 @@ pub struct ValidatedRoleGroupConfig { pub product_config_properties: HashMap>, } -/// The validated cluster: proves that product-config validation and config merging -/// succeeded for every role and role group before any resources are created. -pub struct ValidatedHiveCluster { - pub image: ResolvedProductImage, - pub role_groups: BTreeMap, - pub role_config: Option, - pub metadata_database_connection_details: JdbcDatabaseConnectionDetails, -} - pub fn validate_cluster( hive: &v1alpha1::HiveCluster, image_base_name: &str, From 9cdd1e12fcd7d4de87afb65861af6ed5bc44cec5 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Fri, 15 May 2026 16:13:38 +0200 Subject: [PATCH 10/13] replace parameter with constant --- rust/operator-binary/src/controller.rs | 3 +-- rust/operator-binary/src/controller/validate.rs | 5 ++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/rust/operator-binary/src/controller.rs b/rust/operator-binary/src/controller.rs index fae76ad2..fec67235 100644 --- a/rust/operator-binary/src/controller.rs +++ b/rust/operator-binary/src/controller.rs @@ -117,7 +117,7 @@ use crate::{ pub const HIVE_CONTROLLER_NAME: &str = "hivecluster"; pub const HIVE_FULL_CONTROLLER_NAME: &str = concatcp!(HIVE_CONTROLLER_NAME, '.', OPERATOR_NAME); -const CONTAINER_IMAGE_BASE_NAME: &str = "hive"; +pub const CONTAINER_IMAGE_BASE_NAME: &str = "hive"; pub const MAX_HIVE_LOG_FILES_SIZE: MemoryQuantity = MemoryQuantity { value: 10.0, @@ -356,7 +356,6 @@ pub async fn reconcile_hive( let validated = validate::validate_cluster( hive, - CONTAINER_IMAGE_BASE_NAME, &ctx.operator_environment.image_repository, crate::built_info::PKG_VERSION, &ctx.product_config, diff --git a/rust/operator-binary/src/controller/validate.rs b/rust/operator-binary/src/controller/validate.rs index cd1de60e..4e927b58 100644 --- a/rust/operator-binary/src/controller/validate.rs +++ b/rust/operator-binary/src/controller/validate.rs @@ -12,7 +12,7 @@ use stackable_operator::{ }; use crate::{ - controller::ValidatedHiveCluster, + controller::{CONTAINER_IMAGE_BASE_NAME, ValidatedHiveCluster}, crd::{ HIVE_SITE_XML, HiveRole, JVM_SECURITY_PROPERTIES_FILE, MetaStoreConfig, v1alpha1::{self, HiveMetastoreRoleConfig}, @@ -64,7 +64,6 @@ pub struct ValidatedRoleGroupConfig { pub fn validate_cluster( hive: &v1alpha1::HiveCluster, - image_base_name: &str, image_repository: &str, pkg_version: &str, product_config_manager: &ProductConfigManager, @@ -72,7 +71,7 @@ pub fn validate_cluster( let resolved_product_image = hive .spec .image - .resolve(image_base_name, image_repository, pkg_version) + .resolve(CONTAINER_IMAGE_BASE_NAME, image_repository, pkg_version) .context(ResolveProductImageSnafu)?; let role = hive.spec.metastore.as_ref().context(NoMetaStoreRoleSnafu)?; From 268ec15fc549eb8231b8b06b3687dd96b3814861 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Mon, 18 May 2026 12:48:15 +0200 Subject: [PATCH 11/13] replace parameter with compile-time value; rename function --- rust/operator-binary/src/controller.rs | 3 +-- rust/operator-binary/src/controller/validate.rs | 13 ++++++++----- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/rust/operator-binary/src/controller.rs b/rust/operator-binary/src/controller.rs index fec67235..b40aeb16 100644 --- a/rust/operator-binary/src/controller.rs +++ b/rust/operator-binary/src/controller.rs @@ -330,7 +330,7 @@ impl ReconcilerError for Error { /// succeeded for every role and role group before any resources are created. /// Placed in the controller so that subsequent steps that reference this struct /// only depend on the controller. -pub struct ValidatedHiveCluster { +pub struct ValidatedCluster { pub image: ResolvedProductImage, pub role_groups: BTreeMap, pub role_config: Option, @@ -357,7 +357,6 @@ pub async fn reconcile_hive( let validated = validate::validate_cluster( hive, &ctx.operator_environment.image_repository, - crate::built_info::PKG_VERSION, &ctx.product_config, ) .context(ValidateSnafu)?; diff --git a/rust/operator-binary/src/controller/validate.rs b/rust/operator-binary/src/controller/validate.rs index 4e927b58..2ca91789 100644 --- a/rust/operator-binary/src/controller/validate.rs +++ b/rust/operator-binary/src/controller/validate.rs @@ -12,7 +12,7 @@ use stackable_operator::{ }; use crate::{ - controller::{CONTAINER_IMAGE_BASE_NAME, ValidatedHiveCluster}, + controller::{CONTAINER_IMAGE_BASE_NAME, ValidatedCluster}, crd::{ HIVE_SITE_XML, HiveRole, JVM_SECURITY_PROPERTIES_FILE, MetaStoreConfig, v1alpha1::{self, HiveMetastoreRoleConfig}, @@ -65,13 +65,16 @@ pub struct ValidatedRoleGroupConfig { pub fn validate_cluster( hive: &v1alpha1::HiveCluster, image_repository: &str, - pkg_version: &str, product_config_manager: &ProductConfigManager, -) -> Result { +) -> Result { let resolved_product_image = hive .spec .image - .resolve(CONTAINER_IMAGE_BASE_NAME, image_repository, pkg_version) + .resolve( + CONTAINER_IMAGE_BASE_NAME, + image_repository, + crate::built_info::PKG_VERSION, + ) .context(ResolveProductImageSnafu)?; let role = hive.spec.metastore.as_ref().context(NoMetaStoreRoleSnafu)?; @@ -147,7 +150,7 @@ pub fn validate_cluster( .jdbc_connection_details("METADATA") .context(InvalidMetadataDatabaseConnectionSnafu)?; - Ok(ValidatedHiveCluster { + Ok(ValidatedCluster { image: resolved_product_image, role_groups: group_configs, role_config, From 1788490162ffb0d2496e0269e6ebe06793b852fd Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Mon, 18 May 2026 13:52:57 +0200 Subject: [PATCH 12/13] pass dereferenced fields through to validation --- rust/operator-binary/src/controller.rs | 12 ++++++++---- rust/operator-binary/src/controller/validate.rs | 6 ++++++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/rust/operator-binary/src/controller.rs b/rust/operator-binary/src/controller.rs index b40aeb16..e9945db4 100644 --- a/rust/operator-binary/src/controller.rs +++ b/rust/operator-binary/src/controller.rs @@ -335,6 +335,8 @@ pub struct ValidatedCluster { pub role_groups: BTreeMap, pub role_config: Option, pub metadata_database_connection_details: JdbcDatabaseConnectionDetails, + pub s3_connection_spec: Option, + pub hive_opa_config: Option, } pub async fn reconcile_hive( @@ -358,6 +360,8 @@ pub async fn reconcile_hive( hive, &ctx.operator_environment.image_repository, &ctx.product_config, + dereferenced.s3_connection_spec, + dereferenced.hive_opa_config, ) .context(ValidateSnafu)?; @@ -410,10 +414,10 @@ pub async fn reconcile_hive( &rolegroup, &validated_rg_config.product_config_properties, &validated.metadata_database_connection_details, - dereferenced.s3_connection_spec.as_ref(), + validated.s3_connection_spec.as_ref(), &validated_rg_config.merged_config, &client.kubernetes_cluster_info, - dereferenced.hive_opa_config.as_ref(), + validated.hive_opa_config.as_ref(), )?; let rg_statefulset = build_metastore_rolegroup_statefulset( hive, @@ -422,10 +426,10 @@ pub async fn reconcile_hive( &rolegroup, &validated_rg_config.product_config_properties, &validated.metadata_database_connection_details, - dereferenced.s3_connection_spec.as_ref(), + validated.s3_connection_spec.as_ref(), &validated_rg_config.merged_config, &rbac_sa.name_any(), - dereferenced.hive_opa_config.as_ref(), + validated.hive_opa_config.as_ref(), )?; cluster_resources diff --git a/rust/operator-binary/src/controller/validate.rs b/rust/operator-binary/src/controller/validate.rs index 2ca91789..00ac19e3 100644 --- a/rust/operator-binary/src/controller/validate.rs +++ b/rust/operator-binary/src/controller/validate.rs @@ -7,11 +7,13 @@ use product_config::{ProductConfigManager, types::PropertyNameKind}; use snafu::{OptionExt, ResultExt, Snafu}; use stackable_operator::{ commons::product_image_selection, + crd::s3, product_config_utils::{transform_all_roles_to_config, validate_all_roles_and_groups_config}, role_utils::GenericRoleConfig, }; use crate::{ + config::opa::HiveOpaConfig, controller::{CONTAINER_IMAGE_BASE_NAME, ValidatedCluster}, crd::{ HIVE_SITE_XML, HiveRole, JVM_SECURITY_PROPERTIES_FILE, MetaStoreConfig, @@ -66,6 +68,8 @@ pub fn validate_cluster( hive: &v1alpha1::HiveCluster, image_repository: &str, product_config_manager: &ProductConfigManager, + s3_connection_spec: Option, + hive_opa_config: Option, ) -> Result { let resolved_product_image = hive .spec @@ -155,5 +159,7 @@ pub fn validate_cluster( role_groups: group_configs, role_config, metadata_database_connection_details, + s3_connection_spec, + hive_opa_config, }) } From fe53e9cdb4e9d79e5849b6e39a854d2649c4d01d Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Mon, 18 May 2026 13:58:33 +0200 Subject: [PATCH 13/13] extend assert step to increase test coverage by checking most generated resources --- tests/templates/kuttl/smoke/60-assert.yaml.j2 | 501 +++++++++++++++++- 1 file changed, 491 insertions(+), 10 deletions(-) diff --git a/tests/templates/kuttl/smoke/60-assert.yaml.j2 b/tests/templates/kuttl/smoke/60-assert.yaml.j2 index 37020496..602e0404 100644 --- a/tests/templates/kuttl/smoke/60-assert.yaml.j2 +++ b/tests/templates/kuttl/smoke/60-assert.yaml.j2 @@ -3,36 +3,517 @@ apiVersion: kuttl.dev/v1beta1 kind: TestAssert timeout: 900 --- +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/component: metastore + app.kubernetes.io/instance: hive-metastore + app.kubernetes.io/managed-by: listeners.stackable.tech_listener + app.kubernetes.io/name: listener + app.kubernetes.io/role-group: none + stackable.tech/vendor: Stackable + name: hive-metastore + ownerReferences: + - apiVersion: listeners.stackable.tech/v1alpha1 + blockOwnerDeletion: true + controller: true + kind: Listener + name: hive-metastore +spec: + ports: + - name: hive + port: 9083 + protocol: TCP + targetPort: 9083 + type: ClusterIP +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/component: metastore + app.kubernetes.io/instance: hive + app.kubernetes.io/managed-by: hive.stackable.tech_hivecluster + app.kubernetes.io/name: hive + app.kubernetes.io/role-group: default + stackable.tech/vendor: Stackable + name: hive-metastore-default-headless + ownerReferences: + - apiVersion: hive.stackable.tech/v1alpha1 + controller: true + kind: HiveCluster + name: hive +spec: + ports: + - name: hive + port: 9083 + protocol: TCP + targetPort: 9083 + selector: + app.kubernetes.io/component: metastore + app.kubernetes.io/instance: hive + app.kubernetes.io/name: hive + app.kubernetes.io/role-group: default + type: ClusterIP +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + prometheus.io/path: /metrics + prometheus.io/port: "9084" + prometheus.io/scheme: http + prometheus.io/scrape: "true" + labels: + app.kubernetes.io/component: metastore + app.kubernetes.io/instance: hive + app.kubernetes.io/managed-by: hive.stackable.tech_hivecluster + app.kubernetes.io/name: hive + app.kubernetes.io/role-group: default + prometheus.io/scrape: "true" + stackable.tech/vendor: Stackable + name: hive-metastore-default-metrics + ownerReferences: + - apiVersion: hive.stackable.tech/v1alpha1 + controller: true + kind: HiveCluster + name: hive +spec: + ports: + - name: metrics + port: 9084 + protocol: TCP + targetPort: 9084 + publishNotReadyAddresses: true + selector: + app.kubernetes.io/component: metastore + app.kubernetes.io/instance: hive + app.kubernetes.io/name: hive + app.kubernetes.io/role-group: default + type: ClusterIP +--- apiVersion: apps/v1 kind: StatefulSet metadata: + generation: 1 + labels: + app.kubernetes.io/component: metastore + app.kubernetes.io/instance: hive + app.kubernetes.io/managed-by: hive.stackable.tech_hivecluster + app.kubernetes.io/name: hive + app.kubernetes.io/role-group: default + restarter.stackable.tech/enabled: "true" + stackable.tech/vendor: Stackable name: hive-metastore-default - generation: 1 # There should be no unneeded Pod restarts + ownerReferences: + - apiVersion: hive.stackable.tech/v1alpha1 + controller: true + kind: HiveCluster + name: hive spec: + podManagementPolicy: Parallel + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/component: metastore + app.kubernetes.io/instance: hive + app.kubernetes.io/name: hive + app.kubernetes.io/role-group: default + serviceName: hive-metastore-default-headless template: + metadata: + labels: + app.kubernetes.io/component: metastore + app.kubernetes.io/instance: hive + app.kubernetes.io/managed-by: hive.stackable.tech_hivecluster + app.kubernetes.io/name: hive + app.kubernetes.io/role-group: default + stackable.tech/vendor: Stackable spec: + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchLabels: + app.kubernetes.io/component: metastore + app.kubernetes.io/instance: hive + app.kubernetes.io/name: hive + topologyKey: kubernetes.io/hostname + weight: 70 containers: - - name: hive - resources: - limits: - cpu: "1" - memory: 768Mi - requests: - cpu: 250m - memory: 768Mi {% if lookup('env', 'VECTOR_AGGREGATOR') %} - - name: vector + - name: vector +{% endif %} + - args: + - | + echo copying /stackable/mount/config to /stackable/config + cp -RL /stackable/mount/config/* /stackable/config + echo copying /stackable/mount/log-config/metastore-log4j2.properties to /stackable/config/metastore-log4j2.properties + cp -RL /stackable/mount/log-config/metastore-log4j2.properties /stackable/config/metastore-log4j2.properties + if test -f /stackable/config/core-site.xml; then config-utils template /stackable/config/core-site.xml; fi + if test -f /stackable/config/hive-site.xml; then config-utils template /stackable/config/hive-site.xml; fi + cert-tools generate-pkcs12-truststore --pem /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem --out /stackable/truststore.p12 --out-password changeit +{% if test_scenario['values']['s3-use-tls'] == 'true' %} + cert-tools generate-pkcs12-truststore --pkcs12 /stackable/truststore.p12:changeit --pem /stackable/secrets/minio-tls-certificates/ca.crt --out /stackable/truststore.p12 --out-password changeit +{% endif %} +{% if test_scenario['values']['opa-use-tls'] == 'true' %} + cert-tools generate-pkcs12-truststore --pkcs12 /stackable/truststore.p12:changeit --pem /stackable/secrets/opa-tls/ca.crt --out /stackable/truststore.p12 --out-password changeit {% endif %} + + + + prepare_signal_handlers() + { + unset term_child_pid + unset term_kill_needed + trap 'handle_term_signal' TERM + } + + handle_term_signal() + { + if [ "${term_child_pid}" ]; then + kill -TERM "${term_child_pid}" 2>/dev/null + else + term_kill_needed="yes" + fi + } + + wait_for_termination() + { + set +e + term_child_pid=$1 + if [[ -v term_kill_needed ]]; then + kill -TERM "${term_child_pid}" 2>/dev/null + fi + wait ${term_child_pid} 2>/dev/null + trap - TERM + wait ${term_child_pid} 2>/dev/null + set -e + } + + rm -f /stackable/log/_vector/shutdown + prepare_signal_handlers + containerdebug --output=/stackable/log/containerdebug-state.json --loop & +{% if test_scenario['values']['hive'].split(',')[0].startswith('3.') %} + bin/start-metastore --config /stackable/config --db-type postgres --hive-bin-dir bin & +{% else %} + bin/base --config "/stackable/config" --service schemaTool -dbType "postgres" -initOrUpgradeSchema + bin/base --config "/stackable/config" --service metastore & + +{% endif %} + wait_for_termination $! + mkdir -p /stackable/log/_vector && touch /stackable/log/_vector/shutdown + command: + - /bin/bash + - -x + - -euo + - pipefail + - -c + env: + - name: HADOOP_HEAPSIZE + value: "614" + - name: HADOOP_OPTS + value: -Djava.security.properties=/stackable/config/security.properties + -javaagent:/stackable/jmx/jmx_prometheus_javaagent.jar=9084:/stackable/jmx/jmx_hive_config.yaml + -Djavax.net.ssl.trustStore=/stackable/truststore.p12 -Djavax.net.ssl.trustStorePassword=changeit + -Djavax.net.ssl.trustStoreType=pkcs12 + - name: CONTAINERDEBUG_LOG_DIRECTORY + value: /stackable/log/containerdebug + - name: METADATA_DATABASE_USERNAME + valueFrom: + secretKeyRef: + key: username + name: hive-credentials + - name: METADATA_DATABASE_PASSWORD + valueFrom: + secretKeyRef: + key: password + name: hive-credentials + - name: COMMON_VAR + value: group-value + - name: GROUP_VAR + value: group-value + - name: ROLE_VAR + value: role-value + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 3 + initialDelaySeconds: 30 + periodSeconds: 10 + successThreshold: 1 + tcpSocket: + port: hive + timeoutSeconds: 1 + name: hive + ports: + - containerPort: 9083 + name: hive + protocol: TCP + - containerPort: 9084 + name: metrics + protocol: TCP + readinessProbe: + failureThreshold: 5 + initialDelaySeconds: 10 + periodSeconds: 10 + successThreshold: 1 + tcpSocket: + port: hive + timeoutSeconds: 1 + resources: + limits: + cpu: "1" + memory: 768Mi + requests: + cpu: 250m + memory: 768Mi + volumeMounts: + - mountPath: /stackable/secrets/test-hive-s3-secret-class + name: test-hive-s3-secret-class-s3-credentials +{% if test_scenario['values']['s3-use-tls'] == 'true' %} + - mountPath: /stackable/secrets/minio-tls-certificates + name: minio-tls-certificates-ca-cert +{% endif %} +{% if test_scenario['values']['opa-use-tls'] == 'true' %} + - mountPath: /stackable/secrets/opa-tls + name: opa-tls +{% endif %} + - mountPath: /stackable/config + name: config + - mountPath: /stackable/mount/config + name: config-mount + - mountPath: /stackable/log + name: log + - mountPath: /stackable/mount/log-config + name: log-config-mount + - mountPath: /stackable/listener + name: listener + dnsPolicy: ClusterFirst + enableServiceLinks: false + restartPolicy: Always + schedulerName: default-scheduler + securityContext: + fsGroup: 1000 + serviceAccount: hive-serviceaccount + serviceAccountName: hive-serviceaccount terminationGracePeriodSeconds: 300 + volumes: + - ephemeral: + volumeClaimTemplate: + metadata: + annotations: + secrets.stackable.tech/class: test-hive-s3-secret-class + secrets.stackable.tech/provision-parts: public-private + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: "1" + storageClassName: secrets.stackable.tech + volumeMode: Filesystem + name: test-hive-s3-secret-class-s3-credentials +{% if test_scenario['values']['s3-use-tls'] == 'true' %} + - ephemeral: + volumeClaimTemplate: + metadata: + annotations: + secrets.stackable.tech/class: minio-tls-certificates + secrets.stackable.tech/provision-parts: public + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: "1" + storageClassName: secrets.stackable.tech + volumeMode: Filesystem + name: minio-tls-certificates-ca-cert +{% endif %} +{% if test_scenario['values']['opa-use-tls'] == 'true' %} + - ephemeral: + volumeClaimTemplate: + metadata: + annotations: + secrets.stackable.tech/provision-parts: public + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: "1" + storageClassName: secrets.stackable.tech + volumeMode: Filesystem + name: opa-tls +{% endif %} + - emptyDir: + sizeLimit: 10Mi + name: config + - configMap: + defaultMode: 420 + name: hive-metastore-default + name: config-mount + - emptyDir: + sizeLimit: 30Mi + name: log + - configMap: + defaultMode: 420 + name: hive-metastore-default + name: log-config-mount + volumeClaimTemplates: + - apiVersion: v1 + kind: PersistentVolumeClaim + metadata: + annotations: + listeners.stackable.tech/listener-name: hive-metastore + creationTimestamp: null + labels: + app.kubernetes.io/component: metastore + app.kubernetes.io/instance: hive + app.kubernetes.io/managed-by: hive.stackable.tech_hivecluster + app.kubernetes.io/name: hive + app.kubernetes.io/role-group: default + app.kubernetes.io/version: none + stackable.tech/vendor: Stackable + name: listener + spec: + accessModes: + - ReadWriteMany + resources: + requests: + storage: "1" + storageClassName: listeners.stackable.tech + volumeMode: Filesystem status: readyReplicas: 1 replicas: 1 --- +apiVersion: v1 +#data: +# HIVE: thrift://hive-metastore.$NAMESPACE.svc.cluster.local:9083 +kind: ConfigMap +metadata: + labels: + app.kubernetes.io/component: metastore + app.kubernetes.io/instance: hive + app.kubernetes.io/managed-by: hive.stackable.tech_hivecluster + app.kubernetes.io/name: hive + app.kubernetes.io/role-group: discovery + stackable.tech/vendor: Stackable + name: hive + ownerReferences: + - apiVersion: hive.stackable.tech/v1alpha1 + controller: true + kind: HiveCluster + name: hive +--- +apiVersion: v1 +kind: ConfigMap +metadata: + labels: + app.kubernetes.io/component: metastore + app.kubernetes.io/instance: hive + app.kubernetes.io/managed-by: hive.stackable.tech_hivecluster + app.kubernetes.io/name: hive + app.kubernetes.io/role-group: default + stackable.tech/vendor: Stackable + name: hive-metastore-default + ownerReferences: + - apiVersion: hive.stackable.tech/v1alpha1 + controller: true + kind: HiveCluster + name: hive +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + app.kubernetes.io/instance: hive + app.kubernetes.io/managed-by: hive.stackable.tech_hivecluster + app.kubernetes.io/name: hive + name: hive-serviceaccount + ownerReferences: + - apiVersion: hive.stackable.tech/v1alpha1 + controller: true + kind: HiveCluster + name: hive +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + labels: + app.kubernetes.io/instance: hive + app.kubernetes.io/managed-by: hive.stackable.tech_hivecluster + app.kubernetes.io/name: hive + name: hive-rolebinding + ownerReferences: + - apiVersion: hive.stackable.tech/v1alpha1 + controller: true + kind: HiveCluster + name: hive +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: hive-clusterrole +subjects: +- kind: ServiceAccount + name: hive-serviceaccount +--- apiVersion: policy/v1 kind: PodDisruptionBudget metadata: + generation: 1 + labels: + app.kubernetes.io/component: metastore + app.kubernetes.io/instance: hive + app.kubernetes.io/managed-by: hive.stackable.tech_hivecluster + app.kubernetes.io/name: hive name: hive-metastore + ownerReferences: + - apiVersion: hive.stackable.tech/v1alpha1 + controller: true + kind: HiveCluster + name: hive +spec: + maxUnavailable: 1 + selector: + matchLabels: + app.kubernetes.io/component: metastore + app.kubernetes.io/instance: hive + app.kubernetes.io/name: hive status: expectedPods: 1 currentHealthy: 1 disruptionsAllowed: 1 +--- +apiVersion: listeners.stackable.tech/v1alpha1 +kind: Listener +metadata: + generation: 1 + labels: + app.kubernetes.io/component: metastore + app.kubernetes.io/instance: hive + app.kubernetes.io/managed-by: hive.stackable.tech_hivecluster + app.kubernetes.io/name: hive + app.kubernetes.io/role-group: none + stackable.tech/vendor: Stackable + name: hive-metastore + ownerReferences: + - apiVersion: hive.stackable.tech/v1alpha1 + controller: true + kind: HiveCluster + name: hive +spec: + ports: + - name: hive + port: 9083 + protocol: TCP +status: + ingressAddresses: + # address: hive-metastore.$NAMESPACE.svc.cluster.local + - addressType: Hostname + ports: + hive: 9083 + serviceName: hive-metastore