Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ All notable changes to this project will be documented in this file.
are no longer created with the placeholder `app.kubernetes.io/component: none` and
`app.kubernetes.io/role-group: none` labels.
StatefulSet selectors and volume claim templates are unchanged, so upgrading is non-breaking.
- Make operations infallible where appropriate ([#824]).

### Fixed

Expand All @@ -39,6 +40,7 @@ All notable changes to this project will be documented in this file.
[#814]: https://github.com/stackabletech/hdfs-operator/pull/814
[#819]: https://github.com/stackabletech/hdfs-operator/pull/819
[#821]: https://github.com/stackabletech/hdfs-operator/pull/821
[#824]: https://github.com/stackabletech/hdfs-operator/pull/824

## [26.7.0] - 2026-07-21

Expand Down
305 changes: 139 additions & 166 deletions rust/operator-binary/src/controller/build/container.rs

Large diffs are not rendered by default.

14 changes: 3 additions & 11 deletions rust/operator-binary/src/controller/build/resource/config_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,6 @@ pub enum Error {
source: PropertiesWriterError,
rolegroup: String,
},

#[snafu(display("cannot build config map for role {role:?} and role group {role_group:?}"))]
Assemble {
source: stackable_operator::builder::configmap::Error,
role: String,
role_group: String,
},
}

type Result<T, E = Error> = std::result::Result<T, E>;
Expand Down Expand Up @@ -118,8 +111,7 @@ pub fn build_rolegroup_config_map(
);
}

builder.build().with_context(|_| AssembleSnafu {
role: role.to_string(),
role_group: role_group_name.to_string(),
})
Ok(builder
.build()
.expect("The ConfigMap metadata is set in this function."))
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,6 @@ type Result<T, E = Error> = std::result::Result<T, E>;

#[derive(Snafu, Debug)]
pub enum Error {
#[snafu(display("failed to build ConfigMap"))]
BuildConfigMap {
source: stackable_operator::builder::configmap::Error,
},

#[snafu(display("failed to collect the namenode listener refs"))]
CollectListenerRefs { source: crate::crd::Error },
}
Expand Down Expand Up @@ -88,7 +83,7 @@ pub fn build_discovery_config_map(
build_discovery_core_site_xml(cluster, cluster_info),
)
.build()
.context(BuildConfigMapSnafu)?;
.expect("The ConfigMap metadata is set in this function.");

Ok(Some(config_map))
}
Expand Down
5 changes: 0 additions & 5 deletions rust/operator-binary/src/controller/build/resource/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,6 @@ use crate::{

#[derive(Snafu, Debug)]
pub enum Error {
#[snafu(display("failed to build object meta data"))]
ObjectMeta {
source: stackable_operator::builder::meta::Error,
},

#[snafu(display("failed to build roleGroup selector labels"))]
RoleGroupSelectorLabels { source: LabelError },
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,6 @@ pub enum Error {

#[snafu(display("failed to configure graceful shutdown"))]
GracefulShutdown { source: graceful_shutdown::Error },

#[snafu(display("failed to build role-group volume claim templates from config"))]
BuildRoleGroupVolumeClaimTemplates { source: container::Error },
}

pub(crate) fn build_rolegroup_statefulset(
Expand Down Expand Up @@ -108,8 +105,7 @@ pub(crate) fn build_rolegroup_statefulset(
pod_template.merge_from(rolegroup_config.pod_overrides.clone());

// The same comment regarding labels is valid here as it is for the ContainerConfig::add_containers_and_volumes() call above.
let pvcs = ContainerConfig::volume_claim_templates(merged_config, &rolegroup_selector_labels)
.context(BuildRoleGroupVolumeClaimTemplatesSnafu)?;
let pvcs = ContainerConfig::volume_claim_templates(merged_config, &rolegroup_selector_labels);

let statefulset_spec = StatefulSetSpec {
pod_management_policy: Some("OrderedReady".to_string()),
Expand Down
22 changes: 21 additions & 1 deletion rust/operator-binary/src/controller/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use stackable_operator::{
kube::{Resource, api::ObjectMeta},
v2::{
HasName, HasUid, NameIsValidLabelValue,
role_group_utils::ResourceNames,
role_group_utils::{QualifiedRoleGroupName, ResourceNames},
role_utils::{self, RoleGroupConfig},
types::{
kubernetes::{ConfigMapName, NamespaceName, ServiceName, Uid},
Expand Down Expand Up @@ -211,6 +211,13 @@ impl ValidatedCluster {
role: &HdfsNodeRole,
role_group_name: &RoleGroupName,
) -> ServiceName {
const _: () = assert!(
QualifiedRoleGroupName::MAX_LENGTH <= ServiceName::MAX_LENGTH,
"The string `<qualified_role_group_name>` must not exceed the limit of Service names."
);
let _ = QualifiedRoleGroupName::IS_RFC_1035_LABEL_NAME;
let _ = QualifiedRoleGroupName::IS_VALID_LABEL_VALUE;

ServiceName::from_str(
self.role_group_resource_names(role, role_group_name)
.qualified_role_group_name()
Expand Down Expand Up @@ -337,3 +344,16 @@ impl ValidatedClusterConfig {
pub struct ValidatedRoleConfig {
pub pdb: stackable_operator::commons::pdb::PdbConfig,
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_constants() {
// Test that dereferencing the constants does not panic.
let _ = *PRODUCT_NAME;
let _ = *OPERATOR_NAME;
let _ = *CONTROLLER_NAME;
}
}
19 changes: 17 additions & 2 deletions rust/operator-binary/src/crd/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ use std::str::FromStr;
use stackable_operator::{
constant,
shared::time::Duration,
v2::types::{common::Port, kubernetes::VolumeName},
v2::types::{
common::Port,
kubernetes::{ListenerClassName, VolumeName},
},
};

pub const DEFAULT_DFS_REPLICATION_FACTOR: u8 = 3;
Expand All @@ -23,7 +26,7 @@ pub const SERVICE_PORT_NAME_DATA: &str = "data";
pub const SERVICE_PORT_NAME_METRICS: &str = "metrics";
pub const SERVICE_PORT_NAME_JMX_METRICS: &str = "jmx-metrics";

pub const DEFAULT_LISTENER_CLASS: &str = "cluster-internal";
constant!(pub DEFAULT_LISTENER_CLASS: ListenerClassName = "cluster-internal");

pub const DEFAULT_NAME_NODE_METRICS_PORT: Port = Port(8183);
pub const DEFAULT_NAME_NODE_NATIVE_METRICS_HTTP_PORT: Port = Port(9870);
Expand Down Expand Up @@ -91,3 +94,15 @@ pub const DATANODE_ROOT_DATA_DIR_SUFFIX: &str = "/datanode";

constant!(pub LISTENER_VOLUME_NAME: VolumeName = "listener");
pub const LISTENER_VOLUME_DIR: &str = "/stackable/listener";

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_constants() {
// Test that dereferencing the constants does not panic.
let _ = *LISTENER_VOLUME_NAME;
let _ = *DEFAULT_LISTENER_CLASS;
}
}
37 changes: 12 additions & 25 deletions rust/operator-binary/src/crd/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,7 @@ use stackable_operator::{
PvcConfigFragment, Resources, ResourcesFragment,
},
},
config::{
fragment::{Fragment, ValidationError},
merge::Merge,
},
config::{fragment::Fragment, merge::Merge},
constant,
crd::listener,
deep_merger::ObjectOverrides,
Expand All @@ -32,7 +29,7 @@ use stackable_operator::{
self,
spec::{ContainerLogConfig, Logging},
},
role_utils::{self, GenericRoleConfig},
role_utils::GenericRoleConfig,
schemars::{self, JsonSchema},
shared::time::Duration,
status::condition::{ClusterCondition, HasStatusCondition},
Expand Down Expand Up @@ -94,27 +91,15 @@ type Result<T, E = Error> = std::result::Result<T, E>;

#[derive(Snafu, Debug)]
pub enum Error {
#[snafu(display("object has no associated namespace"))]
NoNamespace,

#[snafu(display("missing role {role:?}"))]
MissingRole { role: String },

#[snafu(display("missing role group {role_group:?} for role {role:?}"))]
MissingRoleGroup { role: String, role_group: String },

#[snafu(display("fragment validation failure"))]
FragmentValidationFailure { source: ValidationError },

#[snafu(display("port {port} ({port_name:?}) is out of bounds, must be within {range:?}", range = 0..=u16::MAX))]
PortOutOfBounds {
source: TryFromIntError,
port_name: String,
port: i32,
},

#[snafu(display("failed to merge jvm argument overrides"))]
MergeJvmArgumentOverrides { source: role_utils::Error },
}

#[versioned(
Expand Down Expand Up @@ -701,12 +686,6 @@ pub enum NameNodeContainer {
FormatZooKeeper,
}

/// The default [`ListenerClassName`] used to expose a role group.
pub fn default_listener_class() -> ListenerClassName {
ListenerClassName::from_str(DEFAULT_LISTENER_CLASS)
.expect("the default listener class is a valid ListenerClassName")
}

#[derive(Clone, Debug, Fragment, JsonSchema, PartialEq)]
#[fragment_attrs(
derive(
Expand Down Expand Up @@ -757,7 +736,7 @@ impl NameNodeConfigFragment {
},
},
logging: product_logging::spec::default_logging(),
listener_class: Some(default_listener_class()),
listener_class: Some(DEFAULT_LISTENER_CLASS.clone()),
common: CommonNodeConfigFragment {
affinity: get_affinity(cluster_name, role),
graceful_shutdown_timeout: Some(DEFAULT_NAME_NODE_GRACEFUL_SHUTDOWN_TIMEOUT),
Expand Down Expand Up @@ -845,7 +824,7 @@ impl DataNodeConfigFragment {
)]),
},
logging: product_logging::spec::default_logging(),
listener_class: Some(default_listener_class()),
listener_class: Some(DEFAULT_LISTENER_CLASS.clone()),
common: CommonNodeConfigFragment {
affinity: get_affinity(cluster_name, role),
graceful_shutdown_timeout: Some(DEFAULT_DATA_NODE_GRACEFUL_SHUTDOWN_TIMEOUT),
Expand Down Expand Up @@ -973,6 +952,14 @@ mod test {
.expect("storage should be defined")
}

#[test]
fn test_constants() {
// Test that dereferencing the constants does not panic.
let _ = *NAMENODE_ROLE_NAME;
let _ = *DATANODE_ROLE_NAME;
let _ = *JOURNALNODE_ROLE_NAME;
}

#[test]
pub fn test_pvc_rolegroup_from_yaml() {
let cr = "
Expand Down
18 changes: 17 additions & 1 deletion rust/operator-binary/src/crd/security.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::str::FromStr;
use serde::{Deserialize, Serialize};
use stackable_operator::{
commons::opa::OpaConfig,
constant,
schemars::{self, JsonSchema},
v2::types::kubernetes::SecretClassName,
};
Expand All @@ -17,8 +18,12 @@ pub struct AuthenticationConfig {
pub kerberos: KerberosConfig,
}

constant!(DEFAULT_TLS_SECRET_CLASS: SecretClassName = "tls");

/// Serde default for `tlsSecretClass`. Kept as a function because `#[serde(default = "...")]`
/// requires a function path.
fn default_tls_secret_class() -> SecretClassName {
SecretClassName::from_str("tls").expect("\"tls\" should be a valid SecretClassName")
DEFAULT_TLS_SECRET_CLASS.clone()
}

#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
Expand All @@ -34,3 +39,14 @@ pub struct AuthorizationConfig {
// No doc - it's in the struct.
pub opa: OpaConfig,
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_constants() {
// Test that dereferencing the constants does not panic.
let _ = *DEFAULT_TLS_SECRET_CLASS;
}
}
4 changes: 0 additions & 4 deletions rust/operator-binary/src/hdfs_controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ use stackable_operator::{
core::{DeserializeGuard, error_boundary},
runtime::{controller::Action, events::Recorder},
},
kvp::LabelError,
logging::controller::ReconcilerError,
shared::time::Duration,
};
Expand Down Expand Up @@ -55,9 +54,6 @@ pub enum Error {
#[snafu(display("failed to create cluster event"))]
FailedToCreateClusterEvent { source: crate::event::Error },

#[snafu(display("failed to build cluster resources label"))]
BuildClusterResourcesLabel { source: LabelError },

#[snafu(display("HdfsCluster object is invalid"))]
InvalidHdfsCluster {
source: error_boundary::InvalidObject,
Expand Down
Loading