Skip to content

Fix technical debts #20

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
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
1 change: 0 additions & 1 deletion rust/operator-binary/src/controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,6 @@ impl IsLabelValue for ValidatedCluster {
}
}

// TODO Remove boilerplate (like derive_more)
impl Resource for ValidatedCluster {
type DynamicType =
<v1alpha1::OpenSearchCluster as stackable_operator::kube::Resource>::DynamicType;
Expand Down
76 changes: 31 additions & 45 deletions rust/operator-binary/src/controller/build/node_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ use super::ValidatedCluster;
use crate::{
controller::OpenSearchRoleGroupConfig,
crd::v1alpha1,
framework::{builder::pod::container::EnvVarSet, role_group_utils},
framework::{
builder::pod::container::{EnvVarName, EnvVarSet},
role_group_utils,
},
};

pub const CONFIGURATION_FILE_OPENSEARCH_YML: &str = "opensearch.yml";
Expand Down Expand Up @@ -132,17 +135,20 @@ impl NodeConfig {
EnvVarSet::new()
// Set the OpenSearch node name to the Pod name.
// The node name is used e.g. for `{INITIAL_CLUSTER_MANAGER_NODES}`.
.with_field_path(CONFIG_OPTION_NODE_NAME, FieldPathEnvVar::Name)
.with_field_path(
EnvVarName::from_str_unsafe(CONFIG_OPTION_NODE_NAME),
FieldPathEnvVar::Name,
)
.with_value(
CONFIG_OPTION_DISCOVERY_SEED_HOSTS,
EnvVarName::from_str_unsafe(CONFIG_OPTION_DISCOVERY_SEED_HOSTS),
&self.discovery_service_name,
)
.with_value(
CONFIG_OPTION_INITIAL_CLUSTER_MANAGER_NODES,
EnvVarName::from_str_unsafe(CONFIG_OPTION_INITIAL_CLUSTER_MANAGER_NODES),
self.initial_cluster_manager_nodes(),
)
.with_value(
CONFIG_OPTION_NODE_ROLES,
EnvVarName::from_str_unsafe(CONFIG_OPTION_NODE_ROLES),
self.role_group_config
.config
.node_roles
Expand All @@ -153,7 +159,7 @@ impl NodeConfig {
// is safe.
.join(","),
)
.with_values(self.role_group_config.env_overrides.clone())
.merge(self.role_group_config.env_overrides.clone())
}

fn to_yaml(kv: serde_json::Map<String, Value>) -> String {
Expand Down Expand Up @@ -250,7 +256,7 @@ mod tests {
affinity::StackableAffinity, product_image_selection::ProductImage,
resources::Resources,
},
k8s_openapi::api::core::v1::{EnvVar, EnvVarSource, ObjectFieldSelector, PodTemplateSpec},
k8s_openapi::api::core::v1::PodTemplateSpec,
kube::api::ObjectMeta,
role_utils::GenericRoleConfig,
};
Expand Down Expand Up @@ -289,7 +295,8 @@ mod tests {
listener_class: "cluster-internal".to_string(),
},
config_overrides: HashMap::default(),
env_overrides: [("TEST".to_owned(), "value".to_owned())].into(),
env_overrides: EnvVarSet::new()
.with_value(EnvVarName::from_str_unsafe("TEST"), "value"),
cli_overrides: BTreeMap::default(),
pod_overrides: PodTemplateSpec::default(),
product_specific_common_config: GenericProductSpecificCommonConfig::default(),
Expand All @@ -303,44 +310,23 @@ mod tests {

let env_vars = node_config.environment_variables();

// TODO Test EnvVarSet and compare EnvVarSets
assert_eq!(
vec![
EnvVar {
name: "TEST".to_owned(),
value: Some("value".to_owned()),
value_from: None
},
EnvVar {
name: "cluster.initial_cluster_manager_nodes".to_owned(),
value: Some("".to_owned()),
value_from: None
},
EnvVar {
name: "discovery.seed_hosts".to_owned(),
value: Some("my-opensearch-cluster-manager".to_owned()),
value_from: None
},
EnvVar {
name: "node.name".to_owned(),
value: None,
value_from: Some(EnvVarSource {
config_map_key_ref: None,
field_ref: Some(ObjectFieldSelector {
api_version: None,
field_path: "metadata.name".to_owned()
}),
resource_field_ref: None,
secret_key_ref: None
})
},
EnvVar {
name: "node.roles".to_owned(),
value: Some("".to_owned()),
value_from: None
}
],
<EnvVarSet as Into<Vec<EnvVar>>>::into(env_vars)
EnvVarSet::new()
.with_value(EnvVarName::from_str_unsafe("TEST"), "value",)
.with_value(
EnvVarName::from_str_unsafe("cluster.initial_cluster_manager_nodes"),
"",
)
.with_value(
EnvVarName::from_str_unsafe("discovery.seed_hosts"),
"my-opensearch-cluster-manager",
)
.with_field_path(
EnvVarName::from_str_unsafe("node.name"),
FieldPathEnvVar::Name
)
.with_value(EnvVarName::from_str_unsafe("node.roles"), "",),
env_vars
);
}
}
23 changes: 19 additions & 4 deletions rust/operator-binary/src/controller/build/role_group_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use crate::{
crd::v1alpha1,
framework::{
RoleGroupName,
builder::meta::ownerreference_from_resource,
builder::{meta::ownerreference_from_resource, pod::container::EnvVarName},
kvp::label::{recommended_labels, role_group_selector, role_selector},
listener::listener_pvc,
role_group_utils::ResourceNames,
Expand Down Expand Up @@ -233,7 +233,8 @@ impl<'a> RoleGroupBuilder<'a> {
}

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

// Use `OPENSEARCH_HOME` from envOverrides or default to `DEFAULT_OPENSEARCH_HOME`.
let opensearch_home = env_vars
.get_env_var("OPENSEARCH_HOME")
.get(EnvVarName::from_str_unsafe("OPENSEARCH_HOME"))
.and_then(|env_var| env_var.value.clone())
.unwrap_or(DEFAULT_OPENSEARCH_HOME.to_owned());
// Use `OPENSEARCH_PATH_CONF` from envOverrides or default to `{OPENSEARCH_HOME}/config`,
// i.e. depend on `OPENSEARCH_HOME`.
let opensearch_path_conf = env_vars
.get_env_var("OPENSEARCH_PATH_CONF")
.get(EnvVarName::from_str_unsafe("OPENSEARCH_PATH_CONF"))
.and_then(|env_var| env_var.value.clone())
.unwrap_or(format!("{opensearch_home}/config"));

Expand Down Expand Up @@ -444,3 +445,17 @@ impl<'a> RoleGroupBuilder<'a> {
)
}
}

#[cfg(test)]
mod tests {
use strum::IntoEnumIterator;

use crate::{controller::build::role_group_builder::RoleGroupBuilder, crd::v1alpha1};

#[test]
fn test_build_node_role_label() {
for node_role in v1alpha1::NodeRole::iter() {
RoleGroupBuilder::build_node_role_label(&node_role);
}
}
}
17 changes: 16 additions & 1 deletion rust/operator-binary/src/controller/validate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use crate::{
crd::v1alpha1::{self, OpenSearchConfig, OpenSearchConfigFragment},
framework::{
ClusterName,
builder::pod::container::{EnvVarName, EnvVarSet},
role_utils::{GenericProductSpecificCommonConfig, RoleGroupConfig, with_validated_config},
},
};
Expand All @@ -41,6 +42,11 @@ pub enum Error {
#[snafu(display("failed to set role-group name"))]
ParseRoleGroupName { source: crate::framework::Error },

#[snafu(display("failed to parse environment variable"))]
ParseEnvironmentVariable {
source: crate::framework::builder::pod::container::Error,
},

#[snafu(display("fragment validation failure"))]
ValidateOpenSearchConfig {
source: stackable_operator::config::fragment::ValidationError,
Expand Down Expand Up @@ -125,12 +131,21 @@ fn validate_role_group_config(
listener_class: merged_role_group.config.config.listener_class,
};

let mut env_overrides = EnvVarSet::new();

for (env_var_name, env_var_value) in merged_role_group.config.env_overrides {
env_overrides = env_overrides.with_value(
EnvVarName::from_str(&env_var_name).context(ParseEnvironmentVariableSnafu)?,
env_var_value,
);
}

Ok(RoleGroupConfig {
// Kubernetes defaults to 1 if not set
replicas: merged_role_group.replicas.unwrap_or(1),
config: validated_config,
config_overrides: merged_role_group.config.config_overrides,
env_overrides: merged_role_group.config.env_overrides,
env_overrides,
cli_overrides: merged_role_group.config.cli_overrides,
pod_overrides: merged_role_group.config.pod_overrides,
product_specific_common_config: merged_role_group.config.product_specific_common_config,
Expand Down
50 changes: 39 additions & 11 deletions rust/operator-binary/src/crd/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use stackable_operator::{
time::Duration,
versioned::versioned,
};
use strum::Display;
use strum::{Display, EnumIter};

use crate::framework::{
ClusterName, IsLabelValue, ProductName, RoleName,
Expand Down Expand Up @@ -76,32 +76,34 @@ pub mod versioned {
// https://github.com/opensearch-project/ml-commons/blob/3.0.0.0/plugin/src/main/java/org/opensearch/ml/plugin/MachineLearningPlugin.java#L394.
// If such a plugin is added, then this enumeration must be extended accordingly.
#[derive(
Clone, Debug, Deserialize, Display, Eq, JsonSchema, Ord, PartialEq, PartialOrd, Serialize,
Clone,
Debug,
Deserialize,
Display,
EnumIter,
Eq,
JsonSchema,
Ord,
PartialEq,
PartialOrd,
Serialize,
)]
// The OpenSearch configuration uses snake_case. To make it easier to match the log output of
// OpenSearch with this cluster configuration, snake_case is also used here.
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
pub enum NodeRole {
// Built-in node roles
// see https://github.com/opensearch-project/OpenSearch/blob/3.0.0/server/src/main/java/org/opensearch/cluster/node/DiscoveryNodeRole.java#L341-L346

// TODO https://github.com/Peternator7/strum/issues/113
#[strum(serialize = "cluster_manager")]
ClusterManager,
#[strum(serialize = "coordinating_only")]
CoordinatingOnly,
#[strum(serialize = "data")]
Data,
#[strum(serialize = "ingest")]
Ingest,
#[strum(serialize = "remote_cluster_client")]
RemoteClusterClient,
#[strum(serialize = "warm")]
Warm,

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

Expand Down Expand Up @@ -257,3 +259,29 @@ impl NodeRoles {
}

impl Atomic for NodeRoles {}

#[cfg(test)]
mod tests {
use crate::crd::v1alpha1;

#[test]
fn test_node_role() {
assert_eq!(
String::from("cluster_manager"),
v1alpha1::NodeRole::ClusterManager.to_string()
);
assert_eq!(
String::from("cluster_manager"),
format!("{}", v1alpha1::NodeRole::ClusterManager)
);
assert_eq!(
"\"cluster_manager\"",
serde_json::to_string(&v1alpha1::NodeRole::ClusterManager)
.expect("should be serializable")
);
assert_eq!(
v1alpha1::NodeRole::ClusterManager,
serde_json::from_str("\"cluster_manager\"").expect("should be deserializable")
);
}
}
3 changes: 2 additions & 1 deletion rust/operator-binary/src/framework.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,9 @@ pub enum Error {
},
}

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

Expand Down
Loading