diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bce4356..6272b94a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,8 +9,16 @@ ### Changed - `operator-rs` `0.56.1` -> `0.57.0` ([#425]). +- Use new label builders ([#438]). +- Use Snafu instead of thiserror ([#438]). + +### Removed + +- [BREAKING] Removed legacy node selector on roleGroups ([#438]). + [#425]: https://github.com/stackabletech/hbase-operator/pull/425 +[#438]: https://github.com/stackabletech/hbase-operator/pull/438 ## [23.11.0] - 2023-11-24 diff --git a/Cargo.lock b/Cargo.lock index 030449d4..fd58ed06 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -423,6 +423,17 @@ dependencies = [ "syn 2.0.38", ] +[[package]] +name = "delegate" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e018fccbeeb50ff26562ece792ed06659b9c2dae79ece77c4456bb10d9bf79b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + [[package]] name = "derivative" version = "2.2.0" @@ -673,12 +684,6 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - [[package]] name = "hashbrown" version = "0.14.1" @@ -840,16 +845,6 @@ dependencies = [ "unicode-normalization", ] -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", -] - [[package]] name = "indexmap" version = "2.0.2" @@ -857,7 +852,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" dependencies = [ "equivalent", - "hashbrown 0.14.1", + "hashbrown", ] [[package]] @@ -1039,7 +1034,7 @@ dependencies = [ "backoff", "derivative", "futures 0.3.28", - "hashbrown 0.14.1", + "hashbrown", "json-patch", "k8s-openapi", "kube-client", @@ -1199,71 +1194,62 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "opentelemetry" -version = "0.20.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9591d937bc0e6d2feb6f71a559540ab300ea49955229c347a517a28d27784c54" +checksum = "1e32339a5dc40459130b3bd269e9892439f55b33e772d2a9d402a789baaf4e8a" dependencies = [ - "opentelemetry_api", - "opentelemetry_sdk", + "futures-core", + "futures-sink", + "indexmap", + "js-sys", + "once_cell", + "pin-project-lite", + "thiserror", + "urlencoding", ] [[package]] name = "opentelemetry-jaeger" -version = "0.19.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "876958ba9084f390f913fcf04ddf7bbbb822898867bb0a51cc28f2b9e5c1b515" +checksum = "e617c66fd588e40e0dbbd66932fdc87393095b125d4459b1a3a10feb1712f8a1" dependencies = [ "async-trait", "futures-core", "futures-util", "opentelemetry", "opentelemetry-semantic-conventions", + "opentelemetry_sdk", "thrift", "tokio", ] [[package]] name = "opentelemetry-semantic-conventions" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73c9f9340ad135068800e7f1b24e9e09ed9e7143f5bf8518ded3d3ec69789269" +checksum = "f5774f1ef1f982ef2a447f6ee04ec383981a3ab99c8e77a1a7b30182e65bbc84" dependencies = [ "opentelemetry", ] -[[package]] -name = "opentelemetry_api" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a81f725323db1b1206ca3da8bb19874bbd3f57c3bcd59471bfb04525b265b9b" -dependencies = [ - "futures-channel", - "futures-util", - "indexmap 1.9.3", - "js-sys", - "once_cell", - "pin-project-lite", - "thiserror", - "urlencoding", -] - [[package]] name = "opentelemetry_sdk" -version = "0.20.0" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa8e705a0612d48139799fcbaba0d4a90f06277153e43dd2bdc16c6f0edd8026" +checksum = "2f16aec8a98a457a52664d69e0091bac3a0abd18ead9b641cb00202ba4e0efe4" dependencies = [ "async-trait", "crossbeam-channel", "futures-channel", "futures-executor", "futures-util", + "glob", "once_cell", - "opentelemetry_api", - "ordered-float 3.9.2", + "opentelemetry", + "ordered-float 4.2.0", "percent-encoding", "rand", - "regex", "thiserror", "tokio", "tokio-stream", @@ -1280,9 +1266,9 @@ dependencies = [ [[package]] name = "ordered-float" -version = "3.9.2" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1e1c390732d15f1d48471625cd92d154e66db2c56645e29a9cd26f4699f72dc" +checksum = "a76df7075c7d4d01fdcb46c912dd17fba5b60c78ea480b475f2b6ab6f666584e" dependencies = [ "num-traits", ] @@ -1796,7 +1782,7 @@ version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" dependencies = [ - "indexmap 2.0.2", + "indexmap", "itoa", "ryu", "serde", @@ -1817,7 +1803,7 @@ version = "0.9.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a49e178e4452f45cb61d0cd8cebc1b0fafd3e41929e996cef79aa3aca91f574" dependencies = [ - "indexmap 2.0.2", + "indexmap", "itoa", "ryu", "serde", @@ -1972,12 +1958,13 @@ dependencies = [ [[package]] name = "stackable-operator" -version = "0.57.0" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=0.57.0#ab5c5c3f220ae9449e82f6861f44a4a9a6fb7b6b" +version = "0.60.1" +source = "git+https://github.com/stackabletech/operator-rs.git?tag=0.60.1#21f98e8937dac924e374b54bd6ea1d7b2367ca69" dependencies = [ "chrono", "clap", "const_format", + "delegate", "derivative", "dockerfile-parser", "either", @@ -1988,6 +1975,7 @@ dependencies = [ "lazy_static", "opentelemetry", "opentelemetry-jaeger", + "opentelemetry_sdk", "product-config", "rand", "regex", @@ -2004,12 +1992,13 @@ dependencies = [ "tracing", "tracing-opentelemetry", "tracing-subscriber", + "url", ] [[package]] name = "stackable-operator-derive" -version = "0.57.0" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=0.57.0#ab5c5c3f220ae9449e82f6861f44a4a9a6fb7b6b" +version = "0.60.1" +source = "git+https://github.com/stackabletech/operator-rs.git?tag=0.60.1#21f98e8937dac924e374b54bd6ea1d7b2367ca69" dependencies = [ "darling", "proc-macro2", @@ -2237,7 +2226,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.0.2", + "indexmap", "serde", "serde_spanned", "toml_datetime", @@ -2339,20 +2328,33 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + [[package]] name = "tracing-opentelemetry" -version = "0.21.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75327c6b667828ddc28f5e3f169036cb793c3f588d83bf0f262a7f062ffed3c8" +checksum = "c67ac25c5407e7b961fafc6f7e9aa5958fd297aada2d20fa2ae1737357e55596" dependencies = [ + "js-sys", "once_cell", "opentelemetry", "opentelemetry_sdk", "smallvec", "tracing", "tracing-core", - "tracing-log", + "tracing-log 0.2.0", "tracing-subscriber", + "web-time", ] [[package]] @@ -2370,7 +2372,7 @@ dependencies = [ "thread_local", "tracing", "tracing-core", - "tracing-log", + "tracing-log 0.1.3", ] [[package]] @@ -2559,6 +2561,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa30049b1c872b72c89866d458eae9f20380ab280ffd1b1e18df2d3e2d98cfe0" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index 5ec112da..f3f1d3cb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,5 @@ [workspace] -members = [ - "rust/crd", "rust/operator-binary" -] +members = ["rust/crd", "rust/operator-binary"] resolver = "2" [workspace.package] @@ -13,7 +11,7 @@ repository = "https://github.com/stackabletech/hbase-operator" [workspace.dependencies] anyhow = "1.0" -built = { version = "0.6", features = ["chrono", "git2"] } +built = { version = "0.6", features = ["chrono", "git2"] } clap = "4.3" fnv = "1.0" futures = { version = "0.3", features = ["compat"] } @@ -22,7 +20,7 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" serde_yaml = "0.9" snafu = "0.7" -stackable-operator = { git = "https://github.com/stackabletech/operator-rs.git", tag = "0.57.0" } +stackable-operator = { git = "https://github.com/stackabletech/operator-rs.git", tag = "0.60.1" } product-config = { git = "https://github.com/stackabletech/product-config.git", tag = "0.6.0" } strum = { version = "0.25", features = ["derive"] } tokio = { version = "1.29", features = ["full"] } diff --git a/deploy/helm/hbase-operator/crds/crds.yaml b/deploy/helm/hbase-operator/crds/crds.yaml index ee806a26..472d0635 100644 --- a/deploy/helm/hbase-operator/crds/crds.yaml +++ b/deploy/helm/hbase-operator/crds/crds.yaml @@ -7012,37 +7012,6 @@ spec: minimum: 0.0 nullable: true type: integer - selector: - description: A label selector is a label query over a set of resources. The result of matchLabels and matchExpressions are ANDed. An empty label selector matches all objects. A null label selector matches no objects. - nullable: true - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object type: object type: object required: @@ -13940,37 +13909,6 @@ spec: minimum: 0.0 nullable: true type: integer - selector: - description: A label selector is a label query over a set of resources. The result of matchLabels and matchExpressions are ANDed. An empty label selector matches all objects. A null label selector matches no objects. - nullable: true - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object type: object type: object required: @@ -20868,37 +20806,6 @@ spec: minimum: 0.0 nullable: true type: integer - selector: - description: A label selector is a label query over a set of resources. The result of matchLabels and matchExpressions are ANDed. An empty label selector matches all objects. A null label selector matches no objects. - nullable: true - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object type: object type: object required: diff --git a/rust/crd/src/affinity.rs b/rust/crd/src/affinity.rs index 95fbaff8..8825be76 100644 --- a/rust/crd/src/affinity.rs +++ b/rust/crd/src/affinity.rs @@ -84,11 +84,10 @@ mod tests { use crate::HbaseCluster; use stackable_operator::{ - commons::affinity::{StackableAffinity, StackableNodeSelector}, + commons::affinity::StackableAffinity, k8s_openapi::{ api::core::v1::{ - NodeAffinity, NodeSelector, NodeSelectorRequirement, NodeSelectorTerm, PodAffinity, - PodAffinityTerm, PodAntiAffinity, WeightedPodAffinityTerm, + PodAffinity, PodAffinityTerm, PodAntiAffinity, WeightedPodAffinityTerm, }, apimachinery::pkg::apis::meta::v1::LabelSelector, }, @@ -219,124 +218,4 @@ mod tests { } ); } - - #[test] - fn test_affinity_legacy_node_selector() { - let input = r#" - apiVersion: hbase.stackable.tech/v1alpha1 - kind: HbaseCluster - metadata: - name: simple-hbase - spec: - image: - productVersion: 2.4.17 - clusterConfig: - hdfsConfigMapName: simple-hdfs - zookeeperConfigMapName: simple-znode - masters: - roleGroups: - default: - replicas: 1 - selector: - matchLabels: - disktype: ssd - matchExpressions: - - key: topology.kubernetes.io/zone - operator: In - values: - - antarctica-east1 - - antarctica-west1 - regionServers: - roleGroups: - default: - replicas: 1 - restServers: - roleGroups: - default: - replicas: 1 - "#; - let hbase: HbaseCluster = serde_yaml::from_str(input).expect("illegal test input"); - let merged_config = hbase - .merged_config( - &HbaseRole::Master, - "default", - &hbase.spec.cluster_config.hdfs_config_map_name, - ) - .unwrap(); - - assert_eq!( - merged_config.affinity, - StackableAffinity { - pod_affinity: Some(PodAffinity { - preferred_during_scheduling_ignored_during_execution: Some(vec![ - WeightedPodAffinityTerm { - pod_affinity_term: PodAffinityTerm { - label_selector: Some(LabelSelector { - match_expressions: None, - match_labels: Some(BTreeMap::from([ - ("app.kubernetes.io/name".to_string(), "hbase".to_string(),), - ( - "app.kubernetes.io/instance".to_string(), - "simple-hbase".to_string(), - ), - ])) - }), - namespace_selector: None, - namespaces: None, - topology_key: "kubernetes.io/hostname".to_string(), - }, - weight: 20 - } - ]), - required_during_scheduling_ignored_during_execution: None, - }), - pod_anti_affinity: Some(PodAntiAffinity { - preferred_during_scheduling_ignored_during_execution: Some(vec![ - WeightedPodAffinityTerm { - pod_affinity_term: PodAffinityTerm { - label_selector: Some(LabelSelector { - match_expressions: None, - match_labels: Some(BTreeMap::from([ - ("app.kubernetes.io/name".to_string(), "hbase".to_string(),), - ( - "app.kubernetes.io/instance".to_string(), - "simple-hbase".to_string(), - ), - ( - "app.kubernetes.io/component".to_string(), - "master".to_string(), - ) - ])) - }), - namespace_selector: None, - namespaces: None, - topology_key: "kubernetes.io/hostname".to_string(), - }, - weight: 70 - } - ]), - required_during_scheduling_ignored_during_execution: None, - }), - node_affinity: Some(NodeAffinity { - preferred_during_scheduling_ignored_during_execution: None, - required_during_scheduling_ignored_during_execution: Some(NodeSelector { - node_selector_terms: vec![NodeSelectorTerm { - match_expressions: Some(vec![NodeSelectorRequirement { - key: "topology.kubernetes.io/zone".to_string(), - operator: "In".to_string(), - values: Some(vec![ - "antarctica-east1".to_string(), - "antarctica-west1".to_string() - ]), - }]), - match_fields: None, - }] - }), - }), - node_selector: Some(StackableNodeSelector { - node_selector: BTreeMap::from([("disktype".to_string(), "ssd".to_string())]) - }), - } - ); - } } diff --git a/rust/crd/src/lib.rs b/rust/crd/src/lib.rs index 61be136c..64644b3b 100644 --- a/rust/crd/src/lib.rs +++ b/rust/crd/src/lib.rs @@ -73,10 +73,13 @@ pub enum Error { source: strum::ParseError, role: String, }, + #[snafu(display("the HBase role [{role}] is missing from spec"))] MissingHbaseRole { role: String }, + #[snafu(display("the HBase role group [{role_group}] is missing from spec"))] MissingHbaseRoleGroup { role_group: String }, + #[snafu(display("fragment validation failure"))] FragmentValidationFailure { source: ValidationError }, } @@ -166,6 +169,7 @@ pub enum CurrentlySupportedListenerClasses { #[default] #[serde(rename = "cluster-internal")] ClusterInternal, + #[serde(rename = "external-unstable")] ExternalUnstable, } @@ -196,9 +200,11 @@ pub enum HbaseRole { #[serde(rename = "master")] #[strum(serialize = "master")] Master, + #[serde(rename = "regionserver")] #[strum(serialize = "regionserver")] RegionServer, + #[serde(rename = "restserver")] #[strum(serialize = "restserver")] RestServer, @@ -520,17 +526,6 @@ impl HbaseCluster { .map(|rg| rg.config.config.clone()) .unwrap_or_default(); - if let Some(RoleGroup { - selector: Some(selector), - .. - }) = role.role_groups.get(role_group) - { - // Migrate old `selector` attribute, see ADR 26 affinities. - // TODO Can be removed after support for the old `selector` field is dropped. - #[allow(deprecated)] - conf_rolegroup.affinity.add_legacy_selector(selector); - } - // Merge more specific configs into default config // Hierarchy is: // 1. RoleGroup diff --git a/rust/operator-binary/src/discovery.rs b/rust/operator-binary/src/discovery.rs index 358d6953..d3590117 100644 --- a/rust/operator-binary/src/discovery.rs +++ b/rust/operator-binary/src/discovery.rs @@ -1,37 +1,61 @@ use std::collections::BTreeMap; use product_config::writer::to_hadoop_xml; +use snafu::{ResultExt, Snafu}; use stackable_hbase_crd::{HbaseCluster, HbaseRole, HBASE_SITE_XML}; use stackable_operator::{ - builder::{ConfigMapBuilder, ObjectMetaBuilder}, + builder::{ConfigMapBuilder, ObjectMetaBuilder, ObjectMetaBuilderError}, commons::product_image_selection::ResolvedProductImage, - error::OperatorResult, k8s_openapi::api::core::v1::ConfigMap, + kube::runtime::reflector::ObjectRef, }; use crate::{ hbase_controller::build_recommended_labels, zookeeper::ZookeeperConnectionInformation, }; +type Result = std::result::Result; + +#[derive(Snafu, Debug)] +pub enum Error { + #[snafu(display("object {hbase} is missing metadata to build owner reference"))] + ObjectMissingMetadataForOwnerRef { + source: stackable_operator::error::Error, + hbase: ObjectRef, + }, + + #[snafu(display("failed to build ConfigMap"))] + BuildConfigMap { + source: stackable_operator::error::Error, + }, + + #[snafu(display("failed to build object meta data"))] + ObjectMeta { source: ObjectMetaBuilderError }, +} + /// Creates a discovery config map containing the `hbase-site.xml` for clients. pub fn build_discovery_configmap( hbase: &HbaseCluster, zookeeper_connection_information: &ZookeeperConnectionInformation, resolved_product_image: &ResolvedProductImage, -) -> OperatorResult { +) -> Result { let hbase_site = zookeeper_connection_information.as_hbase_settings(); ConfigMapBuilder::new() .metadata( ObjectMetaBuilder::new() .name_and_namespace(hbase) - .ownerreference_from_resource(hbase, None, Some(true))? + .ownerreference_from_resource(hbase, None, Some(true)) + .with_context(|_| ObjectMissingMetadataForOwnerRefSnafu { + hbase: ObjectRef::from_obj(hbase), + })? .with_recommended_labels(build_recommended_labels( hbase, &resolved_product_image.app_version_label, &HbaseRole::RegionServer.to_string(), "discovery", )) + .context(ObjectMetaSnafu)? .build(), ) .add_data( @@ -45,4 +69,5 @@ pub fn build_discovery_configmap( ), ) .build() + .context(BuildConfigMapSnafu) } diff --git a/rust/operator-binary/src/hbase_controller.rs b/rust/operator-binary/src/hbase_controller.rs index 96b08a92..5fec59e6 100644 --- a/rust/operator-binary/src/hbase_controller.rs +++ b/rust/operator-binary/src/hbase_controller.rs @@ -1,6 +1,7 @@ //! Ensures that `Pod`s are configured and running for each [`HbaseCluster`] use std::{ collections::{BTreeMap, HashMap}, + fmt::Write, str::FromStr, sync::Arc, }; @@ -20,7 +21,7 @@ use stackable_hbase_crd::{ use stackable_operator::{ builder::{ resources::ResourceRequirementsBuilder, ConfigMapBuilder, ContainerBuilder, - ObjectMetaBuilder, PodBuilder, PodSecurityContextBuilder, + ObjectMetaBuilder, ObjectMetaBuilderError, PodBuilder, PodSecurityContextBuilder, }, cluster_resources::{ClusterResourceApplyStrategy, ClusterResources}, commons::{ @@ -39,7 +40,7 @@ use stackable_operator::{ DeepMerge, }, kube::{runtime::controller::Action, Resource}, - labels::{role_group_selector_labels, role_selector_labels, ObjectLabels}, + kvp::{Label, LabelError, Labels, ObjectLabels}, logging::controller::ReconcilerError, memory::{BinaryMultiple, MemoryQuantity}, product_config_utils::{transform_all_roles_to_config, validate_all_roles_and_groups_config}, @@ -96,119 +97,148 @@ pub struct Ctx { pub enum Error { #[snafu(display("object defines no version"))] ObjectHasNoVersion, + #[snafu(display("object defines no namespace"))] ObjectHasNoNamespace, + #[snafu(display("object defines no master role"))] NoMasterRole, + #[snafu(display("object defines no regionserver role"))] NoRegionServerRole, + #[snafu(display("failed to calculate global service name"))] GlobalServiceNameNotFound, + #[snafu(display("failed to create cluster resources"))] CreateClusterResources { source: stackable_operator::error::Error, }, + #[snafu(display("failed to delete orphaned resources"))] DeleteOrphanedResources { source: stackable_operator::error::Error, }, + #[snafu(display("failed to apply global Service"))] ApplyRoleService { source: stackable_operator::error::Error, }, + #[snafu(display("failed to apply Service for {}", rolegroup))] ApplyRoleGroupService { source: stackable_operator::error::Error, rolegroup: RoleGroupRef, }, + #[snafu(display("failed to apply discovery configmap"))] ApplyDiscoveryConfigMap { source: stackable_operator::error::Error, }, + #[snafu(display("failed to build discovery configmap"))] - BuildDiscoveryConfigMap { - source: stackable_operator::error::Error, - }, + BuildDiscoveryConfigMap { source: super::discovery::Error }, + #[snafu(display("failed to build ConfigMap for {}", rolegroup))] BuildRoleGroupConfig { source: stackable_operator::error::Error, rolegroup: RoleGroupRef, }, + #[snafu(display("failed to apply ConfigMap for {}", rolegroup))] ApplyRoleGroupConfig { source: stackable_operator::error::Error, rolegroup: RoleGroupRef, }, + #[snafu(display("failed to apply StatefulSet for {}", rolegroup))] ApplyRoleGroupStatefulSet { source: stackable_operator::error::Error, rolegroup: RoleGroupRef, }, + #[snafu(display("failed to generate product config"))] GenerateProductConfig { source: stackable_operator::product_config_utils::ConfigError, }, + #[snafu(display("invalid product config"))] InvalidProductConfig { source: stackable_operator::error::Error, }, + #[snafu(display("failed to retrieve zookeeper connection information"))] RetrieveZookeeperConnectionInformation { source: zookeeper::Error }, + #[snafu(display("object is missing metadata to build owner reference"))] ObjectMissingMetadataForOwnerRef { source: stackable_operator::error::Error, }, + #[snafu(display("no configmap_name for {cm_name} discovery is configured"))] MissingConfigMap { source: stackable_operator::error::Error, cm_name: String, }, + #[snafu(display("failed to retrieve the entry {entry} for config map {cm_name}"))] MissingConfigMapEntry { entry: &'static str, cm_name: String, }, + #[snafu(display("failed to patch service account"))] ApplyServiceAccount { source: stackable_operator::error::Error, }, + #[snafu(display("failed to patch role binding"))] ApplyRoleBinding { source: stackable_operator::error::Error, }, + #[snafu(display("could not parse Hbase role [{role}]"))] UnidentifiedHbaseRole { source: strum::ParseError, role: String, }, + #[snafu(display("failed to retrieve Hbase role group: {source}"))] UnidentifiedHbaseRoleGroup { source: stackable_hbase_crd::Error }, + #[snafu(display("failed to resolve and merge config for role and role group"))] FailedToResolveConfig { source: stackable_hbase_crd::Error }, + #[snafu(display("invalid java heap config - missing default or value in crd?"))] InvalidJavaHeapConfig, + #[snafu(display("failed to convert java heap config to unit [{unit}]"))] FailedToConvertJavaHeap { source: stackable_operator::error::Error, unit: String, }, + #[snafu(display("failed to resolve the Vector aggregator address"))] ResolveVectorAggregatorAddress { source: crate::product_logging::Error, }, + #[snafu(display("failed to add the logging configuration to the ConfigMap [{cm_name}]"))] InvalidLoggingConfig { source: crate::product_logging::Error, cm_name: String, }, + #[snafu(display("failed to update status"))] ApplyStatus { source: stackable_operator::error::Error, }, + #[snafu(display("failed to build RBAC resources"))] BuildRbacResources { source: stackable_operator::error::Error, }, + #[snafu(display( "failed to serialize [{JVM_SECURITY_PROPERTIES_FILE}] for {}", rolegroup @@ -217,6 +247,7 @@ pub enum Error { source: PropertiesWriterError, rolegroup: RoleGroupRef, }, + #[snafu(display("failed to create PodDisruptionBudget"))] FailedToCreatePdb { source: crate::operations::pdb::Error, @@ -226,6 +257,12 @@ pub enum Error { GracefulShutdown { source: crate::operations::graceful_shutdown::Error, }, + + #[snafu(display("failed to build label"))] + BuildLabel { source: LabelError }, + + #[snafu(display("failed to build object meta data"))] + ObjectMeta { source: ObjectMetaBuilderError }, } type Result = std::result::Result; @@ -296,7 +333,9 @@ pub async fn reconcile_hbase(hbase: Arc, ctx: Arc) -> Result< let (rbac_sa, rbac_rolebinding) = build_rbac_resources( hbase.as_ref(), APP_NAME, - cluster_resources.get_required_labels(), + cluster_resources + .get_required_labels() + .context(BuildLabelSnafu)?, ) .context(BuildRbacResourcesSnafu)?; cluster_resources @@ -420,25 +459,33 @@ pub fn build_region_server_role_service( }) .collect(); + let metadata = ObjectMetaBuilder::new() + .name_and_namespace(hbase) + .name(&role_svc_name) + .ownerreference_from_resource(hbase, None, Some(true)) + .context(ObjectMissingMetadataForOwnerRefSnafu)? + .with_recommended_labels(build_recommended_labels( + hbase, + &resolved_product_image.app_version_label, + &role_name, + "global", + )) + .context(ObjectMetaSnafu)? + .build(); + + let service_selector_labels = + Labels::role_selector(hbase, APP_NAME, &role_name).context(BuildLabelSnafu)?; + + let service_spec = ServiceSpec { + type_: Some(hbase.spec.cluster_config.listener_class.k8s_service_type()), + ports: Some(ports), + selector: Some(service_selector_labels.into()), + ..ServiceSpec::default() + }; + Ok(Service { - metadata: ObjectMetaBuilder::new() - .name_and_namespace(hbase) - .name(&role_svc_name) - .ownerreference_from_resource(hbase, None, Some(true)) - .context(ObjectMissingMetadataForOwnerRefSnafu)? - .with_recommended_labels(build_recommended_labels( - hbase, - &resolved_product_image.app_version_label, - &role_name, - "global", - )) - .build(), - spec: Some(ServiceSpec { - type_: Some(hbase.spec.cluster_config.listener_class.k8s_service_type()), - ports: Some(ports), - selector: Some(role_selector_labels(hbase, APP_NAME, &role_name)), - ..ServiceSpec::default() - }), + metadata, + spec: Some(service_spec), status: None, }) } @@ -497,21 +544,22 @@ fn build_rolegroup_config_map( let mut builder = ConfigMapBuilder::new(); + let cm_metadata = ObjectMetaBuilder::new() + .name_and_namespace(hbase) + .name(rolegroup.object_name()) + .ownerreference_from_resource(hbase, None, Some(true)) + .context(ObjectMissingMetadataForOwnerRefSnafu)? + .with_recommended_labels(build_recommended_labels( + hbase, + &resolved_product_image.app_version_label, + &rolegroup.role, + &rolegroup.role_group, + )) + .context(ObjectMetaSnafu)? + .build(); + builder - .metadata( - ObjectMetaBuilder::new() - .name_and_namespace(hbase) - .name(rolegroup.object_name()) - .ownerreference_from_resource(hbase, None, Some(true)) - .context(ObjectMissingMetadataForOwnerRefSnafu)? - .with_recommended_labels(build_recommended_labels( - hbase, - &resolved_product_image.app_version_label, - &rolegroup.role, - &rolegroup.role_group, - )) - .build(), - ) + .metadata(cm_metadata) .add_data( HBASE_SITE_XML, to_hadoop_xml( @@ -568,34 +616,41 @@ fn build_rolegroup_service( }) .collect(); + let prometheus_label = + Label::try_from(("prometheus.io/scrape", "true")).context(BuildLabelSnafu)?; + + let metadata = ObjectMetaBuilder::new() + .name_and_namespace(hbase) + .name(&rolegroup.object_name()) + .ownerreference_from_resource(hbase, None, Some(true)) + .context(ObjectMissingMetadataForOwnerRefSnafu)? + .with_recommended_labels(build_recommended_labels( + hbase, + &resolved_product_image.app_version_label, + &rolegroup.role, + &rolegroup.role_group, + )) + .context(ObjectMetaSnafu)? + .with_label(prometheus_label) + .build(); + + let service_selector = + Labels::role_group_selector(hbase, APP_NAME, &rolegroup.role, &rolegroup.role_group) + .context(BuildLabelSnafu)?; + + let service_spec = ServiceSpec { + // Internal communication does not need to be exposed + type_: Some("ClusterIP".to_string()), + cluster_ip: Some("None".to_string()), + ports: Some(ports), + selector: Some(service_selector.into()), + publish_not_ready_addresses: Some(true), + ..ServiceSpec::default() + }; + Ok(Service { - metadata: ObjectMetaBuilder::new() - .name_and_namespace(hbase) - .name(&rolegroup.object_name()) - .ownerreference_from_resource(hbase, None, Some(true)) - .context(ObjectMissingMetadataForOwnerRefSnafu)? - .with_recommended_labels(build_recommended_labels( - hbase, - &resolved_product_image.app_version_label, - &rolegroup.role, - &rolegroup.role_group, - )) - .with_label("prometheus.io/scrape", "true") - .build(), - spec: Some(ServiceSpec { - // Internal communication does not need to be exposed - type_: Some("ClusterIP".to_string()), - cluster_ip: Some("None".to_string()), - ports: Some(ports), - selector: Some(role_group_selector_labels( - hbase, - APP_NAME, - &rolegroup.role, - &rolegroup.role_group, - )), - publish_not_ready_addresses: Some(true), - ..ServiceSpec::default() - }), + metadata, + spec: Some(service_spec), status: None, }) } @@ -723,15 +778,19 @@ wait_for_termination $! .build(); let mut pod_builder = PodBuilder::new(); + + let pb_metadata = ObjectMetaBuilder::new() + .with_recommended_labels(build_recommended_labels( + hbase, + hbase_version, + &rolegroup_ref.role, + &rolegroup_ref.role_group, + )) + .context(ObjectMetaSnafu)? + .build(); + pod_builder - .metadata_builder(|m| { - m.with_recommended_labels(build_recommended_labels( - hbase, - hbase_version, - &rolegroup_ref.role, - &rolegroup_ref.role_group, - )) - }) + .metadata(pb_metadata) .image_pull_secrets_from_product_image(resolved_product_image) .affinity(&config.affinity) .add_container(container) @@ -817,35 +876,43 @@ wait_for_termination $! pod_template.merge_from(role_group.config.pod_overrides.clone()); } + let metadata = ObjectMetaBuilder::new() + .name_and_namespace(hbase) + .name(&rolegroup_ref.object_name()) + .ownerreference_from_resource(hbase, None, Some(true)) + .context(ObjectMissingMetadataForOwnerRefSnafu)? + .with_recommended_labels(build_recommended_labels( + hbase, + hbase_version, + &rolegroup_ref.role, + &rolegroup_ref.role_group, + )) + .context(ObjectMetaSnafu)? + .build(); + + let statefulset_match_labels = Labels::role_group_selector( + hbase, + APP_NAME, + &rolegroup_ref.role, + &rolegroup_ref.role_group, + ) + .context(BuildLabelSnafu)?; + + let statefulset_spec = StatefulSetSpec { + pod_management_policy: Some("Parallel".to_string()), + replicas: role_group.and_then(|rg| rg.replicas).map(i32::from), + selector: LabelSelector { + match_labels: Some(statefulset_match_labels.into()), + ..LabelSelector::default() + }, + service_name: rolegroup_ref.object_name(), + template: pod_template, + ..StatefulSetSpec::default() + }; + Ok(StatefulSet { - metadata: ObjectMetaBuilder::new() - .name_and_namespace(hbase) - .name(&rolegroup_ref.object_name()) - .ownerreference_from_resource(hbase, None, Some(true)) - .context(ObjectMissingMetadataForOwnerRefSnafu)? - .with_recommended_labels(build_recommended_labels( - hbase, - hbase_version, - &rolegroup_ref.role, - &rolegroup_ref.role_group, - )) - .build(), - spec: Some(StatefulSetSpec { - pod_management_policy: Some("Parallel".to_string()), - replicas: role_group.and_then(|rg| rg.replicas).map(i32::from), - selector: LabelSelector { - match_labels: Some(role_group_selector_labels( - hbase, - APP_NAME, - &rolegroup_ref.role, - &rolegroup_ref.role_group, - )), - ..LabelSelector::default() - }, - service_name: rolegroup_ref.object_name(), - template: pod_template, - ..StatefulSetSpec::default() - }), + metadata, + spec: Some(statefulset_spec), status: None, }) } @@ -898,9 +965,10 @@ fn write_hbase_env_sh<'a, T>(properties: T) -> String where T: Iterator, { - properties - .map(|(variable, value)| format!("export {variable}=\"{value}\"\n")) - .collect() + properties.fold(String::new(), |mut output, (variable, value)| { + let _ = writeln!(output, "export {variable}=\"{value}\""); + output + }) } pub fn error_policy(_obj: Arc, _error: &Error, _ctx: Arc) -> Action { diff --git a/rust/operator-binary/src/operations/pdb.rs b/rust/operator-binary/src/operations/pdb.rs index ccee1546..4f3b95a1 100644 --- a/rust/operator-binary/src/operations/pdb.rs +++ b/rust/operator-binary/src/operations/pdb.rs @@ -14,6 +14,7 @@ pub enum Error { source: stackable_operator::error::Error, role: String, }, + #[snafu(display("Cannot apply PodDisruptionBudget [{name}]"))] ApplyPdb { source: stackable_operator::error::Error, diff --git a/rust/operator-binary/src/product_logging.rs b/rust/operator-binary/src/product_logging.rs index fc05fdd1..ac77a679 100644 --- a/rust/operator-binary/src/product_logging.rs +++ b/rust/operator-binary/src/product_logging.rs @@ -19,18 +19,22 @@ use crate::hbase_controller::{MAX_HBASE_LOG_FILES_SIZE, STACKABLE_LOG_DIR}; pub enum Error { #[snafu(display("object has no namespace"))] ObjectHasNoNamespace, + #[snafu(display("failed to retrieve the ConfigMap [{cm_name}]"))] ConfigMapNotFound { source: stackable_operator::error::Error, cm_name: String, }, + #[snafu(display("failed to retrieve the entry [{entry}] for ConfigMap [{cm_name}]"))] MissingConfigMapEntry { entry: &'static str, cm_name: String, }, + #[snafu(display("crd validation failure"))] CrdValidationFailure { source: stackable_hbase_crd::Error }, + #[snafu(display("vectorAggregatorConfigMapName must be set"))] MissingVectorAggregatorAddress, } diff --git a/rust/operator-binary/src/zookeeper.rs b/rust/operator-binary/src/zookeeper.rs index 6ec1cb1f..51b4d94c 100644 --- a/rust/operator-binary/src/zookeeper.rs +++ b/rust/operator-binary/src/zookeeper.rs @@ -22,13 +22,16 @@ const ZOOKEEPER_ZNODE_PARENT: &str = "zookeeper.znode.parent"; pub enum Error { #[snafu(display("object defines no namespace"))] ObjectHasNoNamespace, + #[snafu(display("failed to retrieve ConfigMap {cm_name}"))] MissingConfigMap { source: stackable_operator::error::Error, cm_name: String, }, + #[snafu(display("failed to retrieve the entry {entry} for ConfigMap {cm_name}"))] MissingConfigMapEntry { cm_name: String, entry: String }, + #[snafu(display("failed to parse the zookeeper port from ConfigMap {cm_name} entry {entry}"))] ParseZookeeperPort { source: ParseIntError,