Skip to main content

zlayer_storage/
types.rs

1//! Core types for layer storage
2
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5use std::path::PathBuf;
6
7/// Identifies a container's persistent layer state
8#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
9pub struct ContainerLayerId {
10    /// Service/deployment name
11    pub service: String,
12    /// Unique container instance ID
13    pub instance_id: String,
14}
15
16impl ContainerLayerId {
17    pub fn new(service: impl Into<String>, instance_id: impl Into<String>) -> Self {
18        Self {
19            service: service.into(),
20            instance_id: instance_id.into(),
21        }
22    }
23
24    pub fn to_key(&self) -> String {
25        format!("{}_{}", self.service, self.instance_id)
26    }
27}
28
29impl std::fmt::Display for ContainerLayerId {
30    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31        write!(f, "{}/{}", self.service, self.instance_id)
32    }
33}
34
35/// Metadata for a persisted layer snapshot
36#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct LayerSnapshot {
38    /// SHA256 digest of the tarball (content-addressed key)
39    pub digest: String,
40    /// Size in bytes (uncompressed)
41    pub size_bytes: u64,
42    /// Size in bytes (compressed with zstd)
43    pub compressed_size_bytes: u64,
44    /// Timestamp when snapshot was created
45    pub created_at: chrono::DateTime<chrono::Utc>,
46    /// Number of files in the layer
47    pub file_count: u64,
48}
49
50/// Tracks sync state between local and S3
51#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct SyncState {
53    /// Container this state belongs to
54    pub container_id: ContainerLayerId,
55    /// Current local layer digest (None if no changes)
56    pub local_digest: Option<String>,
57    /// Last successfully uploaded digest
58    pub remote_digest: Option<String>,
59    /// Timestamp of last successful sync
60    pub last_sync: Option<chrono::DateTime<chrono::Utc>>,
61    /// In-progress upload state (for crash recovery)
62    pub pending_upload: Option<PendingUpload>,
63}
64
65impl SyncState {
66    pub fn new(container_id: ContainerLayerId) -> Self {
67        Self {
68            container_id,
69            local_digest: None,
70            remote_digest: None,
71            last_sync: None,
72            pending_upload: None,
73        }
74    }
75
76    pub fn needs_upload(&self) -> bool {
77        match (&self.local_digest, &self.remote_digest) {
78            (Some(local), Some(remote)) => local != remote,
79            (Some(_), None) => true,
80            _ => false,
81        }
82    }
83}
84
85/// State for resumable multipart uploads
86#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct PendingUpload {
88    /// S3 multipart upload ID
89    pub upload_id: String,
90    /// S3 object key
91    pub object_key: String,
92    /// Total parts expected
93    pub total_parts: u32,
94    /// Parts successfully uploaded (part_number -> ETag)
95    pub completed_parts: HashMap<u32, CompletedPart>,
96    /// Part size in bytes
97    pub part_size: u64,
98    /// Local file path of tarball being uploaded
99    pub local_tarball_path: PathBuf,
100    /// Started at timestamp
101    pub started_at: chrono::DateTime<chrono::Utc>,
102    /// Digest of the layer being uploaded
103    pub digest: String,
104}
105
106impl PendingUpload {
107    pub fn missing_parts(&self) -> Vec<u32> {
108        (1..=self.total_parts)
109            .filter(|p| !self.completed_parts.contains_key(p))
110            .collect()
111    }
112
113    pub fn is_complete(&self) -> bool {
114        self.completed_parts.len() == self.total_parts as usize
115    }
116}
117
118#[derive(Debug, Clone, Serialize, Deserialize)]
119pub struct CompletedPart {
120    pub part_number: u32,
121    pub e_tag: String,
122}