Skip to content

Commit b08ed8e

Browse files
committed
WIP spike: Store cluster domain in Client
1 parent a2ac5f5 commit b08ed8e

File tree

6 files changed

+38
-156
lines changed

6 files changed

+38
-156
lines changed

crates/stackable-operator/fixtures/cluster_domain/fail/invalid.resolv.conf

Lines changed: 0 additions & 2 deletions
This file was deleted.

crates/stackable-operator/fixtures/cluster_domain/pass/kubernetes-multiple.resolv.conf

Lines changed: 0 additions & 4 deletions
This file was deleted.

crates/stackable-operator/fixtures/cluster_domain/pass/kubernetes.resolv.conf

Lines changed: 0 additions & 3 deletions
This file was deleted.

crates/stackable-operator/fixtures/cluster_domain/pass/openshift.resolv.conf

Lines changed: 0 additions & 3 deletions
This file was deleted.

crates/stackable-operator/src/client.rs

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
use crate::commons::networking::DomainName;
12
use crate::kvp::LabelSelectorExt;
2-
use crate::utils::cluster_domain::{self, retrieve_cluster_domain, KUBERNETES_CLUSTER_DOMAIN};
3+
use crate::utils::cluster_domain::{self, retrieve_cluster_domain};
34

45
use either::Either;
56
use futures::StreamExt;
@@ -93,13 +94,15 @@ pub struct Client {
9394
delete_params: DeleteParams,
9495
/// Default namespace as defined in the kubeconfig this client has been created from.
9596
pub default_namespace: String,
97+
pub kubernetes_cluster_domain: DomainName,
9698
}
9799

98100
impl Client {
99101
pub fn new(
100102
client: KubeClient,
101103
field_manager: Option<String>,
102104
default_namespace: String,
105+
kubernetes_cluster_domain: DomainName,
103106
) -> Self {
104107
Client {
105108
client,
@@ -113,6 +116,7 @@ impl Client {
113116
},
114117
delete_params: DeleteParams::default(),
115118
default_namespace,
119+
kubernetes_cluster_domain,
116120
}
117121
}
118122

@@ -627,19 +631,20 @@ where
627631
}
628632

629633
pub async fn initialize_operator(field_manager: Option<String>) -> Result<Client> {
630-
let _ = KUBERNETES_CLUSTER_DOMAIN
631-
.set(retrieve_cluster_domain().context(ResolveKubernetesClusterDomainSnafu)?);
632-
create_client(field_manager).await
633-
}
634-
635-
async fn create_client(field_manager: Option<String>) -> Result<Client> {
636634
let kubeconfig: Config = kube::Config::infer()
637635
.await
638636
.map_err(kube::Error::InferConfig)
639637
.context(InferKubeConfigSnafu)?;
640638
let default_namespace = kubeconfig.default_namespace.clone();
641639
let client = kube::Client::try_from(kubeconfig).context(CreateKubeClientSnafu)?;
642-
Ok(Client::new(client, field_manager, default_namespace))
640+
let cluster_domain = retrieve_cluster_domain().context(ResolveKubernetesClusterDomainSnafu)?;
641+
642+
Ok(Client::new(
643+
client,
644+
field_manager,
645+
default_namespace,
646+
cluster_domain,
647+
))
643648
}
644649

645650
#[cfg(test)]
@@ -657,7 +662,7 @@ mod tests {
657662
#[tokio::test]
658663
#[ignore = "Tests depending on Kubernetes are not ran by default"]
659664
async fn k8s_test_wait_created() {
660-
let client = super::create_client(None)
665+
let client = super::initialize_operator(None)
661666
.await
662667
.expect("KUBECONFIG variable must be configured.");
663668

@@ -735,7 +740,7 @@ mod tests {
735740
#[tokio::test]
736741
#[ignore = "Tests depending on Kubernetes are not ran by default"]
737742
async fn k8s_test_wait_created_timeout() {
738-
let client = super::create_client(None)
743+
let client = super::initialize_operator(None)
739744
.await
740745
.expect("KUBECONFIG variable must be configured.");
741746

@@ -755,7 +760,7 @@ mod tests {
755760
#[tokio::test]
756761
#[ignore = "Tests depending on Kubernetes are not ran by default"]
757762
async fn k8s_test_list_with_label_selector() {
758-
let client = super::create_client(None)
763+
let client = super::initialize_operator(None)
759764
.await
760765
.expect("KUBECONFIG variable must be configured.");
761766

Lines changed: 22 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -1,187 +1,76 @@
1-
use std::{env, path::PathBuf, str::FromStr, sync::OnceLock};
1+
use std::{env, str::FromStr};
22

33
use snafu::{ResultExt, Snafu};
44
use tracing::instrument;
55

66
use crate::commons::networking::DomainName;
77

88
const KUBERNETES_CLUSTER_DOMAIN_ENV: &str = "KUBERNETES_CLUSTER_DOMAIN";
9-
const KUBERNETES_SERVICE_HOST_ENV: &str = "KUBERNETES_SERVICE_HOST";
10-
119
const KUBERNETES_CLUSTER_DOMAIN_DEFAULT: &str = "cluster.local";
12-
const RESOLVE_CONF_FILE_PATH: &str = "/etc/resolv.conf";
1310

1411
#[derive(Debug, Snafu)]
1512
pub enum Error {
16-
#[snafu(display("failed to read resolv config from {RESOLVE_CONF_FILE_PATH:?}"))]
17-
ReadResolvConfFile { source: std::io::Error },
18-
1913
#[snafu(display("failed to parse {cluster_domain:?} as domain name"))]
2014
ParseDomainName {
2115
source: crate::validation::Errors,
2216
cluster_domain: String,
2317
},
24-
25-
#[snafu(display(r#"unable to find "search" entry"#))]
26-
NoSearchEntry,
27-
28-
#[snafu(display(r#"unable to find unambiguous domain in "search" entry"#))]
29-
AmbiguousDomainEntries,
3018
}
3119

3220
/// Tries to retrieve the Kubernetes cluster domain.
3321
///
34-
/// 1. Return `KUBERNETES_CLUSTER_DOMAIN` if set, otherwise
35-
/// 2. Return the cluster domain parsed from the `/etc/resolv.conf` file if `KUBERNETES_SERVICE_HOST`
36-
/// is set, otherwise fall back to `cluster.local`.
37-
///
38-
/// This variable is initialized in [`crate::client::initialize_operator`], which is called in the
39-
/// main function. It can be used as suggested below.
40-
///
41-
/// ## Usage
42-
///
43-
/// ```no_run
44-
/// use stackable_operator::utils::cluster_domain::KUBERNETES_CLUSTER_DOMAIN;
45-
///
46-
/// let kubernetes_cluster_domain = KUBERNETES_CLUSTER_DOMAIN.get()
47-
/// .expect("KUBERNETES_CLUSTER_DOMAIN must first be set by calling initialize_operator");
48-
///
49-
/// tracing::info!(%kubernetes_cluster_domain, "Found cluster domain");
50-
/// ```
51-
///
52-
/// ## See
53-
///
54-
/// - <https://github.com/stackabletech/issues/issues/436>
55-
/// - <https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/>
56-
pub static KUBERNETES_CLUSTER_DOMAIN: OnceLock<DomainName> = OnceLock::new();
57-
22+
/// Return `KUBERNETES_CLUSTER_DOMAIN` if set, otherwise default to
23+
/// [`KUBERNETES_CLUSTER_DOMAIN_DEFAULT`].
5824
#[instrument]
5925
pub(crate) fn retrieve_cluster_domain() -> Result<DomainName, Error> {
60-
// 1. Read KUBERNETES_CLUSTER_DOMAIN env var
6126
tracing::debug!("Trying to determine the Kubernetes cluster domain...");
6227

63-
match env::var(KUBERNETES_CLUSTER_DOMAIN_ENV) {
28+
Ok(match env::var(KUBERNETES_CLUSTER_DOMAIN_ENV) {
6429
Ok(cluster_domain) if !cluster_domain.is_empty() => {
6530
let cluster_domain = DomainName::from_str(&cluster_domain)
6631
.context(ParseDomainNameSnafu { cluster_domain })?;
6732
tracing::info!(
6833
%cluster_domain,
6934
"Using Kubernetes cluster domain from {KUBERNETES_CLUSTER_DOMAIN_ENV:?} environment variable"
7035
);
71-
return Ok(cluster_domain);
72-
}
73-
_ => {}
74-
};
75-
76-
// 2. If no env var is set, check if we run in a clustered (Kubernetes/Openshift) environment
77-
// by checking if KUBERNETES_SERVICE_HOST is set: If not default to 'cluster.local'.
78-
tracing::debug!(
79-
"Trying to determine the operator runtime environment as environment variable \
80-
{KUBERNETES_CLUSTER_DOMAIN_ENV:?} is not set"
81-
);
82-
83-
match env::var(KUBERNETES_SERVICE_HOST_ENV) {
84-
Ok(_) => {
85-
let cluster_domain = retrieve_cluster_domain_from_resolv_conf(RESOLVE_CONF_FILE_PATH)?;
86-
let cluster_domain = DomainName::from_str(&cluster_domain)
87-
.context(ParseDomainNameSnafu { cluster_domain })?;
88-
89-
tracing::info!(
90-
%cluster_domain,
91-
"Using Kubernetes cluster domain from {RESOLVE_CONF_FILE_PATH:?} file"
92-
);
93-
94-
Ok(cluster_domain)
36+
cluster_domain
9537
}
96-
Err(_) => {
38+
_ => {
9739
let cluster_domain = DomainName::from_str(KUBERNETES_CLUSTER_DOMAIN_DEFAULT)
9840
.expect("KUBERNETES_CLUSTER_DOMAIN_DEFAULT constant must a valid domain");
99-
10041
tracing::info!(
10142
%cluster_domain,
102-
"Could not determine Kubernetes cluster domain as the operator is not running within Kubernetes, assuming default Kubernetes cluster domain"
43+
"Using default Kubernetes cluster domain as {KUBERNETES_CLUSTER_DOMAIN_ENV:?} environment variable is not set"
10344
);
104-
105-
Ok(cluster_domain)
45+
cluster_domain
10646
}
107-
}
108-
}
109-
110-
#[instrument]
111-
fn retrieve_cluster_domain_from_resolv_conf(
112-
path: impl Into<PathBuf> + std::fmt::Debug,
113-
) -> Result<String, Error> {
114-
let path = path.into();
115-
let content = std::fs::read_to_string(&path)
116-
.inspect_err(|error| {
117-
tracing::error!(%error, path = %path.display(), "Cannot read resolv conf");
118-
})
119-
.context(ReadResolvConfFileSnafu)?;
120-
121-
// If there are multiple search directives, only the search
122-
// man 5 resolv.conf
123-
let Some(last_search_entry) = content
124-
.lines()
125-
.rev()
126-
.map(|l| l.trim())
127-
.find(|&l| l.starts_with("search"))
128-
.map(|l| l.trim_start_matches("search").trim())
129-
else {
130-
return NoSearchEntrySnafu.fail();
131-
};
132-
133-
let Some(shortest_entry) = last_search_entry
134-
.split_ascii_whitespace()
135-
.min_by_key(|item| item.len())
136-
else {
137-
return AmbiguousDomainEntriesSnafu.fail();
138-
};
139-
140-
// NOTE (@Techassi): This is really sad and bothers me more than I would like to admit. This
141-
// clone could be removed by using the code directly in the calling function. But that would
142-
// remove the possibility to easily test the parsing.
143-
Ok(shortest_entry.to_owned())
47+
})
14448
}
14549

14650
#[cfg(test)]
14751
mod tests {
148-
use std::path::PathBuf;
149-
15052
use super::*;
151-
use rstest::rstest;
53+
54+
#[test]
55+
fn default_kubernetes_cluster_domain_value() {
56+
assert_eq!(
57+
retrieve_cluster_domain().unwrap().to_string(),
58+
"cluster.local"
59+
);
60+
}
15261

15362
#[test]
15463
fn use_different_kubernetes_cluster_domain_value() {
155-
let cluster_domain = "my-cluster.local".to_string();
64+
let cluster_domain = "my-cluster.local";
15665

157-
// set different domain via env var
66+
// Set custom cluster domain via env var
15867
unsafe {
159-
env::set_var(KUBERNETES_CLUSTER_DOMAIN_ENV, &cluster_domain);
68+
env::set_var(KUBERNETES_CLUSTER_DOMAIN_ENV, cluster_domain);
16069
}
16170

162-
// initialize the lock
163-
let _ = KUBERNETES_CLUSTER_DOMAIN.set(retrieve_cluster_domain().unwrap());
164-
16571
assert_eq!(
166-
cluster_domain,
167-
KUBERNETES_CLUSTER_DOMAIN.get().unwrap().to_string()
72+
retrieve_cluster_domain().unwrap().to_string(),
73+
cluster_domain
16874
);
16975
}
170-
171-
#[rstest]
172-
fn parse_resolv_conf_pass(
173-
#[files("fixtures/cluster_domain/pass/*.resolv.conf")] path: PathBuf,
174-
) {
175-
assert_eq!(
176-
retrieve_cluster_domain_from_resolv_conf(path).unwrap(),
177-
KUBERNETES_CLUSTER_DOMAIN_DEFAULT
178-
);
179-
}
180-
181-
#[rstest]
182-
fn parse_resolv_conf_fail(
183-
#[files("fixtures/cluster_domain/fail/*.resolv.conf")] path: PathBuf,
184-
) {
185-
assert!(retrieve_cluster_domain_from_resolv_conf(path).is_err());
186-
}
18776
}

0 commit comments

Comments
 (0)