-
-
Notifications
You must be signed in to change notification settings - Fork 18
Expand file tree
/
Copy pathmod.rs
More file actions
154 lines (136 loc) · 5.42 KB
/
Copy pathmod.rs
File metadata and controls
154 lines (136 loc) · 5.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
use std::borrow::Cow;
use k8s_openapi::apimachinery::pkg::apis::meta::v1::Time;
use kube::CustomResource;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[cfg(doc)]
use crate::kvp::Annotation;
use crate::versioned::versioned;
#[versioned(version(name = "v1alpha1"))]
pub mod versioned {
#[versioned(crd(
group = "autoscaling.stackable.tech",
status = ScalerStatus,
scale(
spec_replicas_path = ".spec.replicas",
status_replicas_path = ".status.replicas",
label_selector_path = ".status.selector"
),
namespaced
))]
#[derive(Clone, Debug, PartialEq, CustomResource, Deserialize, Serialize, JsonSchema)]
pub struct ScalerSpec {
/// Desired replica count.
///
/// Written by the horizontal pod autoscaling mechanism via the /scale subresource.
///
/// NOTE: This and other replica fields)use a [`u16`] instead of a [`i32`] used by
/// [`k8s_openapi`] types to force a non-negative replica count. All [`u16`]s can be
/// converted losslessly to [`i32`]s where needed.
///
/// Upstream issues:
///
/// - https://github.com/kubernetes/kubernetes/issues/105533
/// - https://github.com/Arnavion/k8s-openapi/issues/136
pub replicas: u16,
}
}
/// Status of a StackableScaler.
#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ScalerStatus {
/// The current total number of replicas targeted by the managed StatefulSet.
///
/// Exposed via the `/scale` subresource for horizontal pod autoscaling consumption.
pub replicas: u16,
/// Label selector string for HPA pod counting. Written at `.status.selector`.
#[serde(skip_serializing_if = "Option::is_none")]
pub selector: Option<String>,
/// The current state of the scaler state machine.
pub state: ScalerState,
/// Timestamp indicating when the scaler state last transitioned.
pub last_transition_time: Time,
}
// We use `#[serde(tag)]` and `#[serde(content)]` here to circumvent Kubernetes restrictions in their
// structural schema subset of OpenAPI schemas. They don't allow one variant to be typed as a string
// and others to be typed as objects. We therefore encode the variant data in a separate details
// key/object. With this, all variants can be encoded as strings, while the status can still contain
// additional data in an extra field when needed.
#[derive(Clone, Debug, Deserialize, Serialize, strum::Display)]
#[serde(
tag = "state",
content = "details",
rename_all = "camelCase",
rename_all_fields = "camelCase"
)]
#[strum(serialize_all = "camelCase")]
pub enum ScalerState {
/// No scaling operation is in progress.
Idle,
/// Running the `pre_scale` hook (e.g. data offload).
PreScaling,
/// Waiting for the StatefulSet to converge to the new replica count.
///
/// This stage additionally tracks the previous replica count to be able derive the direction
/// of the scaling operation.
Scaling { previous_replicas: u16 },
/// Running the `post_scale` hook (e.g. cluster rebalance).
///
/// This stage additionally tracks the previous replica count to be able derive the direction
/// of the scaling operation.
PostScaling { previous_replicas: u16 },
/// A hook returned an error.
///
/// The scaler stays here until the user applies the [`Annotation::autoscaling_retry`] annotation
/// to trigger a reset to [`ScalerState::Idle`].
Failed {
/// Which stage produced the error.
failed_in: FailedInState,
/// Human-readable error message from the hook.
reason: String,
},
}
// We manually implement the JSON schema instead of deriving it, because kube's schema transformer
// cannot handle the derived JsonSchema and proceeds to hit the following error: "Property "state"
// has the schema ... but was already defined as ... in another subschema. The schemas for a
// property used in multiple subschemas must be identical".
impl JsonSchema for ScalerState {
fn schema_name() -> Cow<'static, str> {
"ScalerState".into()
}
fn json_schema(generator: &mut schemars::generate::SchemaGenerator) -> schemars::Schema {
schemars::json_schema!({
"type": "object",
"required": ["state"],
"properties": {
"state": {
"type": "string",
"enum": ["idle", "preScaling", "scaling", "postScaling", "failed"]
},
"details": {
"type": "object",
"properties": {
"failedIn": generator.subschema_for::<FailedInState>(),
"previous_replicas": {
"type": "uint16",
"minimum": u16::MIN,
"maximum": u16::MAX
},
"reason": { "type": "string" }
}
}
}
})
}
}
/// In which state the scaling operation failed.
#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub enum FailedInState {
/// The `pre_scale` hook returned an error.
PreScaling,
/// The StatefulSet failed to reach the desired replica count.
Scaling,
/// The `post_scale` hook returned an error.
PostScaling,
}