diff --git a/CHANGELOG.md b/CHANGELOG.md index f3f92a66..020619c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,9 +4,14 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +### Added + +- `Listener.status.addresses` can now be configured to prefer either IP addresses or DNS hostnames ([#233]). + ### Changed -- Listener.status.addresses for NodePort listeners now includes replicas that are currently unavailable ([#231]). +- `Listener.status.addresses` for NodePort listeners now includes replicas that are currently unavailable ([#231]). +- `Listener.status.addresses` now defaults to DNS hostnames for all service types (previously NodePort and ClusterIP would prefer IP addresses, [#233]). ### Fixed @@ -15,6 +20,7 @@ All notable changes to this project will be documented in this file. - Propagate `ListenerClass.spec.serviceAnnotations` to the created Services ([#234]). [#231]: https://github.com/stackabletech/listener-operator/pull/231 +[#233]: https://github.com/stackabletech/listener-operator/pull/233 [#234]: https://github.com/stackabletech/listener-operator/pull/234 ## [24.7.0] - 2024-07-24 diff --git a/Cargo.lock b/Cargo.lock index f5fd108d..9707dceb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2324,8 +2324,8 @@ dependencies = [ [[package]] name = "stackable-operator" -version = "0.76.0" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.76.0#a7e70f174fb043a1766e0a80de95834cb4f7513d" +version = "0.78.0" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=main#d51e8a73e94aa65d5d45338edf4959169b65e00e" dependencies = [ "chrono", "clap", @@ -2335,6 +2335,7 @@ dependencies = [ "dockerfile-parser", "either", "futures", + "indexmap 2.5.0", "json-patch", "k8s-openapi", "kube", @@ -2349,6 +2350,7 @@ dependencies = [ "serde_yaml", "snafu 0.8.4", "stackable-operator-derive", + "stackable-shared", "strum", "tokio", "tracing", @@ -2361,7 +2363,7 @@ dependencies = [ [[package]] name = "stackable-operator-derive" version = "0.3.1" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.76.0#a7e70f174fb043a1766e0a80de95834cb4f7513d" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=main#d51e8a73e94aa65d5d45338edf4959169b65e00e" dependencies = [ "darling", "proc-macro2", @@ -2369,6 +2371,18 @@ dependencies = [ "syn 2.0.77", ] +[[package]] +name = "stackable-shared" +version = "0.0.1" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=main#d51e8a73e94aa65d5d45338edf4959169b65e00e" +dependencies = [ + "kube", + "semver", + "serde", + "serde_yaml", + "snafu 0.8.4", +] + [[package]] name = "strsim" version = "0.11.1" diff --git a/Cargo.nix b/Cargo.nix index 17344144..d6ec8b03 100644 --- a/Cargo.nix +++ b/Cargo.nix @@ -7360,13 +7360,13 @@ rec { }; "stackable-operator" = rec { crateName = "stackable-operator"; - version = "0.76.0"; + version = "0.78.0"; edition = "2021"; workspace_member = null; src = pkgs.fetchgit { - url = "https://github.com/stackabletech/operator-rs.git"; - rev = "a7e70f174fb043a1766e0a80de95834cb4f7513d"; - sha256 = "1cyyyn6lizd0wdq79fc9fjnksnzx073ipydxmh7llciq5si5dnq6"; + url = "https://github.com/stackabletech//operator-rs.git"; + rev = "d51e8a73e94aa65d5d45338edf4959169b65e00e"; + sha256 = "0cq00sqbwashcsakd0bfmpzpdvvm3xh1xcsjnwaspm6mfi38a9cj"; }; libName = "stackable_operator"; authors = [ @@ -7407,6 +7407,10 @@ rec { name = "futures"; packageId = "futures"; } + { + name = "indexmap"; + packageId = "indexmap 2.5.0"; + } { name = "json-patch"; packageId = "json-patch"; @@ -7471,6 +7475,10 @@ rec { name = "stackable-operator-derive"; packageId = "stackable-operator-derive"; } + { + name = "stackable-shared"; + packageId = "stackable-shared"; + } { name = "strum"; packageId = "strum"; @@ -7514,9 +7522,9 @@ rec { edition = "2021"; workspace_member = null; src = pkgs.fetchgit { - url = "https://github.com/stackabletech/operator-rs.git"; - rev = "a7e70f174fb043a1766e0a80de95834cb4f7513d"; - sha256 = "1cyyyn6lizd0wdq79fc9fjnksnzx073ipydxmh7llciq5si5dnq6"; + url = "https://github.com/stackabletech//operator-rs.git"; + rev = "d51e8a73e94aa65d5d45338edf4959169b65e00e"; + sha256 = "0cq00sqbwashcsakd0bfmpzpdvvm3xh1xcsjnwaspm6mfi38a9cj"; }; procMacro = true; libName = "stackable_operator_derive"; @@ -7542,6 +7550,47 @@ rec { } ]; + }; + "stackable-shared" = rec { + crateName = "stackable-shared"; + version = "0.0.1"; + edition = "2021"; + workspace_member = null; + src = pkgs.fetchgit { + url = "https://github.com/stackabletech//operator-rs.git"; + rev = "d51e8a73e94aa65d5d45338edf4959169b65e00e"; + sha256 = "0cq00sqbwashcsakd0bfmpzpdvvm3xh1xcsjnwaspm6mfi38a9cj"; + }; + libName = "stackable_shared"; + authors = [ + "Stackable GmbH " + ]; + dependencies = [ + { + name = "kube"; + packageId = "kube"; + usesDefaultFeatures = false; + features = [ "client" "jsonpatch" "runtime" "derive" "rustls-tls" ]; + } + { + name = "semver"; + packageId = "semver"; + } + { + name = "serde"; + packageId = "serde"; + features = [ "derive" ]; + } + { + name = "serde_yaml"; + packageId = "serde_yaml"; + } + { + name = "snafu"; + packageId = "snafu 0.8.4"; + } + ]; + }; "strsim" = rec { crateName = "strsim"; diff --git a/Cargo.toml b/Cargo.toml index 29bc2bf6..66999bac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ prost = "0.13" prost-types = "0.13" serde = "1.0" snafu = "0.8" -stackable-operator = { git = "https://github.com/stackabletech/operator-rs.git", tag = "stackable-operator-0.76.0" } +stackable-operator = { git = "https://github.com/stackabletech/operator-rs.git", tag = "stackable-operator-0.78.0" } strum = { version = "0.26", features = ["derive"] } socket2 = { version = "0.5", features = ["all"] } tokio = { version = "1.40", features = ["full"] } @@ -31,5 +31,6 @@ tonic-build = "0.12" tonic-reflection = "0.12" tracing = "0.1.40" -# [patch."https://github.com/stackabletech/operator-rs.git"] -# stackable-operator = { git = "https://github.com/stackabletech//operator-rs.git", branch = "main" } +[patch."https://github.com/stackabletech/operator-rs.git"] +# stackable-operator = { path = "../operator-rs/crates/stackable-operator" } +stackable-operator = { git = "https://github.com/stackabletech//operator-rs.git", branch = "main" } diff --git a/crate-hashes.json b/crate-hashes.json index 4ac79c7f..258e5db1 100644 --- a/crate-hashes.json +++ b/crate-hashes.json @@ -1,5 +1,6 @@ { - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.76.0#stackable-operator-derive@0.3.1": "1cyyyn6lizd0wdq79fc9fjnksnzx073ipydxmh7llciq5si5dnq6", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.76.0#stackable-operator@0.76.0": "1cyyyn6lizd0wdq79fc9fjnksnzx073ipydxmh7llciq5si5dnq6", + "git+https://github.com/stackabletech//operator-rs.git?branch=main#stackable-operator-derive@0.3.1": "0cq00sqbwashcsakd0bfmpzpdvvm3xh1xcsjnwaspm6mfi38a9cj", + "git+https://github.com/stackabletech//operator-rs.git?branch=main#stackable-operator@0.78.0": "0cq00sqbwashcsakd0bfmpzpdvvm3xh1xcsjnwaspm6mfi38a9cj", + "git+https://github.com/stackabletech//operator-rs.git?branch=main#stackable-shared@0.0.1": "0cq00sqbwashcsakd0bfmpzpdvvm3xh1xcsjnwaspm6mfi38a9cj", "git+https://github.com/stackabletech/product-config.git?tag=0.7.0#product-config@0.7.0": "0gjsm80g6r75pm3824dcyiz4ysq1ka4c1if6k1mjm9cnd5ym0gny" } \ No newline at end of file diff --git a/deploy/helm/listener-operator/crds/crds.yaml b/deploy/helm/listener-operator/crds/crds.yaml index c78a9200..9cc9f087 100644 --- a/deploy/helm/listener-operator/crds/crds.yaml +++ b/deploy/helm/listener-operator/crds/crds.yaml @@ -24,6 +24,16 @@ spec: spec: description: Defines a policy for how [Listeners](https://docs.stackable.tech/home/nightly/listener-operator/listener) should be exposed. Read the [ListenerClass documentation](https://docs.stackable.tech/home/nightly/listener-operator/listenerclass) for more information. properties: + preferredAddressType: + default: Hostname + description: |- + Whether addresses should prefer using the IP address (`IP`) or the hostname (`Hostname`). + + The other type will be used if the preferred type is not available. By default `Hostname` is used. + enum: + - Hostname + - IP + type: string serviceAnnotations: additionalProperties: type: string diff --git a/docs/modules/listener-operator/pages/listenerclass.adoc b/docs/modules/listener-operator/pages/listenerclass.adoc index ce149c50..8bf85ff4 100644 --- a/docs/modules/listener-operator/pages/listenerclass.adoc +++ b/docs/modules/listener-operator/pages/listenerclass.adoc @@ -30,6 +30,7 @@ How exactly this is accomplished depends on the cloud provider in question, but include::example$listenerclass-internal-gke.yaml[] ---- +[#servicetype] == Service types The service type is defined by `ListenerClass.spec.serviceType`. @@ -62,6 +63,24 @@ Compared to xref:#servicetype-nodeport[], this service type allows Pods to be mo However, it requires https://kubernetes.io/docs/concepts/services-networking/service/#loadbalancer[a cloud controller manager that supports load balancers]. Additionally, many cloud providers charge for load-balanced traffic. +[#addresstype] +== Address types + +The Stackable Listener Operator supports both IP addresses and DNS hostnames. The preferred address type for a given ListenerClass can be configured using the `ListenerClass.spec.preferredAddressType` field. If no `preferredAddressType` is specified then it defaults to xref:#addresstype-hostname[]. + +NOTE: If the preferred address type is not supported for a given environment then another type will be used. + +[#addresstype-ip] +=== IP + +The IP address of a resource. The addresses will be less predictable (especially for xref:#servicetype-clusterip[] services), +but does not require any special client configuration (beyond what the xref:#servicetype[] requires). + +[#addresstype-hostname] +=== Hostname + +The DNS hostname of a resource. Clients must be able to resolve these addresses in order to connect, which may require special DNS configuration. + == Default ListenerClasses The Stackable Data Platform assumes the existence of a few predefined ListenerClasses, and will use them by default as appropriate: diff --git a/rust/operator-binary/src/csi_server/controller.rs b/rust/operator-binary/src/csi_server/controller.rs index 6b501321..c14961e7 100644 --- a/rust/operator-binary/src/csi_server/controller.rs +++ b/rust/operator-binary/src/csi_server/controller.rs @@ -8,7 +8,7 @@ use stackable_operator::{ }; use tonic::{Request, Response, Status}; -use crate::utils::error_full_message; +use crate::utils::error::error_full_message; use super::{tonic_unimplemented, ListenerSelector, ListenerVolumeContext}; diff --git a/rust/operator-binary/src/csi_server/node.rs b/rust/operator-binary/src/csi_server/node.rs index 5167ab27..d7c407d5 100644 --- a/rust/operator-binary/src/csi_server/node.rs +++ b/rust/operator-binary/src/csi_server/node.rs @@ -4,8 +4,8 @@ use snafu::{OptionExt, ResultExt, Snafu}; use stackable_operator::{ builder::meta::OwnerReferenceBuilder, commons::listener::{ - Listener, ListenerIngress, ListenerPort, ListenerSpec, PodListener, PodListenerScope, - PodListeners, PodListenersSpec, + Listener, ListenerClass, ListenerIngress, ListenerPort, ListenerSpec, PodListener, + PodListenerScope, PodListeners, PodListenersSpec, }, k8s_openapi::api::core::v1::{Node, PersistentVolume, PersistentVolumeClaim, Pod, Volume}, kube::{ @@ -21,7 +21,7 @@ use crate::{ listener_mounted_pod_label, listener_persistent_volume_label, ListenerMountedPodLabelError, ListenerPersistentVolumeLabelError, }, - utils::{error_full_message, node_primary_address}, + utils::{address::node_primary_addresses, error::error_full_message}, }; use super::{tonic_unimplemented, ListenerSelector, ListenerVolumeContext}; @@ -72,6 +72,9 @@ enum PublishVolumeError { listener: ObjectRef, }, + #[snafu(display("{listener} has no associated ListenerClass"))] + ListenerHasNoClass { listener: ObjectRef }, + #[snafu(display("{pod} has not been scheduled to a node yet"))] PodHasNoNode { pod: ObjectRef }, @@ -131,6 +134,7 @@ impl From for Status { PublishVolumeError::PodHasNoNode { .. } => Status::unavailable(full_msg), PublishVolumeError::ListenerPvReference { .. } => Status::failed_precondition(full_msg), PublishVolumeError::ListenerPodSelector { .. } => Status::failed_precondition(full_msg), + PublishVolumeError::ListenerHasNoClass { .. } => Status::failed_precondition(full_msg), PublishVolumeError::BuildListenerOwnerRef { .. } => Status::unavailable(full_msg), PublishVolumeError::ApplyListener { .. } => Status::unavailable(full_msg), PublishVolumeError::AddListenerLabelToPv { .. } => Status::unavailable(full_msg), @@ -442,10 +446,25 @@ async fn local_listener_addresses_for_pod( .get::(node_name, &()) .await .with_context(|_| GetObjectSnafu { - obj: { ObjectRef::::new(node_name).erase() }, + obj: ObjectRef::::new(node_name).erase(), + })?; + let listener_class_name = + listener + .spec + .class_name + .as_deref() + .with_context(|| ListenerHasNoClassSnafu { + listener: ObjectRef::from_obj(listener), + })?; + let listener_class = client + .get::(listener_class_name, &()) + .await + .with_context(|_| GetObjectSnafu { + obj: ObjectRef::::new(listener_class_name).erase(), })?; - Ok(node_primary_address(&node) + Ok(node_primary_addresses(&node) + .pick(listener_class.spec.preferred_address_type) .map(|(address, address_type)| ListenerIngress { // nodes: Some(vec![node_name.to_string()]), address: address.to_string(), diff --git a/rust/operator-binary/src/listener_controller.rs b/rust/operator-binary/src/listener_controller.rs index 22e565fc..a0550678 100644 --- a/rust/operator-binary/src/listener_controller.rs +++ b/rust/operator-binary/src/listener_controller.rs @@ -28,7 +28,10 @@ use stackable_operator::{ }; use strum::IntoStaticStr; -use crate::{csi_server::node::NODE_TOPOLOGY_LABEL_HOSTNAME, utils::node_primary_address}; +use crate::{ + csi_server::node::NODE_TOPOLOGY_LABEL_HOSTNAME, + utils::address::{node_primary_addresses, AddressCandidates}, +}; #[cfg(doc)] use stackable_operator::k8s_openapi::api::core::v1::Pod; @@ -270,6 +273,7 @@ pub async fn reconcile(listener: Arc, ctx: Arc) -> Result; + let kubernetes_service_fqdn: String; let addresses: Vec<(&str, AddressType)>; let ports: BTreeMap; match listener_class.spec.service_type { @@ -287,7 +291,9 @@ pub async fn reconcile(listener: Arc, ctx: Arc) -> Result>(); ports = svc .spec @@ -305,11 +311,11 @@ pub async fn reconcile(listener: Arc, ctx: Arc) -> Result, ctx: Arc) -> Result { - addresses = svc - .spec - .iter() - .flat_map(|s| &s.cluster_ips) - .flatten() - .map(|addr| (&**addr, AddressType::Ip)) - .collect::>(); + addresses = match listener_class.spec.preferred_address_type { + AddressType::Ip => svc + .spec + .iter() + .flat_map(|s| &s.cluster_ips) + .flatten() + .map(|addr| (&**addr, AddressType::Ip)) + .collect::>(), + AddressType::Hostname => { + kubernetes_service_fqdn = format!("{svc_name}.{ns}.svc.cluster.local"); + vec![(&kubernetes_service_fqdn, AddressType::Hostname)] + } + }; ports = svc .spec .as_ref() diff --git a/rust/operator-binary/src/main.rs b/rust/operator-binary/src/main.rs index 4101dfbf..4028829f 100644 --- a/rust/operator-binary/src/main.rs +++ b/rust/operator-binary/src/main.rs @@ -17,7 +17,7 @@ use stackable_operator::{ use tokio::signal::unix::{signal, SignalKind}; use tokio_stream::wrappers::UnixListenerStream; use tonic::transport::Server; -use utils::{uds_bind_private, TonicUnixStream}; +use utils::unix_stream::{uds_bind_private, TonicUnixStream}; mod csi_server; mod listener_controller; diff --git a/rust/operator-binary/src/utils/address.rs b/rust/operator-binary/src/utils/address.rs new file mode 100644 index 00000000..f0446804 --- /dev/null +++ b/rust/operator-binary/src/utils/address.rs @@ -0,0 +1,118 @@ +use stackable_operator::{commons::listener::AddressType, k8s_openapi::api::core::v1::Node}; + +/// The primary addresses of an entity, for each type of address. +#[derive(Debug, Clone, Copy)] +pub struct AddressCandidates<'a> { + pub ip: Option<&'a str>, + pub hostname: Option<&'a str>, +} + +impl<'a> AddressCandidates<'a> { + /// Tries to pick the preferred [`AddressType`], falling back if it is not available. + pub fn pick(&self, preferred_address_type: AddressType) -> Option<(&'a str, AddressType)> { + let ip = self.ip.zip(Some(AddressType::Ip)); + let hostname = self.hostname.zip(Some(AddressType::Hostname)); + match preferred_address_type { + AddressType::Ip => ip.or(hostname), + AddressType::Hostname => hostname.or(ip), + } + } +} + +/// Try to guess the primary addresses of a Node, which it is expected that external clients should be able to reach it on +pub fn node_primary_addresses(node: &Node) -> AddressCandidates { + let addrs = node + .status + .as_ref() + .and_then(|s| s.addresses.as_deref()) + .unwrap_or_default(); + + AddressCandidates { + ip: addrs + .iter() + .find(|addr| addr.type_ == "ExternalIP") + .or_else(|| addrs.iter().find(|addr| addr.type_ == "InternalIP")) + .map(|addr| addr.address.as_str()), + hostname: addrs + .iter() + .find(|addr| addr.type_ == "Hostname") + .map(|addr| addr.address.as_str()), + } +} + +#[cfg(test)] +mod tests { + use stackable_operator::{ + commons::listener::AddressType, + k8s_openapi::api::core::v1::{Node, NodeAddress, NodeStatus}, + }; + + use super::node_primary_addresses; + + #[test] + fn node_with_only_ips_primary_address_returns_external_ip() { + let node = node_from_addresses(vec![("InternalIP", "10.1.2.3"), ("ExternalIP", "1.2.3.4")]); + let node_primary_address = node_primary_addresses(&node); + assert_eq!( + node_primary_address.pick(AddressType::Ip), + Some(("1.2.3.4", AddressType::Ip)) + ); + assert_eq!( + node_primary_address.pick(AddressType::Hostname), + Some(("1.2.3.4", AddressType::Ip)) + ); + } + + #[test] + fn node_with_only_hostname_primary_address_returns_hostname() { + let node = node_from_addresses(vec![ + ("Hostname", "first-hostname"), + ("Hostname", "second-hostname"), + ]); + let node_primary_address = node_primary_addresses(&node); + assert_eq!( + node_primary_address.pick(AddressType::Ip), + Some(("first-hostname", AddressType::Hostname)) + ); + assert_eq!( + node_primary_address.pick(AddressType::Hostname), + Some(("first-hostname", AddressType::Hostname)) + ); + } + + #[test] + fn node_with_hostname_and_ips_primary_address() { + let node = node_from_addresses(vec![ + ("Hostname", "node-0"), + ("ExternalIP", "1.2.3.4"), + ("InternalIP", "10.1.2.3"), + ]); + let node_primary_address = node_primary_addresses(&node); + assert_eq!( + node_primary_address.pick(AddressType::Ip), + Some(("1.2.3.4", AddressType::Ip)) + ); + assert_eq!( + node_primary_address.pick(AddressType::Hostname), + Some(("node-0", AddressType::Hostname)) + ); + } + + fn node_from_addresses<'a>(addresses: impl IntoIterator) -> Node { + Node { + status: Some(NodeStatus { + addresses: Some( + addresses + .into_iter() + .map(|(ty, addr)| NodeAddress { + type_: ty.to_string(), + address: addr.to_string(), + }) + .collect(), + ), + ..Default::default() + }), + ..Default::default() + } + } +} diff --git a/rust/operator-binary/src/utils/error.rs b/rust/operator-binary/src/utils/error.rs new file mode 100644 index 00000000..2718c85d --- /dev/null +++ b/rust/operator-binary/src/utils/error.rs @@ -0,0 +1,35 @@ +/// Combines the messages of an error and its sources into a [`String`] of the form `"error: source 1: source 2: root error"` +pub fn error_full_message(err: &dyn std::error::Error) -> String { + use std::fmt::Write; + // Build the full hierarchy of error messages by walking up the stack until an error + // without `source` set is encountered and concatenating all encountered error strings. + let mut full_msg = format!("{}", err); + let mut curr_err = err.source(); + while let Some(curr_source) = curr_err { + write!(full_msg, ": {curr_source}").expect("string formatting should be infallible"); + curr_err = curr_source.source(); + } + full_msg +} + +#[cfg(test)] +pub(crate) mod tests { + use super::error_full_message; + + #[test] + fn error_messages() { + assert_eq!( + error_full_message(anyhow::anyhow!("standalone error").as_ref()), + "standalone error" + ); + assert_eq!( + error_full_message( + anyhow::anyhow!("root error") + .context("middleware") + .context("leaf") + .as_ref() + ), + "leaf: middleware: root error" + ); + } +} diff --git a/rust/operator-binary/src/utils/mod.rs b/rust/operator-binary/src/utils/mod.rs new file mode 100644 index 00000000..39238137 --- /dev/null +++ b/rust/operator-binary/src/utils/mod.rs @@ -0,0 +1,3 @@ +pub mod address; +pub mod error; +pub mod unix_stream; diff --git a/rust/operator-binary/src/utils.rs b/rust/operator-binary/src/utils/unix_stream.rs similarity index 57% rename from rust/operator-binary/src/utils.rs rename to rust/operator-binary/src/utils/unix_stream.rs index fd30c83e..fb0652d4 100644 --- a/rust/operator-binary/src/utils.rs +++ b/rust/operator-binary/src/utils/unix_stream.rs @@ -2,7 +2,6 @@ use std::{os::unix::prelude::AsRawFd, path::Path}; use pin_project::pin_project; use socket2::Socket; -use stackable_operator::{commons::listener::AddressType, k8s_openapi::api::core::v1::Node}; use tokio::{ io::{AsyncRead, AsyncWrite}, net::{UnixListener, UnixStream}, @@ -86,61 +85,3 @@ pub fn uds_bind_private(path: impl AsRef) -> Result String { - use std::fmt::Write; - // Build the full hierarchy of error messages by walking up the stack until an error - // without `source` set is encountered and concatenating all encountered error strings. - let mut full_msg = format!("{}", err); - let mut curr_err = err.source(); - while let Some(curr_source) = curr_err { - write!(full_msg, ": {curr_source}").expect("string formatting should be infallible"); - curr_err = curr_source.source(); - } - full_msg -} - -/// Try to guess the primary address of a Node, which it is expected that external clients should be able to reach it on -pub fn node_primary_address(node: &Node) -> Option<(&str, AddressType)> { - let addrs = node - .status - .as_ref() - .and_then(|s| s.addresses.as_deref()) - .unwrap_or_default(); - // IP addresses are currently preferred over hostnames since nodes don't always have resolvable hostnames - addrs - .iter() - .find(|addr| addr.type_ == "ExternalIP") - .or_else(|| addrs.iter().find(|addr| addr.type_ == "InternalIP")) - .zip(Some(AddressType::Ip)) - .or_else(|| { - addrs - .iter() - .find(|addr| addr.type_ == "Hostname") - .zip(Some(AddressType::Hostname)) - }) - .map(|(addr, ty)| (addr.address.as_str(), ty)) -} - -#[cfg(test)] -mod tests { - use crate::utils::error_full_message; - - #[test] - fn error_messages() { - assert_eq!( - error_full_message(anyhow::anyhow!("standalone error").as_ref()), - "standalone error" - ); - assert_eq!( - error_full_message( - anyhow::anyhow!("root error") - .context("middleware") - .context("leaf") - .as_ref() - ), - "leaf: middleware: root error" - ); - } -} diff --git a/tests/templates/kuttl/smoke-nodeport/05-create-listenerclass.yaml b/tests/templates/kuttl/smoke-nodeport/05-create-listenerclass.yaml.j2 similarity index 69% rename from tests/templates/kuttl/smoke-nodeport/05-create-listenerclass.yaml rename to tests/templates/kuttl/smoke-nodeport/05-create-listenerclass.yaml.j2 index d501f211..d62b0701 100644 --- a/tests/templates/kuttl/smoke-nodeport/05-create-listenerclass.yaml +++ b/tests/templates/kuttl/smoke-nodeport/05-create-listenerclass.yaml.j2 @@ -5,3 +5,4 @@ metadata: name: listener-operator-test-smoke-nodeport spec: serviceType: NodePort + preferredAddressType: {{ test_scenario['values']['addressType'] }} diff --git a/tests/templates/kuttl/smoke-nodeport/10-assert.yaml b/tests/templates/kuttl/smoke-nodeport/10-assert.yaml deleted file mode 100644 index d7ebbae5..00000000 --- a/tests/templates/kuttl/smoke-nodeport/10-assert.yaml +++ /dev/null @@ -1,8 +0,0 @@ ---- -apiVersion: apps/v1 -kind: StatefulSet -metadata: - name: nginx-long-name-approaching-k8s-limits -status: - readyReplicas: 2 - replicas: 2 diff --git a/tests/templates/kuttl/smoke-nodeport/10-assert.yaml.j2 b/tests/templates/kuttl/smoke-nodeport/10-assert.yaml.j2 new file mode 100644 index 00000000..d0c2ca2c --- /dev/null +++ b/tests/templates/kuttl/smoke-nodeport/10-assert.yaml.j2 @@ -0,0 +1,16 @@ +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: nginx-long-name-approaching-k8s-limits +status: + readyReplicas: 2 + replicas: 2 +--- +apiVersion: listeners.stackable.tech/v1alpha1 +kind: Listener +metadata: + name: listener-nginx-long-name-approaching-k8s-limits-0 +status: + ingressAddresses: + - addressType: {{ test_scenario['values']['addressType'] }} diff --git a/tests/test-definition.yaml b/tests/test-definition.yaml index e8a6def3..90acd866 100644 --- a/tests/test-definition.yaml +++ b/tests/test-definition.yaml @@ -3,10 +3,15 @@ dimensions: - name: openshift values: - "false" + - name: addressType + values: + - IP + - Hostname tests: - name: smoke-nodeport dimensions: - openshift + - addressType suites: - name: nightly - name: openshift