Line data Source code
1 : pub mod partitioning;
2 :
3 : use std::{
4 : collections::HashMap,
5 : io::{BufRead, Read},
6 : num::{NonZeroU64, NonZeroUsize},
7 : time::{Duration, SystemTime},
8 : };
9 :
10 : use byteorder::{BigEndian, ReadBytesExt};
11 : use postgres_ffi::BLCKSZ;
12 : use serde::{Deserialize, Serialize};
13 : use serde_with::serde_as;
14 : use strum_macros;
15 : use utils::{
16 : completion,
17 : history_buffer::HistoryBufferWithDropCounter,
18 : id::{NodeId, TenantId, TimelineId},
19 : lsn::Lsn,
20 : };
21 :
22 : use crate::{
23 : reltag::RelTag,
24 : shard::{ShardCount, ShardStripeSize, TenantShardId},
25 : };
26 : use anyhow::bail;
27 : use bytes::{Buf, BufMut, Bytes, BytesMut};
28 :
29 : /// The state of a tenant in this pageserver.
30 : ///
31 : /// ```mermaid
32 : /// stateDiagram-v2
33 : ///
34 : /// [*] --> Loading: spawn_load()
35 : /// [*] --> Attaching: spawn_attach()
36 : ///
37 : /// Loading --> Activating: activate()
38 : /// Attaching --> Activating: activate()
39 : /// Activating --> Active: infallible
40 : ///
41 : /// Loading --> Broken: load() failure
42 : /// Attaching --> Broken: attach() failure
43 : ///
44 : /// Active --> Stopping: set_stopping(), part of shutdown & detach
45 : /// Stopping --> Broken: late error in remove_tenant_from_memory
46 : ///
47 : /// Broken --> [*]: ignore / detach / shutdown
48 : /// Stopping --> [*]: remove_from_memory complete
49 : ///
50 : /// Active --> Broken: cfg(testing)-only tenant break point
51 : /// ```
52 : #[derive(
53 36684 : Clone,
54 4969 : PartialEq,
55 : Eq,
56 882 : serde::Serialize,
57 34 : serde::Deserialize,
58 18 : strum_macros::Display,
59 : strum_macros::EnumVariantNames,
60 0 : strum_macros::AsRefStr,
61 3450 : strum_macros::IntoStaticStr,
62 : )]
63 : #[serde(tag = "slug", content = "data")]
64 : pub enum TenantState {
65 : /// This tenant is being loaded from local disk.
66 : ///
67 : /// `set_stopping()` and `set_broken()` do not work in this state and wait for it to pass.
68 : Loading,
69 : /// This tenant is being attached to the pageserver.
70 : ///
71 : /// `set_stopping()` and `set_broken()` do not work in this state and wait for it to pass.
72 : Attaching,
73 : /// The tenant is transitioning from Loading/Attaching to Active.
74 : ///
75 : /// While in this state, the individual timelines are being activated.
76 : ///
77 : /// `set_stopping()` and `set_broken()` do not work in this state and wait for it to pass.
78 : Activating(ActivatingFrom),
79 : /// The tenant has finished activating and is open for business.
80 : ///
81 : /// Transitions out of this state are possible through `set_stopping()` and `set_broken()`.
82 : Active,
83 : /// The tenant is recognized by pageserver, but it is being detached or the
84 : /// system is being shut down.
85 : ///
86 : /// Transitions out of this state are possible through `set_broken()`.
87 : Stopping {
88 : // Because of https://github.com/serde-rs/serde/issues/2105 this has to be a named field,
89 : // otherwise it will not be skipped during deserialization
90 : #[serde(skip)]
91 : progress: completion::Barrier,
92 : },
93 : /// The tenant is recognized by the pageserver, but can no longer be used for
94 : /// any operations.
95 : ///
96 : /// If the tenant fails to load or attach, it will transition to this state
97 : /// and it is guaranteed that no background tasks are running in its name.
98 : ///
99 : /// The other way to transition into this state is from `Stopping` state
100 : /// through `set_broken()` called from `remove_tenant_from_memory()`. That happens
101 : /// if the cleanup future executed by `remove_tenant_from_memory()` fails.
102 : Broken { reason: String, backtrace: String },
103 : }
104 :
105 : impl TenantState {
106 644 : pub fn attachment_status(&self) -> TenantAttachmentStatus {
107 : use TenantAttachmentStatus::*;
108 :
109 : // Below TenantState::Activating is used as "transient" or "transparent" state for
110 : // attachment_status determining.
111 0 : match self {
112 : // The attach procedure writes the marker file before adding the Attaching tenant to the tenants map.
113 : // So, technically, we can return Attached here.
114 : // However, as soon as Console observes Attached, it will proceed with the Postgres-level health check.
115 : // But, our attach task might still be fetching the remote timelines, etc.
116 : // So, return `Maybe` while Attaching, making Console wait for the attach task to finish.
117 206 : Self::Attaching | Self::Activating(ActivatingFrom::Attaching) => Maybe,
118 : // tenant mgr startup distinguishes attaching from loading via marker file.
119 0 : Self::Loading | Self::Activating(ActivatingFrom::Loading) => Attached,
120 : // We only reach Active after successful load / attach.
121 : // So, call atttachment status Attached.
122 210 : Self::Active => Attached,
123 : // If the (initial or resumed) attach procedure fails, the tenant becomes Broken.
124 : // However, it also becomes Broken if the regular load fails.
125 : // From Console's perspective there's no practical difference
126 : // because attachment_status is polled by console only during attach operation execution.
127 111 : Self::Broken { reason, .. } => Failed {
128 111 : reason: reason.to_owned(),
129 111 : },
130 : // Why is Stopping a Maybe case? Because, during pageserver shutdown,
131 : // we set the Stopping state irrespective of whether the tenant
132 : // has finished attaching or not.
133 117 : Self::Stopping { .. } => Maybe,
134 : }
135 644 : }
136 :
137 58 : pub fn broken_from_reason(reason: String) -> Self {
138 58 : let backtrace_str: String = format!("{}", std::backtrace::Backtrace::force_capture());
139 58 : Self::Broken {
140 58 : reason,
141 58 : backtrace: backtrace_str,
142 58 : }
143 58 : }
144 : }
145 :
146 : impl std::fmt::Debug for TenantState {
147 16 : fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
148 4 : match self {
149 4 : Self::Broken { reason, backtrace } if !reason.is_empty() => {
150 4 : write!(f, "Broken due to: {reason}. Backtrace:\n{backtrace}")
151 : }
152 12 : _ => write!(f, "{self}"),
153 : }
154 16 : }
155 : }
156 :
157 : /// The only [`TenantState`] variants we could be `TenantState::Activating` from.
158 567 : #[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
159 : pub enum ActivatingFrom {
160 : /// Arrived to [`TenantState::Activating`] from [`TenantState::Loading`]
161 : Loading,
162 : /// Arrived to [`TenantState::Activating`] from [`TenantState::Attaching`]
163 : Attaching,
164 : }
165 :
166 : /// A state of a timeline in pageserver's memory.
167 3408738 : #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
168 : pub enum TimelineState {
169 : /// The timeline is recognized by the pageserver but is not yet operational.
170 : /// In particular, the walreceiver connection loop is not running for this timeline.
171 : /// It will eventually transition to state Active or Broken.
172 : Loading,
173 : /// The timeline is fully operational.
174 : /// It can be queried, and the walreceiver connection loop is running.
175 : Active,
176 : /// The timeline was previously Loading or Active but is shutting down.
177 : /// It cannot transition back into any other state.
178 : Stopping,
179 : /// The timeline is broken and not operational (previous states: Loading or Active).
180 : Broken { reason: String, backtrace: String },
181 : }
182 :
183 18568 : #[derive(Serialize, Deserialize)]
184 : pub struct TimelineCreateRequest {
185 : pub new_timeline_id: TimelineId,
186 : #[serde(default)]
187 : pub ancestor_timeline_id: Option<TimelineId>,
188 : #[serde(default)]
189 : pub existing_initdb_timeline_id: Option<TimelineId>,
190 : #[serde(default)]
191 : pub ancestor_start_lsn: Option<Lsn>,
192 : pub pg_version: Option<u32>,
193 : }
194 :
195 : /// Parameters that apply to all shards in a tenant. Used during tenant creation.
196 2900 : #[derive(Serialize, Deserialize, Debug)]
197 : #[serde(deny_unknown_fields)]
198 : pub struct ShardParameters {
199 : pub count: ShardCount,
200 : pub stripe_size: ShardStripeSize,
201 : }
202 :
203 : impl ShardParameters {
204 : pub const DEFAULT_STRIPE_SIZE: ShardStripeSize = ShardStripeSize(256 * 1024 / 8);
205 :
206 918 : pub fn is_unsharded(&self) -> bool {
207 918 : self.count == ShardCount(0)
208 918 : }
209 : }
210 :
211 : impl Default for ShardParameters {
212 721 : fn default() -> Self {
213 721 : Self {
214 721 : count: ShardCount(0),
215 721 : stripe_size: Self::DEFAULT_STRIPE_SIZE,
216 721 : }
217 721 : }
218 : }
219 :
220 11322 : #[derive(Serialize, Deserialize, Debug)]
221 : #[serde(deny_unknown_fields)]
222 : pub struct TenantCreateRequest {
223 : pub new_tenant_id: TenantShardId,
224 : #[serde(default)]
225 : #[serde(skip_serializing_if = "Option::is_none")]
226 : pub generation: Option<u32>,
227 :
228 : // If omitted, create a single shard with TenantShardId::unsharded()
229 : #[serde(default)]
230 : #[serde(skip_serializing_if = "ShardParameters::is_unsharded")]
231 : pub shard_parameters: ShardParameters,
232 :
233 : #[serde(flatten)]
234 : pub config: TenantConfig, // as we have a flattened field, we should reject all unknown fields in it
235 : }
236 :
237 12 : #[derive(Deserialize, Debug)]
238 : #[serde(deny_unknown_fields)]
239 : pub struct TenantLoadRequest {
240 : #[serde(default)]
241 : #[serde(skip_serializing_if = "Option::is_none")]
242 : pub generation: Option<u32>,
243 : }
244 :
245 : impl std::ops::Deref for TenantCreateRequest {
246 : type Target = TenantConfig;
247 :
248 0 : fn deref(&self) -> &Self::Target {
249 0 : &self.config
250 0 : }
251 : }
252 :
253 : /// An alternative representation of `pageserver::tenant::TenantConf` with
254 : /// simpler types.
255 38716 : #[derive(Serialize, Deserialize, Debug, Default, Clone, Eq, PartialEq)]
256 : pub struct TenantConfig {
257 : pub checkpoint_distance: Option<u64>,
258 : pub checkpoint_timeout: Option<String>,
259 : pub compaction_target_size: Option<u64>,
260 : pub compaction_period: Option<String>,
261 : pub compaction_threshold: Option<usize>,
262 : pub gc_horizon: Option<u64>,
263 : pub gc_period: Option<String>,
264 : pub image_creation_threshold: Option<usize>,
265 : pub pitr_interval: Option<String>,
266 : pub walreceiver_connect_timeout: Option<String>,
267 : pub lagging_wal_timeout: Option<String>,
268 : pub max_lsn_wal_lag: Option<NonZeroU64>,
269 : pub trace_read_requests: Option<bool>,
270 : pub eviction_policy: Option<EvictionPolicy>,
271 : pub min_resident_size_override: Option<u64>,
272 : pub evictions_low_residence_duration_metric_threshold: Option<String>,
273 : pub gc_feedback: Option<bool>,
274 : pub heatmap_period: Option<String>,
275 : pub lazy_slru_download: Option<bool>,
276 : }
277 :
278 80 : #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
279 : #[serde(tag = "kind")]
280 : pub enum EvictionPolicy {
281 : NoEviction,
282 : LayerAccessThreshold(EvictionPolicyLayerAccessThreshold),
283 : }
284 :
285 : impl EvictionPolicy {
286 1353 : pub fn discriminant_str(&self) -> &'static str {
287 1353 : match self {
288 1324 : EvictionPolicy::NoEviction => "NoEviction",
289 29 : EvictionPolicy::LayerAccessThreshold(_) => "LayerAccessThreshold",
290 : }
291 1353 : }
292 : }
293 :
294 133 : #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
295 : pub struct EvictionPolicyLayerAccessThreshold {
296 : #[serde(with = "humantime_serde")]
297 : pub period: Duration,
298 : #[serde(with = "humantime_serde")]
299 : pub threshold: Duration,
300 : }
301 :
302 : /// A flattened analog of a `pagesever::tenant::LocationMode`, which
303 : /// lists out all possible states (and the virtual "Detached" state)
304 : /// in a flat form rather than using rust-style enums.
305 1482 : #[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
306 : pub enum LocationConfigMode {
307 : AttachedSingle,
308 : AttachedMulti,
309 : AttachedStale,
310 : Secondary,
311 : Detached,
312 : }
313 :
314 93 : #[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
315 : pub struct LocationConfigSecondary {
316 : pub warm: bool,
317 : }
318 :
319 : /// An alternative representation of `pageserver::tenant::LocationConf`,
320 : /// for use in external-facing APIs.
321 8979 : #[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
322 : pub struct LocationConfig {
323 : pub mode: LocationConfigMode,
324 : /// If attaching, in what generation?
325 : #[serde(default)]
326 : pub generation: Option<u32>,
327 :
328 : // If requesting mode `Secondary`, configuration for that.
329 : #[serde(default)]
330 : pub secondary_conf: Option<LocationConfigSecondary>,
331 :
332 : // Shard parameters: if shard_count is nonzero, then other shard_* fields
333 : // must be set accurately.
334 : #[serde(default)]
335 : pub shard_number: u8,
336 : #[serde(default)]
337 : pub shard_count: u8,
338 : #[serde(default)]
339 : pub shard_stripe_size: u32,
340 :
341 : // This configuration only affects attached mode, but should be provided irrespective
342 : // of the mode, as a secondary location might transition on startup if the response
343 : // to the `/re-attach` control plane API requests it.
344 : pub tenant_conf: TenantConfig,
345 : }
346 :
347 15 : #[derive(Serialize, Deserialize)]
348 : pub struct LocationConfigListResponse {
349 : pub tenant_shards: Vec<(TenantShardId, Option<LocationConfig>)>,
350 : }
351 :
352 43 : #[derive(Serialize, Deserialize)]
353 : #[serde(transparent)]
354 : pub struct TenantCreateResponse(pub TenantId);
355 :
356 610 : #[derive(Serialize)]
357 : pub struct StatusResponse {
358 : pub id: NodeId,
359 : }
360 :
361 6787 : #[derive(Serialize, Deserialize, Debug)]
362 : #[serde(deny_unknown_fields)]
363 : pub struct TenantLocationConfigRequest {
364 : pub tenant_id: TenantShardId,
365 : #[serde(flatten)]
366 : pub config: LocationConfig, // as we have a flattened field, we should reject all unknown fields in it
367 : }
368 :
369 583 : #[derive(Serialize, Deserialize, Debug)]
370 : #[serde(deny_unknown_fields)]
371 : pub struct TenantShardLocation {
372 : pub shard_id: TenantShardId,
373 : pub node_id: NodeId,
374 : }
375 :
376 614 : #[derive(Serialize, Deserialize, Debug)]
377 : #[serde(deny_unknown_fields)]
378 : pub struct TenantLocationConfigResponse {
379 : pub shards: Vec<TenantShardLocation>,
380 : }
381 :
382 457 : #[derive(Serialize, Deserialize, Debug)]
383 : #[serde(deny_unknown_fields)]
384 : pub struct TenantConfigRequest {
385 : pub tenant_id: TenantId,
386 : #[serde(flatten)]
387 : pub config: TenantConfig, // as we have a flattened field, we should reject all unknown fields in it
388 : }
389 :
390 : impl std::ops::Deref for TenantConfigRequest {
391 : type Target = TenantConfig;
392 :
393 0 : fn deref(&self) -> &Self::Target {
394 0 : &self.config
395 0 : }
396 : }
397 :
398 : impl TenantConfigRequest {
399 0 : pub fn new(tenant_id: TenantId) -> TenantConfigRequest {
400 0 : let config = TenantConfig::default();
401 0 : TenantConfigRequest { tenant_id, config }
402 0 : }
403 : }
404 :
405 202 : #[derive(Debug, Deserialize)]
406 : pub struct TenantAttachRequest {
407 : #[serde(default)]
408 : pub config: TenantAttachConfig,
409 : #[serde(default)]
410 : pub generation: Option<u32>,
411 : }
412 :
413 : /// Newtype to enforce deny_unknown_fields on TenantConfig for
414 : /// its usage inside `TenantAttachRequest`.
415 75 : #[derive(Debug, Serialize, Deserialize, Default)]
416 : #[serde(deny_unknown_fields)]
417 : pub struct TenantAttachConfig {
418 : #[serde(flatten)]
419 : allowing_unknown_fields: TenantConfig,
420 : }
421 :
422 : impl std::ops::Deref for TenantAttachConfig {
423 : type Target = TenantConfig;
424 :
425 38 : fn deref(&self) -> &Self::Target {
426 38 : &self.allowing_unknown_fields
427 38 : }
428 : }
429 :
430 : /// See [`TenantState::attachment_status`] and the OpenAPI docs for context.
431 759 : #[derive(Serialize, Deserialize, Clone)]
432 : #[serde(tag = "slug", content = "data", rename_all = "snake_case")]
433 : pub enum TenantAttachmentStatus {
434 : Maybe,
435 : Attached,
436 : Failed { reason: String },
437 : }
438 :
439 648 : #[derive(Serialize, Deserialize, Clone)]
440 : pub struct TenantInfo {
441 : pub id: TenantShardId,
442 : // NB: intentionally not part of OpenAPI, we don't want to commit to a specific set of TenantState's
443 : pub state: TenantState,
444 : /// Sum of the size of all layer files.
445 : /// If a layer is present in both local FS and S3, it counts only once.
446 : pub current_physical_size: Option<u64>, // physical size is only included in `tenant_status` endpoint
447 : pub attachment_status: TenantAttachmentStatus,
448 : #[serde(skip_serializing_if = "Option::is_none")]
449 : pub generation: Option<u32>,
450 : }
451 :
452 486 : #[derive(Serialize, Deserialize, Clone)]
453 : pub struct TenantDetails {
454 : #[serde(flatten)]
455 : pub tenant_info: TenantInfo,
456 :
457 : pub walredo: Option<WalRedoManagerStatus>,
458 :
459 : pub timelines: Vec<TimelineId>,
460 : }
461 :
462 : /// This represents the output of the "timeline_detail" and "timeline_list" API calls.
463 74115 : #[derive(Debug, Serialize, Deserialize, Clone)]
464 : pub struct TimelineInfo {
465 : pub tenant_id: TenantShardId,
466 : pub timeline_id: TimelineId,
467 :
468 : pub ancestor_timeline_id: Option<TimelineId>,
469 : pub ancestor_lsn: Option<Lsn>,
470 : pub last_record_lsn: Lsn,
471 : pub prev_record_lsn: Option<Lsn>,
472 : pub latest_gc_cutoff_lsn: Lsn,
473 : pub disk_consistent_lsn: Lsn,
474 :
475 : /// The LSN that we have succesfully uploaded to remote storage
476 : pub remote_consistent_lsn: Lsn,
477 :
478 : /// The LSN that we are advertizing to safekeepers
479 : pub remote_consistent_lsn_visible: Lsn,
480 :
481 : /// The LSN from the start of the root timeline (never changes)
482 : pub initdb_lsn: Lsn,
483 :
484 : pub current_logical_size: u64,
485 : pub current_logical_size_is_accurate: bool,
486 :
487 : /// Sum of the size of all layer files.
488 : /// If a layer is present in both local FS and S3, it counts only once.
489 : pub current_physical_size: Option<u64>, // is None when timeline is Unloaded
490 : pub current_logical_size_non_incremental: Option<u64>,
491 :
492 : pub timeline_dir_layer_file_size_sum: Option<u64>,
493 :
494 : pub wal_source_connstr: Option<String>,
495 : pub last_received_msg_lsn: Option<Lsn>,
496 : /// the timestamp (in microseconds) of the last received message
497 : pub last_received_msg_ts: Option<u128>,
498 : pub pg_version: u32,
499 :
500 : pub state: TimelineState,
501 :
502 : pub walreceiver_status: String,
503 : }
504 :
505 96 : #[derive(Debug, Clone, Serialize)]
506 : pub struct LayerMapInfo {
507 : pub in_memory_layers: Vec<InMemoryLayerInfo>,
508 : pub historic_layers: Vec<HistoricLayerInfo>,
509 : }
510 :
511 47931720 : #[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, Serialize, Deserialize, enum_map::Enum)]
512 : #[repr(usize)]
513 : pub enum LayerAccessKind {
514 : GetValueReconstructData,
515 : Iter,
516 : KeyIter,
517 : Dump,
518 : }
519 :
520 18200 : #[derive(Debug, Clone, Serialize, Deserialize)]
521 : pub struct LayerAccessStatFullDetails {
522 : pub when_millis_since_epoch: u64,
523 : pub task_kind: &'static str,
524 : pub access_kind: LayerAccessKind,
525 : }
526 :
527 : /// An event that impacts the layer's residence status.
528 : #[serde_as]
529 7052 : #[derive(Debug, Clone, Serialize, Deserialize)]
530 : pub struct LayerResidenceEvent {
531 : /// The time when the event occurred.
532 : /// NB: this timestamp is captured while the residence status changes.
533 : /// So, it might be behind/ahead of the actual residence change by a short amount of time.
534 : ///
535 : #[serde(rename = "timestamp_millis_since_epoch")]
536 : #[serde_as(as = "serde_with::TimestampMilliSeconds")]
537 : pub timestamp: SystemTime,
538 : /// The new residence status of the layer.
539 : pub status: LayerResidenceStatus,
540 : /// The reason why we had to record this event.
541 : pub reason: LayerResidenceEventReason,
542 : }
543 :
544 : /// The reason for recording a given [`LayerResidenceEvent`].
545 7052 : #[derive(Debug, Clone, Copy, Serialize, Deserialize)]
546 : pub enum LayerResidenceEventReason {
547 : /// The layer map is being populated, e.g. during timeline load or attach.
548 : /// This includes [`RemoteLayer`] objects created in [`reconcile_with_remote`].
549 : /// We need to record such events because there is no persistent storage for the events.
550 : ///
551 : // https://github.com/rust-lang/rust/issues/74481
552 : /// [`RemoteLayer`]: ../../tenant/storage_layer/struct.RemoteLayer.html
553 : /// [`reconcile_with_remote`]: ../../tenant/struct.Timeline.html#method.reconcile_with_remote
554 : LayerLoad,
555 : /// We just created the layer (e.g., freeze_and_flush or compaction).
556 : /// Such layers are always [`LayerResidenceStatus::Resident`].
557 : LayerCreate,
558 : /// We on-demand downloaded or evicted the given layer.
559 : ResidenceChange,
560 : }
561 :
562 : /// The residence status of the layer, after the given [`LayerResidenceEvent`].
563 7052 : #[derive(Debug, Clone, Copy, Serialize, Deserialize)]
564 : pub enum LayerResidenceStatus {
565 : /// Residence status for a layer file that exists locally.
566 : /// It may also exist on the remote, we don't care here.
567 : Resident,
568 : /// Residence status for a layer file that only exists on the remote.
569 : Evicted,
570 : }
571 :
572 : impl LayerResidenceEvent {
573 182218 : pub fn new(status: LayerResidenceStatus, reason: LayerResidenceEventReason) -> Self {
574 182218 : Self {
575 182218 : status,
576 182218 : reason,
577 182218 : timestamp: SystemTime::now(),
578 182218 : }
579 182218 : }
580 : }
581 :
582 3014 : #[derive(Debug, Clone, Serialize)]
583 : pub struct LayerAccessStats {
584 : pub access_count_by_access_kind: HashMap<LayerAccessKind, u64>,
585 : pub task_kind_access_flag: Vec<&'static str>,
586 : pub first: Option<LayerAccessStatFullDetails>,
587 : pub accesses_history: HistoryBufferWithDropCounter<LayerAccessStatFullDetails, 16>,
588 : pub residence_events_history: HistoryBufferWithDropCounter<LayerResidenceEvent, 16>,
589 : }
590 :
591 4 : #[derive(Debug, Clone, Serialize)]
592 : #[serde(tag = "kind")]
593 : pub enum InMemoryLayerInfo {
594 : Open { lsn_start: Lsn },
595 : Frozen { lsn_start: Lsn, lsn_end: Lsn },
596 : }
597 :
598 3014 : #[derive(Debug, Clone, Serialize)]
599 : #[serde(tag = "kind")]
600 : pub enum HistoricLayerInfo {
601 : Delta {
602 : layer_file_name: String,
603 : layer_file_size: u64,
604 :
605 : lsn_start: Lsn,
606 : lsn_end: Lsn,
607 : remote: bool,
608 : access_stats: LayerAccessStats,
609 : },
610 : Image {
611 : layer_file_name: String,
612 : layer_file_size: u64,
613 :
614 : lsn_start: Lsn,
615 : remote: bool,
616 : access_stats: LayerAccessStats,
617 : },
618 : }
619 :
620 6 : #[derive(Debug, Serialize, Deserialize)]
621 : pub struct DownloadRemoteLayersTaskSpawnRequest {
622 : pub max_concurrent_downloads: NonZeroUsize,
623 : }
624 :
625 52 : #[derive(Debug, Serialize, Deserialize, Clone)]
626 : pub struct DownloadRemoteLayersTaskInfo {
627 : pub task_id: String,
628 : pub state: DownloadRemoteLayersTaskState,
629 : pub total_layer_count: u64, // stable once `completed`
630 : pub successful_download_count: u64, // stable once `completed`
631 : pub failed_download_count: u64, // stable once `completed`
632 : }
633 :
634 52 : #[derive(Debug, Serialize, Deserialize, Clone)]
635 : pub enum DownloadRemoteLayersTaskState {
636 : Running,
637 : Completed,
638 : ShutDown,
639 : }
640 :
641 753 : #[derive(Debug, Serialize, Deserialize)]
642 : pub struct TimelineGcRequest {
643 : pub gc_horizon: Option<u64>,
644 : }
645 :
646 486 : #[derive(Debug, Clone, Serialize, Deserialize)]
647 : pub struct WalRedoManagerStatus {
648 : pub last_redo_at: Option<chrono::DateTime<chrono::Utc>>,
649 : pub pid: Option<u32>,
650 : }
651 :
652 : // Wrapped in libpq CopyData
653 8 : #[derive(PartialEq, Eq, Debug)]
654 : pub enum PagestreamFeMessage {
655 : Exists(PagestreamExistsRequest),
656 : Nblocks(PagestreamNblocksRequest),
657 : GetLatestPage(PagestreamGetLatestPageRequest), // for compatinility with old clients
658 : DbSize(PagestreamDbSizeRequest),
659 : GetSlruSegment(PagestreamGetSlruSegmentRequest),
660 : GetPage(PagestreamGetPageRequest),
661 : }
662 :
663 : // Wrapped in libpq CopyData
664 0 : #[derive(strum_macros::EnumProperty)]
665 : pub enum PagestreamBeMessage {
666 : Exists(PagestreamExistsResponse),
667 : Nblocks(PagestreamNblocksResponse),
668 : GetPage(PagestreamGetPageResponse),
669 : Error(PagestreamErrorResponse),
670 : DbSize(PagestreamDbSizeResponse),
671 : GetSlruSegment(PagestreamGetSlruSegmentResponse),
672 : }
673 :
674 : // Keep in sync with `pagestore_client.h`
675 : #[repr(u8)]
676 : enum PagestreamBeMessageTag {
677 : Exists = 100,
678 : Nblocks = 101,
679 : GetPage = 102,
680 : Error = 103,
681 : DbSize = 104,
682 : GetSlruSegment = 105,
683 : }
684 : impl TryFrom<u8> for PagestreamBeMessageTag {
685 : type Error = u8;
686 0 : fn try_from(value: u8) -> Result<Self, u8> {
687 0 : match value {
688 0 : 100 => Ok(PagestreamBeMessageTag::Exists),
689 0 : 101 => Ok(PagestreamBeMessageTag::Nblocks),
690 0 : 102 => Ok(PagestreamBeMessageTag::GetPage),
691 0 : 103 => Ok(PagestreamBeMessageTag::Error),
692 0 : 104 => Ok(PagestreamBeMessageTag::DbSize),
693 0 : 105 => Ok(PagestreamBeMessageTag::GetSlruSegment),
694 0 : _ => Err(value),
695 : }
696 0 : }
697 : }
698 :
699 2 : #[derive(Debug, PartialEq, Eq)]
700 : pub struct PagestreamExistsRequest {
701 : pub horizon: Lsn,
702 : pub lsn: Lsn,
703 : pub rel: RelTag,
704 : }
705 :
706 2 : #[derive(Debug, PartialEq, Eq)]
707 : pub struct PagestreamNblocksRequest {
708 : pub horizon: Lsn,
709 : pub lsn: Lsn,
710 : pub rel: RelTag,
711 : }
712 :
713 0 : #[derive(Debug, PartialEq, Eq)]
714 : pub struct PagestreamGetLatestPageRequest {
715 : pub latest: bool,
716 : pub lsn: Lsn,
717 : pub rel: RelTag,
718 : pub blkno: u32,
719 : }
720 :
721 2 : #[derive(Debug, PartialEq, Eq)]
722 : pub struct PagestreamGetPageRequest {
723 : pub horizon: Lsn,
724 : pub lsn: Lsn,
725 : pub rel: RelTag,
726 : pub blkno: u32,
727 : }
728 :
729 2 : #[derive(Debug, PartialEq, Eq)]
730 : pub struct PagestreamDbSizeRequest {
731 : pub horizon: Lsn,
732 : pub lsn: Lsn,
733 : pub dbnode: u32,
734 : }
735 :
736 0 : #[derive(Debug, PartialEq, Eq)]
737 : pub struct PagestreamGetSlruSegmentRequest {
738 : pub horizon: Lsn,
739 : pub lsn: Lsn,
740 : pub kind: u8,
741 : pub segno: u32,
742 : }
743 :
744 0 : #[derive(Debug)]
745 : pub struct PagestreamExistsResponse {
746 : pub exists: bool,
747 : }
748 :
749 0 : #[derive(Debug)]
750 : pub struct PagestreamNblocksResponse {
751 : pub n_blocks: u32,
752 : }
753 :
754 0 : #[derive(Debug)]
755 : pub struct PagestreamGetPageResponse {
756 : pub page: Bytes,
757 : }
758 :
759 0 : #[derive(Debug)]
760 : pub struct PagestreamGetSlruSegmentResponse {
761 : pub segment: Bytes,
762 : }
763 :
764 0 : #[derive(Debug)]
765 : pub struct PagestreamErrorResponse {
766 : pub message: String,
767 : }
768 :
769 0 : #[derive(Debug)]
770 : pub struct PagestreamDbSizeResponse {
771 : pub db_size: i64,
772 : }
773 :
774 : // This is a cut-down version of TenantHistorySize from the pageserver crate, omitting fields
775 : // that require pageserver-internal types. It is sufficient to get the total size.
776 0 : #[derive(Serialize, Deserialize, Debug)]
777 : pub struct TenantHistorySize {
778 : pub id: TenantId,
779 : /// Size is a mixture of WAL and logical size, so the unit is bytes.
780 : ///
781 : /// Will be none if `?inputs_only=true` was given.
782 : pub size: Option<u64>,
783 : }
784 :
785 : impl PagestreamFeMessage {
786 8 : pub fn serialize(&self) -> Bytes {
787 8 : let mut bytes = BytesMut::new();
788 8 :
789 8 : match self {
790 2 : Self::Exists(req) => {
791 2 : bytes.put_u8(0);
792 2 : bytes.put_u64(req.horizon.0);
793 2 : bytes.put_u64(req.lsn.0);
794 2 : bytes.put_u32(req.rel.spcnode);
795 2 : bytes.put_u32(req.rel.dbnode);
796 2 : bytes.put_u32(req.rel.relnode);
797 2 : bytes.put_u8(req.rel.forknum);
798 2 : }
799 :
800 2 : Self::Nblocks(req) => {
801 2 : bytes.put_u8(1);
802 2 : bytes.put_u64(req.horizon.0);
803 2 : bytes.put_u64(req.lsn.0);
804 2 : bytes.put_u32(req.rel.spcnode);
805 2 : bytes.put_u32(req.rel.dbnode);
806 2 : bytes.put_u32(req.rel.relnode);
807 2 : bytes.put_u8(req.rel.forknum);
808 2 : }
809 :
810 0 : Self::GetLatestPage(req) => {
811 0 : bytes.put_u8(2);
812 0 : bytes.put_u8(u8::from(req.latest));
813 0 : bytes.put_u64(req.lsn.0);
814 0 : bytes.put_u32(req.rel.spcnode);
815 0 : bytes.put_u32(req.rel.dbnode);
816 0 : bytes.put_u32(req.rel.relnode);
817 0 : bytes.put_u8(req.rel.forknum);
818 0 : bytes.put_u32(req.blkno);
819 0 : }
820 :
821 2 : Self::DbSize(req) => {
822 2 : bytes.put_u8(3);
823 2 : bytes.put_u64(req.horizon.0);
824 2 : bytes.put_u64(req.lsn.0);
825 2 : bytes.put_u32(req.dbnode);
826 2 : }
827 :
828 0 : Self::GetSlruSegment(req) => {
829 0 : bytes.put_u8(4);
830 0 : bytes.put_u64(req.horizon.0);
831 0 : bytes.put_u64(req.lsn.0);
832 0 : bytes.put_u8(req.kind);
833 0 : bytes.put_u32(req.segno);
834 0 : }
835 :
836 2 : Self::GetPage(req) => {
837 2 : bytes.put_u8(5);
838 2 : bytes.put_u64(req.horizon.0);
839 2 : bytes.put_u64(req.lsn.0);
840 2 : bytes.put_u32(req.rel.spcnode);
841 2 : bytes.put_u32(req.rel.dbnode);
842 2 : bytes.put_u32(req.rel.relnode);
843 2 : bytes.put_u8(req.rel.forknum);
844 2 : bytes.put_u32(req.blkno);
845 2 : }
846 : }
847 :
848 8 : bytes.into()
849 8 : }
850 :
851 5618362 : pub fn parse<R: std::io::Read>(body: &mut R) -> anyhow::Result<PagestreamFeMessage> {
852 : // TODO these gets can fail
853 :
854 : // these correspond to the NeonMessageTag enum in pagestore_client.h
855 : //
856 : // TODO: consider using protobuf or serde bincode for less error prone
857 : // serialization.
858 5618362 : let msg_tag = body.read_u8()?;
859 5618362 : match msg_tag {
860 : 0 => Ok(PagestreamFeMessage::Exists(PagestreamExistsRequest {
861 56248 : horizon: Lsn::from(body.read_u64::<BigEndian>()?),
862 56248 : lsn: Lsn::from(body.read_u64::<BigEndian>()?),
863 : rel: RelTag {
864 56248 : spcnode: body.read_u32::<BigEndian>()?,
865 56248 : dbnode: body.read_u32::<BigEndian>()?,
866 56248 : relnode: body.read_u32::<BigEndian>()?,
867 56248 : forknum: body.read_u8()?,
868 : },
869 : })),
870 : 1 => Ok(PagestreamFeMessage::Nblocks(PagestreamNblocksRequest {
871 19794 : horizon: Lsn::from(body.read_u64::<BigEndian>()?),
872 19794 : lsn: Lsn::from(body.read_u64::<BigEndian>()?),
873 : rel: RelTag {
874 19794 : spcnode: body.read_u32::<BigEndian>()?,
875 19794 : dbnode: body.read_u32::<BigEndian>()?,
876 19794 : relnode: body.read_u32::<BigEndian>()?,
877 19794 : forknum: body.read_u8()?,
878 : },
879 : })),
880 : 2 => Ok(PagestreamFeMessage::GetLatestPage(
881 : PagestreamGetLatestPageRequest {
882 0 : latest: body.read_u8()? != 0,
883 0 : lsn: Lsn::from(body.read_u64::<BigEndian>()?),
884 : rel: RelTag {
885 0 : spcnode: body.read_u32::<BigEndian>()?,
886 0 : dbnode: body.read_u32::<BigEndian>()?,
887 0 : relnode: body.read_u32::<BigEndian>()?,
888 0 : forknum: body.read_u8()?,
889 : },
890 0 : blkno: body.read_u32::<BigEndian>()?,
891 : },
892 : )),
893 : 3 => Ok(PagestreamFeMessage::DbSize(PagestreamDbSizeRequest {
894 7 : horizon: Lsn::from(body.read_u64::<BigEndian>()?),
895 7 : lsn: Lsn::from(body.read_u64::<BigEndian>()?),
896 7 : dbnode: body.read_u32::<BigEndian>()?,
897 : })),
898 : 4 => Ok(PagestreamFeMessage::GetSlruSegment(
899 : PagestreamGetSlruSegmentRequest {
900 0 : horizon: Lsn::from(body.read_u64::<BigEndian>()?),
901 0 : lsn: Lsn::from(body.read_u64::<BigEndian>()?),
902 0 : kind: body.read_u8()?,
903 0 : segno: body.read_u32::<BigEndian>()?,
904 : },
905 : )),
906 : 5 => Ok(PagestreamFeMessage::GetPage(PagestreamGetPageRequest {
907 5542313 : horizon: Lsn::from(body.read_u64::<BigEndian>()?),
908 5542313 : lsn: Lsn::from(body.read_u64::<BigEndian>()?),
909 : rel: RelTag {
910 5542313 : spcnode: body.read_u32::<BigEndian>()?,
911 5542313 : dbnode: body.read_u32::<BigEndian>()?,
912 5542313 : relnode: body.read_u32::<BigEndian>()?,
913 5542313 : forknum: body.read_u8()?,
914 : },
915 5542313 : blkno: body.read_u32::<BigEndian>()?,
916 : })),
917 0 : _ => bail!("unknown smgr message tag: {:?}", msg_tag),
918 : }
919 5618362 : }
920 : }
921 :
922 : impl PagestreamBeMessage {
923 5618347 : pub fn serialize(&self) -> Bytes {
924 5618347 : let mut bytes = BytesMut::new();
925 5618347 :
926 5618347 : use PagestreamBeMessageTag as Tag;
927 5618347 : match self {
928 56246 : Self::Exists(resp) => {
929 56246 : bytes.put_u8(Tag::Exists as u8);
930 56246 : bytes.put_u8(resp.exists as u8);
931 56246 : }
932 :
933 19792 : Self::Nblocks(resp) => {
934 19792 : bytes.put_u8(Tag::Nblocks as u8);
935 19792 : bytes.put_u32(resp.n_blocks);
936 19792 : }
937 :
938 5542301 : Self::GetPage(resp) => {
939 5542301 : bytes.put_u8(Tag::GetPage as u8);
940 5542301 : bytes.put(&resp.page[..]);
941 5542301 : }
942 :
943 3 : Self::Error(resp) => {
944 3 : bytes.put_u8(Tag::Error as u8);
945 3 : bytes.put(resp.message.as_bytes());
946 3 : bytes.put_u8(0); // null terminator
947 3 : }
948 5 : Self::DbSize(resp) => {
949 5 : bytes.put_u8(Tag::DbSize as u8);
950 5 : bytes.put_i64(resp.db_size);
951 5 : }
952 :
953 0 : Self::GetSlruSegment(resp) => {
954 0 : bytes.put_u8(Tag::GetSlruSegment as u8);
955 0 : bytes.put_u32((resp.segment.len() / BLCKSZ as usize) as u32);
956 0 : bytes.put(&resp.segment[..]);
957 0 : }
958 : }
959 :
960 5618347 : bytes.into()
961 5618347 : }
962 :
963 0 : pub fn deserialize(buf: Bytes) -> anyhow::Result<Self> {
964 0 : let mut buf = buf.reader();
965 0 : let msg_tag = buf.read_u8()?;
966 :
967 : use PagestreamBeMessageTag as Tag;
968 0 : let ok =
969 0 : match Tag::try_from(msg_tag).map_err(|tag: u8| anyhow::anyhow!("invalid tag {tag}"))? {
970 : Tag::Exists => {
971 0 : let exists = buf.read_u8()?;
972 0 : Self::Exists(PagestreamExistsResponse {
973 0 : exists: exists != 0,
974 0 : })
975 : }
976 : Tag::Nblocks => {
977 0 : let n_blocks = buf.read_u32::<BigEndian>()?;
978 0 : Self::Nblocks(PagestreamNblocksResponse { n_blocks })
979 : }
980 : Tag::GetPage => {
981 0 : let mut page = vec![0; 8192]; // TODO: use MaybeUninit
982 0 : buf.read_exact(&mut page)?;
983 0 : PagestreamBeMessage::GetPage(PagestreamGetPageResponse { page: page.into() })
984 : }
985 : Tag::Error => {
986 0 : let mut msg = Vec::new();
987 0 : buf.read_until(0, &mut msg)?;
988 0 : let cstring = std::ffi::CString::from_vec_with_nul(msg)?;
989 0 : let rust_str = cstring.to_str()?;
990 0 : PagestreamBeMessage::Error(PagestreamErrorResponse {
991 0 : message: rust_str.to_owned(),
992 0 : })
993 : }
994 : Tag::DbSize => {
995 0 : let db_size = buf.read_i64::<BigEndian>()?;
996 0 : Self::DbSize(PagestreamDbSizeResponse { db_size })
997 : }
998 : Tag::GetSlruSegment => {
999 0 : let n_blocks = buf.read_u32::<BigEndian>()?;
1000 0 : let mut segment = vec![0; n_blocks as usize * BLCKSZ as usize];
1001 0 : buf.read_exact(&mut segment)?;
1002 0 : Self::GetSlruSegment(PagestreamGetSlruSegmentResponse {
1003 0 : segment: segment.into(),
1004 0 : })
1005 : }
1006 : };
1007 0 : let remaining = buf.into_inner();
1008 0 : if !remaining.is_empty() {
1009 0 : anyhow::bail!(
1010 0 : "remaining bytes in msg with tag={msg_tag}: {}",
1011 0 : remaining.len()
1012 0 : );
1013 0 : }
1014 0 : Ok(ok)
1015 0 : }
1016 :
1017 0 : pub fn kind(&self) -> &'static str {
1018 0 : match self {
1019 0 : Self::Exists(_) => "Exists",
1020 0 : Self::Nblocks(_) => "Nblocks",
1021 0 : Self::GetPage(_) => "GetPage",
1022 0 : Self::Error(_) => "Error",
1023 0 : Self::DbSize(_) => "DbSize",
1024 0 : Self::GetSlruSegment(_) => "GetSlruSegment",
1025 : }
1026 0 : }
1027 : }
1028 :
1029 : #[cfg(test)]
1030 : mod tests {
1031 : use bytes::Buf;
1032 : use serde_json::json;
1033 :
1034 : use super::*;
1035 :
1036 2 : #[test]
1037 2 : fn test_pagestream() {
1038 2 : // Test serialization/deserialization of PagestreamFeMessage
1039 2 : let messages = vec![
1040 2 : PagestreamFeMessage::Exists(PagestreamExistsRequest {
1041 2 : horizon: Lsn::MAX,
1042 2 : lsn: Lsn(4),
1043 2 : rel: RelTag {
1044 2 : forknum: 1,
1045 2 : spcnode: 2,
1046 2 : dbnode: 3,
1047 2 : relnode: 4,
1048 2 : },
1049 2 : }),
1050 2 : PagestreamFeMessage::Nblocks(PagestreamNblocksRequest {
1051 2 : horizon: Lsn::INVALID,
1052 2 : lsn: Lsn(4),
1053 2 : rel: RelTag {
1054 2 : forknum: 1,
1055 2 : spcnode: 2,
1056 2 : dbnode: 3,
1057 2 : relnode: 4,
1058 2 : },
1059 2 : }),
1060 2 : PagestreamFeMessage::GetPage(PagestreamGetPageRequest {
1061 2 : horizon: Lsn::MAX,
1062 2 : lsn: Lsn::INVALID,
1063 2 : rel: RelTag {
1064 2 : forknum: 1,
1065 2 : spcnode: 2,
1066 2 : dbnode: 3,
1067 2 : relnode: 4,
1068 2 : },
1069 2 : blkno: 7,
1070 2 : }),
1071 2 : PagestreamFeMessage::DbSize(PagestreamDbSizeRequest {
1072 2 : horizon: Lsn::MAX,
1073 2 : lsn: Lsn(4),
1074 2 : dbnode: 7,
1075 2 : }),
1076 2 : ];
1077 10 : for msg in messages {
1078 8 : let bytes = msg.serialize();
1079 8 : let reconstructed = PagestreamFeMessage::parse(&mut bytes.reader()).unwrap();
1080 8 : assert!(msg == reconstructed);
1081 : }
1082 2 : }
1083 :
1084 2 : #[test]
1085 2 : fn test_tenantinfo_serde() {
1086 2 : // Test serialization/deserialization of TenantInfo
1087 2 : let original_active = TenantInfo {
1088 2 : id: TenantShardId::unsharded(TenantId::generate()),
1089 2 : state: TenantState::Active,
1090 2 : current_physical_size: Some(42),
1091 2 : attachment_status: TenantAttachmentStatus::Attached,
1092 2 : generation: None,
1093 2 : };
1094 2 : let expected_active = json!({
1095 2 : "id": original_active.id.to_string(),
1096 2 : "state": {
1097 2 : "slug": "Active",
1098 2 : },
1099 2 : "current_physical_size": 42,
1100 2 : "attachment_status": {
1101 2 : "slug":"attached",
1102 2 : }
1103 2 : });
1104 2 :
1105 2 : let original_broken = TenantInfo {
1106 2 : id: TenantShardId::unsharded(TenantId::generate()),
1107 2 : state: TenantState::Broken {
1108 2 : reason: "reason".into(),
1109 2 : backtrace: "backtrace info".into(),
1110 2 : },
1111 2 : current_physical_size: Some(42),
1112 2 : attachment_status: TenantAttachmentStatus::Attached,
1113 2 : generation: None,
1114 2 : };
1115 2 : let expected_broken = json!({
1116 2 : "id": original_broken.id.to_string(),
1117 2 : "state": {
1118 2 : "slug": "Broken",
1119 2 : "data": {
1120 2 : "backtrace": "backtrace info",
1121 2 : "reason": "reason",
1122 2 : }
1123 2 : },
1124 2 : "current_physical_size": 42,
1125 2 : "attachment_status": {
1126 2 : "slug":"attached",
1127 2 : }
1128 2 : });
1129 2 :
1130 2 : assert_eq!(
1131 2 : serde_json::to_value(&original_active).unwrap(),
1132 2 : expected_active
1133 2 : );
1134 :
1135 2 : assert_eq!(
1136 2 : serde_json::to_value(&original_broken).unwrap(),
1137 2 : expected_broken
1138 2 : );
1139 2 : assert!(format!("{:?}", &original_broken.state).contains("reason"));
1140 2 : assert!(format!("{:?}", &original_broken.state).contains("backtrace info"));
1141 2 : }
1142 :
1143 2 : #[test]
1144 2 : fn test_reject_unknown_field() {
1145 2 : let id = TenantId::generate();
1146 2 : let create_request = json!({
1147 2 : "new_tenant_id": id.to_string(),
1148 2 : "unknown_field": "unknown_value".to_string(),
1149 2 : });
1150 2 : let err = serde_json::from_value::<TenantCreateRequest>(create_request).unwrap_err();
1151 2 : assert!(
1152 2 : err.to_string().contains("unknown field `unknown_field`"),
1153 0 : "expect unknown field `unknown_field` error, got: {}",
1154 : err
1155 : );
1156 :
1157 2 : let id = TenantId::generate();
1158 2 : let config_request = json!({
1159 2 : "tenant_id": id.to_string(),
1160 2 : "unknown_field": "unknown_value".to_string(),
1161 2 : });
1162 2 : let err = serde_json::from_value::<TenantConfigRequest>(config_request).unwrap_err();
1163 2 : assert!(
1164 2 : err.to_string().contains("unknown field `unknown_field`"),
1165 0 : "expect unknown field `unknown_field` error, got: {}",
1166 : err
1167 : );
1168 :
1169 2 : let attach_request = json!({
1170 2 : "config": {
1171 2 : "unknown_field": "unknown_value".to_string(),
1172 2 : },
1173 2 : });
1174 2 : let err = serde_json::from_value::<TenantAttachRequest>(attach_request).unwrap_err();
1175 2 : assert!(
1176 2 : err.to_string().contains("unknown field `unknown_field`"),
1177 0 : "expect unknown field `unknown_field` error, got: {}",
1178 : err
1179 : );
1180 2 : }
1181 :
1182 2 : #[test]
1183 2 : fn tenantstatus_activating_serde() {
1184 2 : let states = [
1185 2 : TenantState::Activating(ActivatingFrom::Loading),
1186 2 : TenantState::Activating(ActivatingFrom::Attaching),
1187 2 : ];
1188 2 : let expected = "[{\"slug\":\"Activating\",\"data\":\"Loading\"},{\"slug\":\"Activating\",\"data\":\"Attaching\"}]";
1189 2 :
1190 2 : let actual = serde_json::to_string(&states).unwrap();
1191 2 :
1192 2 : assert_eq!(actual, expected);
1193 :
1194 2 : let parsed = serde_json::from_str::<Vec<TenantState>>(&actual).unwrap();
1195 2 :
1196 2 : assert_eq!(states.as_slice(), &parsed);
1197 2 : }
1198 :
1199 2 : #[test]
1200 2 : fn tenantstatus_activating_strum() {
1201 2 : // tests added, because we use these for metrics
1202 2 : let examples = [
1203 2 : (line!(), TenantState::Loading, "Loading"),
1204 2 : (line!(), TenantState::Attaching, "Attaching"),
1205 2 : (
1206 2 : line!(),
1207 2 : TenantState::Activating(ActivatingFrom::Loading),
1208 2 : "Activating",
1209 2 : ),
1210 2 : (
1211 2 : line!(),
1212 2 : TenantState::Activating(ActivatingFrom::Attaching),
1213 2 : "Activating",
1214 2 : ),
1215 2 : (line!(), TenantState::Active, "Active"),
1216 2 : (
1217 2 : line!(),
1218 2 : TenantState::Stopping {
1219 2 : progress: utils::completion::Barrier::default(),
1220 2 : },
1221 2 : "Stopping",
1222 2 : ),
1223 2 : (
1224 2 : line!(),
1225 2 : TenantState::Broken {
1226 2 : reason: "Example".into(),
1227 2 : backtrace: "Looooong backtrace".into(),
1228 2 : },
1229 2 : "Broken",
1230 2 : ),
1231 2 : ];
1232 :
1233 16 : for (line, rendered, expected) in examples {
1234 14 : let actual: &'static str = rendered.into();
1235 14 : assert_eq!(actual, expected, "example on {line}");
1236 : }
1237 2 : }
1238 : }
|