LCOV - code coverage report
Current view: top level - pageserver/src/tenant - config.rs (source / functions) Coverage Total Hit
Test: 07bee600374ccd486c69370d0972d9035964fe68.info Lines: 15.8 % 463 73
Test Date: 2025-02-20 13:11:02 Functions: 9.4 % 106 10

            Line data    Source code
       1              : //! Functions for handling per-tenant configuration options
       2              : //!
       3              : //! If tenant is created with --config option,
       4              : //! the tenant-specific config will be stored in tenant's directory.
       5              : //! Otherwise, global pageserver's config is used.
       6              : //!
       7              : //! If the tenant config file is corrupted, the tenant will be disabled.
       8              : //! We cannot use global or default config instead, because wrong settings
       9              : //! may lead to a data loss.
      10              : //!
      11              : pub(crate) use pageserver_api::config::TenantConfigToml as TenantConf;
      12              : use pageserver_api::models::CompactionAlgorithmSettings;
      13              : use pageserver_api::models::EvictionPolicy;
      14              : use pageserver_api::models::{self, TenantConfigPatch};
      15              : use pageserver_api::shard::{ShardCount, ShardIdentity, ShardNumber, ShardStripeSize};
      16              : use serde::de::IntoDeserializer;
      17              : use serde::{Deserialize, Serialize};
      18              : use serde_json::Value;
      19              : use std::num::NonZeroU64;
      20              : use std::time::Duration;
      21              : use utils::generation::Generation;
      22              : use utils::postgres_client::PostgresClientProtocol;
      23              : 
      24            0 : #[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
      25              : pub(crate) enum AttachmentMode {
      26              :     /// Our generation is current as far as we know, and as far as we know we are the only attached
      27              :     /// pageserver.  This is the "normal" attachment mode.
      28              :     Single,
      29              :     /// Our generation number is current as far as we know, but we are advised that another
      30              :     /// pageserver is still attached, and therefore to avoid executing deletions.   This is
      31              :     /// the attachment mode of a pagesever that is the destination of a migration.
      32              :     Multi,
      33              :     /// Our generation number is superseded, or about to be superseded.  We are advised
      34              :     /// to avoid remote storage writes if possible, and to avoid sending billing data.  This
      35              :     /// is the attachment mode of a pageserver that is the origin of a migration.
      36              :     Stale,
      37              : }
      38              : 
      39            0 : #[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
      40              : pub(crate) struct AttachedLocationConfig {
      41              :     pub(crate) generation: Generation,
      42              :     pub(crate) attach_mode: AttachmentMode,
      43              :     // TODO: add a flag to override AttachmentMode's policies under
      44              :     // disk pressure (i.e. unblock uploads under disk pressure in Stale
      45              :     // state, unblock deletions after timeout in Multi state)
      46              : }
      47              : 
      48            0 : #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
      49              : pub(crate) struct SecondaryLocationConfig {
      50              :     /// If true, keep the local cache warm by polling remote storage
      51              :     pub(crate) warm: bool,
      52              : }
      53              : 
      54            0 : #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
      55              : pub(crate) enum LocationMode {
      56              :     Attached(AttachedLocationConfig),
      57              :     Secondary(SecondaryLocationConfig),
      58              : }
      59              : 
      60              : /// Per-tenant, per-pageserver configuration.  All pageservers use the same TenantConf,
      61              : /// but have distinct LocationConf.
      62            0 : #[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
      63              : pub(crate) struct LocationConf {
      64              :     /// The location-specific part of the configuration, describes the operating
      65              :     /// mode of this pageserver for this tenant.
      66              :     pub(crate) mode: LocationMode,
      67              : 
      68              :     /// The detailed shard identity.  This structure is already scoped within
      69              :     /// a TenantShardId, but we need the full ShardIdentity to enable calculating
      70              :     /// key->shard mappings.
      71              :     #[serde(default = "ShardIdentity::unsharded")]
      72              :     #[serde(skip_serializing_if = "ShardIdentity::is_unsharded")]
      73              :     pub(crate) shard: ShardIdentity,
      74              : 
      75              :     /// The pan-cluster tenant configuration, the same on all locations
      76              :     pub(crate) tenant_conf: TenantConfOpt,
      77              : }
      78              : 
      79              : impl std::fmt::Debug for LocationConf {
      80            0 :     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
      81            0 :         match &self.mode {
      82            0 :             LocationMode::Attached(conf) => {
      83            0 :                 write!(
      84            0 :                     f,
      85            0 :                     "Attached {:?}, gen={:?}",
      86            0 :                     conf.attach_mode, conf.generation
      87            0 :                 )
      88              :             }
      89            0 :             LocationMode::Secondary(conf) => {
      90            0 :                 write!(f, "Secondary, warm={}", conf.warm)
      91              :             }
      92              :         }
      93            0 :     }
      94              : }
      95              : 
      96              : impl AttachedLocationConfig {
      97              :     /// Consult attachment mode to determine whether we are currently permitted
      98              :     /// to delete layers.  This is only advisory, not required for data safety.
      99              :     /// See [`AttachmentMode`] for more context.
     100         2424 :     pub(crate) fn may_delete_layers_hint(&self) -> bool {
     101         2424 :         // TODO: add an override for disk pressure in AttachedLocationConfig,
     102         2424 :         // and respect it here.
     103         2424 :         match &self.attach_mode {
     104         2424 :             AttachmentMode::Single => true,
     105              :             AttachmentMode::Multi | AttachmentMode::Stale => {
     106              :                 // In Multi mode we avoid doing deletions because some other
     107              :                 // attached pageserver might get 404 while trying to read
     108              :                 // a layer we delete which is still referenced in their metadata.
     109              :                 //
     110              :                 // In Stale mode, we avoid doing deletions because we expect
     111              :                 // that they would ultimately fail validation in the deletion
     112              :                 // queue due to our stale generation.
     113            0 :                 false
     114              :             }
     115              :         }
     116         2424 :     }
     117              : 
     118              :     /// Whether we are currently hinted that it is worthwhile to upload layers.
     119              :     /// This is only advisory, not required for data safety.
     120              :     /// See [`AttachmentMode`] for more context.
     121          916 :     pub(crate) fn may_upload_layers_hint(&self) -> bool {
     122          916 :         // TODO: add an override for disk pressure in AttachedLocationConfig,
     123          916 :         // and respect it here.
     124          916 :         match &self.attach_mode {
     125          916 :             AttachmentMode::Single | AttachmentMode::Multi => true,
     126              :             AttachmentMode::Stale => {
     127              :                 // In Stale mode, we avoid doing uploads because we expect that
     128              :                 // our replacement pageserver will already have started its own
     129              :                 // IndexPart that will never reference layers we upload: it is
     130              :                 // wasteful.
     131            0 :                 false
     132              :             }
     133              :         }
     134          916 :     }
     135              : }
     136              : 
     137              : impl LocationConf {
     138              :     /// For use when loading from a legacy configuration: presence of a tenant
     139              :     /// implies it is in AttachmentMode::Single, which used to be the only
     140              :     /// possible state.  This function should eventually be removed.
     141          444 :     pub(crate) fn attached_single(
     142          444 :         tenant_conf: TenantConfOpt,
     143          444 :         generation: Generation,
     144          444 :         shard_params: &models::ShardParameters,
     145          444 :     ) -> Self {
     146          444 :         Self {
     147          444 :             mode: LocationMode::Attached(AttachedLocationConfig {
     148          444 :                 generation,
     149          444 :                 attach_mode: AttachmentMode::Single,
     150          444 :             }),
     151          444 :             shard: ShardIdentity::from_params(ShardNumber(0), shard_params),
     152          444 :             tenant_conf,
     153          444 :         }
     154          444 :     }
     155              : 
     156              :     /// For use when attaching/re-attaching: update the generation stored in this
     157              :     /// structure.  If we were in a secondary state, promote to attached (posession
     158              :     /// of a fresh generation implies this).
     159            0 :     pub(crate) fn attach_in_generation(&mut self, mode: AttachmentMode, generation: Generation) {
     160            0 :         match &mut self.mode {
     161            0 :             LocationMode::Attached(attach_conf) => {
     162            0 :                 attach_conf.generation = generation;
     163            0 :                 attach_conf.attach_mode = mode;
     164            0 :             }
     165              :             LocationMode::Secondary(_) => {
     166              :                 // We are promoted to attached by the control plane's re-attach response
     167            0 :                 self.mode = LocationMode::Attached(AttachedLocationConfig {
     168            0 :                     generation,
     169            0 :                     attach_mode: mode,
     170            0 :                 })
     171              :             }
     172              :         }
     173            0 :     }
     174              : 
     175            0 :     pub(crate) fn try_from(conf: &'_ models::LocationConfig) -> anyhow::Result<Self> {
     176            0 :         let tenant_conf = TenantConfOpt::try_from(&conf.tenant_conf)?;
     177              : 
     178            0 :         fn get_generation(conf: &'_ models::LocationConfig) -> Result<Generation, anyhow::Error> {
     179            0 :             conf.generation
     180            0 :                 .map(Generation::new)
     181            0 :                 .ok_or_else(|| anyhow::anyhow!("Generation must be set when attaching"))
     182            0 :         }
     183              : 
     184            0 :         let mode = match &conf.mode {
     185              :             models::LocationConfigMode::AttachedMulti => {
     186              :                 LocationMode::Attached(AttachedLocationConfig {
     187            0 :                     generation: get_generation(conf)?,
     188            0 :                     attach_mode: AttachmentMode::Multi,
     189              :                 })
     190              :             }
     191              :             models::LocationConfigMode::AttachedSingle => {
     192              :                 LocationMode::Attached(AttachedLocationConfig {
     193            0 :                     generation: get_generation(conf)?,
     194            0 :                     attach_mode: AttachmentMode::Single,
     195              :                 })
     196              :             }
     197              :             models::LocationConfigMode::AttachedStale => {
     198              :                 LocationMode::Attached(AttachedLocationConfig {
     199            0 :                     generation: get_generation(conf)?,
     200            0 :                     attach_mode: AttachmentMode::Stale,
     201              :                 })
     202              :             }
     203              :             models::LocationConfigMode::Secondary => {
     204            0 :                 anyhow::ensure!(conf.generation.is_none());
     205              : 
     206            0 :                 let warm = conf
     207            0 :                     .secondary_conf
     208            0 :                     .as_ref()
     209            0 :                     .map(|c| c.warm)
     210            0 :                     .unwrap_or(false);
     211            0 :                 LocationMode::Secondary(SecondaryLocationConfig { warm })
     212              :             }
     213              :             models::LocationConfigMode::Detached => {
     214              :                 // Should not have been called: API code should translate this mode
     215              :                 // into a detach rather than trying to decode it as a LocationConf
     216            0 :                 return Err(anyhow::anyhow!("Cannot decode a Detached configuration"));
     217              :             }
     218              :         };
     219              : 
     220            0 :         let shard = if conf.shard_count == 0 {
     221            0 :             ShardIdentity::unsharded()
     222              :         } else {
     223            0 :             ShardIdentity::new(
     224            0 :                 ShardNumber(conf.shard_number),
     225            0 :                 ShardCount::new(conf.shard_count),
     226            0 :                 ShardStripeSize(conf.shard_stripe_size),
     227            0 :             )?
     228              :         };
     229              : 
     230            0 :         Ok(Self {
     231            0 :             shard,
     232            0 :             mode,
     233            0 :             tenant_conf,
     234            0 :         })
     235            0 :     }
     236              : }
     237              : 
     238              : impl Default for LocationConf {
     239              :     // TODO: this should be removed once tenant loading can guarantee that we are never
     240              :     // loading from a directory without a configuration.
     241              :     // => tech debt since https://github.com/neondatabase/neon/issues/1555
     242            0 :     fn default() -> Self {
     243            0 :         Self {
     244            0 :             mode: LocationMode::Attached(AttachedLocationConfig {
     245            0 :                 generation: Generation::none(),
     246            0 :                 attach_mode: AttachmentMode::Single,
     247            0 :             }),
     248            0 :             tenant_conf: TenantConfOpt::default(),
     249            0 :             shard: ShardIdentity::unsharded(),
     250            0 :         }
     251            0 :     }
     252              : }
     253              : 
     254              : /// Same as TenantConf, but this struct preserves the information about
     255              : /// which parameters are set and which are not.
     256          244 : #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
     257              : pub struct TenantConfOpt {
     258              :     #[serde(skip_serializing_if = "Option::is_none")]
     259              :     #[serde(default)]
     260              :     pub checkpoint_distance: Option<u64>,
     261              : 
     262              :     #[serde(skip_serializing_if = "Option::is_none")]
     263              :     #[serde(with = "humantime_serde")]
     264              :     #[serde(default)]
     265              :     pub checkpoint_timeout: Option<Duration>,
     266              : 
     267              :     #[serde(skip_serializing_if = "Option::is_none")]
     268              :     #[serde(default)]
     269              :     pub compaction_target_size: Option<u64>,
     270              : 
     271              :     #[serde(skip_serializing_if = "Option::is_none")]
     272              :     #[serde(with = "humantime_serde")]
     273              :     #[serde(default)]
     274              :     pub compaction_period: Option<Duration>,
     275              : 
     276              :     #[serde(skip_serializing_if = "Option::is_none")]
     277              :     #[serde(default)]
     278              :     pub compaction_threshold: Option<usize>,
     279              : 
     280              :     #[serde(skip_serializing_if = "Option::is_none")]
     281              :     #[serde(default)]
     282              :     pub compaction_upper_limit: Option<usize>,
     283              : 
     284              :     #[serde(skip_serializing_if = "Option::is_none")]
     285              :     #[serde(default)]
     286              :     pub compaction_algorithm: Option<CompactionAlgorithmSettings>,
     287              : 
     288              :     #[serde(skip_serializing_if = "Option::is_none")]
     289              :     #[serde(default)]
     290              :     pub compaction_l0_first: Option<bool>,
     291              : 
     292              :     #[serde(skip_serializing_if = "Option::is_none")]
     293              :     #[serde(default)]
     294              :     pub compaction_l0_semaphore: Option<bool>,
     295              : 
     296              :     #[serde(skip_serializing_if = "Option::is_none")]
     297              :     #[serde(default)]
     298              :     pub l0_flush_delay_threshold: Option<usize>,
     299              : 
     300              :     #[serde(skip_serializing_if = "Option::is_none")]
     301              :     #[serde(default)]
     302              :     pub l0_flush_stall_threshold: Option<usize>,
     303              : 
     304              :     #[serde(skip_serializing_if = "Option::is_none")]
     305              :     #[serde(default)]
     306              :     pub l0_flush_wait_upload: Option<bool>,
     307              : 
     308              :     #[serde(skip_serializing_if = "Option::is_none")]
     309              :     #[serde(default)]
     310              :     pub gc_horizon: Option<u64>,
     311              : 
     312              :     #[serde(skip_serializing_if = "Option::is_none")]
     313              :     #[serde(with = "humantime_serde")]
     314              :     #[serde(default)]
     315              :     pub gc_period: Option<Duration>,
     316              : 
     317              :     #[serde(skip_serializing_if = "Option::is_none")]
     318              :     #[serde(default)]
     319              :     pub image_creation_threshold: Option<usize>,
     320              : 
     321              :     #[serde(skip_serializing_if = "Option::is_none")]
     322              :     #[serde(with = "humantime_serde")]
     323              :     #[serde(default)]
     324              :     pub pitr_interval: Option<Duration>,
     325              : 
     326              :     #[serde(skip_serializing_if = "Option::is_none")]
     327              :     #[serde(with = "humantime_serde")]
     328              :     #[serde(default)]
     329              :     pub walreceiver_connect_timeout: Option<Duration>,
     330              : 
     331              :     #[serde(skip_serializing_if = "Option::is_none")]
     332              :     #[serde(with = "humantime_serde")]
     333              :     #[serde(default)]
     334              :     pub lagging_wal_timeout: Option<Duration>,
     335              : 
     336              :     #[serde(skip_serializing_if = "Option::is_none")]
     337              :     #[serde(default)]
     338              :     pub max_lsn_wal_lag: Option<NonZeroU64>,
     339              : 
     340              :     #[serde(skip_serializing_if = "Option::is_none")]
     341              :     #[serde(default)]
     342              :     pub eviction_policy: Option<EvictionPolicy>,
     343              : 
     344              :     #[serde(skip_serializing_if = "Option::is_none")]
     345              :     #[serde(default)]
     346              :     pub min_resident_size_override: Option<u64>,
     347              : 
     348              :     #[serde(skip_serializing_if = "Option::is_none")]
     349              :     #[serde(with = "humantime_serde")]
     350              :     #[serde(default)]
     351              :     pub evictions_low_residence_duration_metric_threshold: Option<Duration>,
     352              : 
     353              :     #[serde(skip_serializing_if = "Option::is_none")]
     354              :     #[serde(with = "humantime_serde")]
     355              :     #[serde(default)]
     356              :     pub heatmap_period: Option<Duration>,
     357              : 
     358              :     #[serde(skip_serializing_if = "Option::is_none")]
     359              :     #[serde(default)]
     360              :     pub lazy_slru_download: Option<bool>,
     361              : 
     362              :     #[serde(skip_serializing_if = "Option::is_none")]
     363              :     pub timeline_get_throttle: Option<pageserver_api::models::ThrottleConfig>,
     364              : 
     365              :     #[serde(skip_serializing_if = "Option::is_none")]
     366              :     pub image_layer_creation_check_threshold: Option<u8>,
     367              : 
     368              :     #[serde(skip_serializing_if = "Option::is_none")]
     369              :     pub image_creation_preempt_threshold: Option<usize>,
     370              : 
     371              :     #[serde(skip_serializing_if = "Option::is_none")]
     372              :     #[serde(with = "humantime_serde")]
     373              :     #[serde(default)]
     374              :     pub lsn_lease_length: Option<Duration>,
     375              : 
     376              :     #[serde(skip_serializing_if = "Option::is_none")]
     377              :     #[serde(with = "humantime_serde")]
     378              :     #[serde(default)]
     379              :     pub lsn_lease_length_for_ts: Option<Duration>,
     380              : 
     381              :     #[serde(skip_serializing_if = "Option::is_none")]
     382              :     #[serde(default)]
     383              :     pub timeline_offloading: Option<bool>,
     384              : 
     385              :     #[serde(skip_serializing_if = "Option::is_none")]
     386              :     pub wal_receiver_protocol_override: Option<PostgresClientProtocol>,
     387              : 
     388              :     #[serde(skip_serializing_if = "Option::is_none")]
     389              :     pub rel_size_v2_enabled: Option<bool>,
     390              : 
     391              :     #[serde(skip_serializing_if = "Option::is_none")]
     392              :     pub gc_compaction_enabled: Option<bool>,
     393              : 
     394              :     #[serde(skip_serializing_if = "Option::is_none")]
     395              :     pub gc_compaction_initial_threshold_kb: Option<u64>,
     396              : 
     397              :     #[serde(skip_serializing_if = "Option::is_none")]
     398              :     pub gc_compaction_ratio_percent: Option<u64>,
     399              : }
     400              : 
     401              : impl TenantConfOpt {
     402            0 :     pub fn merge(&self, global_conf: TenantConf) -> TenantConf {
     403            0 :         TenantConf {
     404            0 :             checkpoint_distance: self
     405            0 :                 .checkpoint_distance
     406            0 :                 .unwrap_or(global_conf.checkpoint_distance),
     407            0 :             checkpoint_timeout: self
     408            0 :                 .checkpoint_timeout
     409            0 :                 .unwrap_or(global_conf.checkpoint_timeout),
     410            0 :             compaction_target_size: self
     411            0 :                 .compaction_target_size
     412            0 :                 .unwrap_or(global_conf.compaction_target_size),
     413            0 :             compaction_period: self
     414            0 :                 .compaction_period
     415            0 :                 .unwrap_or(global_conf.compaction_period),
     416            0 :             compaction_threshold: self
     417            0 :                 .compaction_threshold
     418            0 :                 .unwrap_or(global_conf.compaction_threshold),
     419            0 :             compaction_upper_limit: self
     420            0 :                 .compaction_upper_limit
     421            0 :                 .unwrap_or(global_conf.compaction_upper_limit),
     422            0 :             compaction_algorithm: self
     423            0 :                 .compaction_algorithm
     424            0 :                 .as_ref()
     425            0 :                 .unwrap_or(&global_conf.compaction_algorithm)
     426            0 :                 .clone(),
     427            0 :             compaction_l0_first: self
     428            0 :                 .compaction_l0_first
     429            0 :                 .unwrap_or(global_conf.compaction_l0_first),
     430            0 :             compaction_l0_semaphore: self
     431            0 :                 .compaction_l0_semaphore
     432            0 :                 .unwrap_or(global_conf.compaction_l0_semaphore),
     433            0 :             l0_flush_delay_threshold: self
     434            0 :                 .l0_flush_delay_threshold
     435            0 :                 .or(global_conf.l0_flush_delay_threshold),
     436            0 :             l0_flush_stall_threshold: self
     437            0 :                 .l0_flush_stall_threshold
     438            0 :                 .or(global_conf.l0_flush_stall_threshold),
     439            0 :             l0_flush_wait_upload: self
     440            0 :                 .l0_flush_wait_upload
     441            0 :                 .unwrap_or(global_conf.l0_flush_wait_upload),
     442            0 :             gc_horizon: self.gc_horizon.unwrap_or(global_conf.gc_horizon),
     443            0 :             gc_period: self.gc_period.unwrap_or(global_conf.gc_period),
     444            0 :             image_creation_threshold: self
     445            0 :                 .image_creation_threshold
     446            0 :                 .unwrap_or(global_conf.image_creation_threshold),
     447            0 :             pitr_interval: self.pitr_interval.unwrap_or(global_conf.pitr_interval),
     448            0 :             walreceiver_connect_timeout: self
     449            0 :                 .walreceiver_connect_timeout
     450            0 :                 .unwrap_or(global_conf.walreceiver_connect_timeout),
     451            0 :             lagging_wal_timeout: self
     452            0 :                 .lagging_wal_timeout
     453            0 :                 .unwrap_or(global_conf.lagging_wal_timeout),
     454            0 :             max_lsn_wal_lag: self.max_lsn_wal_lag.unwrap_or(global_conf.max_lsn_wal_lag),
     455            0 :             eviction_policy: self.eviction_policy.unwrap_or(global_conf.eviction_policy),
     456            0 :             min_resident_size_override: self
     457            0 :                 .min_resident_size_override
     458            0 :                 .or(global_conf.min_resident_size_override),
     459            0 :             evictions_low_residence_duration_metric_threshold: self
     460            0 :                 .evictions_low_residence_duration_metric_threshold
     461            0 :                 .unwrap_or(global_conf.evictions_low_residence_duration_metric_threshold),
     462            0 :             heatmap_period: self.heatmap_period.unwrap_or(global_conf.heatmap_period),
     463            0 :             lazy_slru_download: self
     464            0 :                 .lazy_slru_download
     465            0 :                 .unwrap_or(global_conf.lazy_slru_download),
     466            0 :             timeline_get_throttle: self
     467            0 :                 .timeline_get_throttle
     468            0 :                 .clone()
     469            0 :                 .unwrap_or(global_conf.timeline_get_throttle),
     470            0 :             image_layer_creation_check_threshold: self
     471            0 :                 .image_layer_creation_check_threshold
     472            0 :                 .unwrap_or(global_conf.image_layer_creation_check_threshold),
     473            0 :             image_creation_preempt_threshold: self
     474            0 :                 .image_creation_preempt_threshold
     475            0 :                 .unwrap_or(global_conf.image_creation_preempt_threshold),
     476            0 :             lsn_lease_length: self
     477            0 :                 .lsn_lease_length
     478            0 :                 .unwrap_or(global_conf.lsn_lease_length),
     479            0 :             lsn_lease_length_for_ts: self
     480            0 :                 .lsn_lease_length_for_ts
     481            0 :                 .unwrap_or(global_conf.lsn_lease_length_for_ts),
     482            0 :             timeline_offloading: self
     483            0 :                 .timeline_offloading
     484            0 :                 .unwrap_or(global_conf.timeline_offloading),
     485            0 :             wal_receiver_protocol_override: self
     486            0 :                 .wal_receiver_protocol_override
     487            0 :                 .or(global_conf.wal_receiver_protocol_override),
     488            0 :             rel_size_v2_enabled: self
     489            0 :                 .rel_size_v2_enabled
     490            0 :                 .unwrap_or(global_conf.rel_size_v2_enabled),
     491            0 :             gc_compaction_enabled: self
     492            0 :                 .gc_compaction_enabled
     493            0 :                 .unwrap_or(global_conf.gc_compaction_enabled),
     494            0 :             gc_compaction_initial_threshold_kb: self
     495            0 :                 .gc_compaction_initial_threshold_kb
     496            0 :                 .unwrap_or(global_conf.gc_compaction_initial_threshold_kb),
     497            0 :             gc_compaction_ratio_percent: self
     498            0 :                 .gc_compaction_ratio_percent
     499            0 :                 .unwrap_or(global_conf.gc_compaction_ratio_percent),
     500            0 :         }
     501            0 :     }
     502              : 
     503            0 :     pub fn apply_patch(self, patch: TenantConfigPatch) -> anyhow::Result<TenantConfOpt> {
     504            0 :         let Self {
     505            0 :             mut checkpoint_distance,
     506            0 :             mut checkpoint_timeout,
     507            0 :             mut compaction_target_size,
     508            0 :             mut compaction_period,
     509            0 :             mut compaction_threshold,
     510            0 :             mut compaction_upper_limit,
     511            0 :             mut compaction_algorithm,
     512            0 :             mut compaction_l0_first,
     513            0 :             mut compaction_l0_semaphore,
     514            0 :             mut l0_flush_delay_threshold,
     515            0 :             mut l0_flush_stall_threshold,
     516            0 :             mut l0_flush_wait_upload,
     517            0 :             mut gc_horizon,
     518            0 :             mut gc_period,
     519            0 :             mut image_creation_threshold,
     520            0 :             mut pitr_interval,
     521            0 :             mut walreceiver_connect_timeout,
     522            0 :             mut lagging_wal_timeout,
     523            0 :             mut max_lsn_wal_lag,
     524            0 :             mut eviction_policy,
     525            0 :             mut min_resident_size_override,
     526            0 :             mut evictions_low_residence_duration_metric_threshold,
     527            0 :             mut heatmap_period,
     528            0 :             mut lazy_slru_download,
     529            0 :             mut timeline_get_throttle,
     530            0 :             mut image_layer_creation_check_threshold,
     531            0 :             mut image_creation_preempt_threshold,
     532            0 :             mut lsn_lease_length,
     533            0 :             mut lsn_lease_length_for_ts,
     534            0 :             mut timeline_offloading,
     535            0 :             mut wal_receiver_protocol_override,
     536            0 :             mut rel_size_v2_enabled,
     537            0 :             mut gc_compaction_enabled,
     538            0 :             mut gc_compaction_initial_threshold_kb,
     539            0 :             mut gc_compaction_ratio_percent,
     540            0 :         } = self;
     541            0 : 
     542            0 :         patch.checkpoint_distance.apply(&mut checkpoint_distance);
     543            0 :         patch
     544            0 :             .checkpoint_timeout
     545            0 :             .map(|v| humantime::parse_duration(&v))?
     546            0 :             .apply(&mut checkpoint_timeout);
     547            0 :         patch
     548            0 :             .compaction_target_size
     549            0 :             .apply(&mut compaction_target_size);
     550            0 :         patch
     551            0 :             .compaction_period
     552            0 :             .map(|v| humantime::parse_duration(&v))?
     553            0 :             .apply(&mut compaction_period);
     554            0 :         patch.compaction_threshold.apply(&mut compaction_threshold);
     555            0 :         patch
     556            0 :             .compaction_upper_limit
     557            0 :             .apply(&mut compaction_upper_limit);
     558            0 :         patch.compaction_algorithm.apply(&mut compaction_algorithm);
     559            0 :         patch.compaction_l0_first.apply(&mut compaction_l0_first);
     560            0 :         patch
     561            0 :             .compaction_l0_semaphore
     562            0 :             .apply(&mut compaction_l0_semaphore);
     563            0 :         patch
     564            0 :             .l0_flush_delay_threshold
     565            0 :             .apply(&mut l0_flush_delay_threshold);
     566            0 :         patch
     567            0 :             .l0_flush_stall_threshold
     568            0 :             .apply(&mut l0_flush_stall_threshold);
     569            0 :         patch.l0_flush_wait_upload.apply(&mut l0_flush_wait_upload);
     570            0 :         patch.gc_horizon.apply(&mut gc_horizon);
     571            0 :         patch
     572            0 :             .gc_period
     573            0 :             .map(|v| humantime::parse_duration(&v))?
     574            0 :             .apply(&mut gc_period);
     575            0 :         patch
     576            0 :             .image_creation_threshold
     577            0 :             .apply(&mut image_creation_threshold);
     578            0 :         patch
     579            0 :             .pitr_interval
     580            0 :             .map(|v| humantime::parse_duration(&v))?
     581            0 :             .apply(&mut pitr_interval);
     582            0 :         patch
     583            0 :             .walreceiver_connect_timeout
     584            0 :             .map(|v| humantime::parse_duration(&v))?
     585            0 :             .apply(&mut walreceiver_connect_timeout);
     586            0 :         patch
     587            0 :             .lagging_wal_timeout
     588            0 :             .map(|v| humantime::parse_duration(&v))?
     589            0 :             .apply(&mut lagging_wal_timeout);
     590            0 :         patch.max_lsn_wal_lag.apply(&mut max_lsn_wal_lag);
     591            0 :         patch.eviction_policy.apply(&mut eviction_policy);
     592            0 :         patch
     593            0 :             .min_resident_size_override
     594            0 :             .apply(&mut min_resident_size_override);
     595            0 :         patch
     596            0 :             .evictions_low_residence_duration_metric_threshold
     597            0 :             .map(|v| humantime::parse_duration(&v))?
     598            0 :             .apply(&mut evictions_low_residence_duration_metric_threshold);
     599            0 :         patch
     600            0 :             .heatmap_period
     601            0 :             .map(|v| humantime::parse_duration(&v))?
     602            0 :             .apply(&mut heatmap_period);
     603            0 :         patch.lazy_slru_download.apply(&mut lazy_slru_download);
     604            0 :         patch
     605            0 :             .timeline_get_throttle
     606            0 :             .apply(&mut timeline_get_throttle);
     607            0 :         patch
     608            0 :             .image_layer_creation_check_threshold
     609            0 :             .apply(&mut image_layer_creation_check_threshold);
     610            0 :         patch
     611            0 :             .image_creation_preempt_threshold
     612            0 :             .apply(&mut image_creation_preempt_threshold);
     613            0 :         patch
     614            0 :             .lsn_lease_length
     615            0 :             .map(|v| humantime::parse_duration(&v))?
     616            0 :             .apply(&mut lsn_lease_length);
     617            0 :         patch
     618            0 :             .lsn_lease_length_for_ts
     619            0 :             .map(|v| humantime::parse_duration(&v))?
     620            0 :             .apply(&mut lsn_lease_length_for_ts);
     621            0 :         patch.timeline_offloading.apply(&mut timeline_offloading);
     622            0 :         patch
     623            0 :             .wal_receiver_protocol_override
     624            0 :             .apply(&mut wal_receiver_protocol_override);
     625            0 :         patch.rel_size_v2_enabled.apply(&mut rel_size_v2_enabled);
     626            0 :         patch
     627            0 :             .gc_compaction_enabled
     628            0 :             .apply(&mut gc_compaction_enabled);
     629            0 :         patch
     630            0 :             .gc_compaction_initial_threshold_kb
     631            0 :             .apply(&mut gc_compaction_initial_threshold_kb);
     632            0 :         patch
     633            0 :             .gc_compaction_ratio_percent
     634            0 :             .apply(&mut gc_compaction_ratio_percent);
     635            0 : 
     636            0 :         Ok(Self {
     637            0 :             checkpoint_distance,
     638            0 :             checkpoint_timeout,
     639            0 :             compaction_target_size,
     640            0 :             compaction_period,
     641            0 :             compaction_threshold,
     642            0 :             compaction_upper_limit,
     643            0 :             compaction_algorithm,
     644            0 :             compaction_l0_first,
     645            0 :             compaction_l0_semaphore,
     646            0 :             l0_flush_delay_threshold,
     647            0 :             l0_flush_stall_threshold,
     648            0 :             l0_flush_wait_upload,
     649            0 :             gc_horizon,
     650            0 :             gc_period,
     651            0 :             image_creation_threshold,
     652            0 :             pitr_interval,
     653            0 :             walreceiver_connect_timeout,
     654            0 :             lagging_wal_timeout,
     655            0 :             max_lsn_wal_lag,
     656            0 :             eviction_policy,
     657            0 :             min_resident_size_override,
     658            0 :             evictions_low_residence_duration_metric_threshold,
     659            0 :             heatmap_period,
     660            0 :             lazy_slru_download,
     661            0 :             timeline_get_throttle,
     662            0 :             image_layer_creation_check_threshold,
     663            0 :             image_creation_preempt_threshold,
     664            0 :             lsn_lease_length,
     665            0 :             lsn_lease_length_for_ts,
     666            0 :             timeline_offloading,
     667            0 :             wal_receiver_protocol_override,
     668            0 :             rel_size_v2_enabled,
     669            0 :             gc_compaction_enabled,
     670            0 :             gc_compaction_initial_threshold_kb,
     671            0 :             gc_compaction_ratio_percent,
     672            0 :         })
     673            0 :     }
     674              : }
     675              : 
     676              : impl TryFrom<&'_ models::TenantConfig> for TenantConfOpt {
     677              :     type Error = anyhow::Error;
     678              : 
     679            8 :     fn try_from(request_data: &'_ models::TenantConfig) -> Result<Self, Self::Error> {
     680              :         // Convert the request_data to a JSON Value
     681            8 :         let json_value: Value = serde_json::to_value(request_data)?;
     682              : 
     683              :         // Create a Deserializer from the JSON Value
     684            8 :         let deserializer = json_value.into_deserializer();
     685              : 
     686              :         // Use serde_path_to_error to deserialize the JSON Value into TenantConfOpt
     687            8 :         let tenant_conf: TenantConfOpt = serde_path_to_error::deserialize(deserializer)?;
     688              : 
     689            4 :         Ok(tenant_conf)
     690            8 :     }
     691              : }
     692              : 
     693              : /// This is a conversion from our internal tenant config object to the one used
     694              : /// in external APIs.
     695              : impl From<TenantConfOpt> for models::TenantConfig {
     696            0 :     fn from(value: TenantConfOpt) -> Self {
     697            0 :         fn humantime(d: Duration) -> String {
     698            0 :             format!("{}s", d.as_secs())
     699            0 :         }
     700            0 :         Self {
     701            0 :             checkpoint_distance: value.checkpoint_distance,
     702            0 :             checkpoint_timeout: value.checkpoint_timeout.map(humantime),
     703            0 :             compaction_algorithm: value.compaction_algorithm,
     704            0 :             compaction_target_size: value.compaction_target_size,
     705            0 :             compaction_period: value.compaction_period.map(humantime),
     706            0 :             compaction_threshold: value.compaction_threshold,
     707            0 :             compaction_upper_limit: value.compaction_upper_limit,
     708            0 :             compaction_l0_first: value.compaction_l0_first,
     709            0 :             compaction_l0_semaphore: value.compaction_l0_semaphore,
     710            0 :             l0_flush_delay_threshold: value.l0_flush_delay_threshold,
     711            0 :             l0_flush_stall_threshold: value.l0_flush_stall_threshold,
     712            0 :             l0_flush_wait_upload: value.l0_flush_wait_upload,
     713            0 :             gc_horizon: value.gc_horizon,
     714            0 :             gc_period: value.gc_period.map(humantime),
     715            0 :             image_creation_threshold: value.image_creation_threshold,
     716            0 :             pitr_interval: value.pitr_interval.map(humantime),
     717            0 :             walreceiver_connect_timeout: value.walreceiver_connect_timeout.map(humantime),
     718            0 :             lagging_wal_timeout: value.lagging_wal_timeout.map(humantime),
     719            0 :             max_lsn_wal_lag: value.max_lsn_wal_lag,
     720            0 :             eviction_policy: value.eviction_policy,
     721            0 :             min_resident_size_override: value.min_resident_size_override,
     722            0 :             evictions_low_residence_duration_metric_threshold: value
     723            0 :                 .evictions_low_residence_duration_metric_threshold
     724            0 :                 .map(humantime),
     725            0 :             heatmap_period: value.heatmap_period.map(humantime),
     726            0 :             lazy_slru_download: value.lazy_slru_download,
     727            0 :             timeline_get_throttle: value.timeline_get_throttle,
     728            0 :             image_layer_creation_check_threshold: value.image_layer_creation_check_threshold,
     729            0 :             image_creation_preempt_threshold: value.image_creation_preempt_threshold,
     730            0 :             lsn_lease_length: value.lsn_lease_length.map(humantime),
     731            0 :             lsn_lease_length_for_ts: value.lsn_lease_length_for_ts.map(humantime),
     732            0 :             timeline_offloading: value.timeline_offloading,
     733            0 :             wal_receiver_protocol_override: value.wal_receiver_protocol_override,
     734            0 :             rel_size_v2_enabled: value.rel_size_v2_enabled,
     735            0 :             gc_compaction_enabled: value.gc_compaction_enabled,
     736            0 :             gc_compaction_initial_threshold_kb: value.gc_compaction_initial_threshold_kb,
     737            0 :             gc_compaction_ratio_percent: value.gc_compaction_ratio_percent,
     738            0 :         }
     739            0 :     }
     740              : }
     741              : 
     742              : #[cfg(test)]
     743              : mod tests {
     744              :     use super::*;
     745              :     use models::TenantConfig;
     746              : 
     747              :     #[test]
     748            4 :     fn de_serializing_pageserver_config_omits_empty_values() {
     749            4 :         let small_conf = TenantConfOpt {
     750            4 :             gc_horizon: Some(42),
     751            4 :             ..TenantConfOpt::default()
     752            4 :         };
     753            4 : 
     754            4 :         let toml_form = toml_edit::ser::to_string(&small_conf).unwrap();
     755            4 :         assert_eq!(toml_form, "gc_horizon = 42\n");
     756            4 :         assert_eq!(small_conf, toml_edit::de::from_str(&toml_form).unwrap());
     757              : 
     758            4 :         let json_form = serde_json::to_string(&small_conf).unwrap();
     759            4 :         assert_eq!(json_form, "{\"gc_horizon\":42}");
     760            4 :         assert_eq!(small_conf, serde_json::from_str(&json_form).unwrap());
     761            4 :     }
     762              : 
     763              :     #[test]
     764            4 :     fn test_try_from_models_tenant_config_err() {
     765            4 :         let tenant_config = models::TenantConfig {
     766            4 :             lagging_wal_timeout: Some("5a".to_string()),
     767            4 :             ..TenantConfig::default()
     768            4 :         };
     769            4 : 
     770            4 :         let tenant_conf_opt = TenantConfOpt::try_from(&tenant_config);
     771            4 : 
     772            4 :         assert!(
     773            4 :             tenant_conf_opt.is_err(),
     774            0 :             "Suceeded to convert TenantConfig to TenantConfOpt"
     775              :         );
     776              : 
     777            4 :         let expected_error_str =
     778            4 :             "lagging_wal_timeout: invalid value: string \"5a\", expected a duration";
     779            4 :         assert_eq!(tenant_conf_opt.unwrap_err().to_string(), expected_error_str);
     780            4 :     }
     781              : 
     782              :     #[test]
     783            4 :     fn test_try_from_models_tenant_config_success() {
     784            4 :         let tenant_config = models::TenantConfig {
     785            4 :             lagging_wal_timeout: Some("5s".to_string()),
     786            4 :             ..TenantConfig::default()
     787            4 :         };
     788            4 : 
     789            4 :         let tenant_conf_opt = TenantConfOpt::try_from(&tenant_config).unwrap();
     790            4 : 
     791            4 :         assert_eq!(
     792            4 :             tenant_conf_opt.lagging_wal_timeout,
     793            4 :             Some(Duration::from_secs(5))
     794            4 :         );
     795            4 :     }
     796              : }
        

Generated by: LCOV version 2.1-beta