Line data Source code
1 : use std::str::FromStr;
2 :
3 : /// Request/response types for the storage controller
4 : /// API (`/control/v1` prefix). Implemented by the server
5 : /// in [`storage_controller::http`]
6 : use serde::{Deserialize, Serialize};
7 : use utils::id::{NodeId, TenantId};
8 :
9 : use crate::{
10 : models::{ShardParameters, TenantConfig},
11 : shard::{ShardStripeSize, TenantShardId},
12 : };
13 :
14 6 : #[derive(Serialize, Deserialize, Debug)]
15 : #[serde(deny_unknown_fields)]
16 : pub struct TenantCreateRequest {
17 : pub new_tenant_id: TenantShardId,
18 : #[serde(default)]
19 : #[serde(skip_serializing_if = "Option::is_none")]
20 : pub generation: Option<u32>,
21 :
22 : // If omitted, create a single shard with TenantShardId::unsharded()
23 : #[serde(default)]
24 : #[serde(skip_serializing_if = "ShardParameters::is_unsharded")]
25 : pub shard_parameters: ShardParameters,
26 :
27 : #[serde(default)]
28 : #[serde(skip_serializing_if = "Option::is_none")]
29 : pub placement_policy: Option<PlacementPolicy>,
30 :
31 : #[serde(flatten)]
32 : pub config: TenantConfig, // as we have a flattened field, we should reject all unknown fields in it
33 : }
34 :
35 0 : #[derive(Serialize, Deserialize)]
36 : pub struct TenantCreateResponseShard {
37 : pub shard_id: TenantShardId,
38 : pub node_id: NodeId,
39 : pub generation: u32,
40 : }
41 :
42 0 : #[derive(Serialize, Deserialize)]
43 : pub struct TenantCreateResponse {
44 : pub shards: Vec<TenantCreateResponseShard>,
45 : }
46 :
47 0 : #[derive(Serialize, Deserialize)]
48 : pub struct NodeRegisterRequest {
49 : pub node_id: NodeId,
50 :
51 : pub listen_pg_addr: String,
52 : pub listen_pg_port: u16,
53 :
54 : pub listen_http_addr: String,
55 : pub listen_http_port: u16,
56 : }
57 :
58 0 : #[derive(Serialize, Deserialize)]
59 : pub struct NodeConfigureRequest {
60 : pub node_id: NodeId,
61 :
62 : pub availability: Option<NodeAvailabilityWrapper>,
63 : pub scheduling: Option<NodeSchedulingPolicy>,
64 : }
65 :
66 0 : #[derive(Serialize, Deserialize)]
67 : pub struct TenantPolicyRequest {
68 : pub placement: Option<PlacementPolicy>,
69 : pub scheduling: Option<ShardSchedulingPolicy>,
70 : }
71 :
72 0 : #[derive(Serialize, Deserialize, Debug)]
73 : pub struct TenantLocateResponseShard {
74 : pub shard_id: TenantShardId,
75 : pub node_id: NodeId,
76 :
77 : pub listen_pg_addr: String,
78 : pub listen_pg_port: u16,
79 :
80 : pub listen_http_addr: String,
81 : pub listen_http_port: u16,
82 : }
83 :
84 0 : #[derive(Serialize, Deserialize)]
85 : pub struct TenantLocateResponse {
86 : pub shards: Vec<TenantLocateResponseShard>,
87 : pub shard_params: ShardParameters,
88 : }
89 :
90 0 : #[derive(Serialize, Deserialize)]
91 : pub struct TenantDescribeResponse {
92 : pub tenant_id: TenantId,
93 : pub shards: Vec<TenantDescribeResponseShard>,
94 : pub stripe_size: ShardStripeSize,
95 : pub policy: PlacementPolicy,
96 : pub config: TenantConfig,
97 : }
98 :
99 0 : #[derive(Serialize, Deserialize)]
100 : pub struct NodeDescribeResponse {
101 : pub id: NodeId,
102 :
103 : pub availability: NodeAvailabilityWrapper,
104 : pub scheduling: NodeSchedulingPolicy,
105 :
106 : pub listen_http_addr: String,
107 : pub listen_http_port: u16,
108 :
109 : pub listen_pg_addr: String,
110 : pub listen_pg_port: u16,
111 : }
112 :
113 0 : #[derive(Serialize, Deserialize)]
114 : pub struct TenantDescribeResponseShard {
115 : pub tenant_shard_id: TenantShardId,
116 :
117 : pub node_attached: Option<NodeId>,
118 : pub node_secondary: Vec<NodeId>,
119 :
120 : pub last_error: String,
121 :
122 : /// A task is currently running to reconcile this tenant's intent state with the state on pageservers
123 : pub is_reconciling: bool,
124 : /// This shard failed in sending a compute notification to the cloud control plane, and a retry is pending.
125 : pub is_pending_compute_notification: bool,
126 : /// A shard split is currently underway
127 : pub is_splitting: bool,
128 :
129 : pub scheduling_policy: ShardSchedulingPolicy,
130 : }
131 :
132 : /// Explicitly migrating a particular shard is a low level operation
133 : /// TODO: higher level "Reschedule tenant" operation where the request
134 : /// specifies some constraints, e.g. asking it to get off particular node(s)
135 0 : #[derive(Serialize, Deserialize, Debug)]
136 : pub struct TenantShardMigrateRequest {
137 : pub tenant_shard_id: TenantShardId,
138 : pub node_id: NodeId,
139 : }
140 :
141 : /// Utilisation score indicating how good a candidate a pageserver
142 : /// is for scheduling the next tenant. See [`crate::models::PageserverUtilization`].
143 : /// Lower values are better.
144 0 : #[derive(Serialize, Deserialize, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Debug)]
145 : pub struct UtilizationScore(pub u64);
146 :
147 : impl UtilizationScore {
148 44 : pub fn worst() -> Self {
149 44 : UtilizationScore(u64::MAX)
150 44 : }
151 : }
152 :
153 0 : #[derive(Serialize, Deserialize, Clone, Copy, Debug)]
154 : #[serde(into = "NodeAvailabilityWrapper")]
155 : pub enum NodeAvailability {
156 : // Normal, happy state
157 : Active(UtilizationScore),
158 : // Offline: Tenants shouldn't try to attach here, but they may assume that their
159 : // secondary locations on this node still exist. Newly added nodes are in this
160 : // state until we successfully contact them.
161 : Offline,
162 : }
163 :
164 : impl PartialEq for NodeAvailability {
165 0 : fn eq(&self, other: &Self) -> bool {
166 : use NodeAvailability::*;
167 0 : matches!((self, other), (Active(_), Active(_)) | (Offline, Offline))
168 0 : }
169 : }
170 :
171 : impl Eq for NodeAvailability {}
172 :
173 : // This wrapper provides serde functionality and it should only be used to
174 : // communicate with external callers which don't know or care about the
175 : // utilisation score of the pageserver it is targeting.
176 0 : #[derive(Serialize, Deserialize, Clone, Copy, Debug)]
177 : pub enum NodeAvailabilityWrapper {
178 : Active,
179 : Offline,
180 : }
181 :
182 : impl From<NodeAvailabilityWrapper> for NodeAvailability {
183 0 : fn from(val: NodeAvailabilityWrapper) -> Self {
184 0 : match val {
185 : // Assume the worst utilisation score to begin with. It will later be updated by
186 : // the heartbeats.
187 0 : NodeAvailabilityWrapper::Active => NodeAvailability::Active(UtilizationScore::worst()),
188 0 : NodeAvailabilityWrapper::Offline => NodeAvailability::Offline,
189 : }
190 0 : }
191 : }
192 :
193 : impl From<NodeAvailability> for NodeAvailabilityWrapper {
194 0 : fn from(val: NodeAvailability) -> Self {
195 0 : match val {
196 0 : NodeAvailability::Active(_) => NodeAvailabilityWrapper::Active,
197 0 : NodeAvailability::Offline => NodeAvailabilityWrapper::Offline,
198 : }
199 0 : }
200 : }
201 :
202 0 : #[derive(Serialize, Deserialize, Clone, Copy, Eq, PartialEq, Debug)]
203 : pub enum ShardSchedulingPolicy {
204 : // Normal mode: the tenant's scheduled locations may be updated at will, including
205 : // for non-essential optimization.
206 : Active,
207 :
208 : // Disable optimizations, but permit scheduling when necessary to fulfil the PlacementPolicy.
209 : // For example, this still permits a node's attachment location to change to a secondary in
210 : // response to a node failure, or to assign a new secondary if a node was removed.
211 : Essential,
212 :
213 : // No scheduling: leave the shard running wherever it currently is. Even if the shard is
214 : // unavailable, it will not be rescheduled to another node.
215 : Pause,
216 :
217 : // No reconciling: we will make no location_conf API calls to pageservers at all. If the
218 : // shard is unavailable, it stays that way. If a node fails, this shard doesn't get failed over.
219 : Stop,
220 : }
221 :
222 : impl Default for ShardSchedulingPolicy {
223 22 : fn default() -> Self {
224 22 : Self::Active
225 22 : }
226 : }
227 :
228 0 : #[derive(Serialize, Deserialize, Clone, Copy, Eq, PartialEq, Debug)]
229 : pub enum NodeSchedulingPolicy {
230 : Active,
231 : Filling,
232 : Pause,
233 : PauseForRestart,
234 : Draining,
235 : }
236 :
237 : impl FromStr for NodeSchedulingPolicy {
238 : type Err = anyhow::Error;
239 :
240 0 : fn from_str(s: &str) -> Result<Self, Self::Err> {
241 0 : match s {
242 0 : "active" => Ok(Self::Active),
243 0 : "filling" => Ok(Self::Filling),
244 0 : "pause" => Ok(Self::Pause),
245 0 : "pause_for_restart" => Ok(Self::PauseForRestart),
246 0 : "draining" => Ok(Self::Draining),
247 0 : _ => Err(anyhow::anyhow!("Unknown scheduling state '{s}'")),
248 : }
249 0 : }
250 : }
251 :
252 : impl From<NodeSchedulingPolicy> for String {
253 0 : fn from(value: NodeSchedulingPolicy) -> String {
254 0 : use NodeSchedulingPolicy::*;
255 0 : match value {
256 0 : Active => "active",
257 0 : Filling => "filling",
258 0 : Pause => "pause",
259 0 : PauseForRestart => "pause_for_restart",
260 0 : Draining => "draining",
261 : }
262 0 : .to_string()
263 0 : }
264 : }
265 :
266 : /// Controls how tenant shards are mapped to locations on pageservers, e.g. whether
267 : /// to create secondary locations.
268 8 : #[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
269 : pub enum PlacementPolicy {
270 : /// Normal live state: one attached pageserver and zero or more secondaries.
271 : Attached(usize),
272 : /// Create one secondary mode locations. This is useful when onboarding
273 : /// a tenant, or for an idle tenant that we might want to bring online quickly.
274 : Secondary,
275 :
276 : /// Do not attach to any pageservers. This is appropriate for tenants that
277 : /// have been idle for a long time, where we do not mind some delay in making
278 : /// them available in future.
279 : Detached,
280 : }
281 :
282 0 : #[derive(Serialize, Deserialize, Debug)]
283 : pub struct TenantShardMigrateResponse {}
284 :
285 : #[cfg(test)]
286 : mod test {
287 : use super::*;
288 : use serde_json;
289 :
290 : /// Check stability of PlacementPolicy's serialization
291 : #[test]
292 2 : fn placement_policy_encoding() -> anyhow::Result<()> {
293 2 : let v = PlacementPolicy::Attached(1);
294 2 : let encoded = serde_json::to_string(&v)?;
295 2 : assert_eq!(encoded, "{\"Attached\":1}");
296 2 : assert_eq!(serde_json::from_str::<PlacementPolicy>(&encoded)?, v);
297 :
298 2 : let v = PlacementPolicy::Detached;
299 2 : let encoded = serde_json::to_string(&v)?;
300 2 : assert_eq!(encoded, "\"Detached\"");
301 2 : assert_eq!(serde_json::from_str::<PlacementPolicy>(&encoded)?, v);
302 2 : Ok(())
303 2 : }
304 :
305 : #[test]
306 2 : fn test_reject_unknown_field() {
307 2 : let id = TenantId::generate();
308 2 : let create_request = serde_json::json!({
309 2 : "new_tenant_id": id.to_string(),
310 2 : "unknown_field": "unknown_value".to_string(),
311 2 : });
312 2 : let err = serde_json::from_value::<TenantCreateRequest>(create_request).unwrap_err();
313 2 : assert!(
314 2 : err.to_string().contains("unknown field `unknown_field`"),
315 0 : "expect unknown field `unknown_field` error, got: {}",
316 : err
317 : );
318 2 : }
319 : }
|