diff --git a/CHANGELOG.md b/CHANGELOG.md index a39fdf1d..1b04ec2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 dependent on static inputs ([#824]). ### Fixed @@ -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 diff --git a/rust/operator-binary/src/controller/build/container.rs b/rust/operator-binary/src/controller/build/container.rs index c756ed0d..5b50f552 100644 --- a/rust/operator-binary/src/controller/build/container.rs +++ b/rust/operator-binary/src/controller/build/container.rs @@ -128,11 +128,6 @@ pub enum Error { #[snafu(display("failed to construct JVM arguments fro role {role:?}"))] ConstructJvmArguments { source: jvm::Error, role: String }, - #[snafu(display( - "could not determine any ContainerConfig actions for {container_name:?}. Container not recognized." - ))] - UnrecognizedContainerName { container_name: String }, - #[snafu(display("failed to build secret volume for {volume_name:?}"))] BuildSecretVolume { source: SecretOperatorVolumeSourceBuilderError, @@ -347,7 +342,7 @@ impl ContainerConfig { match role { HdfsNodeRole::Name => { // Zookeeper fail over container - let zkfc_container_config = Self::try_from(NameNodeContainer::Zkfc.to_string())?; + let zkfc_container_config = Self::zkfc(); pb.add_volumes(zkfc_container_config.volumes( merged_config, &object_name, @@ -363,8 +358,7 @@ impl ContainerConfig { )?); // Format namenode init container - let format_namenodes_container_config = - Self::try_from(NameNodeContainer::FormatNameNodes.to_string())?; + let format_namenodes_container_config = Self::format_namenodes(); pb.add_volumes(format_namenodes_container_config.volumes( merged_config, &object_name, @@ -381,8 +375,7 @@ impl ContainerConfig { )?); // Format ZooKeeper init container - let format_zookeeper_container_config = - Self::try_from(NameNodeContainer::FormatZooKeeper.to_string())?; + let format_zookeeper_container_config = Self::format_zookeeper(); pb.add_volumes(format_zookeeper_container_config.volumes( merged_config, &object_name, @@ -400,8 +393,7 @@ impl ContainerConfig { } HdfsNodeRole::Data => { // Wait for namenode init container - let wait_for_namenodes_container_config = - Self::try_from(DataNodeContainer::WaitForNameNodes.to_string())?; + let wait_for_namenodes_container_config = Self::wait_for_namenodes(); pb.add_volumes(wait_for_namenodes_container_config.volumes( merged_config, &object_name, @@ -436,7 +428,7 @@ impl ContainerConfig { .build_ephemeral() .context(BuildListenerVolumeSnafu)? .volume_claim_template - .unwrap(); + .expect("The listener volume source builder always sets a volume claim template."); let pvcs = vec![ node.resources.storage.data.build_pvc( @@ -446,7 +438,9 @@ impl ContainerConfig { PersistentVolumeClaim { metadata: ObjectMeta { name: Some(LISTENER_VOLUME_NAME.to_string()), - ..listener.metadata.unwrap() + ..listener.metadata.expect( + "The listener volume claim template always carries metadata.", + ) }, spec: Some(listener.spec), ..Default::default() @@ -1146,8 +1140,9 @@ impl ContainerConfig { // Adding this for all containers, as not only the main container needs Kerberos or TLS if cluster.has_kerberos_enabled() { - volume_mounts - .push(VolumeMountBuilder::new("kerberos", KERBEROS_CONTAINER_PATH).build()); + volume_mounts.push( + VolumeMountBuilder::new(&*KERBEROS_VOLUME_NAME, KERBEROS_CONTAINER_PATH).build(), + ); } if cluster.has_https_enabled() { // This volume will be propagated by the create-tls-cert-bundle container @@ -1419,41 +1414,56 @@ impl From for ContainerConfig { } } -impl TryFrom for ContainerConfig { - type Error = Error; - - fn try_from(container_name: String) -> Result { - match HdfsNodeRole::from_str(container_name.as_str()) { - Ok(role) => Ok(ContainerConfig::from(role)), - // No hadoop main process container - Err(_) => match container_name { - // namenode side container - name if name == NameNodeContainer::Zkfc.to_string() => Ok(Self::Zkfc { - volume_mounts: ContainerVolumeDirs::try_from(name.as_str())?, - container_name: name, - }), - // namenode init containers - name if name == NameNodeContainer::FormatNameNodes.to_string() => { - Ok(Self::FormatNameNodes { - volume_mounts: ContainerVolumeDirs::try_from(name.as_str())?, - container_name: name, - }) - } - name if name == NameNodeContainer::FormatZooKeeper.to_string() => { - Ok(Self::FormatZooKeeper { - volume_mounts: ContainerVolumeDirs::try_from(name.as_str())?, - container_name: name, - }) - } - // datanode init containers - name if name == DataNodeContainer::WaitForNameNodes.to_string() => { - Ok(Self::WaitForNameNodes { - volume_mounts: ContainerVolumeDirs::try_from(name.as_str())?, - container_name: name, - }) - } - _ => Err(Error::UnrecognizedContainerName { container_name }), - }, +impl ContainerConfig { + /// The ZooKeeper fail-over controller side container of the namenodes. + fn zkfc() -> Self { + let container_name = NameNodeContainer::Zkfc.to_string(); + Self::Zkfc { + volume_mounts: ContainerVolumeDirs::for_container( + &container_name, + Self::ZKFC_CONFIG_VOLUME_MOUNT_NAME, + Self::ZKFC_LOG_VOLUME_MOUNT_NAME, + ), + container_name, + } + } + + /// The init container formatting the namenodes. + fn format_namenodes() -> Self { + let container_name = NameNodeContainer::FormatNameNodes.to_string(); + Self::FormatNameNodes { + volume_mounts: ContainerVolumeDirs::for_container( + &container_name, + Self::FORMAT_NAMENODES_CONFIG_VOLUME_MOUNT_NAME, + Self::FORMAT_NAMENODES_LOG_VOLUME_MOUNT_NAME, + ), + container_name, + } + } + + /// The init container formatting ZooKeeper for the namenodes. + fn format_zookeeper() -> Self { + let container_name = NameNodeContainer::FormatZooKeeper.to_string(); + Self::FormatZooKeeper { + volume_mounts: ContainerVolumeDirs::for_container( + &container_name, + Self::FORMAT_ZOOKEEPER_CONFIG_VOLUME_MOUNT_NAME, + Self::FORMAT_ZOOKEEPER_LOG_VOLUME_MOUNT_NAME, + ), + container_name, + } + } + + /// The init container of the datanodes waiting for the namenodes. + fn wait_for_namenodes() -> Self { + let container_name = DataNodeContainer::WaitForNameNodes.to_string(); + Self::WaitForNameNodes { + volume_mounts: ContainerVolumeDirs::for_container( + &container_name, + Self::WAIT_FOR_NAMENODES_CONFIG_VOLUME_MOUNT_NAME, + Self::WAIT_FOR_NAMENODES_LOG_VOLUME_MOUNT_NAME, + ), + container_name, } } } @@ -1546,59 +1556,22 @@ impl From<&HdfsNodeRole> for ContainerVolumeDirs { } } -impl TryFrom<&str> for ContainerVolumeDirs { - type Error = Error; - - fn try_from(container_name: &str) -> Result { - if let Ok(role) = HdfsNodeRole::from_str(container_name) { - return Ok(ContainerVolumeDirs::from(role)); - } - - let (config_mount_name, log_mount_name) = match container_name { - // namenode side container - name if name == NameNodeContainer::Zkfc.to_string() => ( - ContainerConfig::ZKFC_CONFIG_VOLUME_MOUNT_NAME.to_string(), - ContainerConfig::ZKFC_LOG_VOLUME_MOUNT_NAME.to_string(), - ), - // namenode init containers - name if name == NameNodeContainer::FormatNameNodes.to_string() => ( - ContainerConfig::FORMAT_NAMENODES_CONFIG_VOLUME_MOUNT_NAME.to_string(), - ContainerConfig::FORMAT_NAMENODES_LOG_VOLUME_MOUNT_NAME.to_string(), - ), - name if name == NameNodeContainer::FormatZooKeeper.to_string() => ( - ContainerConfig::FORMAT_ZOOKEEPER_CONFIG_VOLUME_MOUNT_NAME.to_string(), - ContainerConfig::FORMAT_ZOOKEEPER_LOG_VOLUME_MOUNT_NAME.to_string(), +impl ContainerVolumeDirs { + /// The volume dirs of a side or init container with the given fixed name and mount names. + fn for_container(container_name: &str, config_mount_name: &str, log_mount_name: &str) -> Self { + ContainerVolumeDirs { + final_config_dir: format!("{base}/{container_name}", base = Self::NODE_BASE_CONFIG_DIR), + config_mount: format!( + "{base}/{container_name}", + base = Self::NODE_BASE_CONFIG_DIR_MOUNT ), - // datanode init containers - name if name == DataNodeContainer::WaitForNameNodes.to_string() => ( - ContainerConfig::WAIT_FOR_NAMENODES_CONFIG_VOLUME_MOUNT_NAME.to_string(), - ContainerConfig::WAIT_FOR_NAMENODES_LOG_VOLUME_MOUNT_NAME.to_string(), + config_mount_name: config_mount_name.to_owned(), + log_mount: format!( + "{base}/{container_name}", + base = Self::NODE_BASE_LOG_DIR_MOUNT ), - _ => { - return Err(Error::UnrecognizedContainerName { - container_name: container_name.to_string(), - }); - } - }; - - let final_config_dir = - format!("{base}/{container_name}", base = Self::NODE_BASE_CONFIG_DIR); - let config_mount = format!( - "{base}/{container_name}", - base = Self::NODE_BASE_CONFIG_DIR_MOUNT - ); - let log_mount = format!( - "{base}/{container_name}", - base = Self::NODE_BASE_LOG_DIR_MOUNT - ); - - Ok(ContainerVolumeDirs { - final_config_dir, - config_mount, - config_mount_name, - log_mount, - log_mount_name, - }) + log_mount_name: log_mount_name.to_owned(), + } } } @@ -1638,3 +1611,18 @@ fn bash_capture_shell_helper(container_name: &str) -> String { "### } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_constants() { + // Test that dereferencing the constants does not panic. + let _ = *TLS_STORE_VOLUME_NAME; + let _ = *KERBEROS_VOLUME_NAME; + let _ = *VECTOR_CONTAINER_NAME; + let _ = *VECTOR_CONFIG_VOLUME_NAME; + let _ = *VECTOR_LOG_VOLUME_NAME; + } +} diff --git a/rust/operator-binary/src/controller/build/resource/service.rs b/rust/operator-binary/src/controller/build/resource/service.rs index 8d31c139..57dd3a34 100644 --- a/rust/operator-binary/src/controller/build/resource/service.rs +++ b/rust/operator-binary/src/controller/build/resource/service.rs @@ -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 }, } diff --git a/rust/operator-binary/src/controller/mod.rs b/rust/operator-binary/src/controller/mod.rs index 4db91552..afbbfedc 100644 --- a/rust/operator-binary/src/controller/mod.rs +++ b/rust/operator-binary/src/controller/mod.rs @@ -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}, @@ -211,6 +211,13 @@ impl ValidatedCluster { role: &HdfsNodeRole, role_group_name: &RoleGroupName, ) -> ServiceName { + const _: () = assert!( + QualifiedRoleGroupName::MAX_LENGTH <= ServiceName::MAX_LENGTH, + "The string `` 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() @@ -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; + } +} diff --git a/rust/operator-binary/src/crd/constants.rs b/rust/operator-binary/src/crd/constants.rs index 7afe1bf0..23d6850e 100644 --- a/rust/operator-binary/src/crd/constants.rs +++ b/rust/operator-binary/src/crd/constants.rs @@ -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; @@ -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); @@ -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; + } +} diff --git a/rust/operator-binary/src/crd/mod.rs b/rust/operator-binary/src/crd/mod.rs index 8a6404b3..8c254786 100644 --- a/rust/operator-binary/src/crd/mod.rs +++ b/rust/operator-binary/src/crd/mod.rs @@ -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, @@ -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}, @@ -94,27 +91,15 @@ type Result = std::result::Result; #[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( @@ -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( @@ -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), @@ -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), @@ -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 = " diff --git a/rust/operator-binary/src/crd/security.rs b/rust/operator-binary/src/crd/security.rs index cc111361..e2625920 100644 --- a/rust/operator-binary/src/crd/security.rs +++ b/rust/operator-binary/src/crd/security.rs @@ -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, }; @@ -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)] @@ -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; + } +} diff --git a/rust/operator-binary/src/hdfs_controller.rs b/rust/operator-binary/src/hdfs_controller.rs index 0f358590..99a2eb07 100644 --- a/rust/operator-binary/src/hdfs_controller.rs +++ b/rust/operator-binary/src/hdfs_controller.rs @@ -10,7 +10,6 @@ use stackable_operator::{ core::{DeserializeGuard, error_boundary}, runtime::{controller::Action, events::Recorder}, }, - kvp::LabelError, logging::controller::ReconcilerError, shared::time::Duration, }; @@ -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, diff --git a/tests/templates/kuttl/smoke/30-install-hdfs.yaml.j2 b/tests/templates/kuttl/smoke/30-install-hdfs.yaml.j2 index 0bd4ba9e..fe2aac56 100644 --- a/tests/templates/kuttl/smoke/30-install-hdfs.yaml.j2 +++ b/tests/templates/kuttl/smoke/30-install-hdfs.yaml.j2 @@ -25,6 +25,16 @@ spec: vectorAggregatorConfigMapName: vector-aggregator-discovery {% endif %} nameNodes: + configOverrides: + core-site.xml: + # After the chaos monkey (60-unleash-the-chaosmonkey.yaml.j2) force-deletes all pods, + # the format-namenodes probe of the other namenode can resolve its name to the stale + # pre-delete pod IP (DNS cache) and silently retry a black-hole TCP connect for + # 45 x 20s = 15 min by default, exceeding the chaos-monkey step's 10 min `kubectl wait`. + # zkfc is unaffected: graceful fencing and health monitoring override their retries via + # ha.failover-controller.graceful-fence.connection.retries and + # ha.health-monitor.rpc.connect.max.retries respectively. + ipc.client.connect.max.retries.on.timeouts: "3" envOverrides: COMMON_VAR: role-value # overridden by role group below ROLE_VAR: role-value # only defined here at role level @@ -51,6 +61,11 @@ spec: - -Xms820m - -Xmx820m dataNodes: + configOverrides: + core-site.xml: + # See the identical nameNodes override: wait-for-namenodes runs the same haadmin + # probe against namenodes that may resolve to a stale pod IP after the chaos monkey. + ipc.client.connect.max.retries.on.timeouts: "3" envOverrides: COMMON_VAR: role-value # overridden by role group below ROLE_VAR: role-value # only defined here at role level diff --git a/tests/templates/kuttl/smoke/31-assert.yaml.j2 b/tests/templates/kuttl/smoke/31-assert.yaml.j2 index 0258bfd7..57c8c278 100644 --- a/tests/templates/kuttl/smoke/31-assert.yaml.j2 +++ b/tests/templates/kuttl/smoke/31-assert.yaml.j2 @@ -11,6 +11,12 @@ # both `$NAMESPACE` (the core-site DNS, which we DO want substituted) and the literal # `${NAMESPACE}` in vector.yaml, we temporarily mask the braces form around envsubst. # +# Both sides are canonicalised to JSON via `yq -o=json`, so the snapshot files need +# not byte-match yq's YAML serialisation. The `*.log4j.properties` values end with an +# empty line (the operator-rs log4j template ends with one), which YAML writes as a +# `|+` block followed by a blank line; the snapshot files terminate that blank line +# with a comment so that the end-of-file-fixer pre-commit hook does not strip it. +# # kuttl runs script commands with sh, not bash, so process substitution # is unavailable. Temp files namespaced under $NAMESPACE are used for # diff output to avoid collisions when tests run concurrently. @@ -22,8 +28,8 @@ commands: # ConfigMap data snapshot: hdfs-namenode-default # - script: | - sed 's/${NAMESPACE}/__VECTOR_NS__/g' 31_configmap_hdfs-namenode-default.yaml | envsubst '$NAMESPACE' | sed 's/__VECTOR_NS__/${NAMESPACE}/g' > /tmp/$NAMESPACE-expected.yaml - kubectl -n $NAMESPACE get cm hdfs-namenode-default -o yaml | yq '.data' > /tmp/$NAMESPACE-actual.yaml + sed 's/${NAMESPACE}/__VECTOR_NS__/g' 31_configmap_hdfs-namenode-default.yaml | envsubst '$NAMESPACE' | sed 's/__VECTOR_NS__/${NAMESPACE}/g' | yq -o=json > /tmp/$NAMESPACE-expected.yaml + kubectl -n $NAMESPACE get cm hdfs-namenode-default -o yaml | yq -o=json '.data' > /tmp/$NAMESPACE-actual.yaml if ! diff /tmp/$NAMESPACE-expected.yaml /tmp/$NAMESPACE-actual.yaml; then echo "ERROR: ConfigMap hdfs-namenode-default data drifted from snapshot." exit 1 @@ -37,8 +43,8 @@ commands: {% else %} export DATANODE_DATA_DIR='[DISK]/stackable/data/data/datanode' {% endif %} - sed 's/${NAMESPACE}/__VECTOR_NS__/g' 31_configmap_hdfs-datanode-default.yaml | envsubst '$NAMESPACE $DATANODE_DATA_DIR' | sed 's/__VECTOR_NS__/${NAMESPACE}/g' > /tmp/$NAMESPACE-expected.yaml - kubectl -n $NAMESPACE get cm hdfs-datanode-default -o yaml | yq '.data' > /tmp/$NAMESPACE-actual.yaml + sed 's/${NAMESPACE}/__VECTOR_NS__/g' 31_configmap_hdfs-datanode-default.yaml | envsubst '$NAMESPACE $DATANODE_DATA_DIR' | sed 's/__VECTOR_NS__/${NAMESPACE}/g' | yq -o=json > /tmp/$NAMESPACE-expected.yaml + kubectl -n $NAMESPACE get cm hdfs-datanode-default -o yaml | yq -o=json '.data' > /tmp/$NAMESPACE-actual.yaml if ! diff /tmp/$NAMESPACE-expected.yaml /tmp/$NAMESPACE-actual.yaml; then echo "ERROR: ConfigMap hdfs-datanode-default data drifted from snapshot." exit 1 @@ -47,8 +53,8 @@ commands: # ConfigMap data snapshot: hdfs-journalnode-default # - script: | - sed 's/${NAMESPACE}/__VECTOR_NS__/g' 31_configmap_hdfs-journalnode-default.yaml | envsubst '$NAMESPACE' | sed 's/__VECTOR_NS__/${NAMESPACE}/g' > /tmp/$NAMESPACE-expected.yaml - kubectl -n $NAMESPACE get cm hdfs-journalnode-default -o yaml | yq '.data' > /tmp/$NAMESPACE-actual.yaml + sed 's/${NAMESPACE}/__VECTOR_NS__/g' 31_configmap_hdfs-journalnode-default.yaml | envsubst '$NAMESPACE' | sed 's/__VECTOR_NS__/${NAMESPACE}/g' | yq -o=json > /tmp/$NAMESPACE-expected.yaml + kubectl -n $NAMESPACE get cm hdfs-journalnode-default -o yaml | yq -o=json '.data' > /tmp/$NAMESPACE-actual.yaml if ! diff /tmp/$NAMESPACE-expected.yaml /tmp/$NAMESPACE-actual.yaml; then echo "ERROR: ConfigMap hdfs-journalnode-default data drifted from snapshot." exit 1 diff --git a/tests/templates/kuttl/smoke/31_configmap_hdfs-datanode-default.yaml.j2 b/tests/templates/kuttl/smoke/31_configmap_hdfs-datanode-default.yaml.j2 index bba4b7db..a9bda13c 100644 --- a/tests/templates/kuttl/smoke/31_configmap_hdfs-datanode-default.yaml.j2 +++ b/tests/templates/kuttl/smoke/31_configmap_hdfs-datanode-default.yaml.j2 @@ -17,6 +17,10 @@ core-site.xml: |- io.file.buffer.size 131072 + + ipc.client.connect.max.retries.on.timeouts + 3 + hadoop-policy.xml: |- @@ -360,3 +364,4 @@ wait-for-namenodes.log4j.properties: |+ log4j.appender.FILE.MaxBackupIndex=1 log4j.appender.FILE.layout=org.apache.log4j.xml.XMLLayout +# The blank line above is part of the value: the operator-rs log4j template ends with an empty line. diff --git a/tests/templates/kuttl/smoke/31_configmap_hdfs-namenode-default.yaml.j2 b/tests/templates/kuttl/smoke/31_configmap_hdfs-namenode-default.yaml.j2 index a8a9c34e..3d76bd2a 100644 --- a/tests/templates/kuttl/smoke/31_configmap_hdfs-namenode-default.yaml.j2 +++ b/tests/templates/kuttl/smoke/31_configmap_hdfs-namenode-default.yaml.j2 @@ -17,6 +17,10 @@ core-site.xml: |- io.file.buffer.size 131072 + + ipc.client.connect.max.retries.on.timeouts + 3 + format-namenodes.log4j.properties: |+ log4j.rootLogger=INFO, CONSOLE, FILE @@ -386,3 +390,4 @@ zkfc.log4j.properties: |+ log4j.appender.FILE.MaxBackupIndex=1 log4j.appender.FILE.layout=org.apache.log4j.xml.XMLLayout +# The blank line above is part of the value: the operator-rs log4j template ends with an empty line.