LCOV - code coverage report
Current view: top level - pageserver/src/tenant/remote_timeline_client - index.rs (source / functions) Coverage Total Hit
Test: 8ac049b474321fdc72ddcb56d7165153a1a900e8.info Lines: 97.6 % 251 245
Test Date: 2023-09-06 10:18:01 Functions: 60.6 % 66 40

            Line data    Source code
       1              : //! In-memory index to track the tenant files on the remote storage.
       2              : //! Able to restore itself from the storage index parts, that are located in every timeline's remote directory and contain all data about
       3              : //! remote timeline layers and its metadata.
       4              : 
       5              : use std::collections::HashMap;
       6              : 
       7              : use chrono::NaiveDateTime;
       8              : use serde::{Deserialize, Serialize};
       9              : use serde_with::{serde_as, DisplayFromStr};
      10              : use utils::bin_ser::SerializeError;
      11              : 
      12              : use crate::tenant::metadata::TimelineMetadata;
      13              : use crate::tenant::storage_layer::LayerFileName;
      14              : use crate::tenant::upload_queue::UploadQueueInitialized;
      15              : use crate::tenant::Generation;
      16              : 
      17              : use utils::lsn::Lsn;
      18              : 
      19              : /// Metadata gathered for each of the layer files.
      20              : ///
      21              : /// Fields have to be `Option`s because remote [`IndexPart`]'s can be from different version, which
      22              : /// might have less or more metadata depending if upgrading or rolling back an upgrade.
      23       370721 : #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
      24              : //#[cfg_attr(test, derive(Default))]
      25              : pub struct LayerFileMetadata {
      26              :     file_size: u64,
      27              : 
      28              :     pub(crate) generation: Generation,
      29              : }
      30              : 
      31              : impl From<&'_ IndexLayerMetadata> for LayerFileMetadata {
      32        11772 :     fn from(other: &IndexLayerMetadata) -> Self {
      33        11772 :         LayerFileMetadata {
      34        11772 :             file_size: other.file_size,
      35        11772 :             generation: other.generation,
      36        11772 :         }
      37        11772 :     }
      38              : }
      39              : 
      40              : impl LayerFileMetadata {
      41        29571 :     pub fn new(file_size: u64, generation: Generation) -> Self {
      42        29571 :         LayerFileMetadata {
      43        29571 :             file_size,
      44        29571 :             generation,
      45        29571 :         }
      46        29571 :     }
      47              : 
      48        52748 :     pub fn file_size(&self) -> u64 {
      49        52748 :         self.file_size
      50        52748 :     }
      51              : }
      52              : 
      53              : // TODO seems like another part of the remote storage file format
      54              : // compatibility issue, see https://github.com/neondatabase/neon/issues/3072
      55              : /// In-memory representation of an `index_part.json` file
      56              : ///
      57              : /// Contains the data about all files in the timeline, present remotely and its metadata.
      58              : ///
      59              : /// This type needs to be backwards and forwards compatible. When changing the fields,
      60              : /// remember to add a test case for the changed version.
      61              : #[serde_as]
      62         5239 : #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
      63              : pub struct IndexPart {
      64              :     /// Debugging aid describing the version of this type.
      65              :     #[serde(default)]
      66              :     version: usize,
      67              : 
      68              :     #[serde(default)]
      69              :     #[serde(skip_serializing_if = "Option::is_none")]
      70              :     pub deleted_at: Option<NaiveDateTime>,
      71              : 
      72              :     /// Per layer file name metadata, which can be present for a present or missing layer file.
      73              :     ///
      74              :     /// Older versions of `IndexPart` will not have this property or have only a part of metadata
      75              :     /// that latest version stores.
      76              :     pub layer_metadata: HashMap<LayerFileName, IndexLayerMetadata>,
      77              : 
      78              :     // 'disk_consistent_lsn' is a copy of the 'disk_consistent_lsn' in the metadata.
      79              :     // It's duplicated for convenience when reading the serialized structure, but is
      80              :     // private because internally we would read from metadata instead.
      81              :     #[serde_as(as = "DisplayFromStr")]
      82              :     disk_consistent_lsn: Lsn,
      83              : 
      84              :     #[serde(rename = "metadata_bytes")]
      85              :     pub metadata: TimelineMetadata,
      86              : }
      87              : 
      88              : impl IndexPart {
      89              :     /// When adding or modifying any parts of `IndexPart`, increment the version so that it can be
      90              :     /// used to understand later versions.
      91              :     ///
      92              :     /// Version is currently informative only.
      93              :     /// Version history
      94              :     /// - 2: added `deleted_at`
      95              :     /// - 3: no longer deserialize `timeline_layers` (serialized format is the same, but timeline_layers
      96              :     ///      is always generated from the keys of `layer_metadata`)
      97              :     /// - 4: timeline_layers is fully removed.
      98              :     const LATEST_VERSION: usize = 4;
      99              : 
     100              :     // Versions we may see when reading from a bucket.
     101              :     pub const KNOWN_VERSIONS: &[usize] = &[1, 2, 3, 4];
     102              : 
     103              :     pub const FILE_NAME: &'static str = "index_part.json";
     104              : 
     105         4520 :     pub fn new(
     106         4520 :         layers_and_metadata: HashMap<LayerFileName, LayerFileMetadata>,
     107         4520 :         disk_consistent_lsn: Lsn,
     108         4520 :         metadata: TimelineMetadata,
     109         4520 :     ) -> Self {
     110         4520 :         // Transform LayerFileMetadata into IndexLayerMetadata
     111         4520 :         let layer_metadata = layers_and_metadata
     112         4520 :             .into_iter()
     113       337969 :             .map(|(k, v)| (k, IndexLayerMetadata::from(v)))
     114         4520 :             .collect();
     115         4520 : 
     116         4520 :         Self {
     117         4520 :             version: Self::LATEST_VERSION,
     118         4520 :             layer_metadata,
     119         4520 :             disk_consistent_lsn,
     120         4520 :             metadata,
     121         4520 :             deleted_at: None,
     122         4520 :         }
     123         4520 :     }
     124              : 
     125            0 :     pub fn get_version(&self) -> usize {
     126            0 :         self.version
     127            0 :     }
     128              : 
     129              :     /// If you want this under normal operations, read it from self.metadata:
     130              :     /// this method is just for the scrubber to use when validating an index.
     131            0 :     pub fn get_disk_consistent_lsn(&self) -> Lsn {
     132            0 :         self.disk_consistent_lsn
     133            0 :     }
     134              : }
     135              : 
     136              : impl TryFrom<&UploadQueueInitialized> for IndexPart {
     137              :     type Error = SerializeError;
     138              : 
     139          184 :     fn try_from(upload_queue: &UploadQueueInitialized) -> Result<Self, Self::Error> {
     140          184 :         let disk_consistent_lsn = upload_queue.latest_metadata.disk_consistent_lsn();
     141          184 :         let metadata = upload_queue.latest_metadata.clone();
     142          184 : 
     143          184 :         Ok(Self::new(
     144          184 :             upload_queue.latest_files.clone(),
     145          184 :             disk_consistent_lsn,
     146          184 :             metadata,
     147          184 :         ))
     148          184 :     }
     149              : }
     150              : 
     151              : /// Serialized form of [`LayerFileMetadata`].
     152       345190 : #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
     153              : pub struct IndexLayerMetadata {
     154              :     pub file_size: u64,
     155              : 
     156              :     #[serde(default = "Generation::none")]
     157              :     #[serde(skip_serializing_if = "Generation::is_none")]
     158              :     pub(super) generation: Generation,
     159              : }
     160              : 
     161              : impl From<LayerFileMetadata> for IndexLayerMetadata {
     162       337969 :     fn from(other: LayerFileMetadata) -> Self {
     163       337969 :         IndexLayerMetadata {
     164       337969 :             file_size: other.file_size,
     165       337969 :             generation: other.generation,
     166       337969 :         }
     167       337969 :     }
     168              : }
     169              : 
     170              : #[cfg(test)]
     171              : mod tests {
     172              :     use super::*;
     173              : 
     174            1 :     #[test]
     175            1 :     fn v1_indexpart_is_parsed() {
     176            1 :         let example = r#"{
     177            1 :             "version":1,
     178            1 :             "timeline_layers":["000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9"],
     179            1 :             "layer_metadata":{
     180            1 :                 "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9": { "file_size": 25600000 },
     181            1 :                 "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51": { "file_size": 9007199254741001 }
     182            1 :             },
     183            1 :             "disk_consistent_lsn":"0/16960E8",
     184            1 :             "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]
     185            1 :         }"#;
     186            1 : 
     187            1 :         let expected = IndexPart {
     188            1 :             // note this is not verified, could be anything, but exists for humans debugging.. could be the git version instead?
     189            1 :             version: 1,
     190            1 :             layer_metadata: HashMap::from([
     191            1 :                 ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9".parse().unwrap(), IndexLayerMetadata {
     192            1 :                     file_size: 25600000,
     193            1 :                     generation: Generation::none()
     194            1 :                 }),
     195            1 :                 ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51".parse().unwrap(), IndexLayerMetadata {
     196            1 :                     // serde_json should always parse this but this might be a double with jq for
     197            1 :                     // example.
     198            1 :                     file_size: 9007199254741001,
     199            1 :                     generation: Generation::none()
     200            1 :                 })
     201            1 :             ]),
     202            1 :             disk_consistent_lsn: "0/16960E8".parse::<Lsn>().unwrap(),
     203            1 :             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(),
     204            1 :             deleted_at: None,
     205            1 :         };
     206            1 : 
     207            1 :         let part = serde_json::from_str::<IndexPart>(example).unwrap();
     208            1 :         assert_eq!(part, expected);
     209            1 :     }
     210              : 
     211            1 :     #[test]
     212            1 :     fn v1_indexpart_is_parsed_with_optional_missing_layers() {
     213            1 :         let example = r#"{
     214            1 :             "version":1,
     215            1 :             "timeline_layers":["000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9"],
     216            1 :             "missing_layers":["This shouldn't fail deserialization"],
     217            1 :             "layer_metadata":{
     218            1 :                 "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9": { "file_size": 25600000 },
     219            1 :                 "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51": { "file_size": 9007199254741001 }
     220            1 :             },
     221            1 :             "disk_consistent_lsn":"0/16960E8",
     222            1 :             "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]
     223            1 :         }"#;
     224            1 : 
     225            1 :         let expected = IndexPart {
     226            1 :             // note this is not verified, could be anything, but exists for humans debugging.. could be the git version instead?
     227            1 :             version: 1,
     228            1 :             layer_metadata: HashMap::from([
     229            1 :                 ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9".parse().unwrap(), IndexLayerMetadata {
     230            1 :                     file_size: 25600000,
     231            1 :                     generation: Generation::none()
     232            1 :                 }),
     233            1 :                 ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51".parse().unwrap(), IndexLayerMetadata {
     234            1 :                     // serde_json should always parse this but this might be a double with jq for
     235            1 :                     // example.
     236            1 :                     file_size: 9007199254741001,
     237            1 :                     generation: Generation::none()
     238            1 :                 })
     239            1 :             ]),
     240            1 :             disk_consistent_lsn: "0/16960E8".parse::<Lsn>().unwrap(),
     241            1 :             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(),
     242            1 :             deleted_at: None,
     243            1 :         };
     244            1 : 
     245            1 :         let part = serde_json::from_str::<IndexPart>(example).unwrap();
     246            1 :         assert_eq!(part, expected);
     247            1 :     }
     248              : 
     249            1 :     #[test]
     250            1 :     fn v2_indexpart_is_parsed_with_deleted_at() {
     251            1 :         let example = r#"{
     252            1 :             "version":2,
     253            1 :             "timeline_layers":["000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9"],
     254            1 :             "missing_layers":["This shouldn't fail deserialization"],
     255            1 :             "layer_metadata":{
     256            1 :                 "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9": { "file_size": 25600000 },
     257            1 :                 "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51": { "file_size": 9007199254741001 }
     258            1 :             },
     259            1 :             "disk_consistent_lsn":"0/16960E8",
     260            1 :             "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],
     261            1 :             "deleted_at": "2023-07-31T09:00:00.123"
     262            1 :         }"#;
     263            1 : 
     264            1 :         let expected = IndexPart {
     265            1 :             // note this is not verified, could be anything, but exists for humans debugging.. could be the git version instead?
     266            1 :             version: 2,
     267            1 :             layer_metadata: HashMap::from([
     268            1 :                 ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9".parse().unwrap(), IndexLayerMetadata {
     269            1 :                     file_size: 25600000,
     270            1 :                     generation: Generation::none()
     271            1 :                 }),
     272            1 :                 ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51".parse().unwrap(), IndexLayerMetadata {
     273            1 :                     // serde_json should always parse this but this might be a double with jq for
     274            1 :                     // example.
     275            1 :                     file_size: 9007199254741001,
     276            1 :                     generation: Generation::none()
     277            1 :                 })
     278            1 :             ]),
     279            1 :             disk_consistent_lsn: "0/16960E8".parse::<Lsn>().unwrap(),
     280            1 :             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(),
     281            1 :             deleted_at: Some(chrono::NaiveDateTime::parse_from_str(
     282            1 :                 "2023-07-31T09:00:00.123000000", "%Y-%m-%dT%H:%M:%S.%f").unwrap())
     283            1 :         };
     284            1 : 
     285            1 :         let part = serde_json::from_str::<IndexPart>(example).unwrap();
     286            1 :         assert_eq!(part, expected);
     287            1 :     }
     288              : 
     289            1 :     #[test]
     290            1 :     fn empty_layers_are_parsed() {
     291            1 :         let empty_layers_json = r#"{
     292            1 :             "version":1,
     293            1 :             "timeline_layers":[],
     294            1 :             "layer_metadata":{},
     295            1 :             "disk_consistent_lsn":"0/2532648",
     296            1 :             "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]
     297            1 :         }"#;
     298            1 : 
     299            1 :         let expected = IndexPart {
     300            1 :             version: 1,
     301            1 :             layer_metadata: HashMap::new(),
     302            1 :             disk_consistent_lsn: "0/2532648".parse::<Lsn>().unwrap(),
     303            1 :             metadata: TimelineMetadata::from_bytes(&[
     304            1 :                 136, 151, 49, 208, 0, 70, 0, 4, 0, 0, 0, 0, 2, 83, 38, 72, 1, 0, 0, 0, 0, 2, 83,
     305            1 :                 38, 32, 1, 87, 198, 240, 135, 97, 119, 45, 125, 38, 29, 155, 161, 140, 141, 255,
     306            1 :                 210, 0, 0, 0, 0, 2, 83, 38, 72, 0, 0, 0, 0, 1, 73, 240, 192, 0, 0, 0, 0, 1, 73,
     307            1 :                 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,
     308            1 :                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     309            1 :                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     310            1 :                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     311            1 :                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     312            1 :                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     313            1 :                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     314            1 :                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     315            1 :                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     316            1 :                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     317            1 :                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     318            1 :                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     319            1 :                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     320            1 :                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     321            1 :                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     322            1 :                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     323            1 :                 0, 0,
     324            1 :             ])
     325            1 :             .unwrap(),
     326            1 :             deleted_at: None,
     327            1 :         };
     328            1 : 
     329            1 :         let empty_layers_parsed = serde_json::from_str::<IndexPart>(empty_layers_json).unwrap();
     330            1 : 
     331            1 :         assert_eq!(empty_layers_parsed, expected);
     332            1 :     }
     333              : 
     334            1 :     #[test]
     335            1 :     fn v4_indexpart_is_parsed() {
     336            1 :         let example = r#"{
     337            1 :             "version":4,
     338            1 :             "layer_metadata":{
     339            1 :                 "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9": { "file_size": 25600000 },
     340            1 :                 "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51": { "file_size": 9007199254741001 }
     341            1 :             },
     342            1 :             "disk_consistent_lsn":"0/16960E8",
     343            1 :             "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],
     344            1 :             "deleted_at": "2023-07-31T09:00:00.123"
     345            1 :         }"#;
     346            1 : 
     347            1 :         let expected = IndexPart {
     348            1 :             version: 4,
     349            1 :             layer_metadata: HashMap::from([
     350            1 :                 ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9".parse().unwrap(), IndexLayerMetadata {
     351            1 :                     file_size: 25600000,
     352            1 :                     generation: Generation::none()
     353            1 :                 }),
     354            1 :                 ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51".parse().unwrap(), IndexLayerMetadata {
     355            1 :                     // serde_json should always parse this but this might be a double with jq for
     356            1 :                     // example.
     357            1 :                     file_size: 9007199254741001,
     358            1 :                     generation: Generation::none()
     359            1 :                 })
     360            1 :             ]),
     361            1 :             disk_consistent_lsn: "0/16960E8".parse::<Lsn>().unwrap(),
     362            1 :             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(),
     363            1 :             deleted_at: Some(chrono::NaiveDateTime::parse_from_str(
     364            1 :                 "2023-07-31T09:00:00.123000000", "%Y-%m-%dT%H:%M:%S.%f").unwrap())
     365            1 :         };
     366            1 : 
     367            1 :         let part = serde_json::from_str::<IndexPart>(example).unwrap();
     368            1 :         assert_eq!(part, expected);
     369            1 :     }
     370              : }
        

Generated by: LCOV version 2.1-beta