Skip to content

Commit 519f8ce

Browse files
Fix technical debts (#20)
* test: Add unit test for RoleGroupBuilder::build_node_role_label * test: Test serialization and deserialization of NodeRole * chore: Extend comment of MAX_OBJECT_NAME_LENGTH * chore: Remove TODO because the code is okay as it is * Validate environment variable names * Remove completed TODO * Fix the remaining TODOs or remove them if not necessary
1 parent b5cc45b commit 519f8ce

File tree

15 files changed

+165
-137
lines changed

15 files changed

+165
-137
lines changed

rust/operator-binary/src/controller.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,6 @@ impl IsLabelValue for ValidatedCluster {
185185
}
186186
}
187187

188-
// TODO Remove boilerplate (like derive_more)
189188
impl Resource for ValidatedCluster {
190189
type DynamicType =
191190
<v1alpha1::OpenSearchCluster as stackable_operator::kube::Resource>::DynamicType;

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

Lines changed: 35 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -7,52 +7,24 @@ use super::ValidatedCluster;
77
use crate::{
88
controller::OpenSearchRoleGroupConfig,
99
crd::v1alpha1,
10-
framework::{builder::pod::container::EnvVarSet, role_group_utils},
10+
framework::{
11+
builder::pod::container::{EnvVarName, EnvVarSet},
12+
role_group_utils,
13+
},
1114
};
1215

1316
pub const CONFIGURATION_FILE_OPENSEARCH_YML: &str = "opensearch.yml";
1417

15-
// TODO Document how to enter config_overrides of various types, e.g. string, list, boolean,
16-
// object, ...
17-
18-
// Configuration file format
19-
//
20-
// This is not well documented.
21-
//
22-
// A list setting can be written as
23-
// - a comma-separated list, e.g.
24-
// ```
25-
// setting: a,b,c
26-
// ```
27-
// Commas in the values cannot be escaped.
28-
// - a JSON list, e.g.
29-
// ```
30-
// setting: ["a", "b", "c"]
31-
// ```
32-
// - a YAML list, e.g.
33-
// ```
34-
// setting:
35-
// - a
36-
// - b
37-
// - c
38-
// ```
39-
// - a (legacy) flat list, e.g.
40-
// ```
41-
// setting.0: a
42-
// setting.1: b
43-
// setting.2: b
44-
// ```
45-
4618
/// type: string
4719
pub const CONFIG_OPTION_CLUSTER_NAME: &str = "cluster.name";
4820

49-
/// type: list of strings
21+
/// type: (comma-separated) list of strings
5022
pub const CONFIG_OPTION_DISCOVERY_SEED_HOSTS: &str = "discovery.seed_hosts";
5123

5224
/// type: string
5325
pub const CONFIG_OPTION_DISCOVERY_TYPE: &str = "discovery.type";
5426

55-
/// type: list of strings
27+
/// type: (comma-separated) list of strings
5628
pub const CONFIG_OPTION_INITIAL_CLUSTER_MANAGER_NODES: &str =
5729
"cluster.initial_cluster_manager_nodes";
5830

@@ -62,10 +34,10 @@ pub const CONFIG_OPTION_NETWORK_HOST: &str = "network.host";
6234
/// type: string
6335
pub const CONFIG_OPTION_NODE_NAME: &str = "node.name";
6436

65-
/// type: list of strings
37+
/// type: (comma-separated) list of strings
6638
pub const CONFIG_OPTION_NODE_ROLES: &str = "node.roles";
6739

68-
/// type: list of strings
40+
/// type: (comma-separated) list of strings
6941
pub const CONFIG_OPTION_PLUGINS_SECURITY_NODES_DN: &str = "plugins.security.nodes_dn";
7042

7143
pub struct NodeConfig {
@@ -156,17 +128,20 @@ impl NodeConfig {
156128
EnvVarSet::new()
157129
// Set the OpenSearch node name to the Pod name.
158130
// The node name is used e.g. for `{INITIAL_CLUSTER_MANAGER_NODES}`.
159-
.with_field_path(CONFIG_OPTION_NODE_NAME, FieldPathEnvVar::Name)
131+
.with_field_path(
132+
EnvVarName::from_str_unsafe(CONFIG_OPTION_NODE_NAME),
133+
FieldPathEnvVar::Name,
134+
)
160135
.with_value(
161-
CONFIG_OPTION_DISCOVERY_SEED_HOSTS,
136+
EnvVarName::from_str_unsafe(CONFIG_OPTION_DISCOVERY_SEED_HOSTS),
162137
&self.discovery_service_name,
163138
)
164139
.with_value(
165-
CONFIG_OPTION_INITIAL_CLUSTER_MANAGER_NODES,
140+
EnvVarName::from_str_unsafe(CONFIG_OPTION_INITIAL_CLUSTER_MANAGER_NODES),
166141
self.initial_cluster_manager_nodes(),
167142
)
168143
.with_value(
169-
CONFIG_OPTION_NODE_ROLES,
144+
EnvVarName::from_str_unsafe(CONFIG_OPTION_NODE_ROLES),
170145
self.role_group_config
171146
.config
172147
.node_roles
@@ -177,7 +152,7 @@ impl NodeConfig {
177152
// is safe.
178153
.join(","),
179154
)
180-
.with_values(self.role_group_config.env_overrides.clone())
155+
.merge(self.role_group_config.env_overrides.clone())
181156
}
182157

183158
fn to_yaml(kv: serde_json::Map<String, Value>) -> String {
@@ -274,7 +249,7 @@ mod tests {
274249
affinity::StackableAffinity, product_image_selection::ProductImage,
275250
resources::Resources,
276251
},
277-
k8s_openapi::api::core::v1::{EnvVar, EnvVarSource, ObjectFieldSelector, PodTemplateSpec},
252+
k8s_openapi::api::core::v1::PodTemplateSpec,
278253
kube::api::ObjectMeta,
279254
role_utils::GenericRoleConfig,
280255
};
@@ -350,7 +325,8 @@ mod tests {
350325
listener_class: "cluster-internal".to_string(),
351326
},
352327
config_overrides: HashMap::default(),
353-
env_overrides: [("TEST".to_owned(), "value".to_owned())].into(),
328+
env_overrides: EnvVarSet::new()
329+
.with_value(EnvVarName::from_str_unsafe("TEST"), "value"),
354330
cli_overrides: BTreeMap::default(),
355331
pod_overrides: PodTemplateSpec::default(),
356332
product_specific_common_config: GenericProductSpecificCommonConfig::default(),
@@ -364,44 +340,23 @@ mod tests {
364340

365341
let env_vars = node_config.environment_variables();
366342

367-
// TODO Test EnvVarSet and compare EnvVarSets
368343
assert_eq!(
369-
vec![
370-
EnvVar {
371-
name: "TEST".to_owned(),
372-
value: Some("value".to_owned()),
373-
value_from: None
374-
},
375-
EnvVar {
376-
name: "cluster.initial_cluster_manager_nodes".to_owned(),
377-
value: Some("".to_owned()),
378-
value_from: None
379-
},
380-
EnvVar {
381-
name: "discovery.seed_hosts".to_owned(),
382-
value: Some("my-opensearch-cluster-manager".to_owned()),
383-
value_from: None
384-
},
385-
EnvVar {
386-
name: "node.name".to_owned(),
387-
value: None,
388-
value_from: Some(EnvVarSource {
389-
config_map_key_ref: None,
390-
field_ref: Some(ObjectFieldSelector {
391-
api_version: None,
392-
field_path: "metadata.name".to_owned()
393-
}),
394-
resource_field_ref: None,
395-
secret_key_ref: None
396-
})
397-
},
398-
EnvVar {
399-
name: "node.roles".to_owned(),
400-
value: Some("".to_owned()),
401-
value_from: None
402-
}
403-
],
404-
<EnvVarSet as Into<Vec<EnvVar>>>::into(env_vars)
344+
EnvVarSet::new()
345+
.with_value(EnvVarName::from_str_unsafe("TEST"), "value",)
346+
.with_value(
347+
EnvVarName::from_str_unsafe("cluster.initial_cluster_manager_nodes"),
348+
"",
349+
)
350+
.with_value(
351+
EnvVarName::from_str_unsafe("discovery.seed_hosts"),
352+
"my-opensearch-cluster-manager",
353+
)
354+
.with_field_path(
355+
EnvVarName::from_str_unsafe("node.name"),
356+
FieldPathEnvVar::Name
357+
)
358+
.with_value(EnvVarName::from_str_unsafe("node.roles"), "",),
359+
env_vars
405360
);
406361
}
407362
}

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,6 @@ impl<'a> RoleBuilder<'a> {
4949
}
5050
}
5151

52-
// TODO Only one builder function which calls the other ones?
53-
5452
pub fn role_group_builders(&self) -> Vec<RoleGroupBuilder<'_>> {
5553
self.cluster
5654
.role_group_configs

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

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use crate::{
2222
crd::v1alpha1,
2323
framework::{
2424
RoleGroupName,
25-
builder::meta::ownerreference_from_resource,
25+
builder::{meta::ownerreference_from_resource, pod::container::EnvVarName},
2626
kvp::label::{recommended_labels, role_group_selector, role_selector},
2727
listener::listener_pvc,
2828
role_group_utils::ResourceNames,
@@ -235,7 +235,8 @@ impl<'a> RoleGroupBuilder<'a> {
235235
}
236236

237237
fn build_node_role_label(node_role: &v1alpha1::NodeRole) -> Label {
238-
// TODO Check the maximum length at compile-time
238+
// It is not possible to check the infallibility of the following statement at
239+
// compile-time. Instead, it is tested in `tests::test_build_node_role_label`.
239240
Label::try_from((
240241
format!("stackable.tech/opensearch-role.{node_role}"),
241242
"true".to_string(),
@@ -276,13 +277,13 @@ impl<'a> RoleGroupBuilder<'a> {
276277

277278
// Use `OPENSEARCH_HOME` from envOverrides or default to `DEFAULT_OPENSEARCH_HOME`.
278279
let opensearch_home = env_vars
279-
.get_env_var("OPENSEARCH_HOME")
280+
.get(EnvVarName::from_str_unsafe("OPENSEARCH_HOME"))
280281
.and_then(|env_var| env_var.value.clone())
281282
.unwrap_or(DEFAULT_OPENSEARCH_HOME.to_owned());
282283
// Use `OPENSEARCH_PATH_CONF` from envOverrides or default to `{OPENSEARCH_HOME}/config`,
283284
// i.e. depend on `OPENSEARCH_HOME`.
284285
let opensearch_path_conf = env_vars
285-
.get_env_var("OPENSEARCH_PATH_CONF")
286+
.get(EnvVarName::from_str_unsafe("OPENSEARCH_PATH_CONF"))
286287
.and_then(|env_var| env_var.value.clone())
287288
.unwrap_or(format!("{opensearch_home}/config"));
288289

@@ -482,7 +483,17 @@ impl<'a> RoleGroupBuilder<'a> {
482483

483484
#[cfg(test)]
484485
mod tests {
486+
use strum::IntoEnumIterator;
487+
485488
use super::RoleGroupBuilder;
489+
use crate::crd::v1alpha1;
490+
491+
#[test]
492+
fn test_build_node_role_label() {
493+
for node_role in v1alpha1::NodeRole::iter() {
494+
RoleGroupBuilder::build_node_role_label(&node_role);
495+
}
496+
}
486497

487498
#[test]
488499
pub fn test_prometheus_labels() {

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

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use crate::{
1616
crd::v1alpha1::{self, OpenSearchConfig, OpenSearchConfigFragment},
1717
framework::{
1818
ClusterName,
19+
builder::pod::container::{EnvVarName, EnvVarSet},
1920
role_utils::{GenericProductSpecificCommonConfig, RoleGroupConfig, with_validated_config},
2021
},
2122
};
@@ -41,6 +42,11 @@ pub enum Error {
4142
#[snafu(display("failed to set role-group name"))]
4243
ParseRoleGroupName { source: crate::framework::Error },
4344

45+
#[snafu(display("failed to parse environment variable"))]
46+
ParseEnvironmentVariable {
47+
source: crate::framework::builder::pod::container::Error,
48+
},
49+
4450
#[snafu(display("fragment validation failure"))]
4551
ValidateOpenSearchConfig {
4652
source: stackable_operator::config::fragment::ValidationError,
@@ -125,12 +131,21 @@ fn validate_role_group_config(
125131
listener_class: merged_role_group.config.config.listener_class,
126132
};
127133

134+
let mut env_overrides = EnvVarSet::new();
135+
136+
for (env_var_name, env_var_value) in merged_role_group.config.env_overrides {
137+
env_overrides = env_overrides.with_value(
138+
EnvVarName::from_str(&env_var_name).context(ParseEnvironmentVariableSnafu)?,
139+
env_var_value,
140+
);
141+
}
142+
128143
Ok(RoleGroupConfig {
129144
// Kubernetes defaults to 1 if not set
130145
replicas: merged_role_group.replicas.unwrap_or(1),
131146
config: validated_config,
132147
config_overrides: merged_role_group.config.config_overrides,
133-
env_overrides: merged_role_group.config.env_overrides,
148+
env_overrides,
134149
cli_overrides: merged_role_group.config.cli_overrides,
135150
pod_overrides: merged_role_group.config.pod_overrides,
136151
product_specific_common_config: merged_role_group.config.product_specific_common_config,

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

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use stackable_operator::{
2323
time::Duration,
2424
versioned::versioned,
2525
};
26-
use strum::Display;
26+
use strum::{Display, EnumIter};
2727

2828
use crate::framework::{
2929
ClusterName, IsLabelValue, ProductName, RoleName,
@@ -76,32 +76,34 @@ pub mod versioned {
7676
// https://github.com/opensearch-project/ml-commons/blob/3.0.0.0/plugin/src/main/java/org/opensearch/ml/plugin/MachineLearningPlugin.java#L394.
7777
// If such a plugin is added, then this enumeration must be extended accordingly.
7878
#[derive(
79-
Clone, Debug, Deserialize, Display, Eq, JsonSchema, Ord, PartialEq, PartialOrd, Serialize,
79+
Clone,
80+
Debug,
81+
Deserialize,
82+
Display,
83+
EnumIter,
84+
Eq,
85+
JsonSchema,
86+
Ord,
87+
PartialEq,
88+
PartialOrd,
89+
Serialize,
8090
)]
8191
// The OpenSearch configuration uses snake_case. To make it easier to match the log output of
8292
// OpenSearch with this cluster configuration, snake_case is also used here.
8393
#[serde(rename_all = "snake_case")]
94+
#[strum(serialize_all = "snake_case")]
8495
pub enum NodeRole {
8596
// Built-in node roles
8697
// see https://github.com/opensearch-project/OpenSearch/blob/3.0.0/server/src/main/java/org/opensearch/cluster/node/DiscoveryNodeRole.java#L341-L346
87-
88-
// TODO https://github.com/Peternator7/strum/issues/113
89-
#[strum(serialize = "cluster_manager")]
9098
ClusterManager,
91-
#[strum(serialize = "coordinating_only")]
9299
CoordinatingOnly,
93-
#[strum(serialize = "data")]
94100
Data,
95-
#[strum(serialize = "ingest")]
96101
Ingest,
97-
#[strum(serialize = "remote_cluster_client")]
98102
RemoteClusterClient,
99-
#[strum(serialize = "warm")]
100103
Warm,
101104

102105
// Search node role
103106
// see https://github.com/opensearch-project/OpenSearch/blob/3.0.0/server/src/main/java/org/opensearch/cluster/node/DiscoveryNodeRole.java#L313-L339
104-
#[strum(serialize = "search")]
105107
Search,
106108
}
107109

@@ -257,3 +259,29 @@ impl NodeRoles {
257259
}
258260

259261
impl Atomic for NodeRoles {}
262+
263+
#[cfg(test)]
264+
mod tests {
265+
use crate::crd::v1alpha1;
266+
267+
#[test]
268+
fn test_node_role() {
269+
assert_eq!(
270+
String::from("cluster_manager"),
271+
v1alpha1::NodeRole::ClusterManager.to_string()
272+
);
273+
assert_eq!(
274+
String::from("cluster_manager"),
275+
format!("{}", v1alpha1::NodeRole::ClusterManager)
276+
);
277+
assert_eq!(
278+
"\"cluster_manager\"",
279+
serde_json::to_string(&v1alpha1::NodeRole::ClusterManager)
280+
.expect("should be serializable")
281+
);
282+
assert_eq!(
283+
v1alpha1::NodeRole::ClusterManager,
284+
serde_json::from_str("\"cluster_manager\"").expect("should be deserializable")
285+
);
286+
}
287+
}

rust/operator-binary/src/framework.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,9 @@ pub enum Error {
3333
},
3434
}
3535

36-
// TODO The maximum length of objects differs.
3736
/// Maximum length of a DNS subdomain name as defined in RFC 1123.
37+
/// The object names of most types, e.g. ConfigMap and StatefulSet, must not exceed this limit.
38+
/// However, there are kinds that allow longer object names, e.g. ClusterRole.
3839
#[allow(dead_code)]
3940
pub const MAX_OBJECT_NAME_LENGTH: usize = 253;
4041

0 commit comments

Comments
 (0)