Skip to content

Commit 54b7527

Browse files
sbernauerTechassi
andauthored
chore(operator/scaling)!: Rework Scaler state to complex enum (#1195)
* chore!: Change Scaler.status.state (and others) to accept PascalCase variants * Rework Scaler status to complex enum * changelog * Update CRD preview * chore: Remove outdated dev comment * feat: Add test-utils feature gate * Revert "feat: Add test-utils feature gate" This reverts commit 67ddfec. * Remove tests --------- Co-authored-by: Techassi <git@techassi.dev>
1 parent ea49d1b commit 54b7527

File tree

5 files changed

+91
-75
lines changed

5 files changed

+91
-75
lines changed

crates/stackable-operator/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ All notable changes to this project will be documented in this file.
99
- Add generic database connection mechanism ([#1163]).
1010
- Add `config_overrides` module with `KeyValueOverridesProvider` trait, enabling
1111
structured config file formats (e.g. JSON) in addition to key-value overrides ([#1177]).
12+
- Add `Scaler` CRD ([#1190], [#1195]).
1213

1314
### Changed
1415

@@ -28,9 +29,11 @@ All notable changes to this project will be documented in this file.
2829

2930
[#1163]: https://github.com/stackabletech/operator-rs/pull/1163
3031
[#1177]: https://github.com/stackabletech/operator-rs/pull/1177
32+
[#1190]: https://github.com/stackabletech/operator-rs/pull/1190
3133
[#1191]: https://github.com/stackabletech/operator-rs/pull/1191
3234
[#1192]: https://github.com/stackabletech/operator-rs/pull/1192
3335
[#1194]: https://github.com/stackabletech/operator-rs/pull/1194
36+
[#1195]: https://github.com/stackabletech/operator-rs/pull/1195
3437

3538
## [0.109.0] - 2026-04-07
3639

crates/stackable-operator/crds/Scaler.yaml

Lines changed: 62 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -65,38 +65,80 @@ spec:
6565
type: string
6666
state:
6767
description: The current state of the scaler state machine.
68+
oneOf:
69+
- required:
70+
- idle
71+
- required:
72+
- preScaling
73+
- required:
74+
- scaling
75+
- required:
76+
- postScaling
77+
- required:
78+
- failed
6879
properties:
69-
details:
80+
failed:
81+
description: |-
82+
A hook returned an error.
83+
84+
The scaler stays here until the user applies the [`Annotation::autoscaling_retry`] annotation
85+
to trigger a reset to [`ScalerState::Idle`].
7086
properties:
7187
failedIn:
72-
description: In which state the scaling operation failed.
88+
description: Which stage produced the error.
7389
enum:
74-
- preScaling
75-
- scaling
76-
- postScaling
90+
- PreScaling
91+
- Scaling
92+
- PostScaling
7793
type: string
78-
previous_replicas:
79-
maximum: 65535.0
80-
minimum: 0.0
81-
type: uint16
8294
reason:
95+
description: Human-readable error message from the hook.
8396
type: string
97+
required:
98+
- failedIn
99+
- reason
100+
type: object
101+
idle:
102+
description: No scaling operation is in progress.
103+
type: object
104+
postScaling:
105+
description: |-
106+
Running the `post_scale` hook (e.g. cluster rebalance).
107+
108+
This stage additionally tracks the previous replica count to be able derive the direction
109+
of the scaling operation.
110+
properties:
111+
previousReplicas:
112+
format: uint16
113+
maximum: 65535.0
114+
minimum: 0.0
115+
type: integer
116+
required:
117+
- previousReplicas
118+
type: object
119+
preScaling:
120+
description: Running the `pre_scale` hook (e.g. data offload).
121+
type: object
122+
scaling:
123+
description: |-
124+
Waiting for the StatefulSet to converge to the new replica count.
125+
126+
This stage additionally tracks the previous replica count to be able derive the direction
127+
of the scaling operation.
128+
properties:
129+
previousReplicas:
130+
format: uint16
131+
maximum: 65535.0
132+
minimum: 0.0
133+
type: integer
134+
required:
135+
- previousReplicas
84136
type: object
85-
state:
86-
enum:
87-
- idle
88-
- preScaling
89-
- scaling
90-
- postScaling
91-
- failed
92-
type: string
93-
required:
94-
- state
95137
type: object
96138
required:
139+
- lastTransitionTime
97140
- replicas
98141
- state
99-
- lastTransitionTime
100142
type: object
101143
required:
102144
- spec

crates/stackable-operator/src/crd/scaler/mod.rs

Lines changed: 8 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
use std::borrow::Cow;
2-
31
use k8s_openapi::apimachinery::pkg::apis::meta::v1::Time;
42
use kube::CustomResource;
53
use schemars::JsonSchema;
@@ -21,7 +19,7 @@ pub mod versioned {
2119
),
2220
namespaced
2321
))]
24-
#[derive(Clone, Debug, PartialEq, CustomResource, Deserialize, Serialize, JsonSchema)]
22+
#[derive(Clone, Debug, PartialEq, Eq, CustomResource, Deserialize, Serialize, JsonSchema)]
2523
pub struct ScalerSpec {
2624
/// Desired replica count.
2725
///
@@ -40,7 +38,7 @@ pub mod versioned {
4038
}
4139

4240
/// Status of a StackableScaler.
43-
#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)]
41+
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, JsonSchema)]
4442
#[serde(rename_all = "camelCase")]
4543
pub struct ScalerStatus {
4644
/// The current total number of replicas targeted by the managed StatefulSet.
@@ -59,25 +57,15 @@ pub struct ScalerStatus {
5957
pub last_transition_time: Time,
6058
}
6159

62-
// We use `#[serde(tag)]` and `#[serde(content)]` here to circumvent Kubernetes restrictions in their
63-
// structural schema subset of OpenAPI schemas. They don't allow one variant to be typed as a string
64-
// and others to be typed as objects. We therefore encode the variant data in a separate details
65-
// key/object. With this, all variants can be encoded as strings, while the status can still contain
66-
// additional data in an extra field when needed.
67-
#[derive(Clone, Debug, Deserialize, Serialize, strum::Display)]
68-
#[serde(
69-
tag = "state",
70-
content = "details",
71-
rename_all = "camelCase",
72-
rename_all_fields = "camelCase"
73-
)]
60+
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, JsonSchema, strum::Display)]
61+
#[serde(rename_all = "camelCase", rename_all_fields = "camelCase")]
7462
#[strum(serialize_all = "camelCase")]
7563
pub enum ScalerState {
7664
/// No scaling operation is in progress.
77-
Idle,
65+
Idle {},
7866

7967
/// Running the `pre_scale` hook (e.g. data offload).
80-
PreScaling,
68+
PreScaling {},
8169

8270
/// Waiting for the StatefulSet to converge to the new replica count.
8371
///
@@ -104,44 +92,9 @@ pub enum ScalerState {
10492
},
10593
}
10694

107-
// We manually implement the JSON schema instead of deriving it, because kube's schema transformer
108-
// cannot handle the derived JsonSchema and proceeds to hit the following error: "Property "state"
109-
// has the schema ... but was already defined as ... in another subschema. The schemas for a
110-
// property used in multiple subschemas must be identical".
111-
impl JsonSchema for ScalerState {
112-
fn schema_name() -> Cow<'static, str> {
113-
"ScalerState".into()
114-
}
115-
116-
fn json_schema(generator: &mut schemars::generate::SchemaGenerator) -> schemars::Schema {
117-
schemars::json_schema!({
118-
"type": "object",
119-
"required": ["state"],
120-
"properties": {
121-
"state": {
122-
"type": "string",
123-
"enum": ["idle", "preScaling", "scaling", "postScaling", "failed"]
124-
},
125-
"details": {
126-
"type": "object",
127-
"properties": {
128-
"failedIn": generator.subschema_for::<FailedInState>(),
129-
"previous_replicas": {
130-
"type": "uint16",
131-
"minimum": u16::MIN,
132-
"maximum": u16::MAX
133-
},
134-
"reason": { "type": "string" }
135-
}
136-
}
137-
}
138-
})
139-
}
140-
}
141-
14295
/// In which state the scaling operation failed.
143-
#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)]
144-
#[serde(rename_all = "camelCase")]
96+
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, JsonSchema)]
97+
#[serde(rename_all = "PascalCase")]
14598
pub enum FailedInState {
14699
/// The `pre_scale` hook returned an error.
147100
PreScaling,

crates/stackable-operator/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ pub mod product_config_utils;
3131
pub mod product_logging;
3232
pub mod role_utils;
3333
pub mod status;
34+
pub mod test_utils;
3435
pub mod utils;
3536
pub mod validation;
3637

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/// Please use only in tests, as we have non-ideal error handling in case serde_yaml produced
2+
/// non-utf8 output.
3+
pub fn serialize_to_yaml_with_singleton_map<S>(input: &S) -> Result<String, serde_yaml::Error>
4+
where
5+
S: serde::Serialize,
6+
{
7+
use serde::ser::Error as _;
8+
9+
let mut buffer = Vec::new();
10+
let mut serializer = serde_yaml::Serializer::new(&mut buffer);
11+
serde_yaml::with::singleton_map_recursive::serialize(input, &mut serializer)?;
12+
String::from_utf8(buffer).map_err(|err| {
13+
serde_yaml::Error::custom(format!(
14+
"For *some* reason, serde_yaml produced non-utf8 output: {err}"
15+
))
16+
})
17+
}

0 commit comments

Comments
 (0)