model: store x-amz-checksum-type (full_object | composite)

This commit is contained in:
Alex Auvolat
2025-05-09 15:10:26 +02:00
parent 38ca35eb0f
commit abe0546ab0
5 changed files with 249 additions and 5 deletions
+218 -2
View File
@@ -259,7 +259,9 @@ mod v010 {
compressed: bool,
/// Whether the encryption uses an Object Encryption Key derived
/// from the master SSE-C key, instead of the master SSE-C key itself.
/// This is the case of objects created in Garage v2+
/// This is the case of objects created in Garage v2+.
/// This field is kept for compatibility with Garage v2.0.0-beta1,
/// which did not yet implement the v2 module below.
#[serde(default)]
use_oek: bool,
},
@@ -378,7 +380,221 @@ mod v010 {
}
}
pub use v010::*;
mod v2 {
use garage_util::data::{Hash, Uuid};
use garage_util::migrate::Migrate;
use serde::{Deserialize, Serialize};
use super::v010;
pub use v010::{ChecksumAlgorithm, ChecksumValue};
/// An object
#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)]
pub struct Object {
/// The bucket in which the object is stored, used as partition key
pub bucket_id: Uuid,
/// The key at which the object is stored in its bucket, used as sorting key
pub key: String,
/// The list of currently stored versions of the object
pub(super) versions: Vec<ObjectVersion>,
}
/// Information about a version of an object
#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)]
pub struct ObjectVersion {
/// Id of the version
pub uuid: Uuid,
/// Timestamp of when the object was created
pub timestamp: u64,
/// State of the version
pub state: ObjectVersionState,
}
/// State of an object version
#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)]
pub enum ObjectVersionState {
/// The version is being received
Uploading {
/// Indicates whether this is a multipart upload
multipart: bool,
/// Checksum algorithm to use
checksum_algorithm: Option<ChecksumAlgorithm>,
/// Checksum algorithm type (full object or composite)
checksum_type: Option<ChecksumType>,
/// Encryption params + headers to be included in the final object
encryption: ObjectVersionEncryption,
},
/// The version is fully received
Complete(ObjectVersionData),
/// The version uploaded containded errors or the upload was explicitly aborted
Aborted,
}
/// Data stored in object version
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Debug, Serialize, Deserialize)]
pub enum ObjectVersionData {
/// The object was deleted, this Version is a tombstone to mark it as such
DeleteMarker,
/// The object is short, it's stored inlined.
/// It is never compressed. For encrypted objects, it is encrypted using
/// AES256-GCM, like the encrypted headers.
Inline(ObjectVersionMeta, #[serde(with = "serde_bytes")] Vec<u8>),
/// The object is not short, Hash of first block is stored here, next segments hashes are
/// stored in the version table
FirstBlock(ObjectVersionMeta, Hash),
}
/// Metadata about the object version
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Debug, Serialize, Deserialize)]
pub struct ObjectVersionMeta {
/// Size of the object. If object is encrypted/compressed,
/// this is always the size of the unencrypted/uncompressed data
pub size: u64,
/// etag of the object
pub etag: String,
/// Encryption params + headers (encrypted or plaintext)
pub encryption: ObjectVersionEncryption,
}
/// Encryption information + metadata
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Debug, Serialize, Deserialize)]
pub enum ObjectVersionEncryption {
SseC {
/// Encrypted serialized ObjectVersionInner struct.
/// This is never compressed, just encrypted using AES256-GCM.
#[serde(with = "serde_bytes")]
inner: Vec<u8>,
/// Whether data blocks are compressed in addition to being encrypted
/// (compression happens before encryption, whereas for non-encrypted
/// objects, compression is handled at the level of the block manager)
compressed: bool,
/// Whether the encryption uses an Object Encryption Key derived
/// from the master SSE-C key, instead of the master SSE-C key itself.
/// This is the case of objects created in Garage v2+
use_oek: bool,
},
Plaintext {
/// Plain-text headers
inner: ObjectVersionMetaInner,
},
}
/// Vector of headers, as tuples of the format (header name, header value)
/// Note: checksum can be Some(_) with checksum_type = None for objects that
/// have been migrated from Garage version before v2.0, as the distinction between
/// full-object and composite checksums was not implemented yet.
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Debug, Serialize, Deserialize)]
pub struct ObjectVersionMetaInner {
pub headers: HeaderList,
pub checksum: Option<ChecksumValue>,
pub checksum_type: Option<ChecksumType>,
}
pub type HeaderList = Vec<(String, String)>;
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Debug, Serialize, Deserialize)]
pub enum ChecksumType {
FullObject,
Composite,
}
impl garage_util::migrate::Migrate for Object {
const VERSION_MARKER: &'static [u8] = b"G2s3ob";
type Previous = v010::Object;
fn migrate(old: v010::Object) -> Object {
Object {
bucket_id: old.bucket_id,
key: old.key,
versions: old.versions.into_iter().map(migrate_version).collect(),
}
}
}
fn migrate_version(old: v010::ObjectVersion) -> ObjectVersion {
ObjectVersion {
uuid: old.uuid,
timestamp: old.timestamp,
state: match old.state {
v010::ObjectVersionState::Uploading {
multipart,
checksum_algorithm,
encryption,
} => ObjectVersionState::Uploading {
multipart,
checksum_algorithm,
checksum_type: Some(match multipart {
false => ChecksumType::FullObject,
true => ChecksumType::Composite,
}),
encryption: migrate_encryption(encryption),
},
v010::ObjectVersionState::Complete(d) => {
ObjectVersionState::Complete(migrate_data(d))
}
v010::ObjectVersionState::Aborted => ObjectVersionState::Aborted,
},
}
}
fn migrate_data(old: v010::ObjectVersionData) -> ObjectVersionData {
match old {
v010::ObjectVersionData::DeleteMarker => ObjectVersionData::DeleteMarker,
v010::ObjectVersionData::Inline(meta, data) => {
ObjectVersionData::Inline(migrate_meta(meta), data)
}
v010::ObjectVersionData::FirstBlock(meta, fb) => {
ObjectVersionData::FirstBlock(migrate_meta(meta), fb)
}
}
}
fn migrate_meta(old: v010::ObjectVersionMeta) -> ObjectVersionMeta {
ObjectVersionMeta {
size: old.size,
etag: old.etag,
encryption: migrate_encryption(old.encryption),
}
}
fn migrate_encryption(old: v010::ObjectVersionEncryption) -> ObjectVersionEncryption {
match old {
v010::ObjectVersionEncryption::SseC {
inner,
compressed,
use_oek,
} => ObjectVersionEncryption::SseC {
inner,
compressed,
use_oek,
},
v010::ObjectVersionEncryption::Plaintext { inner } => {
ObjectVersionEncryption::Plaintext {
inner: ObjectVersionMetaInner::migrate(inner),
}
}
}
}
impl Migrate for ObjectVersionMetaInner {
const VERSION_MARKER: &'static [u8] = b"G2s3om";
type Previous = v010::ObjectVersionMetaInner;
fn migrate(old: v010::ObjectVersionMetaInner) -> ObjectVersionMetaInner {
ObjectVersionMetaInner {
headers: old.headers,
checksum: old.checksum,
checksum_type: None,
}
}
}
}
pub use v2::*;
impl Object {
/// Initialize an Object struct from parts