mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-24 13:16:28 +00:00
Fix: add support for additional AWS S3 storage classes and validation logic (#487)
* Fix: add pagination fields to S3 response * Fix: add support for additional AWS S3 storage classes and validation logic * Fix: improve handling of optional fields in S3 response --------- Co-authored-by: DamonXue <damonxue2@gmail.com>
This commit is contained in:
@@ -36,6 +36,17 @@ pub fn default_parity_count(drive: usize) -> usize {
|
|||||||
pub const RRS: &str = "REDUCED_REDUNDANCY";
|
pub const RRS: &str = "REDUCED_REDUNDANCY";
|
||||||
pub const STANDARD: &str = "STANDARD";
|
pub const STANDARD: &str = "STANDARD";
|
||||||
|
|
||||||
|
// AWS S3 Storage Classes
|
||||||
|
pub const DEEP_ARCHIVE: &str = "DEEP_ARCHIVE";
|
||||||
|
pub const EXPRESS_ONEZONE: &str = "EXPRESS_ONEZONE";
|
||||||
|
pub const GLACIER: &str = "GLACIER";
|
||||||
|
pub const GLACIER_IR: &str = "GLACIER_IR";
|
||||||
|
pub const INTELLIGENT_TIERING: &str = "INTELLIGENT_TIERING";
|
||||||
|
pub const ONEZONE_IA: &str = "ONEZONE_IA";
|
||||||
|
pub const OUTPOSTS: &str = "OUTPOSTS";
|
||||||
|
pub const SNOW: &str = "SNOW";
|
||||||
|
pub const STANDARD_IA: &str = "STANDARD_IA";
|
||||||
|
|
||||||
// Standard constants for config info storage class
|
// Standard constants for config info storage class
|
||||||
pub const CLASS_STANDARD: &str = "standard";
|
pub const CLASS_STANDARD: &str = "standard";
|
||||||
pub const CLASS_RRS: &str = "rrs";
|
pub const CLASS_RRS: &str = "rrs";
|
||||||
@@ -115,6 +126,15 @@ impl Config {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// All these storage classes use standard parity configuration
|
||||||
|
STANDARD | DEEP_ARCHIVE | EXPRESS_ONEZONE | GLACIER | GLACIER_IR | INTELLIGENT_TIERING | ONEZONE_IA | OUTPOSTS
|
||||||
|
| SNOW | STANDARD_IA => {
|
||||||
|
if self.initialized {
|
||||||
|
Some(self.standard.parity)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
if self.initialized {
|
if self.initialized {
|
||||||
Some(self.standard.parity)
|
Some(self.standard.parity)
|
||||||
|
|||||||
@@ -4778,10 +4778,18 @@ impl StorageAPI for SetDisks {
|
|||||||
|
|
||||||
let part_number_marker = part_number_marker.unwrap_or_default();
|
let part_number_marker = part_number_marker.unwrap_or_default();
|
||||||
|
|
||||||
|
// Extract storage class from metadata, default to STANDARD if not found
|
||||||
|
let storage_class = fi
|
||||||
|
.metadata
|
||||||
|
.get(rustfs_filemeta::headers::AMZ_STORAGE_CLASS)
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_else(|| storageclass::STANDARD.to_string());
|
||||||
|
|
||||||
let mut ret = ListPartsInfo {
|
let mut ret = ListPartsInfo {
|
||||||
bucket: bucket.to_owned(),
|
bucket: bucket.to_owned(),
|
||||||
object: object.to_owned(),
|
object: object.to_owned(),
|
||||||
upload_id: upload_id.to_owned(),
|
upload_id: upload_id.to_owned(),
|
||||||
|
storage_class,
|
||||||
max_parts,
|
max_parts,
|
||||||
part_number_marker,
|
part_number_marker,
|
||||||
user_defined: fi.metadata.clone(),
|
user_defined: fi.metadata.clone(),
|
||||||
@@ -6039,6 +6047,40 @@ pub fn should_prevent_write(oi: &ObjectInfo, if_none_match: Option<String>, if_m
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Validates if the given storage class is supported
|
||||||
|
pub fn is_valid_storage_class(storage_class: &str) -> bool {
|
||||||
|
matches!(
|
||||||
|
storage_class,
|
||||||
|
storageclass::STANDARD
|
||||||
|
| storageclass::RRS
|
||||||
|
| storageclass::DEEP_ARCHIVE
|
||||||
|
| storageclass::EXPRESS_ONEZONE
|
||||||
|
| storageclass::GLACIER
|
||||||
|
| storageclass::GLACIER_IR
|
||||||
|
| storageclass::INTELLIGENT_TIERING
|
||||||
|
| storageclass::ONEZONE_IA
|
||||||
|
| storageclass::OUTPOSTS
|
||||||
|
| storageclass::SNOW
|
||||||
|
| storageclass::STANDARD_IA
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the storage class is a cold storage tier that requires special handling
|
||||||
|
pub fn is_cold_storage_class(storage_class: &str) -> bool {
|
||||||
|
matches!(
|
||||||
|
storage_class,
|
||||||
|
storageclass::DEEP_ARCHIVE | storageclass::GLACIER | storageclass::GLACIER_IR
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the storage class is an infrequent access tier
|
||||||
|
pub fn is_infrequent_access_class(storage_class: &str) -> bool {
|
||||||
|
matches!(
|
||||||
|
storage_class,
|
||||||
|
storageclass::ONEZONE_IA | storageclass::STANDARD_IA | storageclass::INTELLIGENT_TIERING
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -6528,4 +6570,53 @@ mod tests {
|
|||||||
let if_match = None;
|
let if_match = None;
|
||||||
assert!(!should_prevent_write(&oi, if_none_match, if_match));
|
assert!(!should_prevent_write(&oi, if_none_match, if_match));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_is_valid_storage_class() {
|
||||||
|
// Test valid storage classes
|
||||||
|
assert!(is_valid_storage_class(storageclass::STANDARD));
|
||||||
|
assert!(is_valid_storage_class(storageclass::RRS));
|
||||||
|
assert!(is_valid_storage_class(storageclass::DEEP_ARCHIVE));
|
||||||
|
assert!(is_valid_storage_class(storageclass::EXPRESS_ONEZONE));
|
||||||
|
assert!(is_valid_storage_class(storageclass::GLACIER));
|
||||||
|
assert!(is_valid_storage_class(storageclass::GLACIER_IR));
|
||||||
|
assert!(is_valid_storage_class(storageclass::INTELLIGENT_TIERING));
|
||||||
|
assert!(is_valid_storage_class(storageclass::ONEZONE_IA));
|
||||||
|
assert!(is_valid_storage_class(storageclass::OUTPOSTS));
|
||||||
|
assert!(is_valid_storage_class(storageclass::SNOW));
|
||||||
|
assert!(is_valid_storage_class(storageclass::STANDARD_IA));
|
||||||
|
|
||||||
|
// Test invalid storage classes
|
||||||
|
assert!(!is_valid_storage_class("INVALID"));
|
||||||
|
assert!(!is_valid_storage_class(""));
|
||||||
|
assert!(!is_valid_storage_class("standard")); // lowercase
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_is_cold_storage_class() {
|
||||||
|
// Test cold storage classes
|
||||||
|
assert!(is_cold_storage_class(storageclass::DEEP_ARCHIVE));
|
||||||
|
assert!(is_cold_storage_class(storageclass::GLACIER));
|
||||||
|
assert!(is_cold_storage_class(storageclass::GLACIER_IR));
|
||||||
|
|
||||||
|
// Test non-cold storage classes
|
||||||
|
assert!(!is_cold_storage_class(storageclass::STANDARD));
|
||||||
|
assert!(!is_cold_storage_class(storageclass::RRS));
|
||||||
|
assert!(!is_cold_storage_class(storageclass::STANDARD_IA));
|
||||||
|
assert!(!is_cold_storage_class(storageclass::EXPRESS_ONEZONE));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_is_infrequent_access_class() {
|
||||||
|
// Test infrequent access classes
|
||||||
|
assert!(is_infrequent_access_class(storageclass::ONEZONE_IA));
|
||||||
|
assert!(is_infrequent_access_class(storageclass::STANDARD_IA));
|
||||||
|
assert!(is_infrequent_access_class(storageclass::INTELLIGENT_TIERING));
|
||||||
|
|
||||||
|
// Test frequent access classes
|
||||||
|
assert!(!is_infrequent_access_class(storageclass::STANDARD));
|
||||||
|
assert!(!is_infrequent_access_class(storageclass::RRS));
|
||||||
|
assert!(!is_infrequent_access_class(storageclass::DEEP_ARCHIVE));
|
||||||
|
assert!(!is_infrequent_access_class(storageclass::EXPRESS_ONEZONE));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,8 +57,8 @@ use rustfs_ecstore::compress::MIN_COMPRESSIBLE_SIZE;
|
|||||||
use rustfs_ecstore::compress::is_compressible;
|
use rustfs_ecstore::compress::is_compressible;
|
||||||
use rustfs_ecstore::error::StorageError;
|
use rustfs_ecstore::error::StorageError;
|
||||||
use rustfs_ecstore::new_object_layer_fn;
|
use rustfs_ecstore::new_object_layer_fn;
|
||||||
use rustfs_ecstore::set_disk::DEFAULT_READ_BUFFER_SIZE;
|
|
||||||
use rustfs_ecstore::set_disk::MAX_PARTS_COUNT;
|
use rustfs_ecstore::set_disk::MAX_PARTS_COUNT;
|
||||||
|
use rustfs_ecstore::set_disk::{DEFAULT_READ_BUFFER_SIZE, is_valid_storage_class};
|
||||||
use rustfs_ecstore::store_api::BucketOptions;
|
use rustfs_ecstore::store_api::BucketOptions;
|
||||||
use rustfs_ecstore::store_api::CompletePart;
|
use rustfs_ecstore::store_api::CompletePart;
|
||||||
use rustfs_ecstore::store_api::DeleteBucketOptions;
|
use rustfs_ecstore::store_api::DeleteBucketOptions;
|
||||||
@@ -1385,8 +1385,7 @@ impl S3 for FS {
|
|||||||
let input = req.input;
|
let input = req.input;
|
||||||
|
|
||||||
if let Some(ref storage_class) = input.storage_class {
|
if let Some(ref storage_class) = input.storage_class {
|
||||||
let is_valid = ["STANDARD", "REDUCED_REDUNDANCY"].contains(&storage_class.as_str());
|
if !is_valid_storage_class(storage_class.as_str()) {
|
||||||
if !is_valid {
|
|
||||||
return Err(s3_error!(InvalidStorageClass));
|
return Err(s3_error!(InvalidStorageClass));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1530,9 +1529,17 @@ impl S3 for FS {
|
|||||||
key,
|
key,
|
||||||
tagging,
|
tagging,
|
||||||
version_id,
|
version_id,
|
||||||
|
storage_class,
|
||||||
..
|
..
|
||||||
} = req.input.clone();
|
} = req.input.clone();
|
||||||
|
|
||||||
|
// Validate storage class if provided
|
||||||
|
if let Some(ref storage_class) = storage_class {
|
||||||
|
if !is_valid_storage_class(storage_class.as_str()) {
|
||||||
|
return Err(s3_error!(InvalidStorageClass));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// mc cp step 3
|
// mc cp step 3
|
||||||
|
|
||||||
// debug!("create_multipart_upload meta {:?}", &metadata);
|
// debug!("create_multipart_upload meta {:?}", &metadata);
|
||||||
@@ -1900,6 +1907,15 @@ impl S3 for FS {
|
|||||||
id: RUSTFS_OWNER.id.clone(),
|
id: RUSTFS_OWNER.id.clone(),
|
||||||
display_name: RUSTFS_OWNER.display_name.clone(),
|
display_name: RUSTFS_OWNER.display_name.clone(),
|
||||||
}),
|
}),
|
||||||
|
is_truncated: Some(res.is_truncated),
|
||||||
|
next_part_number_marker: res.next_part_number_marker.try_into().ok(),
|
||||||
|
max_parts: res.max_parts.try_into().ok(),
|
||||||
|
part_number_marker: res.part_number_marker.try_into().ok(),
|
||||||
|
storage_class: if res.storage_class.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(res.storage_class.into())
|
||||||
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
Ok(S3Response::new(output))
|
Ok(S3Response::new(output))
|
||||||
|
|||||||
Reference in New Issue
Block a user