Skip to content

Commit 05ba65f

Browse files
adwk67claude
andcommitted
refactor: extract dereference and validate steps into controller modules
Move external-reference resolution (product image, ZooKeeper, OPA) into controller/dereference.rs and product-config validation with config merging into controller/validate.rs. The reconciler now calls dereference → validate as an explicit pipeline, matching the pattern established in the airflow and hive operators. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent c8df041 commit 05ba65f

6 files changed

Lines changed: 241 additions & 111 deletions

File tree

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
use snafu::{ResultExt, Snafu};
2+
use stackable_operator::commons::product_image_selection::{self, ResolvedProductImage};
3+
4+
use crate::{
5+
crd::v1alpha1, security::opa::HbaseOpaConfig, zookeeper::ZookeeperConnectionInformation,
6+
};
7+
8+
#[derive(Snafu, Debug)]
9+
pub enum Error {
10+
#[snafu(display("failed to resolve product image"))]
11+
ResolveProductImage {
12+
source: product_image_selection::Error,
13+
},
14+
15+
#[snafu(display("failed to retrieve zookeeper connection information"))]
16+
RetrieveZookeeperConnectionInformation { source: crate::zookeeper::Error },
17+
18+
#[snafu(display("invalid OPA configuration"))]
19+
InvalidOpaConfig { source: crate::security::opa::Error },
20+
}
21+
22+
/// External references resolved during the dereference step.
23+
pub struct DereferencedObjects {
24+
pub resolved_product_image: ResolvedProductImage,
25+
pub zookeeper_connection_information: ZookeeperConnectionInformation,
26+
pub hbase_opa_config: Option<HbaseOpaConfig>,
27+
}
28+
29+
pub async fn dereference(
30+
client: &stackable_operator::client::Client,
31+
hbase: &v1alpha1::HbaseCluster,
32+
image_base_name: &str,
33+
image_repository: &str,
34+
pkg_version: &str,
35+
) -> Result<DereferencedObjects, Error> {
36+
let resolved_product_image = hbase
37+
.spec
38+
.image
39+
.resolve(image_base_name, image_repository, pkg_version)
40+
.context(ResolveProductImageSnafu)?;
41+
42+
let zookeeper_connection_information = ZookeeperConnectionInformation::retrieve(hbase, client)
43+
.await
44+
.context(RetrieveZookeeperConnectionInformationSnafu)?;
45+
46+
let hbase_opa_config = match &hbase.spec.cluster_config.authorization {
47+
Some(opa_config) => Some(
48+
HbaseOpaConfig::from_opa_config(client, hbase, opa_config)
49+
.await
50+
.context(InvalidOpaConfigSnafu)?,
51+
),
52+
None => None,
53+
};
54+
55+
Ok(DereferencedObjects {
56+
resolved_product_image,
57+
zookeeper_connection_information,
58+
hbase_opa_config,
59+
})
60+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
pub mod dereference;
2+
pub mod validate;
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
use std::{
2+
collections::{BTreeMap, HashMap},
3+
str::FromStr,
4+
};
5+
6+
use product_config::{ProductConfigManager, types::PropertyNameKind};
7+
use snafu::{ResultExt, Snafu};
8+
use stackable_operator::{
9+
commons::product_image_selection::ResolvedProductImage,
10+
product_config_utils::{transform_all_roles_to_config, validate_all_roles_and_groups_config},
11+
role_utils::GenericRoleConfig,
12+
};
13+
14+
use super::dereference::DereferencedObjects;
15+
use crate::crd::{AnyServiceConfig, HbaseRole, v1alpha1};
16+
17+
#[derive(Snafu, Debug)]
18+
pub enum Error {
19+
#[snafu(display("invalid role properties"))]
20+
RoleProperties { source: crate::crd::Error },
21+
22+
#[snafu(display("failed to generate product config"))]
23+
GenerateProductConfig {
24+
source: stackable_operator::product_config_utils::Error,
25+
},
26+
27+
#[snafu(display("invalid product config"))]
28+
InvalidProductConfig {
29+
source: stackable_operator::product_config_utils::Error,
30+
},
31+
32+
#[snafu(display("could not parse Hbase role [{role}]"))]
33+
UnidentifiedHbaseRole {
34+
source: strum::ParseError,
35+
role: String,
36+
},
37+
38+
#[snafu(display("failed to resolve and merge config for role and role group"))]
39+
FailedToResolveConfig { source: crate::crd::Error },
40+
}
41+
42+
/// Per-role configuration extracted during validation.
43+
#[derive(Clone, Debug)]
44+
pub struct ValidatedRoleConfig {
45+
pub pdb: stackable_operator::commons::pdb::PdbConfig,
46+
}
47+
48+
/// Per-rolegroup configuration: the merged CRD config plus the product-config properties.
49+
#[derive(Clone, Debug)]
50+
pub struct ValidatedRoleGroupConfig {
51+
pub merged_config: AnyServiceConfig,
52+
pub product_config_properties: HashMap<PropertyNameKind, BTreeMap<String, String>>,
53+
}
54+
55+
/// The validated cluster: proves that product-config validation and config merging
56+
/// succeeded for every role and role group before any resources are created.
57+
#[derive(Clone, Debug)]
58+
pub struct ValidatedHbaseCluster {
59+
pub image: ResolvedProductImage,
60+
pub role_groups: BTreeMap<HbaseRole, BTreeMap<String, ValidatedRoleGroupConfig>>,
61+
pub role_configs: BTreeMap<HbaseRole, ValidatedRoleConfig>,
62+
}
63+
64+
pub fn validate_cluster(
65+
hbase: &v1alpha1::HbaseCluster,
66+
dereferenced: &DereferencedObjects,
67+
product_config_manager: &ProductConfigManager,
68+
) -> Result<ValidatedHbaseCluster, Error> {
69+
let roles = hbase.build_role_properties().context(RolePropertiesSnafu)?;
70+
71+
let validated_config = validate_all_roles_and_groups_config(
72+
&dereferenced.resolved_product_image.app_version_label_value,
73+
&transform_all_roles_to_config(hbase, &roles).context(GenerateProductConfigSnafu)?,
74+
product_config_manager,
75+
false,
76+
false,
77+
)
78+
.context(InvalidProductConfigSnafu)?;
79+
80+
let mut role_groups = BTreeMap::new();
81+
let mut role_configs = BTreeMap::new();
82+
83+
for (role_name, group_config) in validated_config.iter() {
84+
let hbase_role = HbaseRole::from_str(role_name).context(UnidentifiedHbaseRoleSnafu {
85+
role: role_name.to_string(),
86+
})?;
87+
88+
if let Some(GenericRoleConfig {
89+
pod_disruption_budget: pdb,
90+
}) = hbase.role_config(&hbase_role)
91+
{
92+
role_configs.insert(hbase_role.clone(), ValidatedRoleConfig { pdb: pdb.clone() });
93+
}
94+
95+
let mut group_configs = BTreeMap::new();
96+
for (rolegroup_name, rolegroup_config) in group_config.iter() {
97+
let rolegroup = hbase.server_rolegroup_ref(role_name, rolegroup_name);
98+
99+
let merged_config = hbase
100+
.merged_config(
101+
&hbase_role,
102+
&rolegroup.role_group,
103+
&hbase.spec.cluster_config.hdfs_config_map_name,
104+
)
105+
.context(FailedToResolveConfigSnafu)?;
106+
107+
group_configs.insert(
108+
rolegroup_name.clone(),
109+
ValidatedRoleGroupConfig {
110+
merged_config,
111+
product_config_properties: rolegroup_config.clone(),
112+
},
113+
);
114+
}
115+
116+
role_groups.insert(hbase_role, group_configs);
117+
}
118+
119+
Ok(ValidatedHbaseCluster {
120+
image: dereferenced.resolved_product_image.clone(),
121+
role_groups,
122+
role_configs,
123+
})
124+
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -623,7 +623,9 @@ pub fn merged_env(rolegroup_config: Option<&BTreeMap<String, String>>) -> Vec<En
623623
Eq,
624624
Hash,
625625
JsonSchema,
626+
Ord,
626627
PartialEq,
628+
PartialOrd,
627629
Serialize,
628630
EnumString,
629631
)]
@@ -1253,6 +1255,7 @@ pub struct HbaseClusterStatus {
12531255
pub conditions: Vec<ClusterCondition>,
12541256
}
12551257

1258+
#[derive(Clone, Debug)]
12561259
pub enum AnyServiceConfig {
12571260
Master(HbaseConfig),
12581261
RegionServer(RegionServerConfig),

0 commit comments

Comments
 (0)