Skip to content

Commit e5e899d

Browse files
adwk67claude
andauthored
refactor: extract dereference/validate pipeline from reconcile_hbase (#757)
* 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> * fix: pass product_version instead of app_version_label_value to product config validation The label-safe composite version string (e.g. "2.6.4-stackable24.7.0") is not suitable for semver matching against fromVersion/asOfVersion constraints in properties.yaml. Use the raw product_version consistently with all other operators. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * 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 <noreply@anthropic.com> * changelog * linting * use constant for image base name * formatting * move validated struct to controller, add dereferenced fields to validated cluster struct * remove securityContext from assert (might not work on Openshift) * refine assert * replace version with compile-time constant * use already-resolved hbase role * nix update * pass dereferenced struct rather than each field * move related structs together in controller --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 8638ed3 commit e5e899d

13 files changed

Lines changed: 1175 additions & 214 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@
1313
`hbase-env.sh`, `ssl-server.xml`, `ssl-client.xml` and `security.properties`).
1414
Previously, arbitrary file names were silently accepted and ignored ([#751]).
1515
- Bump `stackable-operator` to 0.111.1 and snafu to 0.9 ([#751], [#752]).
16+
- Internal operator refactoring: introduce dereference() and validate() steps in the reconciler ([#757]).
1617

1718
[#745]: https://github.com/stackabletech/hbase-operator/pull/745
1819
[#751]: https://github.com/stackabletech/hbase-operator/pull/751
1920
[#752]: https://github.com/stackabletech/hbase-operator/pull/752
21+
[#757]: https://github.com/stackabletech/hbase-operator/pull/757
2022

2123
## [26.3.0] - 2026-03-16
2224

Cargo.nix

Lines changed: 9 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crate-hashes.json

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

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)