Skip to content

Commit ac51ec7

Browse files
authored
Use ClusterResources for Listener controller (#232)
* Use ClusterResources for Listener controller Fixes #221 * Changelog * Update op-rs to fix the labels bug * Update to op-rs 0.79.0
1 parent bdb4e8b commit ac51ec7

File tree

7 files changed

+118
-43
lines changed

7 files changed

+118
-43
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ All notable changes to this project will be documented in this file.
1212

1313
- `Listener.status.addresses` for NodePort listeners now includes replicas that are currently unavailable ([#231]).
1414
- `Listener.status.addresses` now defaults to DNS hostnames for all service types (previously NodePort and ClusterIP would prefer IP addresses, [#233]).
15+
- Stale Listener subobjects will now be deleted ([#232]).
16+
- Tagged Listener Services with the SDP labels ([#232]).
1517

1618
### Fixed
1719

@@ -20,6 +22,7 @@ All notable changes to this project will be documented in this file.
2022
- Propagate `ListenerClass.spec.serviceAnnotations` to the created Services ([#234]).
2123

2224
[#231]: https://github.com/stackabletech/listener-operator/pull/231
25+
[#232]: https://github.com/stackabletech/listener-operator/pull/232
2326
[#233]: https://github.com/stackabletech/listener-operator/pull/233
2427
[#234]: https://github.com/stackabletech/listener-operator/pull/234
2528

Cargo.lock

+4-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.nix

+10-10
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ prost = "0.13"
2121
prost-types = "0.13"
2222
serde = "1.0"
2323
snafu = "0.8"
24-
stackable-operator = { git = "https://github.com/stackabletech/operator-rs.git", tag = "stackable-operator-0.78.0" }
24+
stackable-operator = { git = "https://github.com/stackabletech/operator-rs.git", tag = "stackable-operator-0.79.0" }
2525
strum = { version = "0.26", features = ["derive"] }
2626
socket2 = { version = "0.5", features = ["all"] }
2727
tokio = { version = "1.40", features = ["full"] }
@@ -33,4 +33,4 @@ tracing = "0.1.40"
3333

3434
[patch."https://github.com/stackabletech/operator-rs.git"]
3535
# stackable-operator = { path = "../operator-rs/crates/stackable-operator" }
36-
stackable-operator = { git = "https://github.com/stackabletech//operator-rs.git", branch = "main" }
36+
# stackable-operator = { git = "https://github.com/stackabletech//operator-rs.git", branch = "main" }

crate-hashes.json

+3-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rust/operator-binary/src/listener_controller.rs

+93-23
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,23 @@ use futures::{
99
};
1010
use snafu::{OptionExt, ResultExt, Snafu};
1111
use stackable_operator::{
12-
builder::meta::OwnerReferenceBuilder,
12+
builder::meta::ObjectMetaBuilder,
13+
cluster_resources::{ClusterResourceApplyStrategy, ClusterResources},
1314
commons::listener::{
1415
AddressType, Listener, ListenerClass, ListenerIngress, ListenerPort, ListenerSpec,
1516
ListenerStatus, ServiceType,
1617
},
18+
iter::TryFromIterator,
1719
k8s_openapi::{
1820
api::core::v1::{Endpoints, Node, PersistentVolume, Service, ServicePort, ServiceSpec},
1921
apimachinery::pkg::apis::meta::v1::LabelSelector,
2022
},
2123
kube::{
2224
api::{DynamicObject, ObjectMeta},
2325
runtime::{controller, reflector::ObjectRef, watcher},
24-
ResourceExt,
26+
Resource, ResourceExt,
2527
},
28+
kvp::{Annotations, Labels},
2629
logging::controller::{report_controller_reconciled, ReconcilerError},
2730
time::Duration,
2831
};
@@ -31,12 +34,13 @@ use strum::IntoStaticStr;
3134
use crate::{
3235
csi_server::node::NODE_TOPOLOGY_LABEL_HOSTNAME,
3336
utils::address::{node_primary_addresses, AddressCandidates},
37+
APP_NAME, OPERATOR_KEY,
3438
};
3539

3640
#[cfg(doc)]
3741
use stackable_operator::k8s_openapi::api::core::v1::Pod;
3842

39-
const FIELD_MANAGER_SCOPE: &str = "listener";
43+
const CONTROLLER_NAME: &str = "listener";
4044

4145
pub async fn run(client: stackable_operator::client::Client) {
4246
let controller =
@@ -115,6 +119,11 @@ pub enum Error {
115119
#[snafu(display("object has no name"))]
116120
NoName,
117121

122+
#[snafu(display("failed to create cluster resources"))]
123+
CreateClusterResources {
124+
source: stackable_operator::cluster_resources::Error,
125+
},
126+
118127
#[snafu(display("object has no ListenerClass (.spec.class_name)"))]
119128
NoListenerClass,
120129

@@ -133,6 +142,22 @@ pub enum Error {
133142
source: stackable_operator::client::Error,
134143
},
135144

145+
#[snafu(display("failed to validate labels passed through from Listener"))]
146+
ValidateListenerLabels {
147+
source: stackable_operator::kvp::LabelError,
148+
},
149+
150+
#[snafu(display("failed to validate annotations specified by {listener_class}"))]
151+
ValidateListenerClassAnnotations {
152+
source: stackable_operator::kvp::AnnotationError,
153+
listener_class: ObjectRef<ListenerClass>,
154+
},
155+
156+
#[snafu(display("failed to build cluster resource labels"))]
157+
BuildClusterResourcesLabels {
158+
source: stackable_operator::kvp::LabelError,
159+
},
160+
136161
#[snafu(display("failed to get {obj}"))]
137162
GetObject {
138163
source: stackable_operator::client::Error,
@@ -146,10 +171,15 @@ pub enum Error {
146171

147172
#[snafu(display("failed to apply {svc}"))]
148173
ApplyService {
149-
source: stackable_operator::client::Error,
174+
source: stackable_operator::cluster_resources::Error,
150175
svc: ObjectRef<Service>,
151176
},
152177

178+
#[snafu(display("failed to delete orphaned resources"))]
179+
DeleteOrphans {
180+
source: stackable_operator::cluster_resources::Error,
181+
},
182+
153183
#[snafu(display("failed to apply status for Listener"))]
154184
ApplyStatus {
155185
source: stackable_operator::client::Error,
@@ -165,19 +195,37 @@ impl ReconcilerError for Error {
165195
match self {
166196
Self::NoNs => None,
167197
Self::NoName => None,
198+
Self::CreateClusterResources { source: _ } => None,
168199
Self::NoListenerClass => None,
169200
Self::ListenerPvSelector { source: _ } => None,
170201
Self::ListenerPodSelector { source: _ } => None,
171202
Self::GetListenerPvs { source: _ } => None,
203+
Self::ValidateListenerLabels { source: _ } => None,
204+
Self::ValidateListenerClassAnnotations {
205+
source: _,
206+
listener_class,
207+
} => Some(listener_class.clone().erase()),
208+
Self::BuildClusterResourcesLabels { source: _ } => None,
172209
Self::GetObject { source: _, obj } => Some(obj.clone()),
173210
Self::BuildListenerOwnerRef { .. } => None,
174211
Self::ApplyService { source: _, svc } => Some(svc.clone().erase()),
212+
Self::DeleteOrphans { source: _ } => None,
175213
Self::ApplyStatus { source: _ } => None,
176214
}
177215
}
178216
}
179217

180218
pub async fn reconcile(listener: Arc<Listener>, ctx: Arc<Ctx>) -> Result<controller::Action> {
219+
let mut cluster_resources = ClusterResources::new(
220+
APP_NAME,
221+
OPERATOR_KEY,
222+
CONTROLLER_NAME,
223+
&listener.object_ref(&()),
224+
// Listeners don't currently support pausing
225+
ClusterResourceApplyStrategy::Default,
226+
)
227+
.context(CreateClusterResourcesSnafu)?;
228+
181229
let ns = listener.metadata.namespace.as_deref().context(NoNsSnafu)?;
182230
let listener_class_name = listener
183231
.spec
@@ -231,18 +279,36 @@ pub async fn reconcile(listener: Arc<Listener>, ctx: Arc<Ctx>) -> Result<control
231279
};
232280

233281
let svc = Service {
234-
metadata: ObjectMeta {
235-
namespace: Some(ns.to_string()),
236-
name: Some(svc_name.clone()),
237-
owner_references: Some(vec![OwnerReferenceBuilder::new()
238-
.initialize_from_resource(&*listener)
239-
.build()
240-
.context(BuildListenerOwnerRefSnafu)?]),
241-
// Propagate the labels from the Listener object to the Service object, so it can be found easier
242-
labels: listener.metadata.labels.clone(),
243-
annotations: Some(listener_class.spec.service_annotations),
244-
..Default::default()
245-
},
282+
metadata: ObjectMetaBuilder::new()
283+
.namespace(ns)
284+
.name(&svc_name)
285+
.ownerreference_from_resource(&*listener, Some(true), Some(true))
286+
.context(BuildListenerOwnerRefSnafu)?
287+
.with_labels(
288+
Labels::try_from(
289+
listener
290+
.metadata
291+
.labels
292+
.as_ref()
293+
.unwrap_or(&BTreeMap::new()),
294+
)
295+
.context(ValidateListenerLabelsSnafu)?,
296+
)
297+
.with_labels(
298+
cluster_resources
299+
// Not using Labels::recommended, since it carries a bunch of extra information that is
300+
// only relevant for stacklets (such as rolegroups and product versions).
301+
.get_required_labels()
302+
.context(BuildClusterResourcesLabelsSnafu)?,
303+
)
304+
.with_annotations(
305+
Annotations::try_from_iter(&listener_class.spec.service_annotations).context(
306+
ValidateListenerClassAnnotationsSnafu {
307+
listener_class: ObjectRef::from_obj(&listener_class),
308+
},
309+
)?,
310+
)
311+
.build(),
246312
spec: Some(ServiceSpec {
247313
// We explicitly match here and do not implement `ToString` as there might be more (non vanilla k8s Service
248314
// types) in the future.
@@ -264,13 +330,11 @@ pub async fn reconcile(listener: Arc<Listener>, ctx: Arc<Ctx>) -> Result<control
264330
}),
265331
..Default::default()
266332
};
267-
let svc = ctx
268-
.client
269-
.apply_patch(FIELD_MANAGER_SCOPE, &svc, &svc)
333+
let svc_ref = ObjectRef::from_obj(&svc);
334+
let svc = cluster_resources
335+
.add(&ctx.client, svc)
270336
.await
271-
.with_context(|_| ApplyServiceSnafu {
272-
svc: ObjectRef::from_obj(&svc),
273-
})?;
337+
.context(ApplyServiceSnafu { svc: svc_ref })?;
274338

275339
let nodes: Vec<Node>;
276340
let kubernetes_service_fqdn: String;
@@ -376,8 +440,14 @@ pub async fn reconcile(listener: Arc<Listener>, ctx: Arc<Ctx>) -> Result<control
376440
),
377441
node_ports: (listener_class.spec.service_type == ServiceType::NodePort).then_some(ports),
378442
};
443+
444+
cluster_resources
445+
.delete_orphaned_resources(&ctx.client)
446+
.await
447+
.context(DeleteOrphansSnafu)?;
448+
379449
ctx.client
380-
.apply_patch_status(FIELD_MANAGER_SCOPE, &listener_status_meta, &listener_status)
450+
.apply_patch_status(CONTROLLER_NAME, &listener_status_meta, &listener_status)
381451
.await
382452
.context(ApplyStatusSnafu)?;
383453

rust/operator-binary/src/main.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ mod csi_server;
2323
mod listener_controller;
2424
mod utils;
2525

26+
const APP_NAME: &str = "listener";
2627
const OPERATOR_KEY: &str = "listeners.stackable.tech";
2728

2829
#[derive(clap::Parser)]
@@ -84,7 +85,8 @@ async fn main() -> anyhow::Result<()> {
8485
built_info::RUSTC_VERSION,
8586
);
8687
let client =
87-
stackable_operator::client::create_client(Some(OPERATOR_KEY.to_string())).await?;
88+
stackable_operator::client::initialize_operator(Some(OPERATOR_KEY.to_string()))
89+
.await?;
8890
if csi_endpoint
8991
.symlink_metadata()
9092
.map_or(false, |meta| meta.file_type().is_socket())

0 commit comments

Comments
 (0)