Skip to content

Commit 4839a2a

Browse files
committed
fix: Let operator deploy listener class presets
1 parent 01b5e36 commit 4839a2a

10 files changed

Lines changed: 144 additions & 60 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.nix

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

deploy/helm/listener-operator/templates/csi-node-driver-daemonset.yaml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,11 @@ spec:
1212
{{- include "operator.selectorLabels" . | nindent 6 }}
1313
template:
1414
metadata:
15-
{{- with .Values.csiNodeDriver.podAnnotations }}
1615
annotations:
17-
{{- toYaml . | nindent 8 }}
18-
{{- end }}
16+
internal.stackable.tech/image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
17+
{{- with .Values.csiNodeDriver.podAnnotations }}
18+
{{- toYaml . | nindent 8 }}
19+
{{- end }}
1920
labels:
2021
app.kubernetes.io/role: node-driver
2122
{{- include "operator.selectorLabels" . | nindent 8 }}

deploy/helm/listener-operator/templates/csi-provisioner-deployment.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,11 @@ spec:
7878
fieldRef:
7979
fieldPath: spec.nodeName
8080

81+
# Pass the selected preset which gets picked by via the CLI and env var and instructs
82+
# the controller to create the appropriate listener classes.
83+
- name: LISTENER_CLASS_PRESET
84+
value: {{ .Values.preset }}
85+
8186
{{- if .Values.kubernetesClusterDomain }}
8287
- name: KUBERNETES_CLUSTER_DOMAIN
8388
value: {{ .Values.kubernetesClusterDomain | quote }}

deploy/helm/listener-operator/templates/roles.yaml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,10 @@ rules:
119119
- listenerclasses
120120
- listeners
121121
verbs:
122+
{{- if .Values.maintenance.customResourceDefinitions.maintain }}
123+
- create
124+
- patch
125+
{{- end }}
122126
- get
123127
- list
124128
- watch
@@ -149,9 +153,9 @@ rules:
149153
{{ if .Values.maintenance.customResourceDefinitions.maintain }}
150154
- apiGroups:
151155
- apiextensions.k8s.io
152-
- resources:
156+
resources:
153157
- customresourcedefinitions
154-
- verbs:
158+
verbs:
155159
- create
156160
- patch
157161
{{ end }}

rust/operator-binary/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ tonic.workspace = true
2525
tonic-reflection.workspace = true
2626
anyhow.workspace = true
2727
serde.workspace = true
28+
serde_yaml.workspace = true
2829
snafu.workspace = true
2930
strum.workspace = true
3031
h2.workspace = true

rust/operator-binary/src/main.rs

Lines changed: 92 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,18 @@ use csi_server::{
1515
use futures::{FutureExt, TryFutureExt, TryStreamExt};
1616
use stackable_operator::{
1717
self, YamlSchema,
18-
cli::{CommonOptions, MaintenanceOptions, OperatorEnvironmentOptions},
18+
cli::{Command, CommonOptions, MaintenanceOptions, OperatorEnvironmentOptions},
19+
client::Client,
1920
crd::listener::{
2021
Listener, ListenerClass, ListenerClassVersion, ListenerVersion, PodListeners,
21-
PodListenersVersion,
22+
PodListenersVersion, v1alpha1,
2223
},
2324
eos::EndOfSupportChecker,
2425
shared::yaml::SerializeOptions,
2526
telemetry::Tracing,
2627
utils::signal::SignalWatcher,
2728
};
29+
use tokio::sync::oneshot;
2830
use tokio_stream::wrappers::UnixListenerStream;
2931
use tonic::transport::Server;
3032
use utils::unix_stream::{TonicUnixStream, uds_bind_private};
@@ -42,14 +44,14 @@ const FIELD_MANAGER: &str = "listener-operator";
4244

4345
#[derive(clap::Parser)]
4446
#[clap(author, version)]
45-
struct Opts {
47+
struct Cli {
4648
#[clap(subcommand)]
47-
cmd: stackable_operator::cli::Command<ListenerOperatorRun>,
49+
cmd: Command<ListenerOperatorRun>,
4850
}
4951

5052
#[derive(clap::Parser)]
5153
struct ListenerOperatorRun {
52-
#[clap(long, env)]
54+
#[arg(long, env)]
5355
csi_endpoint: PathBuf,
5456

5557
#[clap(subcommand)]
@@ -70,12 +72,34 @@ struct ListenerOperatorRun {
7072
#[derive(Debug, clap::Parser, strum::AsRefStr, strum::Display)]
7173
enum RunMode {
7274
/// CSI Controller Service
73-
Controller,
75+
Controller(ControllerArguments),
7476

7577
/// CSI Node Service
7678
Node,
7779
}
7880

81+
#[derive(Debug, clap::Args)]
82+
struct ControllerArguments {
83+
#[arg(long, env, default_value_t)]
84+
listener_class_preset: ListenerClassPreset,
85+
}
86+
87+
#[derive(Clone, Debug, Default, clap::Parser, strum::Display, strum::EnumString)]
88+
#[strum(serialize_all = "kebab-case")]
89+
enum ListenerClassPreset {
90+
/// Deploys no listener class preset.
91+
None,
92+
93+
/// Deploys listener classes for environments in which pods can move freely
94+
/// between nodes. This is common for many managed cloud environments.
95+
#[default]
96+
EphemeralNodes,
97+
98+
/// Deploys listener classes for environments with reliable, long-living
99+
/// nodes and pods don't move between nodes.
100+
StableNodes,
101+
}
102+
79103
mod built_info {
80104
include!(concat!(env!("OUT_DIR"), "/built.rs"));
81105
}
@@ -85,17 +109,17 @@ pub const ENV_VAR_CONSOLE_LOG: &str = "LISTENER_OPERATOR_LOG";
85109

86110
#[tokio::main]
87111
async fn main() -> anyhow::Result<()> {
88-
let opts = Opts::parse();
112+
let opts = Cli::parse();
89113
match opts.cmd {
90-
stackable_operator::cli::Command::Crd => {
114+
Command::Crd => {
91115
ListenerClass::merged_crd(ListenerClassVersion::V1Alpha1)?
92116
.print_yaml_schema(built_info::PKG_VERSION, SerializeOptions::default())?;
93117
Listener::merged_crd(ListenerVersion::V1Alpha1)?
94118
.print_yaml_schema(built_info::PKG_VERSION, SerializeOptions::default())?;
95119
PodListeners::merged_crd(PodListenersVersion::V1Alpha1)?
96120
.print_yaml_schema(built_info::PKG_VERSION, SerializeOptions::default())?;
97121
}
98-
stackable_operator::cli::Command::Run(ListenerOperatorRun {
122+
Command::Run(ListenerOperatorRun {
99123
operator_environment,
100124
csi_endpoint,
101125
maintenance,
@@ -154,29 +178,47 @@ async fn main() -> anyhow::Result<()> {
154178
)
155179
.add_service(IdentityServer::new(ListenerOperatorIdentity));
156180

157-
let webhook_server = create_webhook_server(
158-
&operator_environment,
159-
maintenance.disable_crd_maintenance,
160-
client.as_kube_client(),
161-
)
162-
.await?;
181+
match mode {
182+
RunMode::Controller(ControllerArguments {
183+
listener_class_preset,
184+
}) => {
185+
let (webhook_server, initial_reconcile_rx) = create_webhook_server(
186+
&operator_environment,
187+
maintenance.disable_crd_maintenance,
188+
client.as_kube_client(),
189+
)
190+
.await?;
163191

164-
let webhook_server = webhook_server
165-
.run(sigterm_watcher.handle())
166-
.map_err(|err| anyhow!(err).context("failed to run webhook server"));
192+
let webhook_server = webhook_server
193+
.run(sigterm_watcher.handle())
194+
.map_err(|err| anyhow!(err).context("failed to run webhook server"));
195+
196+
let listener_classes = create_listener_classes(
197+
initial_reconcile_rx,
198+
listener_class_preset,
199+
client.clone(),
200+
)
201+
.map_err(|err| {
202+
anyhow!(err).context("failed to apply listener classes selected by preset")
203+
});
167204

168-
match mode {
169-
RunMode::Controller => {
170205
let csi_server = csi_server
171206
.add_service(ControllerServer::new(ListenerOperatorController {
172207
client: client.clone(),
173208
}))
174209
.serve_with_incoming_shutdown(csi_listener, sigterm_watcher.handle())
175210
.map_err(|err| anyhow!(err).context("failed to run csi server"));
211+
176212
let controller =
177213
listener_controller::run(client, sigterm_watcher.handle()).map(anyhow::Ok);
178214

179-
futures::try_join!(csi_server, controller, eos_checker, webhook_server)?;
215+
futures::try_join!(
216+
listener_classes,
217+
webhook_server,
218+
eos_checker,
219+
csi_server,
220+
controller,
221+
)?;
180222
}
181223
RunMode::Node => {
182224
let node_name = &common.cluster_info.kubernetes_node_name;
@@ -188,10 +230,38 @@ async fn main() -> anyhow::Result<()> {
188230
.serve_with_incoming_shutdown(csi_listener, sigterm_watcher.handle())
189231
.map_err(|err| anyhow!(err).context("failed to run csi server"));
190232

191-
futures::try_join!(csi_server, eos_checker, webhook_server)?;
233+
futures::try_join!(csi_server, eos_checker)?;
192234
}
193235
}
194236
}
195237
}
238+
239+
Ok(())
240+
}
241+
242+
async fn create_listener_classes(
243+
initial_reconcile_rx: oneshot::Receiver<()>,
244+
listener_class_preset: ListenerClassPreset,
245+
client: Client,
246+
) -> anyhow::Result<()> {
247+
initial_reconcile_rx.await?;
248+
249+
tracing::info!("applying \"{listener_class_preset}\" listener class preset");
250+
251+
#[rustfmt::skip]
252+
let bytes = match listener_class_preset {
253+
ListenerClassPreset::None => return Ok(()),
254+
ListenerClassPreset::EphemeralNodes => include_bytes!("manifests/ephemeral-nodes.yaml").to_vec(),
255+
ListenerClassPreset::StableNodes => include_bytes!("manifests/stable-nodes.yaml").to_vec(),
256+
};
257+
258+
for document in serde_yaml::Deserializer::from_slice(&bytes) {
259+
let class: v1alpha1::ListenerClass =
260+
serde_yaml::with::singleton_map_recursive::deserialize(document)
261+
.expect("compile-time included listener classes must be valid YAML");
262+
263+
client.create_if_missing(&class).await?;
264+
}
265+
196266
Ok(())
197267
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
apiVersion: listeners.stackable.tech/v1alpha1
3+
kind: ListenerClass
4+
metadata:
5+
name: cluster-internal
6+
spec:
7+
serviceType: ClusterIP
8+
---
9+
apiVersion: listeners.stackable.tech/v1alpha1
10+
kind: ListenerClass
11+
metadata:
12+
name: external-unstable
13+
spec:
14+
serviceType: NodePort
15+
pinnedNodePorts: false
16+
---
17+
apiVersion: listeners.stackable.tech/v1alpha1
18+
kind: ListenerClass
19+
metadata:
20+
name: external-stable
21+
spec:
22+
serviceType: LoadBalancer

deploy/helm/listener-operator/templates/listener-classes.yaml renamed to rust/operator-binary/src/manifests/stable-nodes.yaml

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,3 @@
1-
{{ if eq .Values.preset "none" }}
2-
{{ else if eq .Values.preset "ephemeral-nodes" }}
3-
---
4-
apiVersion: listeners.stackable.tech/v1alpha1
5-
kind: ListenerClass
6-
metadata:
7-
name: cluster-internal
8-
spec:
9-
serviceType: ClusterIP
10-
---
11-
apiVersion: listeners.stackable.tech/v1alpha1
12-
kind: ListenerClass
13-
metadata:
14-
name: external-unstable
15-
spec:
16-
serviceType: NodePort
17-
pinnedNodePorts: false
18-
---
19-
apiVersion: listeners.stackable.tech/v1alpha1
20-
kind: ListenerClass
21-
metadata:
22-
name: external-stable
23-
spec:
24-
serviceType: LoadBalancer
25-
{{ else if eq .Values.preset "stable-nodes" }}
261
---
272
apiVersion: listeners.stackable.tech/v1alpha1
283
kind: ListenerClass
@@ -54,6 +29,3 @@ spec:
5429
# or MetalLB (https://metallb.org/)).
5530
serviceType: NodePort
5631
pinnedNodePorts: true
57-
{{ else }}
58-
{{ fail "An invalid preset was configured" }}
59-
{{ end }}

rust/operator-binary/src/webhooks/conversion.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use stackable_operator::{
88
webhooks::{ConversionWebhook, ConversionWebhookOptions},
99
},
1010
};
11+
use tokio::sync::oneshot;
1112

1213
use crate::FIELD_MANAGER;
1314

@@ -27,7 +28,7 @@ pub async fn create_webhook_server(
2728
operator_environment: &OperatorEnvironmentOptions,
2829
disable_crd_maintenance: bool,
2930
client: Client,
30-
) -> Result<WebhookServer, Error> {
31+
) -> Result<(WebhookServer, oneshot::Receiver<()>), Error> {
3132
let crds_and_handlers = vec![
3233
(
3334
ListenerClass::merged_crd(ListenerClassVersion::V1Alpha1).context(MergeCrdSnafu)?,
@@ -44,7 +45,7 @@ pub async fn create_webhook_server(
4445
field_manager: FIELD_MANAGER.to_owned(),
4546
};
4647

47-
let (conversion_webhook, _initial_reconcile_rx) =
48+
let (conversion_webhook, initial_reconcile_rx) =
4849
ConversionWebhook::new(crds_and_handlers, client, conversion_webhook_options);
4950

5051
let webhook_server_options = WebhookServerOptions {
@@ -53,7 +54,10 @@ pub async fn create_webhook_server(
5354
webhook_service_name: operator_environment.operator_service_name.to_owned(),
5455
};
5556

56-
WebhookServer::new(vec![Box::new(conversion_webhook)], webhook_server_options)
57-
.await
58-
.context(CreateWebhookSnafu)
57+
let webhook_server =
58+
WebhookServer::new(vec![Box::new(conversion_webhook)], webhook_server_options)
59+
.await
60+
.context(CreateWebhookSnafu)?;
61+
62+
Ok((webhook_server, initial_reconcile_rx))
5963
}

0 commit comments

Comments
 (0)