LCOV - code coverage report
Current view: top level - pageserver/src/tenant/remote_timeline_client - index.rs (source / functions) Coverage Total Hit
Test: 20b6afc7b7f34578dcaab2b3acdaecfe91cd8bf1.info Lines: 88.5 % 678 600
Test Date: 2024-11-25 17:48:16 Functions: 41.3 % 104 43

            Line data    Source code
       1              : //! In-memory index to track the tenant files on the remote storage.
       2              : //!
       3              : //! Able to restore itself from the storage index parts, that are located in every timeline's remote directory and contain all data about
       4              : //! remote timeline layers and its metadata.
       5              : 
       6              : use std::collections::HashMap;
       7              : 
       8              : use chrono::NaiveDateTime;
       9              : use pageserver_api::models::AuxFilePolicy;
      10              : use serde::{Deserialize, Serialize};
      11              : use utils::id::TimelineId;
      12              : 
      13              : use crate::tenant::metadata::TimelineMetadata;
      14              : use crate::tenant::storage_layer::LayerName;
      15              : use crate::tenant::timeline::import_pgdata;
      16              : use crate::tenant::Generation;
      17              : use pageserver_api::shard::ShardIndex;
      18              : 
      19              : use utils::lsn::Lsn;
      20              : 
      21              : /// In-memory representation of an `index_part.json` file
      22              : ///
      23              : /// Contains the data about all files in the timeline, present remotely and its metadata.
      24              : ///
      25              : /// This type needs to be backwards and forwards compatible. When changing the fields,
      26              : /// remember to add a test case for the changed version.
      27         2878 : #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
      28              : pub struct IndexPart {
      29              :     /// Debugging aid describing the version of this type.
      30              :     #[serde(default)]
      31              :     version: usize,
      32              : 
      33              :     #[serde(default)]
      34              :     #[serde(skip_serializing_if = "Option::is_none")]
      35              :     pub deleted_at: Option<NaiveDateTime>,
      36              : 
      37              :     #[serde(default)]
      38              :     #[serde(skip_serializing_if = "Option::is_none")]
      39              :     pub archived_at: Option<NaiveDateTime>,
      40              : 
      41              :     /// This field supports import-from-pgdata ("fast imports" platform feature).
      42              :     /// We don't currently use fast imports, so, this field is None for all production timelines.
      43              :     /// See <https://github.com/neondatabase/neon/pull/9218> for more information.
      44              :     #[serde(default)]
      45              :     #[serde(skip_serializing_if = "Option::is_none")]
      46              :     pub import_pgdata: Option<import_pgdata::index_part_format::Root>,
      47              : 
      48              :     /// Per layer file name metadata, which can be present for a present or missing layer file.
      49              :     ///
      50              :     /// Older versions of `IndexPart` will not have this property or have only a part of metadata
      51              :     /// that latest version stores.
      52              :     pub layer_metadata: HashMap<LayerName, LayerFileMetadata>,
      53              : 
      54              :     /// Because of the trouble of eyeballing the legacy "metadata" field, we copied the
      55              :     /// "disk_consistent_lsn" out. After version 7 this is no longer needed, but the name cannot be
      56              :     /// reused.
      57              :     pub(super) disk_consistent_lsn: Lsn,
      58              : 
      59              :     // TODO: rename as "metadata" next week, keep the alias = "metadata_bytes", bump version Adding
      60              :     // the "alias = metadata" was forgotten in #7693, so we have to use "rewrite = metadata_bytes"
      61              :     // for backwards compatibility.
      62              :     #[serde(
      63              :         rename = "metadata_bytes",
      64              :         alias = "metadata",
      65              :         with = "crate::tenant::metadata::modern_serde"
      66              :     )]
      67              :     pub metadata: TimelineMetadata,
      68              : 
      69              :     #[serde(default)]
      70              :     pub(crate) lineage: Lineage,
      71              : 
      72              :     #[serde(skip_serializing_if = "Option::is_none", default)]
      73              :     pub(crate) gc_blocking: Option<GcBlocking>,
      74              : 
      75              :     /// Describes the kind of aux files stored in the timeline.
      76              :     ///
      77              :     /// The value is modified during file ingestion when the latest wanted value communicated via tenant config is applied if it is acceptable.
      78              :     /// A V1 setting after V2 files have been committed is not accepted.
      79              :     ///
      80              :     /// None means no aux files have been written to the storage before the point
      81              :     /// when this flag is introduced.
      82              :     #[serde(skip_serializing_if = "Option::is_none", default)]
      83              :     pub(crate) last_aux_file_policy: Option<AuxFilePolicy>,
      84              : }
      85              : 
      86              : impl IndexPart {
      87              :     /// When adding or modifying any parts of `IndexPart`, increment the version so that it can be
      88              :     /// used to understand later versions.
      89              :     ///
      90              :     /// Version is currently informative only.
      91              :     /// Version history
      92              :     /// - 2: added `deleted_at`
      93              :     /// - 3: no longer deserialize `timeline_layers` (serialized format is the same, but timeline_layers
      94              :     ///      is always generated from the keys of `layer_metadata`)
      95              :     /// - 4: timeline_layers is fully removed.
      96              :     /// - 5: lineage was added
      97              :     /// - 6: last_aux_file_policy is added.
      98              :     /// - 7: metadata_bytes is no longer written, but still read
      99              :     /// - 8: added `archived_at`
     100              :     /// - 9: +gc_blocking
     101              :     /// - 10: +import_pgdata
     102              :     const LATEST_VERSION: usize = 10;
     103              : 
     104              :     // Versions we may see when reading from a bucket.
     105              :     pub const KNOWN_VERSIONS: &'static [usize] = &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
     106              : 
     107              :     pub const FILE_NAME: &'static str = "index_part.json";
     108              : 
     109          424 :     pub(crate) fn empty(metadata: TimelineMetadata) -> Self {
     110          424 :         IndexPart {
     111          424 :             version: Self::LATEST_VERSION,
     112          424 :             layer_metadata: Default::default(),
     113          424 :             disk_consistent_lsn: metadata.disk_consistent_lsn(),
     114          424 :             metadata,
     115          424 :             deleted_at: None,
     116          424 :             archived_at: None,
     117          424 :             lineage: Default::default(),
     118          424 :             gc_blocking: None,
     119          424 :             last_aux_file_policy: None,
     120          424 :             import_pgdata: None,
     121          424 :         }
     122          424 :     }
     123              : 
     124            0 :     pub fn version(&self) -> usize {
     125            0 :         self.version
     126            0 :     }
     127              : 
     128              :     /// If you want this under normal operations, read it from self.metadata:
     129              :     /// this method is just for the scrubber to use when validating an index.
     130            0 :     pub fn duplicated_disk_consistent_lsn(&self) -> Lsn {
     131            0 :         self.disk_consistent_lsn
     132            0 :     }
     133              : 
     134           22 :     pub fn from_json_bytes(bytes: &[u8]) -> Result<Self, serde_json::Error> {
     135           22 :         serde_json::from_slice::<IndexPart>(bytes)
     136           22 :     }
     137              : 
     138         1414 :     pub fn to_json_bytes(&self) -> serde_json::Result<Vec<u8>> {
     139         1414 :         serde_json::to_vec(self)
     140         1414 :     }
     141              : 
     142              :     #[cfg(test)]
     143           12 :     pub(crate) fn example() -> Self {
     144           12 :         Self::empty(TimelineMetadata::example())
     145           12 :     }
     146              : }
     147              : 
     148              : /// Metadata gathered for each of the layer files.
     149              : ///
     150              : /// Fields have to be `Option`s because remote [`IndexPart`]'s can be from different version, which
     151              : /// might have less or more metadata depending if upgrading or rolling back an upgrade.
     152        12668 : #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
     153              : pub struct LayerFileMetadata {
     154              :     pub file_size: u64,
     155              : 
     156              :     #[serde(default = "Generation::none")]
     157              :     #[serde(skip_serializing_if = "Generation::is_none")]
     158              :     pub generation: Generation,
     159              : 
     160              :     #[serde(default = "ShardIndex::unsharded")]
     161              :     #[serde(skip_serializing_if = "ShardIndex::is_unsharded")]
     162              :     pub shard: ShardIndex,
     163              : }
     164              : 
     165              : impl LayerFileMetadata {
     166         2459 :     pub fn new(file_size: u64, generation: Generation, shard: ShardIndex) -> Self {
     167         2459 :         LayerFileMetadata {
     168         2459 :             file_size,
     169         2459 :             generation,
     170         2459 :             shard,
     171         2459 :         }
     172         2459 :     }
     173              : }
     174              : 
     175              : /// Limited history of earlier ancestors.
     176              : ///
     177              : /// A timeline can have more than 1 earlier ancestor, in the rare case that it was repeatedly
     178              : /// reparented by having an later timeline be detached from it's ancestor.
     179           34 : #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Default)]
     180              : pub(crate) struct Lineage {
     181              :     /// Has the `reparenting_history` been truncated to [`Lineage::REMEMBER_AT_MOST`].
     182              :     #[serde(skip_serializing_if = "is_false", default)]
     183              :     reparenting_history_truncated: bool,
     184              : 
     185              :     /// Earlier ancestors, truncated when [`Self::reparenting_history_truncated`]
     186              :     ///
     187              :     /// These are stored in case we want to support WAL based DR on the timeline. There can be many
     188              :     /// of these and at most one [`Self::original_ancestor`]. There cannot be more reparentings
     189              :     /// after [`Self::original_ancestor`] has been set.
     190              :     #[serde(skip_serializing_if = "Vec::is_empty", default)]
     191              :     reparenting_history: Vec<TimelineId>,
     192              : 
     193              :     /// The ancestor from which this timeline has been detached from and when.
     194              :     ///
     195              :     /// If you are adding support for detaching from a hierarchy, consider changing the ancestry
     196              :     /// into a `Vec<(TimelineId, Lsn)>` to be a path instead.
     197              :     // FIXME: this is insufficient even for path of two timelines for future wal recovery
     198              :     // purposes:
     199              :     //
     200              :     // assuming a "old main" which has received most of the WAL, and has a branch "new main",
     201              :     // starting a bit before "old main" last_record_lsn. the current version works fine,
     202              :     // because we will know to replay wal and branch at the recorded Lsn to do wal recovery.
     203              :     //
     204              :     // then assuming "new main" would similarly receive a branch right before its last_record_lsn,
     205              :     // "new new main". the current implementation would just store ("new main", ancestor_lsn, _)
     206              :     // here. however, we cannot recover from WAL using only that information, we would need the
     207              :     // whole ancestry here:
     208              :     //
     209              :     // ```json
     210              :     // [
     211              :     //   ["old main", ancestor_lsn("new main"), _],
     212              :     //   ["new main", ancestor_lsn("new new main"), _]
     213              :     // ]
     214              :     // ```
     215              :     #[serde(skip_serializing_if = "Option::is_none", default)]
     216              :     original_ancestor: Option<(TimelineId, Lsn, NaiveDateTime)>,
     217              : }
     218              : 
     219         5756 : fn is_false(b: &bool) -> bool {
     220         5756 :     !b
     221         5756 : }
     222              : 
     223              : impl Lineage {
     224              :     const REMEMBER_AT_MOST: usize = 100;
     225              : 
     226            0 :     pub(crate) fn record_previous_ancestor(&mut self, old_ancestor: &TimelineId) -> bool {
     227            0 :         if self.reparenting_history.last() == Some(old_ancestor) {
     228              :             // do not re-record it
     229            0 :             false
     230              :         } else {
     231              :             #[cfg(feature = "testing")]
     232              :             {
     233            0 :                 let existing = self
     234            0 :                     .reparenting_history
     235            0 :                     .iter()
     236            0 :                     .position(|x| x == old_ancestor);
     237            0 :                 assert_eq!(
     238              :                     existing, None,
     239            0 :                     "we cannot reparent onto and off and onto the same timeline twice"
     240              :                 );
     241              :             }
     242            0 :             let drop_oldest = self.reparenting_history.len() + 1 >= Self::REMEMBER_AT_MOST;
     243            0 : 
     244            0 :             self.reparenting_history_truncated |= drop_oldest;
     245            0 :             if drop_oldest {
     246            0 :                 self.reparenting_history.remove(0);
     247            0 :             }
     248            0 :             self.reparenting_history.push(*old_ancestor);
     249            0 :             true
     250              :         }
     251            0 :     }
     252              : 
     253              :     /// Returns true if anything changed.
     254            0 :     pub(crate) fn record_detaching(&mut self, branchpoint: &(TimelineId, Lsn)) -> bool {
     255            0 :         if let Some((id, lsn, _)) = self.original_ancestor {
     256            0 :             assert_eq!(
     257            0 :                 &(id, lsn),
     258              :                 branchpoint,
     259            0 :                 "detaching attempt has to be for the same ancestor we are already detached from"
     260              :             );
     261            0 :             false
     262              :         } else {
     263            0 :             self.original_ancestor =
     264            0 :                 Some((branchpoint.0, branchpoint.1, chrono::Utc::now().naive_utc()));
     265            0 :             true
     266              :         }
     267            0 :     }
     268              : 
     269              :     /// The queried lsn is most likely the basebackup lsn, and this answers question "is it allowed
     270              :     /// to start a read/write primary at this lsn".
     271              :     ///
     272              :     /// Returns true if the Lsn was previously our branch point.
     273            0 :     pub(crate) fn is_previous_ancestor_lsn(&self, lsn: Lsn) -> bool {
     274            0 :         self.original_ancestor
     275            0 :             .is_some_and(|(_, ancestor_lsn, _)| ancestor_lsn == lsn)
     276            0 :     }
     277              : 
     278              :     /// Returns true if the timeline originally had an ancestor, and no longer has one.
     279            0 :     pub(crate) fn is_detached_from_ancestor(&self) -> bool {
     280            0 :         self.original_ancestor.is_some()
     281            0 :     }
     282              : 
     283              :     /// Returns original ancestor timeline id and lsn that this timeline has been detached from.
     284            0 :     pub(crate) fn detached_previous_ancestor(&self) -> Option<(TimelineId, Lsn)> {
     285            0 :         self.original_ancestor.map(|(id, lsn, _)| (id, lsn))
     286            0 :     }
     287              : 
     288            0 :     pub(crate) fn is_reparented(&self) -> bool {
     289            0 :         !self.reparenting_history.is_empty()
     290            0 :     }
     291              : }
     292              : 
     293           12 : #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
     294              : pub(crate) struct GcBlocking {
     295              :     pub(crate) started_at: NaiveDateTime,
     296              :     pub(crate) reasons: enumset::EnumSet<GcBlockingReason>,
     297              : }
     298              : 
     299            8 : #[derive(Debug, enumset::EnumSetType, serde::Serialize, serde::Deserialize)]
     300              : #[enumset(serialize_repr = "list")]
     301              : pub(crate) enum GcBlockingReason {
     302              :     Manual,
     303              :     DetachAncestor,
     304              : }
     305              : 
     306              : impl GcBlocking {
     307            0 :     pub(super) fn started_now_for(reason: GcBlockingReason) -> Self {
     308            0 :         GcBlocking {
     309            0 :             started_at: chrono::Utc::now().naive_utc(),
     310            0 :             reasons: enumset::EnumSet::only(reason),
     311            0 :         }
     312            0 :     }
     313              : 
     314              :     /// Returns true if the given reason is one of the reasons why the gc is blocked.
     315            0 :     pub(crate) fn blocked_by(&self, reason: GcBlockingReason) -> bool {
     316            0 :         self.reasons.contains(reason)
     317            0 :     }
     318              : 
     319              :     /// Returns a version of self with the given reason.
     320            0 :     pub(super) fn with_reason(&self, reason: GcBlockingReason) -> Self {
     321            0 :         assert!(!self.blocked_by(reason));
     322            0 :         let mut reasons = self.reasons;
     323            0 :         reasons.insert(reason);
     324            0 : 
     325            0 :         Self {
     326            0 :             started_at: self.started_at,
     327            0 :             reasons,
     328            0 :         }
     329            0 :     }
     330              : 
     331              :     /// Returns a version of self without the given reason. Assumption is that if
     332              :     /// there are no more reasons, we can unblock the gc by returning `None`.
     333            0 :     pub(super) fn without_reason(&self, reason: GcBlockingReason) -> Option<Self> {
     334            0 :         assert!(self.blocked_by(reason));
     335              : 
     336            0 :         if self.reasons.len() == 1 {
     337            0 :             None
     338              :         } else {
     339            0 :             let mut reasons = self.reasons;
     340            0 :             assert!(reasons.remove(reason));
     341            0 :             assert!(!reasons.is_empty());
     342              : 
     343            0 :             Some(Self {
     344            0 :                 started_at: self.started_at,
     345            0 :                 reasons,
     346            0 :             })
     347              :         }
     348            0 :     }
     349              : }
     350              : 
     351              : #[cfg(test)]
     352              : mod tests {
     353              :     use super::*;
     354              :     use std::str::FromStr;
     355              :     use utils::id::TimelineId;
     356              : 
     357              :     #[test]
     358            2 :     fn v1_indexpart_is_parsed() {
     359            2 :         let example = r#"{
     360            2 :             "version":1,
     361            2 :             "timeline_layers":["000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9"],
     362            2 :             "layer_metadata":{
     363            2 :                 "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9": { "file_size": 25600000 },
     364            2 :                 "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51": { "file_size": 9007199254741001 }
     365            2 :             },
     366            2 :             "disk_consistent_lsn":"0/16960E8",
     367            2 :             "metadata_bytes":[113,11,159,210,0,54,0,4,0,0,0,0,1,105,96,232,1,0,0,0,0,1,105,96,112,0,0,0,0,0,0,0,0,0,0,0,0,0,1,105,96,112,0,0,0,0,1,105,96,112,0,0,0,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
     368            2 :         }"#;
     369            2 : 
     370            2 :         let expected = IndexPart {
     371            2 :             // note this is not verified, could be anything, but exists for humans debugging.. could be the git version instead?
     372            2 :             version: 1,
     373            2 :             layer_metadata: HashMap::from([
     374            2 :                 ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9".parse().unwrap(), LayerFileMetadata {
     375            2 :                     file_size: 25600000,
     376            2 :                     generation: Generation::none(),
     377            2 :                     shard: ShardIndex::unsharded()
     378            2 :                 }),
     379            2 :                 ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51".parse().unwrap(), LayerFileMetadata {
     380            2 :                     // serde_json should always parse this but this might be a double with jq for
     381            2 :                     // example.
     382            2 :                     file_size: 9007199254741001,
     383            2 :                     generation: Generation::none(),
     384            2 :                     shard: ShardIndex::unsharded()
     385            2 :                 })
     386            2 :             ]),
     387            2 :             disk_consistent_lsn: "0/16960E8".parse::<Lsn>().unwrap(),
     388            2 :             metadata: TimelineMetadata::from_bytes(&[113,11,159,210,0,54,0,4,0,0,0,0,1,105,96,232,1,0,0,0,0,1,105,96,112,0,0,0,0,0,0,0,0,0,0,0,0,0,1,105,96,112,0,0,0,0,1,105,96,112,0,0,0,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]).unwrap(),
     389            2 :             deleted_at: None,
     390            2 :             archived_at: None,
     391            2 :             lineage: Lineage::default(),
     392            2 :             gc_blocking: None,
     393            2 :             last_aux_file_policy: None,
     394            2 :             import_pgdata: None,
     395            2 :         };
     396            2 : 
     397            2 :         let part = IndexPart::from_json_bytes(example.as_bytes()).unwrap();
     398            2 :         assert_eq!(part, expected);
     399            2 :     }
     400              : 
     401              :     #[test]
     402            2 :     fn v1_indexpart_is_parsed_with_optional_missing_layers() {
     403            2 :         let example = r#"{
     404            2 :             "version":1,
     405            2 :             "timeline_layers":["000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9"],
     406            2 :             "missing_layers":["This shouldn't fail deserialization"],
     407            2 :             "layer_metadata":{
     408            2 :                 "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9": { "file_size": 25600000 },
     409            2 :                 "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51": { "file_size": 9007199254741001 }
     410            2 :             },
     411            2 :             "disk_consistent_lsn":"0/16960E8",
     412            2 :             "metadata_bytes":[113,11,159,210,0,54,0,4,0,0,0,0,1,105,96,232,1,0,0,0,0,1,105,96,112,0,0,0,0,0,0,0,0,0,0,0,0,0,1,105,96,112,0,0,0,0,1,105,96,112,0,0,0,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
     413            2 :         }"#;
     414            2 : 
     415            2 :         let expected = IndexPart {
     416            2 :             // note this is not verified, could be anything, but exists for humans debugging.. could be the git version instead?
     417            2 :             version: 1,
     418            2 :             layer_metadata: HashMap::from([
     419            2 :                 ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9".parse().unwrap(), LayerFileMetadata {
     420            2 :                     file_size: 25600000,
     421            2 :                     generation: Generation::none(),
     422            2 :                     shard: ShardIndex::unsharded()
     423            2 :                 }),
     424            2 :                 ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51".parse().unwrap(), LayerFileMetadata {
     425            2 :                     // serde_json should always parse this but this might be a double with jq for
     426            2 :                     // example.
     427            2 :                     file_size: 9007199254741001,
     428            2 :                     generation: Generation::none(),
     429            2 :                     shard: ShardIndex::unsharded()
     430            2 :                 })
     431            2 :             ]),
     432            2 :             disk_consistent_lsn: "0/16960E8".parse::<Lsn>().unwrap(),
     433            2 :             metadata: TimelineMetadata::from_bytes(&[113,11,159,210,0,54,0,4,0,0,0,0,1,105,96,232,1,0,0,0,0,1,105,96,112,0,0,0,0,0,0,0,0,0,0,0,0,0,1,105,96,112,0,0,0,0,1,105,96,112,0,0,0,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]).unwrap(),
     434            2 :             deleted_at: None,
     435            2 :             archived_at: None,
     436            2 :             lineage: Lineage::default(),
     437            2 :             gc_blocking: None,
     438            2 :             last_aux_file_policy: None,
     439            2 :             import_pgdata: None,
     440            2 :         };
     441            2 : 
     442            2 :         let part = IndexPart::from_json_bytes(example.as_bytes()).unwrap();
     443            2 :         assert_eq!(part, expected);
     444            2 :     }
     445              : 
     446              :     #[test]
     447            2 :     fn v2_indexpart_is_parsed_with_deleted_at() {
     448            2 :         let example = r#"{
     449            2 :             "version":2,
     450            2 :             "timeline_layers":["000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9"],
     451            2 :             "missing_layers":["This shouldn't fail deserialization"],
     452            2 :             "layer_metadata":{
     453            2 :                 "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9": { "file_size": 25600000 },
     454            2 :                 "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51": { "file_size": 9007199254741001 }
     455            2 :             },
     456            2 :             "disk_consistent_lsn":"0/16960E8",
     457            2 :             "metadata_bytes":[113,11,159,210,0,54,0,4,0,0,0,0,1,105,96,232,1,0,0,0,0,1,105,96,112,0,0,0,0,0,0,0,0,0,0,0,0,0,1,105,96,112,0,0,0,0,1,105,96,112,0,0,0,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
     458            2 :             "deleted_at": "2023-07-31T09:00:00.123"
     459            2 :         }"#;
     460            2 : 
     461            2 :         let expected = IndexPart {
     462            2 :             // note this is not verified, could be anything, but exists for humans debugging.. could be the git version instead?
     463            2 :             version: 2,
     464            2 :             layer_metadata: HashMap::from([
     465            2 :                 ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9".parse().unwrap(), LayerFileMetadata {
     466            2 :                     file_size: 25600000,
     467            2 :                     generation: Generation::none(),
     468            2 :                     shard: ShardIndex::unsharded()
     469            2 :                 }),
     470            2 :                 ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51".parse().unwrap(), LayerFileMetadata {
     471            2 :                     // serde_json should always parse this but this might be a double with jq for
     472            2 :                     // example.
     473            2 :                     file_size: 9007199254741001,
     474            2 :                     generation: Generation::none(),
     475            2 :                     shard: ShardIndex::unsharded()
     476            2 :                 })
     477            2 :             ]),
     478            2 :             disk_consistent_lsn: "0/16960E8".parse::<Lsn>().unwrap(),
     479            2 :             metadata: TimelineMetadata::from_bytes(&[113,11,159,210,0,54,0,4,0,0,0,0,1,105,96,232,1,0,0,0,0,1,105,96,112,0,0,0,0,0,0,0,0,0,0,0,0,0,1,105,96,112,0,0,0,0,1,105,96,112,0,0,0,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]).unwrap(),
     480            2 :             deleted_at: Some(parse_naive_datetime("2023-07-31T09:00:00.123000000")),
     481            2 :             archived_at: None,
     482            2 :             lineage: Lineage::default(),
     483            2 :             gc_blocking: None,
     484            2 :             last_aux_file_policy: None,
     485            2 :             import_pgdata: None,
     486            2 :         };
     487            2 : 
     488            2 :         let part = IndexPart::from_json_bytes(example.as_bytes()).unwrap();
     489            2 :         assert_eq!(part, expected);
     490            2 :     }
     491              : 
     492              :     #[test]
     493            2 :     fn empty_layers_are_parsed() {
     494            2 :         let empty_layers_json = r#"{
     495            2 :             "version":1,
     496            2 :             "timeline_layers":[],
     497            2 :             "layer_metadata":{},
     498            2 :             "disk_consistent_lsn":"0/2532648",
     499            2 :             "metadata_bytes":[136,151,49,208,0,70,0,4,0,0,0,0,2,83,38,72,1,0,0,0,0,2,83,38,32,1,87,198,240,135,97,119,45,125,38,29,155,161,140,141,255,210,0,0,0,0,2,83,38,72,0,0,0,0,1,73,240,192,0,0,0,0,1,73,240,192,0,0,0,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
     500            2 :         }"#;
     501            2 : 
     502            2 :         let expected = IndexPart {
     503            2 :             version: 1,
     504            2 :             layer_metadata: HashMap::new(),
     505            2 :             disk_consistent_lsn: "0/2532648".parse::<Lsn>().unwrap(),
     506            2 :             metadata: TimelineMetadata::from_bytes(&[
     507            2 :                 136, 151, 49, 208, 0, 70, 0, 4, 0, 0, 0, 0, 2, 83, 38, 72, 1, 0, 0, 0, 0, 2, 83,
     508            2 :                 38, 32, 1, 87, 198, 240, 135, 97, 119, 45, 125, 38, 29, 155, 161, 140, 141, 255,
     509            2 :                 210, 0, 0, 0, 0, 2, 83, 38, 72, 0, 0, 0, 0, 1, 73, 240, 192, 0, 0, 0, 0, 1, 73,
     510            2 :                 240, 192, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     511            2 :                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     512            2 :                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     513            2 :                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     514            2 :                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     515            2 :                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     516            2 :                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     517            2 :                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     518            2 :                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     519            2 :                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     520            2 :                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     521            2 :                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     522            2 :                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     523            2 :                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     524            2 :                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     525            2 :                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     526            2 :                 0, 0,
     527            2 :             ])
     528            2 :             .unwrap(),
     529            2 :             deleted_at: None,
     530            2 :             archived_at: None,
     531            2 :             lineage: Lineage::default(),
     532            2 :             gc_blocking: None,
     533            2 :             last_aux_file_policy: None,
     534            2 :             import_pgdata: None,
     535            2 :         };
     536            2 : 
     537            2 :         let empty_layers_parsed = IndexPart::from_json_bytes(empty_layers_json.as_bytes()).unwrap();
     538            2 : 
     539            2 :         assert_eq!(empty_layers_parsed, expected);
     540            2 :     }
     541              : 
     542              :     #[test]
     543            2 :     fn v4_indexpart_is_parsed() {
     544            2 :         let example = r#"{
     545            2 :             "version":4,
     546            2 :             "layer_metadata":{
     547            2 :                 "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9": { "file_size": 25600000 },
     548            2 :                 "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51": { "file_size": 9007199254741001 }
     549            2 :             },
     550            2 :             "disk_consistent_lsn":"0/16960E8",
     551            2 :             "metadata_bytes":[113,11,159,210,0,54,0,4,0,0,0,0,1,105,96,232,1,0,0,0,0,1,105,96,112,0,0,0,0,0,0,0,0,0,0,0,0,0,1,105,96,112,0,0,0,0,1,105,96,112,0,0,0,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
     552            2 :             "deleted_at": "2023-07-31T09:00:00.123"
     553            2 :         }"#;
     554            2 : 
     555            2 :         let expected = IndexPart {
     556            2 :             version: 4,
     557            2 :             layer_metadata: HashMap::from([
     558            2 :                 ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9".parse().unwrap(), LayerFileMetadata {
     559            2 :                     file_size: 25600000,
     560            2 :                     generation: Generation::none(),
     561            2 :                     shard: ShardIndex::unsharded()
     562            2 :                 }),
     563            2 :                 ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51".parse().unwrap(), LayerFileMetadata {
     564            2 :                     // serde_json should always parse this but this might be a double with jq for
     565            2 :                     // example.
     566            2 :                     file_size: 9007199254741001,
     567            2 :                     generation: Generation::none(),
     568            2 :                     shard: ShardIndex::unsharded()
     569            2 :                 })
     570            2 :             ]),
     571            2 :             disk_consistent_lsn: "0/16960E8".parse::<Lsn>().unwrap(),
     572            2 :             metadata: TimelineMetadata::from_bytes(&[113,11,159,210,0,54,0,4,0,0,0,0,1,105,96,232,1,0,0,0,0,1,105,96,112,0,0,0,0,0,0,0,0,0,0,0,0,0,1,105,96,112,0,0,0,0,1,105,96,112,0,0,0,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]).unwrap(),
     573            2 :             deleted_at: Some(parse_naive_datetime("2023-07-31T09:00:00.123000000")),
     574            2 :             archived_at: None,
     575            2 :             lineage: Lineage::default(),
     576            2 :             gc_blocking: None,
     577            2 :             last_aux_file_policy: None,
     578            2 :             import_pgdata: None,
     579            2 :         };
     580            2 : 
     581            2 :         let part = IndexPart::from_json_bytes(example.as_bytes()).unwrap();
     582            2 :         assert_eq!(part, expected);
     583            2 :     }
     584              : 
     585              :     #[test]
     586            2 :     fn v5_indexpart_is_parsed() {
     587            2 :         let example = r#"{
     588            2 :             "version":5,
     589            2 :             "layer_metadata":{
     590            2 :                 "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000014EF420-00000000014EF499":{"file_size":23289856,"generation":1},
     591            2 :                 "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000014EF499-00000000015A7619":{"file_size":1015808,"generation":1}},
     592            2 :                 "disk_consistent_lsn":"0/15A7618",
     593            2 :                 "metadata_bytes":[226,88,25,241,0,46,0,4,0,0,0,0,1,90,118,24,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,78,244,32,0,0,0,0,1,78,244,32,0,0,0,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
     594            2 :                 "lineage":{
     595            2 :                     "original_ancestor":["e2bfd8c633d713d279e6fcd2bcc15b6d","0/15A7618","2024-05-07T18:52:36.322426563"],
     596            2 :                     "reparenting_history":["e1bfd8c633d713d279e6fcd2bcc15b6d"]
     597            2 :                 }
     598            2 :         }"#;
     599            2 : 
     600            2 :         let expected = IndexPart {
     601            2 :             version: 5,
     602            2 :             layer_metadata: HashMap::from([
     603            2 :                 ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000014EF420-00000000014EF499".parse().unwrap(), LayerFileMetadata {
     604            2 :                     file_size: 23289856,
     605            2 :                     generation: Generation::new(1),
     606            2 :                     shard: ShardIndex::unsharded(),
     607            2 :                 }),
     608            2 :                 ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000014EF499-00000000015A7619".parse().unwrap(), LayerFileMetadata {
     609            2 :                     file_size: 1015808,
     610            2 :                     generation: Generation::new(1),
     611            2 :                     shard: ShardIndex::unsharded(),
     612            2 :                 })
     613            2 :             ]),
     614            2 :             disk_consistent_lsn: Lsn::from_str("0/15A7618").unwrap(),
     615            2 :             metadata: TimelineMetadata::from_bytes(&[226,88,25,241,0,46,0,4,0,0,0,0,1,90,118,24,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,78,244,32,0,0,0,0,1,78,244,32,0,0,0,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]).unwrap(),
     616            2 :             deleted_at: None,
     617            2 :             archived_at: None,
     618            2 :             lineage: Lineage {
     619            2 :                 reparenting_history_truncated: false,
     620            2 :                 reparenting_history: vec![TimelineId::from_str("e1bfd8c633d713d279e6fcd2bcc15b6d").unwrap()],
     621            2 :                 original_ancestor: Some((TimelineId::from_str("e2bfd8c633d713d279e6fcd2bcc15b6d").unwrap(), Lsn::from_str("0/15A7618").unwrap(), parse_naive_datetime("2024-05-07T18:52:36.322426563"))),
     622            2 :             },
     623            2 :             gc_blocking: None,
     624            2 :             last_aux_file_policy: None,
     625            2 :             import_pgdata: None,
     626            2 :         };
     627            2 : 
     628            2 :         let part = IndexPart::from_json_bytes(example.as_bytes()).unwrap();
     629            2 :         assert_eq!(part, expected);
     630            2 :     }
     631              : 
     632              :     #[test]
     633            2 :     fn v6_indexpart_is_parsed() {
     634            2 :         let example = r#"{
     635            2 :             "version":6,
     636            2 :             "layer_metadata":{
     637            2 :                 "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9": { "file_size": 25600000 },
     638            2 :                 "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51": { "file_size": 9007199254741001 }
     639            2 :             },
     640            2 :             "disk_consistent_lsn":"0/16960E8",
     641            2 :             "metadata_bytes":[113,11,159,210,0,54,0,4,0,0,0,0,1,105,96,232,1,0,0,0,0,1,105,96,112,0,0,0,0,0,0,0,0,0,0,0,0,0,1,105,96,112,0,0,0,0,1,105,96,112,0,0,0,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
     642            2 :             "deleted_at": "2023-07-31T09:00:00.123",
     643            2 :             "lineage":{
     644            2 :                 "original_ancestor":["e2bfd8c633d713d279e6fcd2bcc15b6d","0/15A7618","2024-05-07T18:52:36.322426563"],
     645            2 :                 "reparenting_history":["e1bfd8c633d713d279e6fcd2bcc15b6d"]
     646            2 :             },
     647            2 :             "last_aux_file_policy": "V2"
     648            2 :         }"#;
     649            2 : 
     650            2 :         let expected = IndexPart {
     651            2 :             version: 6,
     652            2 :             layer_metadata: HashMap::from([
     653            2 :                 ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9".parse().unwrap(), LayerFileMetadata {
     654            2 :                     file_size: 25600000,
     655            2 :                     generation: Generation::none(),
     656            2 :                     shard: ShardIndex::unsharded()
     657            2 :                 }),
     658            2 :                 ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51".parse().unwrap(), LayerFileMetadata {
     659            2 :                     // serde_json should always parse this but this might be a double with jq for
     660            2 :                     // example.
     661            2 :                     file_size: 9007199254741001,
     662            2 :                     generation: Generation::none(),
     663            2 :                     shard: ShardIndex::unsharded()
     664            2 :                 })
     665            2 :             ]),
     666            2 :             disk_consistent_lsn: "0/16960E8".parse::<Lsn>().unwrap(),
     667            2 :             metadata: TimelineMetadata::from_bytes(&[113,11,159,210,0,54,0,4,0,0,0,0,1,105,96,232,1,0,0,0,0,1,105,96,112,0,0,0,0,0,0,0,0,0,0,0,0,0,1,105,96,112,0,0,0,0,1,105,96,112,0,0,0,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]).unwrap(),
     668            2 :             deleted_at: Some(parse_naive_datetime("2023-07-31T09:00:00.123000000")),
     669            2 :             archived_at: None,
     670            2 :             lineage: Lineage {
     671            2 :                 reparenting_history_truncated: false,
     672            2 :                 reparenting_history: vec![TimelineId::from_str("e1bfd8c633d713d279e6fcd2bcc15b6d").unwrap()],
     673            2 :                 original_ancestor: Some((TimelineId::from_str("e2bfd8c633d713d279e6fcd2bcc15b6d").unwrap(), Lsn::from_str("0/15A7618").unwrap(), parse_naive_datetime("2024-05-07T18:52:36.322426563"))),
     674            2 :             },
     675            2 :             gc_blocking: None,
     676            2 :             last_aux_file_policy: Some(AuxFilePolicy::V2),
     677            2 :             import_pgdata: None,
     678            2 :         };
     679            2 : 
     680            2 :         let part = IndexPart::from_json_bytes(example.as_bytes()).unwrap();
     681            2 :         assert_eq!(part, expected);
     682            2 :     }
     683              : 
     684              :     #[test]
     685            2 :     fn v7_indexpart_is_parsed() {
     686            2 :         let example = r#"{
     687            2 :             "version": 7,
     688            2 :             "layer_metadata":{
     689            2 :                 "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9": { "file_size": 25600000 },
     690            2 :                 "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51": { "file_size": 9007199254741001 }
     691            2 :             },
     692            2 :             "disk_consistent_lsn":"0/16960E8",
     693            2 :             "metadata": {
     694            2 :                 "disk_consistent_lsn": "0/16960E8",
     695            2 :                 "prev_record_lsn": "0/1696070",
     696            2 :                 "ancestor_timeline": "e45a7f37d3ee2ff17dc14bf4f4e3f52e",
     697            2 :                 "ancestor_lsn": "0/0",
     698            2 :                 "latest_gc_cutoff_lsn": "0/1696070",
     699            2 :                 "initdb_lsn": "0/1696070",
     700            2 :                 "pg_version": 14
     701            2 :             },
     702            2 :             "deleted_at": "2023-07-31T09:00:00.123"
     703            2 :         }"#;
     704            2 : 
     705            2 :         let expected = IndexPart {
     706            2 :             version: 7,
     707            2 :             layer_metadata: HashMap::from([
     708            2 :                 ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9".parse().unwrap(), LayerFileMetadata {
     709            2 :                     file_size: 25600000,
     710            2 :                     generation: Generation::none(),
     711            2 :                     shard: ShardIndex::unsharded()
     712            2 :                 }),
     713            2 :                 ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51".parse().unwrap(), LayerFileMetadata {
     714            2 :                     file_size: 9007199254741001,
     715            2 :                     generation: Generation::none(),
     716            2 :                     shard: ShardIndex::unsharded()
     717            2 :                 })
     718            2 :             ]),
     719            2 :             disk_consistent_lsn: "0/16960E8".parse::<Lsn>().unwrap(),
     720            2 :             metadata: TimelineMetadata::new(
     721            2 :                 Lsn::from_str("0/16960E8").unwrap(),
     722            2 :                 Some(Lsn::from_str("0/1696070").unwrap()),
     723            2 :                 Some(TimelineId::from_str("e45a7f37d3ee2ff17dc14bf4f4e3f52e").unwrap()),
     724            2 :                 Lsn::INVALID,
     725            2 :                 Lsn::from_str("0/1696070").unwrap(),
     726            2 :                 Lsn::from_str("0/1696070").unwrap(),
     727            2 :                 14,
     728            2 :             ).with_recalculated_checksum().unwrap(),
     729            2 :             deleted_at: Some(parse_naive_datetime("2023-07-31T09:00:00.123000000")),
     730            2 :             archived_at: None,
     731            2 :             lineage: Default::default(),
     732            2 :             gc_blocking: None,
     733            2 :             last_aux_file_policy: Default::default(),
     734            2 :             import_pgdata: None,
     735            2 :         };
     736            2 : 
     737            2 :         let part = IndexPart::from_json_bytes(example.as_bytes()).unwrap();
     738            2 :         assert_eq!(part, expected);
     739            2 :     }
     740              : 
     741              :     #[test]
     742            2 :     fn v8_indexpart_is_parsed() {
     743            2 :         let example = r#"{
     744            2 :             "version": 8,
     745            2 :             "layer_metadata":{
     746            2 :                 "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9": { "file_size": 25600000 },
     747            2 :                 "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51": { "file_size": 9007199254741001 }
     748            2 :             },
     749            2 :             "disk_consistent_lsn":"0/16960E8",
     750            2 :             "metadata": {
     751            2 :                 "disk_consistent_lsn": "0/16960E8",
     752            2 :                 "prev_record_lsn": "0/1696070",
     753            2 :                 "ancestor_timeline": "e45a7f37d3ee2ff17dc14bf4f4e3f52e",
     754            2 :                 "ancestor_lsn": "0/0",
     755            2 :                 "latest_gc_cutoff_lsn": "0/1696070",
     756            2 :                 "initdb_lsn": "0/1696070",
     757            2 :                 "pg_version": 14
     758            2 :             },
     759            2 :             "deleted_at": "2023-07-31T09:00:00.123",
     760            2 :             "archived_at": "2023-04-29T09:00:00.123"
     761            2 :         }"#;
     762            2 : 
     763            2 :         let expected = IndexPart {
     764            2 :             version: 8,
     765            2 :             layer_metadata: HashMap::from([
     766            2 :                 ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9".parse().unwrap(), LayerFileMetadata {
     767            2 :                     file_size: 25600000,
     768            2 :                     generation: Generation::none(),
     769            2 :                     shard: ShardIndex::unsharded()
     770            2 :                 }),
     771            2 :                 ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51".parse().unwrap(), LayerFileMetadata {
     772            2 :                     file_size: 9007199254741001,
     773            2 :                     generation: Generation::none(),
     774            2 :                     shard: ShardIndex::unsharded()
     775            2 :                 })
     776            2 :             ]),
     777            2 :             disk_consistent_lsn: "0/16960E8".parse::<Lsn>().unwrap(),
     778            2 :             metadata: TimelineMetadata::new(
     779            2 :                 Lsn::from_str("0/16960E8").unwrap(),
     780            2 :                 Some(Lsn::from_str("0/1696070").unwrap()),
     781            2 :                 Some(TimelineId::from_str("e45a7f37d3ee2ff17dc14bf4f4e3f52e").unwrap()),
     782            2 :                 Lsn::INVALID,
     783            2 :                 Lsn::from_str("0/1696070").unwrap(),
     784            2 :                 Lsn::from_str("0/1696070").unwrap(),
     785            2 :                 14,
     786            2 :             ).with_recalculated_checksum().unwrap(),
     787            2 :             deleted_at: Some(parse_naive_datetime("2023-07-31T09:00:00.123000000")),
     788            2 :             archived_at: Some(parse_naive_datetime("2023-04-29T09:00:00.123000000")),
     789            2 :             lineage: Default::default(),
     790            2 :             gc_blocking: None,
     791            2 :             last_aux_file_policy: Default::default(),
     792            2 :             import_pgdata: None,
     793            2 :         };
     794            2 : 
     795            2 :         let part = IndexPart::from_json_bytes(example.as_bytes()).unwrap();
     796            2 :         assert_eq!(part, expected);
     797            2 :     }
     798              : 
     799              :     #[test]
     800            2 :     fn v9_indexpart_is_parsed() {
     801            2 :         let example = r#"{
     802            2 :             "version": 9,
     803            2 :             "layer_metadata":{
     804            2 :                 "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9": { "file_size": 25600000 },
     805            2 :                 "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51": { "file_size": 9007199254741001 }
     806            2 :             },
     807            2 :             "disk_consistent_lsn":"0/16960E8",
     808            2 :             "metadata": {
     809            2 :                 "disk_consistent_lsn": "0/16960E8",
     810            2 :                 "prev_record_lsn": "0/1696070",
     811            2 :                 "ancestor_timeline": "e45a7f37d3ee2ff17dc14bf4f4e3f52e",
     812            2 :                 "ancestor_lsn": "0/0",
     813            2 :                 "latest_gc_cutoff_lsn": "0/1696070",
     814            2 :                 "initdb_lsn": "0/1696070",
     815            2 :                 "pg_version": 14
     816            2 :             },
     817            2 :             "gc_blocking": {
     818            2 :                 "started_at": "2024-07-19T09:00:00.123",
     819            2 :                 "reasons": ["DetachAncestor"]
     820            2 :             }
     821            2 :         }"#;
     822            2 : 
     823            2 :         let expected = IndexPart {
     824            2 :             version: 9,
     825            2 :             layer_metadata: HashMap::from([
     826            2 :                 ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9".parse().unwrap(), LayerFileMetadata {
     827            2 :                     file_size: 25600000,
     828            2 :                     generation: Generation::none(),
     829            2 :                     shard: ShardIndex::unsharded()
     830            2 :                 }),
     831            2 :                 ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51".parse().unwrap(), LayerFileMetadata {
     832            2 :                     file_size: 9007199254741001,
     833            2 :                     generation: Generation::none(),
     834            2 :                     shard: ShardIndex::unsharded()
     835            2 :                 })
     836            2 :             ]),
     837            2 :             disk_consistent_lsn: "0/16960E8".parse::<Lsn>().unwrap(),
     838            2 :             metadata: TimelineMetadata::new(
     839            2 :                 Lsn::from_str("0/16960E8").unwrap(),
     840            2 :                 Some(Lsn::from_str("0/1696070").unwrap()),
     841            2 :                 Some(TimelineId::from_str("e45a7f37d3ee2ff17dc14bf4f4e3f52e").unwrap()),
     842            2 :                 Lsn::INVALID,
     843            2 :                 Lsn::from_str("0/1696070").unwrap(),
     844            2 :                 Lsn::from_str("0/1696070").unwrap(),
     845            2 :                 14,
     846            2 :             ).with_recalculated_checksum().unwrap(),
     847            2 :             deleted_at: None,
     848            2 :             lineage: Default::default(),
     849            2 :             gc_blocking: Some(GcBlocking {
     850            2 :                 started_at: parse_naive_datetime("2024-07-19T09:00:00.123000000"),
     851            2 :                 reasons: enumset::EnumSet::from_iter([GcBlockingReason::DetachAncestor]),
     852            2 :             }),
     853            2 :             last_aux_file_policy: Default::default(),
     854            2 :             archived_at: None,
     855            2 :             import_pgdata: None,
     856            2 :         };
     857            2 : 
     858            2 :         let part = IndexPart::from_json_bytes(example.as_bytes()).unwrap();
     859            2 :         assert_eq!(part, expected);
     860            2 :     }
     861              : 
     862              :     #[test]
     863            2 :     fn v10_importpgdata_is_parsed() {
     864            2 :         let example = r#"{
     865            2 :             "version": 10,
     866            2 :             "layer_metadata":{
     867            2 :                 "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9": { "file_size": 25600000 },
     868            2 :                 "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51": { "file_size": 9007199254741001 }
     869            2 :             },
     870            2 :             "disk_consistent_lsn":"0/16960E8",
     871            2 :             "metadata": {
     872            2 :                 "disk_consistent_lsn": "0/16960E8",
     873            2 :                 "prev_record_lsn": "0/1696070",
     874            2 :                 "ancestor_timeline": "e45a7f37d3ee2ff17dc14bf4f4e3f52e",
     875            2 :                 "ancestor_lsn": "0/0",
     876            2 :                 "latest_gc_cutoff_lsn": "0/1696070",
     877            2 :                 "initdb_lsn": "0/1696070",
     878            2 :                 "pg_version": 14
     879            2 :             },
     880            2 :             "gc_blocking": {
     881            2 :                 "started_at": "2024-07-19T09:00:00.123",
     882            2 :                 "reasons": ["DetachAncestor"]
     883            2 :             },
     884            2 :             "import_pgdata": {
     885            2 :                 "V1": {
     886            2 :                     "Done": {
     887            2 :                         "idempotency_key": "specified-by-client-218a5213-5044-4562-a28d-d024c5f057f5",
     888            2 :                         "started_at": "2024-11-13T09:23:42.123",
     889            2 :                         "finished_at": "2024-11-13T09:42:23.123"
     890            2 :                     }
     891            2 :                 }
     892            2 :             }
     893            2 :         }"#;
     894            2 : 
     895            2 :         let expected = IndexPart {
     896            2 :             version: 10,
     897            2 :             layer_metadata: HashMap::from([
     898            2 :                 ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9".parse().unwrap(), LayerFileMetadata {
     899            2 :                     file_size: 25600000,
     900            2 :                     generation: Generation::none(),
     901            2 :                     shard: ShardIndex::unsharded()
     902            2 :                 }),
     903            2 :                 ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51".parse().unwrap(), LayerFileMetadata {
     904            2 :                     file_size: 9007199254741001,
     905            2 :                     generation: Generation::none(),
     906            2 :                     shard: ShardIndex::unsharded()
     907            2 :                 })
     908            2 :             ]),
     909            2 :             disk_consistent_lsn: "0/16960E8".parse::<Lsn>().unwrap(),
     910            2 :             metadata: TimelineMetadata::new(
     911            2 :                 Lsn::from_str("0/16960E8").unwrap(),
     912            2 :                 Some(Lsn::from_str("0/1696070").unwrap()),
     913            2 :                 Some(TimelineId::from_str("e45a7f37d3ee2ff17dc14bf4f4e3f52e").unwrap()),
     914            2 :                 Lsn::INVALID,
     915            2 :                 Lsn::from_str("0/1696070").unwrap(),
     916            2 :                 Lsn::from_str("0/1696070").unwrap(),
     917            2 :                 14,
     918            2 :             ).with_recalculated_checksum().unwrap(),
     919            2 :             deleted_at: None,
     920            2 :             lineage: Default::default(),
     921            2 :             gc_blocking: Some(GcBlocking {
     922            2 :                 started_at: parse_naive_datetime("2024-07-19T09:00:00.123000000"),
     923            2 :                 reasons: enumset::EnumSet::from_iter([GcBlockingReason::DetachAncestor]),
     924            2 :             }),
     925            2 :             last_aux_file_policy: Default::default(),
     926            2 :             archived_at: None,
     927            2 :             import_pgdata: Some(import_pgdata::index_part_format::Root::V1(import_pgdata::index_part_format::V1::Done(import_pgdata::index_part_format::Done{
     928            2 :                 started_at: parse_naive_datetime("2024-11-13T09:23:42.123000000"),
     929            2 :                 finished_at: parse_naive_datetime("2024-11-13T09:42:23.123000000"),
     930            2 :                 idempotency_key: import_pgdata::index_part_format::IdempotencyKey::new("specified-by-client-218a5213-5044-4562-a28d-d024c5f057f5".to_string()),
     931            2 :             })))
     932            2 :         };
     933            2 : 
     934            2 :         let part = IndexPart::from_json_bytes(example.as_bytes()).unwrap();
     935            2 :         assert_eq!(part, expected);
     936            2 :     }
     937              : 
     938           24 :     fn parse_naive_datetime(s: &str) -> NaiveDateTime {
     939           24 :         chrono::NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S.%f").unwrap()
     940           24 :     }
     941              : }
        

Generated by: LCOV version 2.1-beta