Skip to content

Commit 37e7d4e

Browse files
maltesanderclaude
andcommitted
refactor: Remove product-config, build config from typed ValidatedCluster
Replace the product-config-based validation pipeline with a typed, framework- based one mirroring trino-operator: - validate() now produces a ValidatedCluster { name, image, cluster_config, role_group_configs } via framework::role_utils::with_validated_config, instead of the product-config PropertyNameKind map. server.<myid> quorum entries are precomputed into cluster_config so the build step never needs the CRD. - Add zk_controller/build/config_map.rs which builds zoo.cfg and security.properties directly from the typed config, referencing the ZookeeperCluster only for the owner reference. The operator defaults that product-config used to inject from deploy/config-spec/properties.yaml (admin.serverPort, clientPort, dataDir, initLimit, syncLimit, tickTime, metricsProvider.*, networkaddress.cache.*) are now seeded here, byte-identical to before (pinned by the kuttl ConfigMap snapshot). - Drop the product_config Configuration impl from the CRD; add a manual Merge impl for ZookeeperConfigOverrides (CRD schema unchanged) and Ord for ZookeeperRole. - Minimal consumer changes: the StatefulSet derives MYID_OFFSET/ZOOCFGDIR from the merged config plus envOverrides; the metrics Service takes a resolved port. - Remove ProductConfigManager from main.rs/Ctx and the product-config dependency from both Cargo.toml. It remains only as a transitive dependency of stackable-operator itself. Unit tests now exercise the product-config-free path and assert the seeded defaults and security.properties byte-for-byte. The generated CRD is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent ceac76c commit 37e7d4e

10 files changed

Lines changed: 604 additions & 447 deletions

File tree

Cargo.lock

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

Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ edition = "2024"
1010
repository = "https://github.com/stackabletech/zookeeper-operator"
1111

1212
[workspace.dependencies]
13-
product-config = { git = "https://github.com/stackabletech/product-config.git", tag = "0.8.0" }
1413
stackable-operator = { git = "https://github.com/stackabletech/operator-rs.git", tag = "stackable-operator-0.111.1", features = ["webhook"] }
1514

1615
anyhow = "1.0"

rust/operator-binary/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ publish = false
1010
build = "build.rs"
1111

1212
[dependencies]
13-
product-config.workspace = true
1413
stackable-operator.workspace = true
1514

1615
anyhow.workspace = true

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

Lines changed: 31 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ use stackable_operator::{
2525
},
2626
kube::{CustomResource, ResourceExt, runtime::reflector::ObjectRef},
2727
memory::{BinaryMultiple, MemoryQuantity},
28-
product_config_utils::{self, Configuration},
2928
product_logging::{self, spec::Logging},
3029
role_utils::{GenericRoleConfig, JavaCommonConfig, Role, RoleGroup, RoleGroupRef},
3130
schemars::{self, JsonSchema},
@@ -346,6 +345,34 @@ pub mod versioned {
346345
}
347346
}
348347

348+
impl Merge for v1alpha1::ZookeeperConfigOverrides {
349+
/// Merges `defaults` (role level) into `self` (role-group level). Keys present
350+
/// in `self` win; keys only in `defaults` are added. Mirrors how the previous
351+
/// product-config pipeline merged role and role-group `configOverrides`.
352+
fn merge(&mut self, defaults: &Self) {
353+
merge_key_value_overrides(&mut self.zoo_cfg, &defaults.zoo_cfg);
354+
merge_key_value_overrides(&mut self.security_properties, &defaults.security_properties);
355+
}
356+
}
357+
358+
/// Merges two optional [`KeyValueConfigOverrides`], with `this` taking precedence.
359+
fn merge_key_value_overrides(
360+
this: &mut Option<KeyValueConfigOverrides>,
361+
defaults: &Option<KeyValueConfigOverrides>,
362+
) {
363+
match (this.as_mut(), defaults) {
364+
(Some(this), Some(defaults)) => {
365+
for (key, value) in &defaults.overrides {
366+
this.overrides
367+
.entry(key.clone())
368+
.or_insert_with(|| value.clone());
369+
}
370+
}
371+
(None, Some(defaults)) => *this = Some(defaults.clone()),
372+
(Some(_), None) | (None, None) => {}
373+
}
374+
}
375+
349376
impl KeyValueOverridesProvider for v1alpha1::ZookeeperConfigOverrides {
350377
fn get_key_value_overrides(&self, file: &str) -> BTreeMap<String, Option<String>> {
351378
let field = match file {
@@ -368,7 +395,9 @@ impl KeyValueOverridesProvider for v1alpha1::ZookeeperConfigOverrides {
368395
Eq,
369396
Hash,
370397
JsonSchema,
398+
Ord,
371399
PartialEq,
400+
PartialOrd,
372401
Serialize,
373402
EnumString,
374403
)]
@@ -417,7 +446,7 @@ impl v1alpha1::ZookeeperConfig {
417446
pub const SYNC_LIMIT: &'static str = "syncLimit";
418447
pub const TICK_TIME: &'static str = "tickTime";
419448

420-
fn default_server_config(
449+
pub(crate) fn default_server_config(
421450
cluster_name: &str,
422451
role: &ZookeeperRole,
423452
) -> v1alpha1::ZookeeperConfigFragment {
@@ -451,89 +480,6 @@ impl v1alpha1::ZookeeperConfig {
451480
}
452481
}
453482

454-
impl Configuration for v1alpha1::ZookeeperConfigFragment {
455-
type Configurable = v1alpha1::ZookeeperCluster;
456-
457-
fn compute_env(
458-
&self,
459-
resource: &Self::Configurable,
460-
_role_name: &str,
461-
) -> Result<BTreeMap<String, Option<String>>, product_config_utils::Error> {
462-
Ok([
463-
(
464-
v1alpha1::ZookeeperConfig::MYID_OFFSET.to_string(),
465-
self.myid_offset
466-
.or(v1alpha1::ZookeeperConfig::default_server_config(
467-
&resource.name_any(),
468-
&ZookeeperRole::Server,
469-
)
470-
.myid_offset)
471-
.map(|myid_offset| myid_offset.to_string()),
472-
),
473-
// This is used by zkEnv.sh and for the shell scripts in bin/
474-
// If unset it tries to find the conf directory automatically and that fails
475-
(
476-
"ZOOCFGDIR".to_string(),
477-
Some(STACKABLE_RW_CONFIG_DIR.to_string()),
478-
),
479-
]
480-
.into())
481-
}
482-
483-
fn compute_cli(
484-
&self,
485-
_resource: &Self::Configurable,
486-
_role_name: &str,
487-
) -> Result<BTreeMap<String, Option<String>>, product_config_utils::Error> {
488-
Ok(BTreeMap::new())
489-
}
490-
491-
fn compute_files(
492-
&self,
493-
_resource: &Self::Configurable,
494-
_role_name: &str,
495-
file: &str,
496-
) -> Result<BTreeMap<String, Option<String>>, product_config_utils::Error> {
497-
let mut result = BTreeMap::new();
498-
if file == ZOOKEEPER_PROPERTIES_FILE {
499-
if let Some(init_limit) = self.init_limit {
500-
result.insert(
501-
v1alpha1::ZookeeperConfig::INIT_LIMIT.to_string(),
502-
Some(init_limit.to_string()),
503-
);
504-
}
505-
if let Some(sync_limit) = self.sync_limit {
506-
result.insert(
507-
v1alpha1::ZookeeperConfig::SYNC_LIMIT.to_string(),
508-
Some(sync_limit.to_string()),
509-
);
510-
}
511-
if let Some(tick_time) = self.tick_time {
512-
result.insert(
513-
v1alpha1::ZookeeperConfig::TICK_TIME.to_string(),
514-
Some(tick_time.to_string()),
515-
);
516-
}
517-
result.insert(
518-
v1alpha1::ZookeeperConfig::DATA_DIR.to_string(),
519-
Some(STACKABLE_DATA_DIR.to_string()),
520-
);
521-
result.insert(
522-
"metricsProvider.className".to_string(),
523-
Some(
524-
"org.apache.zookeeper.metrics.prometheus.PrometheusMetricsProvider".to_string(),
525-
),
526-
);
527-
result.insert(
528-
METRICS_PROVIDER_HTTP_PORT_KEY.to_string(),
529-
Some(METRICS_PROVIDER_HTTP_PORT.to_string()),
530-
);
531-
}
532-
533-
Ok(result)
534-
}
535-
}
536-
537483
impl ZookeeperRole {
538484
pub fn roles() -> Vec<String> {
539485
let mut roles = vec![];

rust/operator-binary/src/main.rs

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ async fn main() -> anyhow::Result<()> {
7777
Command::Run(RunArguments {
7878
operator_environment,
7979
watch_namespace,
80-
product_config,
80+
product_config: _,
8181
maintenance,
8282
common,
8383
}) => {
@@ -124,11 +124,6 @@ async fn main() -> anyhow::Result<()> {
124124
.run(sigterm_watcher.handle())
125125
.map_err(|err| anyhow!(err).context("failed to run webhook server"));
126126

127-
let product_config = product_config.load(&[
128-
"deploy/config-spec/properties.yaml",
129-
"/etc/stackable/zookeeper-operator/config-spec/properties.yaml",
130-
])?;
131-
132127
let zk_controller = Controller::new(
133128
watch_namespace.get_api::<DeserializeGuard<v1alpha1::ZookeeperCluster>>(&client),
134129
watcher::Config::default(),
@@ -161,7 +156,6 @@ async fn main() -> anyhow::Result<()> {
161156
Arc::new(zk_controller::Ctx {
162157
operator_environment: operator_environment.clone(),
163158
client: client.clone(),
164-
product_config,
165159
}),
166160
)
167161
// We can let the reporting happen in the background

rust/operator-binary/src/service.rs

Lines changed: 5 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,4 @@
1-
use std::{
2-
collections::{BTreeMap, HashMap},
3-
str::FromStr,
4-
};
5-
6-
use product_config::types::PropertyNameKind;
7-
use snafu::{OptionExt, ResultExt, Snafu};
1+
use snafu::{ResultExt, Snafu};
82
use stackable_operator::{
93
builder::meta::ObjectMetaBuilder,
104
commons::product_image_selection::ResolvedProductImage,
@@ -15,10 +9,9 @@ use stackable_operator::{
159

1610
use crate::{
1711
crd::{
18-
APP_NAME, JMX_METRICS_PORT, JMX_METRICS_PORT_NAME, METRICS_PROVIDER_HTTP_PORT,
19-
METRICS_PROVIDER_HTTP_PORT_KEY, METRICS_PROVIDER_HTTP_PORT_NAME, ZOOKEEPER_ELECTION_PORT,
20-
ZOOKEEPER_ELECTION_PORT_NAME, ZOOKEEPER_LEADER_PORT, ZOOKEEPER_LEADER_PORT_NAME,
21-
ZOOKEEPER_PROPERTIES_FILE, v1alpha1,
12+
APP_NAME, JMX_METRICS_PORT, JMX_METRICS_PORT_NAME, METRICS_PROVIDER_HTTP_PORT_NAME,
13+
ZOOKEEPER_ELECTION_PORT, ZOOKEEPER_ELECTION_PORT_NAME, ZOOKEEPER_LEADER_PORT,
14+
ZOOKEEPER_LEADER_PORT_NAME, v1alpha1,
2215
},
2316
utils::build_recommended_labels,
2417
zk_controller::ZK_CONTROLLER_NAME,
@@ -40,12 +33,6 @@ pub enum Error {
4033
BuildLabel {
4134
source: stackable_operator::kvp::LabelError,
4235
},
43-
44-
#[snafu(display("missing zookeeper properties file {ZOOKEEPER_PROPERTIES_FILE} in config"))]
45-
MissingPropertiesFile,
46-
47-
#[snafu(display("missing provider http port key {METRICS_PROVIDER_HTTP_PORT_KEY} in config"))]
48-
MissingProviderHttpPortKey,
4936
}
5037

5138
/// The rolegroup [`Service`] is a headless service that allows internal access to the instances of a certain rolegroup
@@ -110,10 +97,8 @@ pub(crate) fn build_server_rolegroup_metrics_service(
11097
zk: &v1alpha1::ZookeeperCluster,
11198
rolegroup: &RoleGroupRef<v1alpha1::ZookeeperCluster>,
11299
resolved_product_image: &ResolvedProductImage,
113-
rolegroup_config: &HashMap<PropertyNameKind, BTreeMap<String, String>>,
100+
metrics_port: u16,
114101
) -> Result<Service, Error> {
115-
let metrics_port = metrics_port_from_rolegroup_config(rolegroup_config)?;
116-
117102
let metadata = ObjectMetaBuilder::new()
118103
.name_and_namespace(zk)
119104
.name(rolegroup.rolegroup_metrics_service_name())
@@ -166,29 +151,6 @@ pub(crate) fn build_server_rolegroup_metrics_service(
166151
})
167152
}
168153

169-
pub(crate) fn metrics_port_from_rolegroup_config(
170-
rolegroup_config: &HashMap<PropertyNameKind, BTreeMap<String, String>>,
171-
) -> Result<u16, Error> {
172-
let metrics_port = rolegroup_config
173-
.get(&PropertyNameKind::File(
174-
ZOOKEEPER_PROPERTIES_FILE.to_string(),
175-
))
176-
.context(MissingPropertiesFileSnafu)?
177-
.get(METRICS_PROVIDER_HTTP_PORT_KEY)
178-
.context(MissingProviderHttpPortKeySnafu)?;
179-
180-
let port = match u16::from_str(metrics_port) {
181-
Ok(port) => port,
182-
Err(err) => {
183-
tracing::error!("{err}");
184-
tracing::info!("Defaulting to using {METRICS_PROVIDER_HTTP_PORT} as metrics port.");
185-
METRICS_PROVIDER_HTTP_PORT
186-
}
187-
};
188-
189-
Ok(port)
190-
}
191-
192154
/// Common labels for Prometheus
193155
fn prometheus_labels() -> Labels {
194156
Labels::try_from([("prometheus.io/scrape", "true")]).expect("should be a valid label")

0 commit comments

Comments
 (0)