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