LCOV - code coverage report
Current view: top level - libs/pageserver_api/src - models.rs (source / functions) Coverage Total Hit
Test: 12c2fc96834f59604b8ade5b9add28f1dce41ec6.info Lines: 61.9 % 612 379
Test Date: 2024-07-03 15:33:13 Functions: 4.6 % 994 46

            Line data    Source code
       1              : pub mod detach_ancestor;
       2              : pub mod partitioning;
       3              : pub mod utilization;
       4              : 
       5              : pub use utilization::PageserverUtilization;
       6              : 
       7              : use std::{
       8              :     borrow::Cow,
       9              :     collections::HashMap,
      10              :     io::{BufRead, Read},
      11              :     num::{NonZeroU64, NonZeroUsize},
      12              :     sync::atomic::AtomicUsize,
      13              :     time::{Duration, SystemTime},
      14              : };
      15              : 
      16              : use byteorder::{BigEndian, ReadBytesExt};
      17              : use postgres_ffi::BLCKSZ;
      18              : use serde::{Deserialize, Serialize};
      19              : use serde_with::serde_as;
      20              : use utils::{
      21              :     completion,
      22              :     history_buffer::HistoryBufferWithDropCounter,
      23              :     id::{NodeId, TenantId, TimelineId},
      24              :     lsn::Lsn,
      25              :     serde_system_time,
      26              : };
      27              : 
      28              : use crate::{
      29              :     reltag::RelTag,
      30              :     shard::{ShardCount, ShardStripeSize, TenantShardId},
      31              : };
      32              : use anyhow::bail;
      33              : use bytes::{Buf, BufMut, Bytes, BytesMut};
      34              : 
      35              : /// The state of a tenant in this pageserver.
      36              : ///
      37              : /// ```mermaid
      38              : /// stateDiagram-v2
      39              : ///
      40              : ///     [*] --> Loading: spawn_load()
      41              : ///     [*] --> Attaching: spawn_attach()
      42              : ///
      43              : ///     Loading --> Activating: activate()
      44              : ///     Attaching --> Activating: activate()
      45              : ///     Activating --> Active: infallible
      46              : ///
      47              : ///     Loading --> Broken: load() failure
      48              : ///     Attaching --> Broken: attach() failure
      49              : ///
      50              : ///     Active --> Stopping: set_stopping(), part of shutdown & detach
      51              : ///     Stopping --> Broken: late error in remove_tenant_from_memory
      52              : ///
      53              : ///     Broken --> [*]: ignore / detach / shutdown
      54              : ///     Stopping --> [*]: remove_from_memory complete
      55              : ///
      56              : ///     Active --> Broken: cfg(testing)-only tenant break point
      57              : /// ```
      58              : #[derive(
      59              :     Clone,
      60              :     PartialEq,
      61              :     Eq,
      62            2 :     serde::Serialize,
      63           12 :     serde::Deserialize,
      64            0 :     strum_macros::Display,
      65              :     strum_macros::EnumVariantNames,
      66            0 :     strum_macros::AsRefStr,
      67          336 :     strum_macros::IntoStaticStr,
      68              : )]
      69              : #[serde(tag = "slug", content = "data")]
      70              : pub enum TenantState {
      71              :     /// This tenant is being loaded from local disk.
      72              :     ///
      73              :     /// `set_stopping()` and `set_broken()` do not work in this state and wait for it to pass.
      74              :     Loading,
      75              :     /// This tenant is being attached to the pageserver.
      76              :     ///
      77              :     /// `set_stopping()` and `set_broken()` do not work in this state and wait for it to pass.
      78              :     Attaching,
      79              :     /// The tenant is transitioning from Loading/Attaching to Active.
      80              :     ///
      81              :     /// While in this state, the individual timelines are being activated.
      82              :     ///
      83              :     /// `set_stopping()` and `set_broken()` do not work in this state and wait for it to pass.
      84              :     Activating(ActivatingFrom),
      85              :     /// The tenant has finished activating and is open for business.
      86              :     ///
      87              :     /// Transitions out of this state are possible through `set_stopping()` and `set_broken()`.
      88              :     Active,
      89              :     /// The tenant is recognized by pageserver, but it is being detached or the
      90              :     /// system is being shut down.
      91              :     ///
      92              :     /// Transitions out of this state are possible through `set_broken()`.
      93              :     Stopping {
      94              :         // Because of https://github.com/serde-rs/serde/issues/2105 this has to be a named field,
      95              :         // otherwise it will not be skipped during deserialization
      96              :         #[serde(skip)]
      97              :         progress: completion::Barrier,
      98              :     },
      99              :     /// The tenant is recognized by the pageserver, but can no longer be used for
     100              :     /// any operations.
     101              :     ///
     102              :     /// If the tenant fails to load or attach, it will transition to this state
     103              :     /// and it is guaranteed that no background tasks are running in its name.
     104              :     ///
     105              :     /// The other way to transition into this state is from `Stopping` state
     106              :     /// through `set_broken()` called from `remove_tenant_from_memory()`. That happens
     107              :     /// if the cleanup future executed by `remove_tenant_from_memory()` fails.
     108              :     Broken { reason: String, backtrace: String },
     109              : }
     110              : 
     111              : impl TenantState {
     112            0 :     pub fn attachment_status(&self) -> TenantAttachmentStatus {
     113              :         use TenantAttachmentStatus::*;
     114              : 
     115              :         // Below TenantState::Activating is used as "transient" or "transparent" state for
     116              :         // attachment_status determining.
     117            0 :         match self {
     118              :             // The attach procedure writes the marker file before adding the Attaching tenant to the tenants map.
     119              :             // So, technically, we can return Attached here.
     120              :             // However, as soon as Console observes Attached, it will proceed with the Postgres-level health check.
     121              :             // But, our attach task might still be fetching the remote timelines, etc.
     122              :             // So, return `Maybe` while Attaching, making Console wait for the attach task to finish.
     123            0 :             Self::Attaching | Self::Activating(ActivatingFrom::Attaching) => Maybe,
     124              :             // tenant mgr startup distinguishes attaching from loading via marker file.
     125            0 :             Self::Loading | Self::Activating(ActivatingFrom::Loading) => Attached,
     126              :             // We only reach Active after successful load / attach.
     127              :             // So, call atttachment status Attached.
     128            0 :             Self::Active => Attached,
     129              :             // If the (initial or resumed) attach procedure fails, the tenant becomes Broken.
     130              :             // However, it also becomes Broken if the regular load fails.
     131              :             // From Console's perspective there's no practical difference
     132              :             // because attachment_status is polled by console only during attach operation execution.
     133            0 :             Self::Broken { reason, .. } => Failed {
     134            0 :                 reason: reason.to_owned(),
     135            0 :             },
     136              :             // Why is Stopping a Maybe case? Because, during pageserver shutdown,
     137              :             // we set the Stopping state irrespective of whether the tenant
     138              :             // has finished attaching or not.
     139            0 :             Self::Stopping { .. } => Maybe,
     140              :         }
     141            0 :     }
     142              : 
     143            0 :     pub fn broken_from_reason(reason: String) -> Self {
     144            0 :         let backtrace_str: String = format!("{}", std::backtrace::Backtrace::force_capture());
     145            0 :         Self::Broken {
     146            0 :             reason,
     147            0 :             backtrace: backtrace_str,
     148            0 :         }
     149            0 :     }
     150              : }
     151              : 
     152              : impl std::fmt::Debug for TenantState {
     153            4 :     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
     154            4 :         match self {
     155            4 :             Self::Broken { reason, backtrace } if !reason.is_empty() => {
     156            4 :                 write!(f, "Broken due to: {reason}. Backtrace:\n{backtrace}")
     157              :             }
     158            0 :             _ => write!(f, "{self}"),
     159              :         }
     160            4 :     }
     161              : }
     162              : 
     163              : /// A temporary lease to a specific lsn inside a timeline.
     164              : /// Access to the lsn is guaranteed by the pageserver until the expiration indicated by `valid_until`.
     165              : #[serde_as]
     166            0 : #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
     167              : pub struct LsnLease {
     168              :     #[serde_as(as = "SystemTimeAsRfc3339Millis")]
     169              :     pub valid_until: SystemTime,
     170              : }
     171              : 
     172              : serde_with::serde_conv!(
     173              :     SystemTimeAsRfc3339Millis,
     174              :     SystemTime,
     175            0 :     |time: &SystemTime| humantime::format_rfc3339_millis(*time).to_string(),
     176            0 :     |value: String| -> Result<_, humantime::TimestampError> { humantime::parse_rfc3339(&value) }
     177              : );
     178              : 
     179              : impl LsnLease {
     180              :     /// The default length for an explicit LSN lease request (10 minutes).
     181              :     pub const DEFAULT_LENGTH: Duration = Duration::from_secs(10 * 60);
     182              : 
     183              :     /// The default length for an implicit LSN lease granted during
     184              :     /// `get_lsn_by_timestamp` request (1 minutes).
     185              :     pub const DEFAULT_LENGTH_FOR_TS: Duration = Duration::from_secs(60);
     186              : 
     187              :     /// Checks whether the lease is expired.
     188            6 :     pub fn is_expired(&self, now: &SystemTime) -> bool {
     189            6 :         now > &self.valid_until
     190            6 :     }
     191              : }
     192              : 
     193              : /// The only [`TenantState`] variants we could be `TenantState::Activating` from.
     194            8 : #[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
     195              : pub enum ActivatingFrom {
     196              :     /// Arrived to [`TenantState::Activating`] from [`TenantState::Loading`]
     197              :     Loading,
     198              :     /// Arrived to [`TenantState::Activating`] from [`TenantState::Attaching`]
     199              :     Attaching,
     200              : }
     201              : 
     202              : /// A state of a timeline in pageserver's memory.
     203            0 : #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
     204              : pub enum TimelineState {
     205              :     /// The timeline is recognized by the pageserver but is not yet operational.
     206              :     /// In particular, the walreceiver connection loop is not running for this timeline.
     207              :     /// It will eventually transition to state Active or Broken.
     208              :     Loading,
     209              :     /// The timeline is fully operational.
     210              :     /// It can be queried, and the walreceiver connection loop is running.
     211              :     Active,
     212              :     /// The timeline was previously Loading or Active but is shutting down.
     213              :     /// It cannot transition back into any other state.
     214              :     Stopping,
     215              :     /// The timeline is broken and not operational (previous states: Loading or Active).
     216              :     Broken { reason: String, backtrace: String },
     217              : }
     218              : 
     219            0 : #[derive(Serialize, Deserialize, Clone)]
     220              : pub struct TimelineCreateRequest {
     221              :     pub new_timeline_id: TimelineId,
     222              :     #[serde(default)]
     223              :     pub ancestor_timeline_id: Option<TimelineId>,
     224              :     #[serde(default)]
     225              :     pub existing_initdb_timeline_id: Option<TimelineId>,
     226              :     #[serde(default)]
     227              :     pub ancestor_start_lsn: Option<Lsn>,
     228              :     pub pg_version: Option<u32>,
     229              : }
     230              : 
     231            0 : #[derive(Serialize, Deserialize)]
     232              : pub struct TenantShardSplitRequest {
     233              :     pub new_shard_count: u8,
     234              : 
     235              :     // A tenant's stripe size is only meaningful the first time their shard count goes
     236              :     // above 1: therefore during a split from 1->N shards, we may modify the stripe size.
     237              :     //
     238              :     // If this is set while the stripe count is being increased from an already >1 value,
     239              :     // then the request will fail with 400.
     240              :     pub new_stripe_size: Option<ShardStripeSize>,
     241              : }
     242              : 
     243            0 : #[derive(Serialize, Deserialize)]
     244              : pub struct TenantShardSplitResponse {
     245              :     pub new_shards: Vec<TenantShardId>,
     246              : }
     247              : 
     248              : /// Parameters that apply to all shards in a tenant.  Used during tenant creation.
     249            0 : #[derive(Serialize, Deserialize, Debug)]
     250              : #[serde(deny_unknown_fields)]
     251              : pub struct ShardParameters {
     252              :     pub count: ShardCount,
     253              :     pub stripe_size: ShardStripeSize,
     254              : }
     255              : 
     256              : impl ShardParameters {
     257              :     pub const DEFAULT_STRIPE_SIZE: ShardStripeSize = ShardStripeSize(256 * 1024 / 8);
     258              : 
     259            0 :     pub fn is_unsharded(&self) -> bool {
     260            0 :         self.count.is_unsharded()
     261            0 :     }
     262              : }
     263              : 
     264              : impl Default for ShardParameters {
     265          172 :     fn default() -> Self {
     266          172 :         Self {
     267          172 :             count: ShardCount::new(0),
     268          172 :             stripe_size: Self::DEFAULT_STRIPE_SIZE,
     269          172 :         }
     270          172 :     }
     271              : }
     272              : 
     273              : /// An alternative representation of `pageserver::tenant::TenantConf` with
     274              : /// simpler types.
     275            4 : #[derive(Serialize, Deserialize, Debug, Default, Clone, Eq, PartialEq)]
     276              : pub struct TenantConfig {
     277              :     pub checkpoint_distance: Option<u64>,
     278              :     pub checkpoint_timeout: Option<String>,
     279              :     pub compaction_target_size: Option<u64>,
     280              :     pub compaction_period: Option<String>,
     281              :     pub compaction_threshold: Option<usize>,
     282              :     // defer parsing compaction_algorithm, like eviction_policy
     283              :     pub compaction_algorithm: Option<CompactionAlgorithmSettings>,
     284              :     pub gc_horizon: Option<u64>,
     285              :     pub gc_period: Option<String>,
     286              :     pub image_creation_threshold: Option<usize>,
     287              :     pub pitr_interval: Option<String>,
     288              :     pub walreceiver_connect_timeout: Option<String>,
     289              :     pub lagging_wal_timeout: Option<String>,
     290              :     pub max_lsn_wal_lag: Option<NonZeroU64>,
     291              :     pub trace_read_requests: Option<bool>,
     292              :     pub eviction_policy: Option<EvictionPolicy>,
     293              :     pub min_resident_size_override: Option<u64>,
     294              :     pub evictions_low_residence_duration_metric_threshold: Option<String>,
     295              :     pub heatmap_period: Option<String>,
     296              :     pub lazy_slru_download: Option<bool>,
     297              :     pub timeline_get_throttle: Option<ThrottleConfig>,
     298              :     pub image_layer_creation_check_threshold: Option<u8>,
     299              :     pub switch_aux_file_policy: Option<AuxFilePolicy>,
     300              :     pub lsn_lease_length: Option<String>,
     301              :     pub lsn_lease_length_for_ts: Option<String>,
     302              : }
     303              : 
     304              : /// The policy for the aux file storage. It can be switched through `switch_aux_file_policy`
     305              : /// tenant config. When the first aux file written, the policy will be persisted in the
     306              : /// `index_part.json` file and has a limited migration path.
     307              : ///
     308              : /// Currently, we only allow the following migration path:
     309              : ///
     310              : /// Unset -> V1
     311              : ///       -> V2
     312              : ///       -> CrossValidation -> V2
     313              : #[derive(
     314              :     Eq,
     315              :     PartialEq,
     316              :     Debug,
     317              :     Copy,
     318              :     Clone,
     319            8 :     strum_macros::EnumString,
     320           20 :     strum_macros::Display,
     321            0 :     serde_with::DeserializeFromStr,
     322              :     serde_with::SerializeDisplay,
     323              : )]
     324              : #[strum(serialize_all = "kebab-case")]
     325              : pub enum AuxFilePolicy {
     326              :     /// V1 aux file policy: store everything in AUX_FILE_KEY
     327              :     #[strum(ascii_case_insensitive)]
     328              :     V1,
     329              :     /// V2 aux file policy: store in the AUX_FILE keyspace
     330              :     #[strum(ascii_case_insensitive)]
     331              :     V2,
     332              :     /// Cross validation runs both formats on the write path and does validation
     333              :     /// on the read path.
     334              :     #[strum(ascii_case_insensitive)]
     335              :     CrossValidation,
     336              : }
     337              : 
     338              : impl AuxFilePolicy {
     339           54 :     pub fn is_valid_migration_path(from: Option<Self>, to: Self) -> bool {
     340           34 :         matches!(
     341           54 :             (from, to),
     342              :             (None, _) | (Some(AuxFilePolicy::CrossValidation), AuxFilePolicy::V2)
     343              :         )
     344           54 :     }
     345              : 
     346              :     /// If a tenant writes aux files without setting `switch_aux_policy`, this value will be used.
     347          382 :     pub fn default_tenant_config() -> Self {
     348          382 :         Self::V1
     349          382 :     }
     350              : }
     351              : 
     352              : /// The aux file policy memory flag. Users can store `Option<AuxFilePolicy>` into this atomic flag. 0 == unspecified.
     353              : pub struct AtomicAuxFilePolicy(AtomicUsize);
     354              : 
     355              : impl AtomicAuxFilePolicy {
     356          384 :     pub fn new(policy: Option<AuxFilePolicy>) -> Self {
     357          384 :         Self(AtomicUsize::new(
     358          384 :             policy.map(AuxFilePolicy::to_usize).unwrap_or_default(),
     359          384 :         ))
     360          384 :     }
     361              : 
     362          306 :     pub fn load(&self) -> Option<AuxFilePolicy> {
     363          306 :         match self.0.load(std::sync::atomic::Ordering::Acquire) {
     364          240 :             0 => None,
     365           66 :             other => Some(AuxFilePolicy::from_usize(other)),
     366              :         }
     367          306 :     }
     368              : 
     369           22 :     pub fn store(&self, policy: Option<AuxFilePolicy>) {
     370           22 :         self.0.store(
     371           22 :             policy.map(AuxFilePolicy::to_usize).unwrap_or_default(),
     372           22 :             std::sync::atomic::Ordering::Release,
     373           22 :         );
     374           22 :     }
     375              : }
     376              : 
     377              : impl AuxFilePolicy {
     378           20 :     pub fn to_usize(self) -> usize {
     379           20 :         match self {
     380           14 :             Self::V1 => 1,
     381            2 :             Self::CrossValidation => 2,
     382            4 :             Self::V2 => 3,
     383              :         }
     384           20 :     }
     385              : 
     386           66 :     pub fn try_from_usize(this: usize) -> Option<Self> {
     387           66 :         match this {
     388           36 :             1 => Some(Self::V1),
     389            6 :             2 => Some(Self::CrossValidation),
     390           24 :             3 => Some(Self::V2),
     391            0 :             _ => None,
     392              :         }
     393           66 :     }
     394              : 
     395           66 :     pub fn from_usize(this: usize) -> Self {
     396           66 :         Self::try_from_usize(this).unwrap()
     397           66 :     }
     398              : }
     399              : 
     400            4 : #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
     401              : #[serde(tag = "kind")]
     402              : pub enum EvictionPolicy {
     403              :     NoEviction,
     404              :     LayerAccessThreshold(EvictionPolicyLayerAccessThreshold),
     405              :     OnlyImitiate(EvictionPolicyLayerAccessThreshold),
     406              : }
     407              : 
     408              : impl EvictionPolicy {
     409            0 :     pub fn discriminant_str(&self) -> &'static str {
     410            0 :         match self {
     411            0 :             EvictionPolicy::NoEviction => "NoEviction",
     412            0 :             EvictionPolicy::LayerAccessThreshold(_) => "LayerAccessThreshold",
     413            0 :             EvictionPolicy::OnlyImitiate(_) => "OnlyImitiate",
     414              :         }
     415            0 :     }
     416              : }
     417              : 
     418              : #[derive(
     419              :     Eq,
     420              :     PartialEq,
     421              :     Debug,
     422              :     Copy,
     423              :     Clone,
     424            0 :     strum_macros::EnumString,
     425            0 :     strum_macros::Display,
     426            0 :     serde_with::DeserializeFromStr,
     427              :     serde_with::SerializeDisplay,
     428              : )]
     429              : #[strum(serialize_all = "kebab-case")]
     430              : pub enum CompactionAlgorithm {
     431              :     Legacy,
     432              :     Tiered,
     433              : }
     434              : 
     435              : #[derive(
     436              :     Debug,
     437              :     Clone,
     438              :     Copy,
     439              :     PartialEq,
     440              :     Eq,
     441              :     Serialize,
     442            0 :     Deserialize,
     443            0 :     strum_macros::FromRepr,
     444            0 :     strum_macros::EnumString,
     445              : )]
     446              : #[strum(serialize_all = "kebab-case")]
     447              : pub enum ImageCompressionAlgorithm {
     448              :     /// Zstandard compression. Level 0 means and None mean the same (default level). Levels can be negative as well.
     449              :     /// For details, see the [manual](http://facebook.github.io/zstd/zstd_manual.html).
     450              :     Zstd { level: Option<i8> },
     451              : }
     452              : 
     453            0 : #[derive(Eq, PartialEq, Debug, Clone, Serialize, Deserialize)]
     454              : pub struct CompactionAlgorithmSettings {
     455              :     pub kind: CompactionAlgorithm,
     456              : }
     457              : 
     458           20 : #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
     459              : pub struct EvictionPolicyLayerAccessThreshold {
     460              :     #[serde(with = "humantime_serde")]
     461              :     pub period: Duration,
     462              :     #[serde(with = "humantime_serde")]
     463              :     pub threshold: Duration,
     464              : }
     465              : 
     466            0 : #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
     467              : pub struct ThrottleConfig {
     468              :     pub task_kinds: Vec<String>, // TaskKind
     469              :     pub initial: usize,
     470              :     #[serde(with = "humantime_serde")]
     471              :     pub refill_interval: Duration,
     472              :     pub refill_amount: NonZeroUsize,
     473              :     pub max: usize,
     474              :     pub fair: bool,
     475              : }
     476              : 
     477              : impl ThrottleConfig {
     478          364 :     pub fn disabled() -> Self {
     479          364 :         Self {
     480          364 :             task_kinds: vec![], // effectively disables the throttle
     481          364 :             // other values don't matter with emtpy `task_kinds`.
     482          364 :             initial: 0,
     483          364 :             refill_interval: Duration::from_millis(1),
     484          364 :             refill_amount: NonZeroUsize::new(1).unwrap(),
     485          364 :             max: 1,
     486          364 :             fair: true,
     487          364 :         }
     488          364 :     }
     489              :     /// The requests per second allowed  by the given config.
     490            0 :     pub fn steady_rps(&self) -> f64 {
     491            0 :         (self.refill_amount.get() as f64) / (self.refill_interval.as_secs_f64())
     492            0 :     }
     493              : }
     494              : 
     495              : /// A flattened analog of a `pagesever::tenant::LocationMode`, which
     496              : /// lists out all possible states (and the virtual "Detached" state)
     497              : /// in a flat form rather than using rust-style enums.
     498            0 : #[derive(Serialize, Deserialize, Debug, Clone, Copy, Eq, PartialEq)]
     499              : pub enum LocationConfigMode {
     500              :     AttachedSingle,
     501              :     AttachedMulti,
     502              :     AttachedStale,
     503              :     Secondary,
     504              :     Detached,
     505              : }
     506              : 
     507            0 : #[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
     508              : pub struct LocationConfigSecondary {
     509              :     pub warm: bool,
     510              : }
     511              : 
     512              : /// An alternative representation of `pageserver::tenant::LocationConf`,
     513              : /// for use in external-facing APIs.
     514            0 : #[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
     515              : pub struct LocationConfig {
     516              :     pub mode: LocationConfigMode,
     517              :     /// If attaching, in what generation?
     518              :     #[serde(default)]
     519              :     pub generation: Option<u32>,
     520              : 
     521              :     // If requesting mode `Secondary`, configuration for that.
     522              :     #[serde(default)]
     523              :     pub secondary_conf: Option<LocationConfigSecondary>,
     524              : 
     525              :     // Shard parameters: if shard_count is nonzero, then other shard_* fields
     526              :     // must be set accurately.
     527              :     #[serde(default)]
     528              :     pub shard_number: u8,
     529              :     #[serde(default)]
     530              :     pub shard_count: u8,
     531              :     #[serde(default)]
     532              :     pub shard_stripe_size: u32,
     533              : 
     534              :     // This configuration only affects attached mode, but should be provided irrespective
     535              :     // of the mode, as a secondary location might transition on startup if the response
     536              :     // to the `/re-attach` control plane API requests it.
     537              :     pub tenant_conf: TenantConfig,
     538              : }
     539              : 
     540            0 : #[derive(Serialize, Deserialize)]
     541              : pub struct LocationConfigListResponse {
     542              :     pub tenant_shards: Vec<(TenantShardId, Option<LocationConfig>)>,
     543              : }
     544              : 
     545              : #[derive(Serialize)]
     546              : pub struct StatusResponse {
     547              :     pub id: NodeId,
     548              : }
     549              : 
     550            0 : #[derive(Serialize, Deserialize, Debug)]
     551              : #[serde(deny_unknown_fields)]
     552              : pub struct TenantLocationConfigRequest {
     553              :     #[serde(flatten)]
     554              :     pub config: LocationConfig, // as we have a flattened field, we should reject all unknown fields in it
     555              : }
     556              : 
     557            0 : #[derive(Serialize, Deserialize, Debug)]
     558              : #[serde(deny_unknown_fields)]
     559              : pub struct TenantTimeTravelRequest {
     560              :     pub shard_counts: Vec<ShardCount>,
     561              : }
     562              : 
     563            0 : #[derive(Serialize, Deserialize, Debug)]
     564              : #[serde(deny_unknown_fields)]
     565              : pub struct TenantShardLocation {
     566              :     pub shard_id: TenantShardId,
     567              :     pub node_id: NodeId,
     568              : }
     569              : 
     570            0 : #[derive(Serialize, Deserialize, Debug)]
     571              : #[serde(deny_unknown_fields)]
     572              : pub struct TenantLocationConfigResponse {
     573              :     pub shards: Vec<TenantShardLocation>,
     574              :     // If the shards' ShardCount count is >1, stripe_size will be set.
     575              :     pub stripe_size: Option<ShardStripeSize>,
     576              : }
     577              : 
     578            6 : #[derive(Serialize, Deserialize, Debug)]
     579              : #[serde(deny_unknown_fields)]
     580              : pub struct TenantConfigRequest {
     581              :     pub tenant_id: TenantId,
     582              :     #[serde(flatten)]
     583              :     pub config: TenantConfig, // as we have a flattened field, we should reject all unknown fields in it
     584              : }
     585              : 
     586              : impl std::ops::Deref for TenantConfigRequest {
     587              :     type Target = TenantConfig;
     588              : 
     589            0 :     fn deref(&self) -> &Self::Target {
     590            0 :         &self.config
     591            0 :     }
     592              : }
     593              : 
     594              : impl TenantConfigRequest {
     595            0 :     pub fn new(tenant_id: TenantId) -> TenantConfigRequest {
     596            0 :         let config = TenantConfig::default();
     597            0 :         TenantConfigRequest { tenant_id, config }
     598            0 :     }
     599              : }
     600              : 
     601              : /// See [`TenantState::attachment_status`] and the OpenAPI docs for context.
     602            0 : #[derive(Serialize, Deserialize, Clone)]
     603              : #[serde(tag = "slug", content = "data", rename_all = "snake_case")]
     604              : pub enum TenantAttachmentStatus {
     605              :     Maybe,
     606              :     Attached,
     607              :     Failed { reason: String },
     608              : }
     609              : 
     610            0 : #[derive(Serialize, Deserialize, Clone)]
     611              : pub struct TenantInfo {
     612              :     pub id: TenantShardId,
     613              :     // NB: intentionally not part of OpenAPI, we don't want to commit to a specific set of TenantState's
     614              :     pub state: TenantState,
     615              :     /// Sum of the size of all layer files.
     616              :     /// If a layer is present in both local FS and S3, it counts only once.
     617              :     pub current_physical_size: Option<u64>, // physical size is only included in `tenant_status` endpoint
     618              :     pub attachment_status: TenantAttachmentStatus,
     619              :     pub generation: u32,
     620              : }
     621              : 
     622            0 : #[derive(Serialize, Deserialize, Clone)]
     623              : pub struct TenantDetails {
     624              :     #[serde(flatten)]
     625              :     pub tenant_info: TenantInfo,
     626              : 
     627              :     pub walredo: Option<WalRedoManagerStatus>,
     628              : 
     629              :     pub timelines: Vec<TimelineId>,
     630              : }
     631              : 
     632              : /// This represents the output of the "timeline_detail" and "timeline_list" API calls.
     633            0 : #[derive(Debug, Serialize, Deserialize, Clone)]
     634              : pub struct TimelineInfo {
     635              :     pub tenant_id: TenantShardId,
     636              :     pub timeline_id: TimelineId,
     637              : 
     638              :     pub ancestor_timeline_id: Option<TimelineId>,
     639              :     pub ancestor_lsn: Option<Lsn>,
     640              :     pub last_record_lsn: Lsn,
     641              :     pub prev_record_lsn: Option<Lsn>,
     642              :     pub latest_gc_cutoff_lsn: Lsn,
     643              :     pub disk_consistent_lsn: Lsn,
     644              : 
     645              :     /// The LSN that we have succesfully uploaded to remote storage
     646              :     pub remote_consistent_lsn: Lsn,
     647              : 
     648              :     /// The LSN that we are advertizing to safekeepers
     649              :     pub remote_consistent_lsn_visible: Lsn,
     650              : 
     651              :     /// The LSN from the start of the root timeline (never changes)
     652              :     pub initdb_lsn: Lsn,
     653              : 
     654              :     pub current_logical_size: u64,
     655              :     pub current_logical_size_is_accurate: bool,
     656              : 
     657              :     pub directory_entries_counts: Vec<u64>,
     658              : 
     659              :     /// Sum of the size of all layer files.
     660              :     /// If a layer is present in both local FS and S3, it counts only once.
     661              :     pub current_physical_size: Option<u64>, // is None when timeline is Unloaded
     662              :     pub current_logical_size_non_incremental: Option<u64>,
     663              : 
     664              :     pub timeline_dir_layer_file_size_sum: Option<u64>,
     665              : 
     666              :     pub wal_source_connstr: Option<String>,
     667              :     pub last_received_msg_lsn: Option<Lsn>,
     668              :     /// the timestamp (in microseconds) of the last received message
     669              :     pub last_received_msg_ts: Option<u128>,
     670              :     pub pg_version: u32,
     671              : 
     672              :     pub state: TimelineState,
     673              : 
     674              :     pub walreceiver_status: String,
     675              : 
     676              :     /// The last aux file policy being used on this timeline
     677              :     pub last_aux_file_policy: Option<AuxFilePolicy>,
     678              : }
     679              : 
     680            0 : #[derive(Debug, Clone, Serialize, Deserialize)]
     681              : pub struct LayerMapInfo {
     682              :     pub in_memory_layers: Vec<InMemoryLayerInfo>,
     683              :     pub historic_layers: Vec<HistoricLayerInfo>,
     684              : }
     685              : 
     686            0 : #[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, Serialize, Deserialize, enum_map::Enum)]
     687              : #[repr(usize)]
     688              : pub enum LayerAccessKind {
     689              :     GetValueReconstructData,
     690              :     Iter,
     691              :     KeyIter,
     692              :     Dump,
     693              : }
     694              : 
     695            0 : #[derive(Debug, Clone, Serialize, Deserialize)]
     696              : pub struct LayerAccessStatFullDetails {
     697              :     pub when_millis_since_epoch: u64,
     698              :     pub task_kind: Cow<'static, str>,
     699              :     pub access_kind: LayerAccessKind,
     700              : }
     701              : 
     702              : /// An event that impacts the layer's residence status.
     703              : #[serde_as]
     704            0 : #[derive(Debug, Clone, Serialize, Deserialize)]
     705              : pub struct LayerResidenceEvent {
     706              :     /// The time when the event occurred.
     707              :     /// NB: this timestamp is captured while the residence status changes.
     708              :     /// So, it might be behind/ahead of the actual residence change by a short amount of time.
     709              :     ///
     710              :     #[serde(rename = "timestamp_millis_since_epoch")]
     711              :     #[serde_as(as = "serde_with::TimestampMilliSeconds")]
     712              :     pub timestamp: SystemTime,
     713              :     /// The new residence status of the layer.
     714              :     pub status: LayerResidenceStatus,
     715              :     /// The reason why we had to record this event.
     716              :     pub reason: LayerResidenceEventReason,
     717              : }
     718              : 
     719              : /// The reason for recording a given [`LayerResidenceEvent`].
     720            0 : #[derive(Debug, Clone, Copy, Serialize, Deserialize)]
     721              : pub enum LayerResidenceEventReason {
     722              :     /// The layer map is being populated, e.g. during timeline load or attach.
     723              :     /// This includes [`RemoteLayer`] objects created in [`reconcile_with_remote`].
     724              :     /// We need to record such events because there is no persistent storage for the events.
     725              :     ///
     726              :     // https://github.com/rust-lang/rust/issues/74481
     727              :     /// [`RemoteLayer`]: ../../tenant/storage_layer/struct.RemoteLayer.html
     728              :     /// [`reconcile_with_remote`]: ../../tenant/struct.Timeline.html#method.reconcile_with_remote
     729              :     LayerLoad,
     730              :     /// We just created the layer (e.g., freeze_and_flush or compaction).
     731              :     /// Such layers are always [`LayerResidenceStatus::Resident`].
     732              :     LayerCreate,
     733              :     /// We on-demand downloaded or evicted the given layer.
     734              :     ResidenceChange,
     735              : }
     736              : 
     737              : /// The residence status of the layer, after the given [`LayerResidenceEvent`].
     738            0 : #[derive(Debug, Clone, Copy, Serialize, Deserialize)]
     739              : pub enum LayerResidenceStatus {
     740              :     /// Residence status for a layer file that exists locally.
     741              :     /// It may also exist on the remote, we don't care here.
     742              :     Resident,
     743              :     /// Residence status for a layer file that only exists on the remote.
     744              :     Evicted,
     745              : }
     746              : 
     747              : impl LayerResidenceEvent {
     748         3228 :     pub fn new(status: LayerResidenceStatus, reason: LayerResidenceEventReason) -> Self {
     749         3228 :         Self {
     750         3228 :             status,
     751         3228 :             reason,
     752         3228 :             timestamp: SystemTime::now(),
     753         3228 :         }
     754         3228 :     }
     755              : }
     756              : 
     757            0 : #[derive(Debug, Clone, Serialize, Deserialize)]
     758              : pub struct LayerAccessStats {
     759              :     pub access_count_by_access_kind: HashMap<LayerAccessKind, u64>,
     760              :     pub task_kind_access_flag: Vec<Cow<'static, str>>,
     761              :     pub first: Option<LayerAccessStatFullDetails>,
     762              :     pub accesses_history: HistoryBufferWithDropCounter<LayerAccessStatFullDetails, 16>,
     763              :     pub residence_events_history: HistoryBufferWithDropCounter<LayerResidenceEvent, 16>,
     764              : }
     765              : 
     766            0 : #[derive(Debug, Clone, Serialize, Deserialize)]
     767              : #[serde(tag = "kind")]
     768              : pub enum InMemoryLayerInfo {
     769              :     Open { lsn_start: Lsn },
     770              :     Frozen { lsn_start: Lsn, lsn_end: Lsn },
     771              : }
     772              : 
     773            0 : #[derive(Debug, Clone, Serialize, Deserialize)]
     774              : #[serde(tag = "kind")]
     775              : pub enum HistoricLayerInfo {
     776              :     Delta {
     777              :         layer_file_name: String,
     778              :         layer_file_size: u64,
     779              : 
     780              :         lsn_start: Lsn,
     781              :         lsn_end: Lsn,
     782              :         remote: bool,
     783              :         access_stats: LayerAccessStats,
     784              : 
     785              :         l0: bool,
     786              :     },
     787              :     Image {
     788              :         layer_file_name: String,
     789              :         layer_file_size: u64,
     790              : 
     791              :         lsn_start: Lsn,
     792              :         remote: bool,
     793              :         access_stats: LayerAccessStats,
     794              :     },
     795              : }
     796              : 
     797              : impl HistoricLayerInfo {
     798            0 :     pub fn layer_file_name(&self) -> &str {
     799            0 :         match self {
     800              :             HistoricLayerInfo::Delta {
     801            0 :                 layer_file_name, ..
     802            0 :             } => layer_file_name,
     803              :             HistoricLayerInfo::Image {
     804            0 :                 layer_file_name, ..
     805            0 :             } => layer_file_name,
     806              :         }
     807            0 :     }
     808            0 :     pub fn is_remote(&self) -> bool {
     809            0 :         match self {
     810            0 :             HistoricLayerInfo::Delta { remote, .. } => *remote,
     811            0 :             HistoricLayerInfo::Image { remote, .. } => *remote,
     812              :         }
     813            0 :     }
     814            0 :     pub fn set_remote(&mut self, value: bool) {
     815            0 :         let field = match self {
     816            0 :             HistoricLayerInfo::Delta { remote, .. } => remote,
     817            0 :             HistoricLayerInfo::Image { remote, .. } => remote,
     818              :         };
     819            0 :         *field = value;
     820            0 :     }
     821            0 :     pub fn layer_file_size(&self) -> u64 {
     822            0 :         match self {
     823              :             HistoricLayerInfo::Delta {
     824            0 :                 layer_file_size, ..
     825            0 :             } => *layer_file_size,
     826              :             HistoricLayerInfo::Image {
     827            0 :                 layer_file_size, ..
     828            0 :             } => *layer_file_size,
     829              :         }
     830            0 :     }
     831              : }
     832              : 
     833            0 : #[derive(Debug, Serialize, Deserialize)]
     834              : pub struct DownloadRemoteLayersTaskSpawnRequest {
     835              :     pub max_concurrent_downloads: NonZeroUsize,
     836              : }
     837              : 
     838            0 : #[derive(Debug, Serialize, Deserialize)]
     839              : pub struct IngestAuxFilesRequest {
     840              :     pub aux_files: HashMap<String, String>,
     841              : }
     842              : 
     843            0 : #[derive(Debug, Serialize, Deserialize)]
     844              : pub struct ListAuxFilesRequest {
     845              :     pub lsn: Lsn,
     846              : }
     847              : 
     848            0 : #[derive(Debug, Serialize, Deserialize, Clone)]
     849              : pub struct DownloadRemoteLayersTaskInfo {
     850              :     pub task_id: String,
     851              :     pub state: DownloadRemoteLayersTaskState,
     852              :     pub total_layer_count: u64,         // stable once `completed`
     853              :     pub successful_download_count: u64, // stable once `completed`
     854              :     pub failed_download_count: u64,     // stable once `completed`
     855              : }
     856              : 
     857            0 : #[derive(Debug, Serialize, Deserialize, Clone)]
     858              : pub enum DownloadRemoteLayersTaskState {
     859              :     Running,
     860              :     Completed,
     861              :     ShutDown,
     862              : }
     863              : 
     864            0 : #[derive(Debug, Serialize, Deserialize)]
     865              : pub struct TimelineGcRequest {
     866              :     pub gc_horizon: Option<u64>,
     867              : }
     868              : 
     869            0 : #[derive(Debug, Clone, Serialize, Deserialize)]
     870              : pub struct WalRedoManagerProcessStatus {
     871              :     pub pid: u32,
     872              : }
     873              : 
     874            0 : #[derive(Debug, Clone, Serialize, Deserialize)]
     875              : pub struct WalRedoManagerStatus {
     876              :     pub last_redo_at: Option<chrono::DateTime<chrono::Utc>>,
     877              :     pub process: Option<WalRedoManagerProcessStatus>,
     878              : }
     879              : 
     880              : /// The progress of a secondary tenant is mostly useful when doing a long running download: e.g. initiating
     881              : /// a download job, timing out while waiting for it to run, and then inspecting this status to understand
     882              : /// what's happening.
     883            0 : #[derive(Default, Debug, Serialize, Deserialize, Clone)]
     884              : pub struct SecondaryProgress {
     885              :     /// The remote storage LastModified time of the heatmap object we last downloaded.
     886              :     pub heatmap_mtime: Option<serde_system_time::SystemTime>,
     887              : 
     888              :     /// The number of layers currently on-disk
     889              :     pub layers_downloaded: usize,
     890              :     /// The number of layers in the most recently seen heatmap
     891              :     pub layers_total: usize,
     892              : 
     893              :     /// The number of layer bytes currently on-disk
     894              :     pub bytes_downloaded: u64,
     895              :     /// The number of layer bytes in the most recently seen heatmap
     896              :     pub bytes_total: u64,
     897              : }
     898              : 
     899            0 : #[derive(Serialize, Deserialize, Debug)]
     900              : pub struct TenantScanRemoteStorageShard {
     901              :     pub tenant_shard_id: TenantShardId,
     902              :     pub generation: Option<u32>,
     903              : }
     904              : 
     905            0 : #[derive(Serialize, Deserialize, Debug, Default)]
     906              : pub struct TenantScanRemoteStorageResponse {
     907              :     pub shards: Vec<TenantScanRemoteStorageShard>,
     908              : }
     909              : 
     910            0 : #[derive(Serialize, Deserialize, Debug, Clone)]
     911              : #[serde(rename_all = "snake_case")]
     912              : pub enum TenantSorting {
     913              :     ResidentSize,
     914              :     MaxLogicalSize,
     915              : }
     916              : 
     917              : impl Default for TenantSorting {
     918            0 :     fn default() -> Self {
     919            0 :         Self::ResidentSize
     920            0 :     }
     921              : }
     922              : 
     923            0 : #[derive(Serialize, Deserialize, Debug, Clone)]
     924              : pub struct TopTenantShardsRequest {
     925              :     // How would you like to sort the tenants?
     926              :     pub order_by: TenantSorting,
     927              : 
     928              :     // How many results?
     929              :     pub limit: usize,
     930              : 
     931              :     // Omit tenants with more than this many shards (e.g. if this is the max number of shards
     932              :     // that the caller would ever split to)
     933              :     pub where_shards_lt: Option<ShardCount>,
     934              : 
     935              :     // Omit tenants where the ordering metric is less than this (this is an optimization to
     936              :     // let us quickly exclude numerous tiny shards)
     937              :     pub where_gt: Option<u64>,
     938              : }
     939              : 
     940            0 : #[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
     941              : pub struct TopTenantShardItem {
     942              :     pub id: TenantShardId,
     943              : 
     944              :     /// Total size of layers on local disk for all timelines in this tenant
     945              :     pub resident_size: u64,
     946              : 
     947              :     /// Total size of layers in remote storage for all timelines in this tenant
     948              :     pub physical_size: u64,
     949              : 
     950              :     /// The largest logical size of a timeline within this tenant
     951              :     pub max_logical_size: u64,
     952              : }
     953              : 
     954            0 : #[derive(Serialize, Deserialize, Debug, Default)]
     955              : pub struct TopTenantShardsResponse {
     956              :     pub shards: Vec<TopTenantShardItem>,
     957              : }
     958              : 
     959              : pub mod virtual_file {
     960              :     #[derive(
     961              :         Copy,
     962              :         Clone,
     963              :         PartialEq,
     964              :         Eq,
     965              :         Hash,
     966          356 :         strum_macros::EnumString,
     967            0 :         strum_macros::Display,
     968            0 :         serde_with::DeserializeFromStr,
     969              :         serde_with::SerializeDisplay,
     970              :         Debug,
     971              :     )]
     972              :     #[strum(serialize_all = "kebab-case")]
     973              :     pub enum IoEngineKind {
     974              :         StdFs,
     975              :         #[cfg(target_os = "linux")]
     976              :         TokioEpollUring,
     977              :     }
     978              : }
     979              : 
     980              : // Wrapped in libpq CopyData
     981              : #[derive(PartialEq, Eq, Debug)]
     982              : pub enum PagestreamFeMessage {
     983              :     Exists(PagestreamExistsRequest),
     984              :     Nblocks(PagestreamNblocksRequest),
     985              :     GetPage(PagestreamGetPageRequest),
     986              :     DbSize(PagestreamDbSizeRequest),
     987              :     GetSlruSegment(PagestreamGetSlruSegmentRequest),
     988              : }
     989              : 
     990              : // Wrapped in libpq CopyData
     991            0 : #[derive(strum_macros::EnumProperty)]
     992              : pub enum PagestreamBeMessage {
     993              :     Exists(PagestreamExistsResponse),
     994              :     Nblocks(PagestreamNblocksResponse),
     995              :     GetPage(PagestreamGetPageResponse),
     996              :     Error(PagestreamErrorResponse),
     997              :     DbSize(PagestreamDbSizeResponse),
     998              :     GetSlruSegment(PagestreamGetSlruSegmentResponse),
     999              : }
    1000              : 
    1001              : // Keep in sync with `pagestore_client.h`
    1002              : #[repr(u8)]
    1003              : enum PagestreamBeMessageTag {
    1004              :     Exists = 100,
    1005              :     Nblocks = 101,
    1006              :     GetPage = 102,
    1007              :     Error = 103,
    1008              :     DbSize = 104,
    1009              :     GetSlruSegment = 105,
    1010              : }
    1011              : impl TryFrom<u8> for PagestreamBeMessageTag {
    1012              :     type Error = u8;
    1013            0 :     fn try_from(value: u8) -> Result<Self, u8> {
    1014            0 :         match value {
    1015            0 :             100 => Ok(PagestreamBeMessageTag::Exists),
    1016            0 :             101 => Ok(PagestreamBeMessageTag::Nblocks),
    1017            0 :             102 => Ok(PagestreamBeMessageTag::GetPage),
    1018            0 :             103 => Ok(PagestreamBeMessageTag::Error),
    1019            0 :             104 => Ok(PagestreamBeMessageTag::DbSize),
    1020            0 :             105 => Ok(PagestreamBeMessageTag::GetSlruSegment),
    1021            0 :             _ => Err(value),
    1022              :         }
    1023            0 :     }
    1024              : }
    1025              : 
    1026              : // In the V2 protocol version, a GetPage request contains two LSN values:
    1027              : //
    1028              : // request_lsn: Get the page version at this point in time.  Lsn::Max is a special value that means
    1029              : // "get the latest version present". It's used by the primary server, which knows that no one else
    1030              : // is writing WAL. 'not_modified_since' must be set to a proper value even if request_lsn is
    1031              : // Lsn::Max. Standby servers use the current replay LSN as the request LSN.
    1032              : //
    1033              : // not_modified_since: Hint to the pageserver that the client knows that the page has not been
    1034              : // modified between 'not_modified_since' and the request LSN. It's always correct to set
    1035              : // 'not_modified_since equal' to 'request_lsn' (unless Lsn::Max is used as the 'request_lsn'), but
    1036              : // passing an earlier LSN can speed up the request, by allowing the pageserver to process the
    1037              : // request without waiting for 'request_lsn' to arrive.
    1038              : //
    1039              : // The legacy V1 interface contained only one LSN, and a boolean 'latest' flag. The V1 interface was
    1040              : // sufficient for the primary; the 'lsn' was equivalent to the 'not_modified_since' value, and
    1041              : // 'latest' was set to true. The V2 interface was added because there was no correct way for a
    1042              : // standby to request a page at a particular non-latest LSN, and also include the
    1043              : // 'not_modified_since' hint. That led to an awkward choice of either using an old LSN in the
    1044              : // request, if the standby knows that the page hasn't been modified since, and risk getting an error
    1045              : // if that LSN has fallen behind the GC horizon, or requesting the current replay LSN, which could
    1046              : // require the pageserver unnecessarily to wait for the WAL to arrive up to that point. The new V2
    1047              : // interface allows sending both LSNs, and let the pageserver do the right thing. There is no
    1048              : // difference in the responses between V1 and V2.
    1049              : //
    1050              : // The Request structs below reflect the V2 interface. If V1 is used, the parse function
    1051              : // maps the old format requests to the new format.
    1052              : //
    1053              : #[derive(Clone, Copy)]
    1054              : pub enum PagestreamProtocolVersion {
    1055              :     V1,
    1056              :     V2,
    1057              : }
    1058              : 
    1059              : #[derive(Debug, PartialEq, Eq)]
    1060              : pub struct PagestreamExistsRequest {
    1061              :     pub request_lsn: Lsn,
    1062              :     pub not_modified_since: Lsn,
    1063              :     pub rel: RelTag,
    1064              : }
    1065              : 
    1066              : #[derive(Debug, PartialEq, Eq)]
    1067              : pub struct PagestreamNblocksRequest {
    1068              :     pub request_lsn: Lsn,
    1069              :     pub not_modified_since: Lsn,
    1070              :     pub rel: RelTag,
    1071              : }
    1072              : 
    1073              : #[derive(Debug, PartialEq, Eq)]
    1074              : pub struct PagestreamGetPageRequest {
    1075              :     pub request_lsn: Lsn,
    1076              :     pub not_modified_since: Lsn,
    1077              :     pub rel: RelTag,
    1078              :     pub blkno: u32,
    1079              : }
    1080              : 
    1081              : #[derive(Debug, PartialEq, Eq)]
    1082              : pub struct PagestreamDbSizeRequest {
    1083              :     pub request_lsn: Lsn,
    1084              :     pub not_modified_since: Lsn,
    1085              :     pub dbnode: u32,
    1086              : }
    1087              : 
    1088              : #[derive(Debug, PartialEq, Eq)]
    1089              : pub struct PagestreamGetSlruSegmentRequest {
    1090              :     pub request_lsn: Lsn,
    1091              :     pub not_modified_since: Lsn,
    1092              :     pub kind: u8,
    1093              :     pub segno: u32,
    1094              : }
    1095              : 
    1096              : #[derive(Debug)]
    1097              : pub struct PagestreamExistsResponse {
    1098              :     pub exists: bool,
    1099              : }
    1100              : 
    1101              : #[derive(Debug)]
    1102              : pub struct PagestreamNblocksResponse {
    1103              :     pub n_blocks: u32,
    1104              : }
    1105              : 
    1106              : #[derive(Debug)]
    1107              : pub struct PagestreamGetPageResponse {
    1108              :     pub page: Bytes,
    1109              : }
    1110              : 
    1111              : #[derive(Debug)]
    1112              : pub struct PagestreamGetSlruSegmentResponse {
    1113              :     pub segment: Bytes,
    1114              : }
    1115              : 
    1116              : #[derive(Debug)]
    1117              : pub struct PagestreamErrorResponse {
    1118              :     pub message: String,
    1119              : }
    1120              : 
    1121              : #[derive(Debug)]
    1122              : pub struct PagestreamDbSizeResponse {
    1123              :     pub db_size: i64,
    1124              : }
    1125              : 
    1126              : // This is a cut-down version of TenantHistorySize from the pageserver crate, omitting fields
    1127              : // that require pageserver-internal types.  It is sufficient to get the total size.
    1128            0 : #[derive(Serialize, Deserialize, Debug)]
    1129              : pub struct TenantHistorySize {
    1130              :     pub id: TenantId,
    1131              :     /// Size is a mixture of WAL and logical size, so the unit is bytes.
    1132              :     ///
    1133              :     /// Will be none if `?inputs_only=true` was given.
    1134              :     pub size: Option<u64>,
    1135              : }
    1136              : 
    1137              : impl PagestreamFeMessage {
    1138              :     /// Serialize a compute -> pageserver message. This is currently only used in testing
    1139              :     /// tools. Always uses protocol version 2.
    1140            8 :     pub fn serialize(&self) -> Bytes {
    1141            8 :         let mut bytes = BytesMut::new();
    1142            8 : 
    1143            8 :         match self {
    1144            2 :             Self::Exists(req) => {
    1145            2 :                 bytes.put_u8(0);
    1146            2 :                 bytes.put_u64(req.request_lsn.0);
    1147            2 :                 bytes.put_u64(req.not_modified_since.0);
    1148            2 :                 bytes.put_u32(req.rel.spcnode);
    1149            2 :                 bytes.put_u32(req.rel.dbnode);
    1150            2 :                 bytes.put_u32(req.rel.relnode);
    1151            2 :                 bytes.put_u8(req.rel.forknum);
    1152            2 :             }
    1153              : 
    1154            2 :             Self::Nblocks(req) => {
    1155            2 :                 bytes.put_u8(1);
    1156            2 :                 bytes.put_u64(req.request_lsn.0);
    1157            2 :                 bytes.put_u64(req.not_modified_since.0);
    1158            2 :                 bytes.put_u32(req.rel.spcnode);
    1159            2 :                 bytes.put_u32(req.rel.dbnode);
    1160            2 :                 bytes.put_u32(req.rel.relnode);
    1161            2 :                 bytes.put_u8(req.rel.forknum);
    1162            2 :             }
    1163              : 
    1164            2 :             Self::GetPage(req) => {
    1165            2 :                 bytes.put_u8(2);
    1166            2 :                 bytes.put_u64(req.request_lsn.0);
    1167            2 :                 bytes.put_u64(req.not_modified_since.0);
    1168            2 :                 bytes.put_u32(req.rel.spcnode);
    1169            2 :                 bytes.put_u32(req.rel.dbnode);
    1170            2 :                 bytes.put_u32(req.rel.relnode);
    1171            2 :                 bytes.put_u8(req.rel.forknum);
    1172            2 :                 bytes.put_u32(req.blkno);
    1173            2 :             }
    1174              : 
    1175            2 :             Self::DbSize(req) => {
    1176            2 :                 bytes.put_u8(3);
    1177            2 :                 bytes.put_u64(req.request_lsn.0);
    1178            2 :                 bytes.put_u64(req.not_modified_since.0);
    1179            2 :                 bytes.put_u32(req.dbnode);
    1180            2 :             }
    1181              : 
    1182            0 :             Self::GetSlruSegment(req) => {
    1183            0 :                 bytes.put_u8(4);
    1184            0 :                 bytes.put_u64(req.request_lsn.0);
    1185            0 :                 bytes.put_u64(req.not_modified_since.0);
    1186            0 :                 bytes.put_u8(req.kind);
    1187            0 :                 bytes.put_u32(req.segno);
    1188            0 :             }
    1189              :         }
    1190              : 
    1191            8 :         bytes.into()
    1192            8 :     }
    1193              : 
    1194            8 :     pub fn parse<R: std::io::Read>(
    1195            8 :         body: &mut R,
    1196            8 :         protocol_version: PagestreamProtocolVersion,
    1197            8 :     ) -> anyhow::Result<PagestreamFeMessage> {
    1198              :         // these correspond to the NeonMessageTag enum in pagestore_client.h
    1199              :         //
    1200              :         // TODO: consider using protobuf or serde bincode for less error prone
    1201              :         // serialization.
    1202            8 :         let msg_tag = body.read_u8()?;
    1203              : 
    1204            8 :         let (request_lsn, not_modified_since) = match protocol_version {
    1205              :             PagestreamProtocolVersion::V2 => (
    1206            8 :                 Lsn::from(body.read_u64::<BigEndian>()?),
    1207            8 :                 Lsn::from(body.read_u64::<BigEndian>()?),
    1208              :             ),
    1209              :             PagestreamProtocolVersion::V1 => {
    1210              :                 // In the old protocol, each message starts with a boolean 'latest' flag,
    1211              :                 // followed by 'lsn'. Convert that to the two LSNs, 'request_lsn' and
    1212              :                 // 'not_modified_since', used in the new protocol version.
    1213            0 :                 let latest = body.read_u8()? != 0;
    1214            0 :                 let request_lsn = Lsn::from(body.read_u64::<BigEndian>()?);
    1215            0 :                 if latest {
    1216            0 :                     (Lsn::MAX, request_lsn) // get latest version
    1217              :                 } else {
    1218            0 :                     (request_lsn, request_lsn) // get version at specified LSN
    1219              :                 }
    1220              :             }
    1221              :         };
    1222              : 
    1223              :         // The rest of the messages are the same between V1 and V2
    1224            8 :         match msg_tag {
    1225              :             0 => Ok(PagestreamFeMessage::Exists(PagestreamExistsRequest {
    1226            2 :                 request_lsn,
    1227            2 :                 not_modified_since,
    1228            2 :                 rel: RelTag {
    1229            2 :                     spcnode: body.read_u32::<BigEndian>()?,
    1230            2 :                     dbnode: body.read_u32::<BigEndian>()?,
    1231            2 :                     relnode: body.read_u32::<BigEndian>()?,
    1232            2 :                     forknum: body.read_u8()?,
    1233              :                 },
    1234              :             })),
    1235              :             1 => Ok(PagestreamFeMessage::Nblocks(PagestreamNblocksRequest {
    1236            2 :                 request_lsn,
    1237            2 :                 not_modified_since,
    1238            2 :                 rel: RelTag {
    1239            2 :                     spcnode: body.read_u32::<BigEndian>()?,
    1240            2 :                     dbnode: body.read_u32::<BigEndian>()?,
    1241            2 :                     relnode: body.read_u32::<BigEndian>()?,
    1242            2 :                     forknum: body.read_u8()?,
    1243              :                 },
    1244              :             })),
    1245              :             2 => Ok(PagestreamFeMessage::GetPage(PagestreamGetPageRequest {
    1246            2 :                 request_lsn,
    1247            2 :                 not_modified_since,
    1248            2 :                 rel: RelTag {
    1249            2 :                     spcnode: body.read_u32::<BigEndian>()?,
    1250            2 :                     dbnode: body.read_u32::<BigEndian>()?,
    1251            2 :                     relnode: body.read_u32::<BigEndian>()?,
    1252            2 :                     forknum: body.read_u8()?,
    1253              :                 },
    1254            2 :                 blkno: body.read_u32::<BigEndian>()?,
    1255              :             })),
    1256              :             3 => Ok(PagestreamFeMessage::DbSize(PagestreamDbSizeRequest {
    1257            2 :                 request_lsn,
    1258            2 :                 not_modified_since,
    1259            2 :                 dbnode: body.read_u32::<BigEndian>()?,
    1260              :             })),
    1261              :             4 => Ok(PagestreamFeMessage::GetSlruSegment(
    1262              :                 PagestreamGetSlruSegmentRequest {
    1263            0 :                     request_lsn,
    1264            0 :                     not_modified_since,
    1265            0 :                     kind: body.read_u8()?,
    1266            0 :                     segno: body.read_u32::<BigEndian>()?,
    1267              :                 },
    1268              :             )),
    1269            0 :             _ => bail!("unknown smgr message tag: {:?}", msg_tag),
    1270              :         }
    1271            8 :     }
    1272              : }
    1273              : 
    1274              : impl PagestreamBeMessage {
    1275            0 :     pub fn serialize(&self) -> Bytes {
    1276            0 :         let mut bytes = BytesMut::new();
    1277            0 : 
    1278            0 :         use PagestreamBeMessageTag as Tag;
    1279            0 :         match self {
    1280            0 :             Self::Exists(resp) => {
    1281            0 :                 bytes.put_u8(Tag::Exists as u8);
    1282            0 :                 bytes.put_u8(resp.exists as u8);
    1283            0 :             }
    1284              : 
    1285            0 :             Self::Nblocks(resp) => {
    1286            0 :                 bytes.put_u8(Tag::Nblocks as u8);
    1287            0 :                 bytes.put_u32(resp.n_blocks);
    1288            0 :             }
    1289              : 
    1290            0 :             Self::GetPage(resp) => {
    1291            0 :                 bytes.put_u8(Tag::GetPage as u8);
    1292            0 :                 bytes.put(&resp.page[..]);
    1293            0 :             }
    1294              : 
    1295            0 :             Self::Error(resp) => {
    1296            0 :                 bytes.put_u8(Tag::Error as u8);
    1297            0 :                 bytes.put(resp.message.as_bytes());
    1298            0 :                 bytes.put_u8(0); // null terminator
    1299            0 :             }
    1300            0 :             Self::DbSize(resp) => {
    1301            0 :                 bytes.put_u8(Tag::DbSize as u8);
    1302            0 :                 bytes.put_i64(resp.db_size);
    1303            0 :             }
    1304              : 
    1305            0 :             Self::GetSlruSegment(resp) => {
    1306            0 :                 bytes.put_u8(Tag::GetSlruSegment as u8);
    1307            0 :                 bytes.put_u32((resp.segment.len() / BLCKSZ as usize) as u32);
    1308            0 :                 bytes.put(&resp.segment[..]);
    1309            0 :             }
    1310              :         }
    1311              : 
    1312            0 :         bytes.into()
    1313            0 :     }
    1314              : 
    1315            0 :     pub fn deserialize(buf: Bytes) -> anyhow::Result<Self> {
    1316            0 :         let mut buf = buf.reader();
    1317            0 :         let msg_tag = buf.read_u8()?;
    1318              : 
    1319              :         use PagestreamBeMessageTag as Tag;
    1320            0 :         let ok =
    1321            0 :             match Tag::try_from(msg_tag).map_err(|tag: u8| anyhow::anyhow!("invalid tag {tag}"))? {
    1322              :                 Tag::Exists => {
    1323            0 :                     let exists = buf.read_u8()?;
    1324            0 :                     Self::Exists(PagestreamExistsResponse {
    1325            0 :                         exists: exists != 0,
    1326            0 :                     })
    1327              :                 }
    1328              :                 Tag::Nblocks => {
    1329            0 :                     let n_blocks = buf.read_u32::<BigEndian>()?;
    1330            0 :                     Self::Nblocks(PagestreamNblocksResponse { n_blocks })
    1331              :                 }
    1332              :                 Tag::GetPage => {
    1333            0 :                     let mut page = vec![0; 8192]; // TODO: use MaybeUninit
    1334            0 :                     buf.read_exact(&mut page)?;
    1335            0 :                     PagestreamBeMessage::GetPage(PagestreamGetPageResponse { page: page.into() })
    1336              :                 }
    1337              :                 Tag::Error => {
    1338            0 :                     let mut msg = Vec::new();
    1339            0 :                     buf.read_until(0, &mut msg)?;
    1340            0 :                     let cstring = std::ffi::CString::from_vec_with_nul(msg)?;
    1341            0 :                     let rust_str = cstring.to_str()?;
    1342            0 :                     PagestreamBeMessage::Error(PagestreamErrorResponse {
    1343            0 :                         message: rust_str.to_owned(),
    1344            0 :                     })
    1345              :                 }
    1346              :                 Tag::DbSize => {
    1347            0 :                     let db_size = buf.read_i64::<BigEndian>()?;
    1348            0 :                     Self::DbSize(PagestreamDbSizeResponse { db_size })
    1349              :                 }
    1350              :                 Tag::GetSlruSegment => {
    1351            0 :                     let n_blocks = buf.read_u32::<BigEndian>()?;
    1352            0 :                     let mut segment = vec![0; n_blocks as usize * BLCKSZ as usize];
    1353            0 :                     buf.read_exact(&mut segment)?;
    1354            0 :                     Self::GetSlruSegment(PagestreamGetSlruSegmentResponse {
    1355            0 :                         segment: segment.into(),
    1356            0 :                     })
    1357              :                 }
    1358              :             };
    1359            0 :         let remaining = buf.into_inner();
    1360            0 :         if !remaining.is_empty() {
    1361            0 :             anyhow::bail!(
    1362            0 :                 "remaining bytes in msg with tag={msg_tag}: {}",
    1363            0 :                 remaining.len()
    1364            0 :             );
    1365            0 :         }
    1366            0 :         Ok(ok)
    1367            0 :     }
    1368              : 
    1369            0 :     pub fn kind(&self) -> &'static str {
    1370            0 :         match self {
    1371            0 :             Self::Exists(_) => "Exists",
    1372            0 :             Self::Nblocks(_) => "Nblocks",
    1373            0 :             Self::GetPage(_) => "GetPage",
    1374            0 :             Self::Error(_) => "Error",
    1375            0 :             Self::DbSize(_) => "DbSize",
    1376            0 :             Self::GetSlruSegment(_) => "GetSlruSegment",
    1377              :         }
    1378            0 :     }
    1379              : }
    1380              : 
    1381              : #[cfg(test)]
    1382              : mod tests {
    1383              :     use serde_json::json;
    1384              :     use std::str::FromStr;
    1385              : 
    1386              :     use super::*;
    1387              : 
    1388              :     #[test]
    1389            2 :     fn test_pagestream() {
    1390            2 :         // Test serialization/deserialization of PagestreamFeMessage
    1391            2 :         let messages = vec![
    1392            2 :             PagestreamFeMessage::Exists(PagestreamExistsRequest {
    1393            2 :                 request_lsn: Lsn(4),
    1394            2 :                 not_modified_since: Lsn(3),
    1395            2 :                 rel: RelTag {
    1396            2 :                     forknum: 1,
    1397            2 :                     spcnode: 2,
    1398            2 :                     dbnode: 3,
    1399            2 :                     relnode: 4,
    1400            2 :                 },
    1401            2 :             }),
    1402            2 :             PagestreamFeMessage::Nblocks(PagestreamNblocksRequest {
    1403            2 :                 request_lsn: Lsn(4),
    1404            2 :                 not_modified_since: Lsn(4),
    1405            2 :                 rel: RelTag {
    1406            2 :                     forknum: 1,
    1407            2 :                     spcnode: 2,
    1408            2 :                     dbnode: 3,
    1409            2 :                     relnode: 4,
    1410            2 :                 },
    1411            2 :             }),
    1412            2 :             PagestreamFeMessage::GetPage(PagestreamGetPageRequest {
    1413            2 :                 request_lsn: Lsn(4),
    1414            2 :                 not_modified_since: Lsn(3),
    1415            2 :                 rel: RelTag {
    1416            2 :                     forknum: 1,
    1417            2 :                     spcnode: 2,
    1418            2 :                     dbnode: 3,
    1419            2 :                     relnode: 4,
    1420            2 :                 },
    1421            2 :                 blkno: 7,
    1422            2 :             }),
    1423            2 :             PagestreamFeMessage::DbSize(PagestreamDbSizeRequest {
    1424            2 :                 request_lsn: Lsn(4),
    1425            2 :                 not_modified_since: Lsn(3),
    1426            2 :                 dbnode: 7,
    1427            2 :             }),
    1428            2 :         ];
    1429           10 :         for msg in messages {
    1430            8 :             let bytes = msg.serialize();
    1431            8 :             let reconstructed =
    1432            8 :                 PagestreamFeMessage::parse(&mut bytes.reader(), PagestreamProtocolVersion::V2)
    1433            8 :                     .unwrap();
    1434            8 :             assert!(msg == reconstructed);
    1435              :         }
    1436            2 :     }
    1437              : 
    1438              :     #[test]
    1439            2 :     fn test_tenantinfo_serde() {
    1440            2 :         // Test serialization/deserialization of TenantInfo
    1441            2 :         let original_active = TenantInfo {
    1442            2 :             id: TenantShardId::unsharded(TenantId::generate()),
    1443            2 :             state: TenantState::Active,
    1444            2 :             current_physical_size: Some(42),
    1445            2 :             attachment_status: TenantAttachmentStatus::Attached,
    1446            2 :             generation: 1,
    1447            2 :         };
    1448            2 :         let expected_active = json!({
    1449            2 :             "id": original_active.id.to_string(),
    1450            2 :             "state": {
    1451            2 :                 "slug": "Active",
    1452            2 :             },
    1453            2 :             "current_physical_size": 42,
    1454            2 :             "attachment_status": {
    1455            2 :                 "slug":"attached",
    1456            2 :             },
    1457            2 :             "generation" : 1
    1458            2 :         });
    1459            2 : 
    1460            2 :         let original_broken = TenantInfo {
    1461            2 :             id: TenantShardId::unsharded(TenantId::generate()),
    1462            2 :             state: TenantState::Broken {
    1463            2 :                 reason: "reason".into(),
    1464            2 :                 backtrace: "backtrace info".into(),
    1465            2 :             },
    1466            2 :             current_physical_size: Some(42),
    1467            2 :             attachment_status: TenantAttachmentStatus::Attached,
    1468            2 :             generation: 1,
    1469            2 :         };
    1470            2 :         let expected_broken = json!({
    1471            2 :             "id": original_broken.id.to_string(),
    1472            2 :             "state": {
    1473            2 :                 "slug": "Broken",
    1474            2 :                 "data": {
    1475            2 :                     "backtrace": "backtrace info",
    1476            2 :                     "reason": "reason",
    1477            2 :                 }
    1478            2 :             },
    1479            2 :             "current_physical_size": 42,
    1480            2 :             "attachment_status": {
    1481            2 :                 "slug":"attached",
    1482            2 :             },
    1483            2 :             "generation" : 1
    1484            2 :         });
    1485            2 : 
    1486            2 :         assert_eq!(
    1487            2 :             serde_json::to_value(&original_active).unwrap(),
    1488            2 :             expected_active
    1489            2 :         );
    1490              : 
    1491            2 :         assert_eq!(
    1492            2 :             serde_json::to_value(&original_broken).unwrap(),
    1493            2 :             expected_broken
    1494            2 :         );
    1495            2 :         assert!(format!("{:?}", &original_broken.state).contains("reason"));
    1496            2 :         assert!(format!("{:?}", &original_broken.state).contains("backtrace info"));
    1497            2 :     }
    1498              : 
    1499              :     #[test]
    1500            2 :     fn test_reject_unknown_field() {
    1501            2 :         let id = TenantId::generate();
    1502            2 :         let config_request = json!({
    1503            2 :             "tenant_id": id.to_string(),
    1504            2 :             "unknown_field": "unknown_value".to_string(),
    1505            2 :         });
    1506            2 :         let err = serde_json::from_value::<TenantConfigRequest>(config_request).unwrap_err();
    1507            2 :         assert!(
    1508            2 :             err.to_string().contains("unknown field `unknown_field`"),
    1509            0 :             "expect unknown field `unknown_field` error, got: {}",
    1510              :             err
    1511              :         );
    1512            2 :     }
    1513              : 
    1514              :     #[test]
    1515            2 :     fn tenantstatus_activating_serde() {
    1516            2 :         let states = [
    1517            2 :             TenantState::Activating(ActivatingFrom::Loading),
    1518            2 :             TenantState::Activating(ActivatingFrom::Attaching),
    1519            2 :         ];
    1520            2 :         let expected = "[{\"slug\":\"Activating\",\"data\":\"Loading\"},{\"slug\":\"Activating\",\"data\":\"Attaching\"}]";
    1521            2 : 
    1522            2 :         let actual = serde_json::to_string(&states).unwrap();
    1523            2 : 
    1524            2 :         assert_eq!(actual, expected);
    1525              : 
    1526            2 :         let parsed = serde_json::from_str::<Vec<TenantState>>(&actual).unwrap();
    1527            2 : 
    1528            2 :         assert_eq!(states.as_slice(), &parsed);
    1529            2 :     }
    1530              : 
    1531              :     #[test]
    1532            2 :     fn tenantstatus_activating_strum() {
    1533            2 :         // tests added, because we use these for metrics
    1534            2 :         let examples = [
    1535            2 :             (line!(), TenantState::Loading, "Loading"),
    1536            2 :             (line!(), TenantState::Attaching, "Attaching"),
    1537            2 :             (
    1538            2 :                 line!(),
    1539            2 :                 TenantState::Activating(ActivatingFrom::Loading),
    1540            2 :                 "Activating",
    1541            2 :             ),
    1542            2 :             (
    1543            2 :                 line!(),
    1544            2 :                 TenantState::Activating(ActivatingFrom::Attaching),
    1545            2 :                 "Activating",
    1546            2 :             ),
    1547            2 :             (line!(), TenantState::Active, "Active"),
    1548            2 :             (
    1549            2 :                 line!(),
    1550            2 :                 TenantState::Stopping {
    1551            2 :                     progress: utils::completion::Barrier::default(),
    1552            2 :                 },
    1553            2 :                 "Stopping",
    1554            2 :             ),
    1555            2 :             (
    1556            2 :                 line!(),
    1557            2 :                 TenantState::Broken {
    1558            2 :                     reason: "Example".into(),
    1559            2 :                     backtrace: "Looooong backtrace".into(),
    1560            2 :                 },
    1561            2 :                 "Broken",
    1562            2 :             ),
    1563            2 :         ];
    1564              : 
    1565           16 :         for (line, rendered, expected) in examples {
    1566           14 :             let actual: &'static str = rendered.into();
    1567           14 :             assert_eq!(actual, expected, "example on {line}");
    1568              :         }
    1569            2 :     }
    1570              : 
    1571              :     #[test]
    1572            2 :     fn test_aux_file_migration_path() {
    1573            2 :         assert!(AuxFilePolicy::is_valid_migration_path(
    1574            2 :             None,
    1575            2 :             AuxFilePolicy::V1
    1576            2 :         ));
    1577            2 :         assert!(AuxFilePolicy::is_valid_migration_path(
    1578            2 :             None,
    1579            2 :             AuxFilePolicy::V2
    1580            2 :         ));
    1581            2 :         assert!(AuxFilePolicy::is_valid_migration_path(
    1582            2 :             None,
    1583            2 :             AuxFilePolicy::CrossValidation
    1584            2 :         ));
    1585              :         // Self-migration is not a valid migration path, and the caller should handle it by itself.
    1586            2 :         assert!(!AuxFilePolicy::is_valid_migration_path(
    1587            2 :             Some(AuxFilePolicy::V1),
    1588            2 :             AuxFilePolicy::V1
    1589            2 :         ));
    1590            2 :         assert!(!AuxFilePolicy::is_valid_migration_path(
    1591            2 :             Some(AuxFilePolicy::V2),
    1592            2 :             AuxFilePolicy::V2
    1593            2 :         ));
    1594            2 :         assert!(!AuxFilePolicy::is_valid_migration_path(
    1595            2 :             Some(AuxFilePolicy::CrossValidation),
    1596            2 :             AuxFilePolicy::CrossValidation
    1597            2 :         ));
    1598              :         // Migrations not allowed
    1599            2 :         assert!(!AuxFilePolicy::is_valid_migration_path(
    1600            2 :             Some(AuxFilePolicy::CrossValidation),
    1601            2 :             AuxFilePolicy::V1
    1602            2 :         ));
    1603            2 :         assert!(!AuxFilePolicy::is_valid_migration_path(
    1604            2 :             Some(AuxFilePolicy::V1),
    1605            2 :             AuxFilePolicy::V2
    1606            2 :         ));
    1607            2 :         assert!(!AuxFilePolicy::is_valid_migration_path(
    1608            2 :             Some(AuxFilePolicy::V2),
    1609            2 :             AuxFilePolicy::V1
    1610            2 :         ));
    1611            2 :         assert!(!AuxFilePolicy::is_valid_migration_path(
    1612            2 :             Some(AuxFilePolicy::V2),
    1613            2 :             AuxFilePolicy::CrossValidation
    1614            2 :         ));
    1615            2 :         assert!(!AuxFilePolicy::is_valid_migration_path(
    1616            2 :             Some(AuxFilePolicy::V1),
    1617            2 :             AuxFilePolicy::CrossValidation
    1618            2 :         ));
    1619              :         // Migrations allowed
    1620            2 :         assert!(AuxFilePolicy::is_valid_migration_path(
    1621            2 :             Some(AuxFilePolicy::CrossValidation),
    1622            2 :             AuxFilePolicy::V2
    1623            2 :         ));
    1624            2 :     }
    1625              : 
    1626              :     #[test]
    1627            2 :     fn test_aux_parse() {
    1628            2 :         assert_eq!(AuxFilePolicy::from_str("V2").unwrap(), AuxFilePolicy::V2);
    1629            2 :         assert_eq!(AuxFilePolicy::from_str("v2").unwrap(), AuxFilePolicy::V2);
    1630            2 :         assert_eq!(
    1631            2 :             AuxFilePolicy::from_str("cross-validation").unwrap(),
    1632            2 :             AuxFilePolicy::CrossValidation
    1633            2 :         );
    1634            2 :     }
    1635              : }
        

Generated by: LCOV version 2.1-beta