Skip to content

Commit d8e960a

Browse files
committed
test: add unit test for region mover & znode
1 parent b2084d2 commit d8e960a

2 files changed

Lines changed: 189 additions & 19 deletions

File tree

rust/operator-binary/src/controller/build/region_mover.rs

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,146 @@ impl AnyServiceConfig {
6666
}
6767
}
6868
}
69+
70+
#[cfg(test)]
71+
mod tests {
72+
use indoc::indoc;
73+
74+
use crate::{crd::HbaseRole, test_utils};
75+
76+
/// Renders the region-mover args for the default region-server role group of a cluster whose
77+
/// `regionServers.config` is built from `config_lines`. Each entry is one YAML line nested
78+
/// under `config:`; indentation relative to `config:` is written as leading spaces in the entry
79+
/// (e.g. `" runBeforeShutdown: true"` for a key under `regionMover:`).
80+
fn region_mover_args_for(config_lines: &[&str]) -> String {
81+
// `regionServers.config` is placed last so the generated config lines simply extend the
82+
// document, avoiding any trailing-key indentation juggling.
83+
const HEADER: &str = indoc! {r#"
84+
apiVersion: hbase.stackable.tech/v1alpha1
85+
kind: HbaseCluster
86+
metadata:
87+
name: test-hbase
88+
namespace: default
89+
uid: 12345678-1234-1234-1234-123456789012
90+
spec:
91+
image:
92+
productVersion: 2.6.4
93+
clusterConfig:
94+
hdfsConfigMapName: test-hdfs
95+
zookeeperConfigMapName: test-znode
96+
masters:
97+
roleGroups:
98+
default:
99+
replicas: 1
100+
restServers:
101+
roleGroups:
102+
default:
103+
replicas: 1
104+
regionServers:
105+
roleGroups:
106+
default:
107+
replicas: 1
108+
config:
109+
"#};
110+
111+
let config = config_lines
112+
.iter()
113+
.map(|line| format!(" {line}"))
114+
.collect::<Vec<_>>()
115+
.join("\n");
116+
let yaml = format!("{HEADER}{config}\n");
117+
118+
let hbase = test_utils::hbase_from_yaml(&yaml);
119+
let validated = test_utils::validated_cluster_from(&hbase);
120+
test_utils::merged_config_for(&validated, &HbaseRole::RegionServer, "default")
121+
.region_mover_args()
122+
}
123+
124+
#[test]
125+
fn empty_when_disabled() {
126+
assert_eq!(
127+
region_mover_args_for(&["regionMover:", " runBeforeShutdown: false"]),
128+
""
129+
);
130+
}
131+
132+
#[test]
133+
fn empty_for_non_region_server_role() {
134+
// The region mover only applies to region servers; every other role returns no args.
135+
let validated = test_utils::validated_cluster();
136+
assert_eq!(
137+
test_utils::merged_config(&validated, &HbaseRole::Master).region_mover_args(),
138+
""
139+
);
140+
}
141+
142+
#[test]
143+
fn uses_default_graceful_shutdown_timeout_minus_delta() {
144+
// Default region-server graceful shutdown timeout is 60m (3600s); the region mover reserves
145+
// a 1m (60s) delta, leaving 3540s.
146+
assert_eq!(
147+
region_mover_args_for(&["regionMover:", " runBeforeShutdown: true"]),
148+
"--maxthreads 1 --timeout 3540"
149+
);
150+
}
151+
152+
#[test]
153+
fn subtracts_delta_when_timeout_above_delta() {
154+
// 5m (300s) - 1m delta = 240s.
155+
assert_eq!(
156+
region_mover_args_for(&[
157+
"gracefulShutdownTimeout: 5m",
158+
"regionMover:",
159+
" runBeforeShutdown: true",
160+
]),
161+
"--maxthreads 1 --timeout 240"
162+
);
163+
}
164+
165+
#[test]
166+
fn keeps_timeout_when_exactly_at_delta() {
167+
// 1m (60s) is not greater than the 60s delta, so it is used verbatim (no underflow).
168+
assert_eq!(
169+
region_mover_args_for(&[
170+
"gracefulShutdownTimeout: 1m",
171+
"regionMover:",
172+
" runBeforeShutdown: true",
173+
]),
174+
"--maxthreads 1 --timeout 60"
175+
);
176+
}
177+
178+
#[test]
179+
fn keeps_timeout_when_below_delta() {
180+
// 30s is below the 60s delta, so it is used verbatim rather than underflowing.
181+
assert_eq!(
182+
region_mover_args_for(&[
183+
"gracefulShutdownTimeout: 30s",
184+
"regionMover:",
185+
" runBeforeShutdown: true",
186+
]),
187+
"--maxthreads 1 --timeout 30"
188+
);
189+
}
190+
191+
#[test]
192+
fn appends_noack_when_ack_disabled() {
193+
assert_eq!(
194+
region_mover_args_for(&["regionMover:", " runBeforeShutdown: true", " ack: false",]),
195+
"--maxthreads 1 --timeout 3540 --noack"
196+
);
197+
}
198+
199+
#[test]
200+
fn appends_shell_escaped_extra_options() {
201+
// Extra options are passed through verbatim except for shell escaping of unsafe values.
202+
assert_eq!(
203+
region_mover_args_for(&[
204+
"regionMover:",
205+
" runBeforeShutdown: true",
206+
r#" additionalMoverOptions: ["--foo", "a b"]"#,
207+
]),
208+
"--maxthreads 1 --timeout 3540 --foo 'a b'"
209+
);
210+
}
211+
}

rust/operator-binary/src/controller/zookeeper.rs

Lines changed: 46 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ const HBASE_ZOOKEEPER_QUORUM: &str = "hbase.zookeeper.quorum";
1717
const HBASE_ZOOKEEPER_PROPERTY_CLIENT_PORT: &str = "hbase.zookeeper.property.clientPort";
1818
const ZOOKEEPER_ZNODE_PARENT: &str = "zookeeper.znode.parent";
1919

20+
/// The znode sub-path HBase stores its data under, appended to the chroot read from the discovery
21+
/// `ConfigMap` (see [`chroot_with_hbase_suffix`]).
22+
const HBASE_ZNODE_SUFFIX: &str = "hbase";
23+
2024
#[derive(Snafu, Debug, EnumDiscriminants)]
2125
#[strum_discriminants(derive(IntoStaticStr))]
2226
pub enum Error {
@@ -91,7 +95,7 @@ impl ZookeeperConnectionInformation {
9195
cm_name: zk_discovery_cm_name.to_string(),
9296
entry: ZOOKEEPER_DISCOVERY_CM_HOSTS_ENTRY,
9397
})?;
94-
let mut chroot = zk_discovery_cm
98+
let chroot = zk_discovery_cm
9599
.data
96100
.as_mut()
97101
.and_then(|data| data.remove(ZOOKEEPER_DISCOVERY_CM_CHROOT_ENTRY))
@@ -113,26 +117,9 @@ impl ZookeeperConnectionInformation {
113117
entry: ZOOKEEPER_DISCOVERY_CM_CLIENT_PORT_ENTRY,
114118
})?;
115119

116-
// IMPORTANT!
117-
// Before https://github.com/stackabletech/hbase-operator/issues/354, hbase automatically added a `/hbase` suffix, ending up with a chroot of e.g. `/znode-fe51edff-8df9-43a8-ac5f-4781b071ae5f/hbase`.
118-
// Because the chroot we read from the ZNode discovery CM is only `/znode-fe51edff-8df9-43a8-ac5f-4781b071ae5f`, we need to prepend the `/hbase` suffix ourselves.
119-
// If we don't do so, hbase clusters created before #354 would need to be migrated to the different znode path!
120-
121-
// Check if a user points to a discovery CM of a HBaseCluster rather than a ZNode.
122-
if chroot == "/" {
123-
warn!(
124-
"It is recommended to let the HBase cluster point to a discovery ConfigMap of a ZNode rather than a ZookeeperCluster. \
125-
This prevents accidental reuse of the same Zookeeper path for multiple product instances. \
126-
See https://docs.stackable.tech/home/stable/zookeeper/getting_started/first_steps for details"
127-
);
128-
chroot = "/hbase".to_string();
129-
} else {
130-
chroot = format!("{chroot}/hbase");
131-
}
132-
133120
Ok(Self {
134121
hosts,
135-
chroot,
122+
chroot: chroot_with_hbase_suffix(&chroot),
136123
port,
137124
})
138125
}
@@ -155,3 +142,43 @@ impl ZookeeperConnectionInformation {
155142
])
156143
}
157144
}
145+
146+
/// Appends the [`HBASE_ZNODE_SUFFIX`] to the chroot read from the discovery `ConfigMap`.
147+
///
148+
/// IMPORTANT!
149+
/// Before <https://github.com/stackabletech/hbase-operator/issues/354>, hbase automatically added a
150+
/// `/hbase` suffix, ending up with a chroot of e.g. `/znode-fe51edff-.../hbase`. Because the chroot
151+
/// we read from the ZNode discovery `ConfigMap` is only `/znode-fe51edff-...`, we need to append the
152+
/// `/hbase` suffix ourselves. If we don't, hbase clusters created before #354 would need to be
153+
/// migrated to the different znode path!
154+
fn chroot_with_hbase_suffix(chroot: &str) -> String {
155+
// Check if a user points to a discovery CM of a HBaseCluster rather than a ZNode.
156+
if chroot == "/" {
157+
warn!(
158+
"It is recommended to let the HBase cluster point to a discovery ConfigMap of a ZNode rather than a ZookeeperCluster. \
159+
This prevents accidental reuse of the same Zookeeper path for multiple product instances. \
160+
See https://docs.stackable.tech/home/stable/zookeeper/getting_started/first_steps for details"
161+
);
162+
format!("/{HBASE_ZNODE_SUFFIX}")
163+
} else {
164+
format!("{chroot}/{HBASE_ZNODE_SUFFIX}")
165+
}
166+
}
167+
168+
#[cfg(test)]
169+
mod tests {
170+
use super::*;
171+
172+
#[test]
173+
fn appends_suffix_to_znode_chroot() {
174+
// A ZNode discovery CM exposes e.g. `/znode-test`; HBase stores its data under `.../hbase`.
175+
assert_eq!(chroot_with_hbase_suffix("/znode-test"), "/znode-test/hbase");
176+
}
177+
178+
#[test]
179+
fn replaces_root_chroot_with_suffix() {
180+
// Pointing directly at a ZookeeperCluster discovery CM yields a `/` chroot, which becomes
181+
// `/hbase` (rather than `//hbase`).
182+
assert_eq!(chroot_with_hbase_suffix("/"), "/hbase");
183+
}
184+
}

0 commit comments

Comments
 (0)