Skip to content

Commit 5807c4a

Browse files
authored
Listener: Use headless and metrics service (#644)
* Add headless service, move listener logic to own file * Clippy lints * fix docs * Update test to probe for headless service * Update changelog * Moving services into own module * Updating according to feedback on hive * Update rustdocs
1 parent c8d527e commit 5807c4a

8 files changed

Lines changed: 270 additions & 118 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
- Use `--file-log-rotation-period` (or `FILE_LOG_ROTATION_PERIOD`) to configure the frequency of rotation.
1010
- Use `--console-log-format` (or `CONSOLE_LOG_FORMAT`) to set the format to `plain` (default) or `json`.
1111
- BREAKING: Added listener support for Superset ([#625]).
12+
- Add internal headless service in addition to the metrics service and move listener logic to listener.rs ([#644])
1213

1314
### Changed
1415

@@ -45,6 +46,7 @@
4546
[#635]: https://github.com/stackabletech/superset-operator/pull/635
4647
[#637]: https://github.com/stackabletech/superset-operator/pull/637
4748
[#643]: https://github.com/stackabletech/superset-operator/pull/643
49+
[#644]: https://github.com/stackabletech/superset-operator/pull/644
4850

4951
## [25.3.0] - 2025-03-21
5052

rust/operator-binary/src/crd/mod.rs

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,10 @@ use stackable_operator::{
3232
};
3333
use strum::{Display, EnumIter, EnumString, IntoEnumIterator};
3434

35-
use crate::crd::v1alpha1::{SupersetConfigFragment, SupersetRoleConfig};
35+
use crate::{
36+
crd::v1alpha1::{SupersetConfigFragment, SupersetRoleConfig},
37+
listener::default_listener_class,
38+
};
3639

3740
pub mod affinity;
3841
pub mod authentication;
@@ -49,9 +52,6 @@ pub const MAX_LOG_FILES_SIZE: MemoryQuantity = MemoryQuantity {
4952
unit: BinaryMultiple::Mebi,
5053
};
5154

52-
pub const LISTENER_VOLUME_NAME: &str = "listener";
53-
pub const LISTENER_VOLUME_DIR: &str = "/stackable/listener";
54-
5555
pub const APP_PORT_NAME: &str = "http";
5656
pub const APP_PORT: u16 = 8088;
5757
pub const METRICS_PORT_NAME: &str = "metrics";
@@ -311,10 +311,6 @@ impl Default for v1alpha1::SupersetRoleConfig {
311311
}
312312
}
313313

314-
fn default_listener_class() -> String {
315-
"cluster-internal".to_string()
316-
}
317-
318314
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
319315
#[serde(rename_all = "camelCase")]
320316
pub struct SupersetCredentials {
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
use snafu::{ResultExt, Snafu};
2+
use stackable_operator::{builder::meta::ObjectMetaBuilder, crd::listener, kvp::ObjectLabels};
3+
4+
use crate::crd::{APP_PORT, APP_PORT_NAME, v1alpha1};
5+
6+
pub const LISTENER_VOLUME_NAME: &str = "listener";
7+
pub const LISTENER_VOLUME_DIR: &str = "/stackable/listener";
8+
9+
#[derive(Snafu, Debug)]
10+
pub enum Error {
11+
#[snafu(display("object is missing metadata to build owner reference"))]
12+
ObjectMissingMetadataForOwnerRef {
13+
source: stackable_operator::builder::meta::Error,
14+
},
15+
#[snafu(display("failed to build Metadata"))]
16+
MetadataBuild {
17+
source: stackable_operator::builder::meta::Error,
18+
},
19+
}
20+
21+
pub fn build_group_listener(
22+
superset: &v1alpha1::SupersetCluster,
23+
object_labels: ObjectLabels<v1alpha1::SupersetCluster>,
24+
listener_class: String,
25+
listener_group_name: String,
26+
) -> Result<listener::v1alpha1::Listener, Error> {
27+
let metadata = ObjectMetaBuilder::new()
28+
.name_and_namespace(superset)
29+
.name(listener_group_name)
30+
.ownerreference_from_resource(superset, None, Some(true))
31+
.context(ObjectMissingMetadataForOwnerRefSnafu)?
32+
.with_recommended_labels(object_labels)
33+
.context(MetadataBuildSnafu)?
34+
.build();
35+
36+
let spec = listener::v1alpha1::ListenerSpec {
37+
class_name: Some(listener_class),
38+
ports: Some(listener_ports()),
39+
..Default::default()
40+
};
41+
42+
let listener = listener::v1alpha1::Listener {
43+
metadata,
44+
spec,
45+
status: None,
46+
};
47+
48+
Ok(listener)
49+
}
50+
51+
pub fn listener_ports() -> Vec<listener::v1alpha1::ListenerPort> {
52+
vec![listener::v1alpha1::ListenerPort {
53+
name: APP_PORT_NAME.to_owned(),
54+
port: APP_PORT.into(),
55+
protocol: Some("TCP".to_owned()),
56+
}]
57+
}
58+
59+
pub fn default_listener_class() -> String {
60+
"cluster-internal".to_string()
61+
}

rust/operator-binary/src/main.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,11 @@ mod config;
4242
mod controller_commons;
4343
mod crd;
4444
mod druid_connection_controller;
45+
mod listener;
4546
mod operations;
4647
mod product_logging;
4748
mod rbac;
49+
mod service;
4850
mod superset_controller;
4951
mod util;
5052

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
use snafu::{ResultExt, Snafu};
2+
use stackable_operator::{
3+
builder::meta::ObjectMetaBuilder,
4+
commons::product_image_selection::ResolvedProductImage,
5+
k8s_openapi::api::core::v1::{Service, ServicePort, ServiceSpec},
6+
kvp::{Label, Labels},
7+
role_utils::RoleGroupRef,
8+
};
9+
10+
use crate::{
11+
crd::{APP_NAME, APP_PORT, APP_PORT_NAME, METRICS_PORT, METRICS_PORT_NAME, v1alpha1},
12+
superset_controller::SUPERSET_CONTROLLER_NAME,
13+
util::build_recommended_labels,
14+
};
15+
#[derive(Debug, Snafu)]
16+
pub enum Error {
17+
#[snafu(display("object is missing metadata to build owner reference"))]
18+
ObjectMissingMetadataForOwnerRef {
19+
source: stackable_operator::builder::meta::Error,
20+
},
21+
#[snafu(display("failed to build Metadata"))]
22+
MetadataBuild {
23+
source: stackable_operator::builder::meta::Error,
24+
},
25+
#[snafu(display("failed to build Labels"))]
26+
LabelBuild {
27+
source: stackable_operator::kvp::LabelError,
28+
},
29+
}
30+
31+
/// The rolegroup [`Service`] is a headless service that allows direct access to the instances of a certain rolegroup
32+
///
33+
/// This is mostly useful for internal communication between peers, or for clients that perform client-side load balancing.
34+
pub fn build_node_rolegroup_headless_service(
35+
superset: &v1alpha1::SupersetCluster,
36+
resolved_product_image: &ResolvedProductImage,
37+
rolegroup: &RoleGroupRef<v1alpha1::SupersetCluster>,
38+
) -> Result<Service, Error> {
39+
let headless_service = Service {
40+
metadata: ObjectMetaBuilder::new()
41+
.name_and_namespace(superset)
42+
.name(rolegroup_headless_service_name(rolegroup))
43+
.ownerreference_from_resource(superset, None, Some(true))
44+
.context(ObjectMissingMetadataForOwnerRefSnafu)?
45+
.with_recommended_labels(build_recommended_labels(
46+
superset,
47+
SUPERSET_CONTROLLER_NAME,
48+
&resolved_product_image.app_version_label,
49+
&rolegroup.role,
50+
&rolegroup.role_group,
51+
))
52+
.context(MetadataBuildSnafu)?
53+
.build(),
54+
spec: Some(ServiceSpec {
55+
// Internal communication does not need to be exposed
56+
type_: Some("ClusterIP".to_owned()),
57+
cluster_ip: Some("None".to_owned()),
58+
ports: Some(service_ports()),
59+
selector: Some(
60+
Labels::role_group_selector(
61+
superset,
62+
APP_NAME,
63+
&rolegroup.role,
64+
&rolegroup.role_group,
65+
)
66+
.context(LabelBuildSnafu)?
67+
.into(),
68+
),
69+
publish_not_ready_addresses: Some(true),
70+
..ServiceSpec::default()
71+
}),
72+
status: None,
73+
};
74+
Ok(headless_service)
75+
}
76+
77+
/// The rolegroup metrics [`Service`] is a service that exposes metrics and a prometheus scraping label
78+
pub fn build_node_rolegroup_metrics_service(
79+
superset: &v1alpha1::SupersetCluster,
80+
resolved_product_image: &ResolvedProductImage,
81+
rolegroup: &RoleGroupRef<v1alpha1::SupersetCluster>,
82+
) -> Result<Service, Error> {
83+
let metrics_service = Service {
84+
metadata: ObjectMetaBuilder::new()
85+
.name_and_namespace(superset)
86+
.name(rolegroup_metrics_service_name(rolegroup))
87+
.ownerreference_from_resource(superset, None, Some(true))
88+
.context(ObjectMissingMetadataForOwnerRefSnafu)?
89+
.with_recommended_labels(build_recommended_labels(
90+
superset,
91+
SUPERSET_CONTROLLER_NAME,
92+
&resolved_product_image.app_version_label,
93+
&rolegroup.role,
94+
&rolegroup.role_group,
95+
))
96+
.context(MetadataBuildSnafu)?
97+
.with_label(Label::try_from(("prometheus.io/scrape", "true")).context(LabelBuildSnafu)?)
98+
.build(),
99+
spec: Some(ServiceSpec {
100+
// Internal communication does not need to be exposed
101+
type_: Some("ClusterIP".to_owned()),
102+
cluster_ip: Some("None".to_owned()),
103+
ports: Some(metrics_ports()),
104+
selector: Some(
105+
Labels::role_group_selector(
106+
superset,
107+
APP_NAME,
108+
&rolegroup.role,
109+
&rolegroup.role_group,
110+
)
111+
.context(LabelBuildSnafu)?
112+
.into(),
113+
),
114+
publish_not_ready_addresses: Some(true),
115+
..ServiceSpec::default()
116+
}),
117+
status: None,
118+
};
119+
120+
Ok(metrics_service)
121+
}
122+
123+
/// Headless service for cluster internal purposes only.
124+
// TODO: Move to operator-rs
125+
pub fn rolegroup_headless_service_name(
126+
rolegroup: &RoleGroupRef<v1alpha1::SupersetCluster>,
127+
) -> String {
128+
format!("{name}-headless", name = rolegroup.object_name())
129+
}
130+
131+
/// Headless metrics service exposes Prometheus endpoint only
132+
// TODO: Move to operator-rs
133+
pub fn rolegroup_metrics_service_name(
134+
rolegroup: &RoleGroupRef<v1alpha1::SupersetCluster>,
135+
) -> String {
136+
format!("{name}-metrics", name = rolegroup.object_name())
137+
}
138+
139+
fn metrics_ports() -> Vec<ServicePort> {
140+
vec![ServicePort {
141+
name: Some(METRICS_PORT_NAME.to_string()),
142+
port: METRICS_PORT.into(),
143+
protocol: Some("TCP".to_string()),
144+
..ServicePort::default()
145+
}]
146+
}
147+
148+
fn service_ports() -> Vec<ServicePort> {
149+
vec![ServicePort {
150+
name: Some(APP_PORT_NAME.to_string()),
151+
port: APP_PORT.into(),
152+
protocol: Some("TCP".to_string()),
153+
..ServicePort::default()
154+
}]
155+
}

0 commit comments

Comments
 (0)