mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-22 20:36:38 +00:00
fix(ecstore): normalize internal metadata aliases
This commit is contained in:
@@ -1620,15 +1620,100 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn resolve_latest_object_info_candidates_rejects_user_defined_identity_conflict() {
|
fn resolve_latest_object_info_candidates_accepts_internal_metadata_aliases() {
|
||||||
let base = object_info_with_identity(10, false, Uuid::from_u128(1), Some("etag-a".to_string()));
|
let base = object_info_with_identity(10, false, Uuid::from_u128(1), Some("etag-a".to_string()));
|
||||||
let mut divergent = base.clone();
|
let mut rustfs_alias = base.clone();
|
||||||
divergent.user_defined = std::sync::Arc::new(std::collections::HashMap::from([(
|
rustfs_alias.user_defined = std::sync::Arc::new(std::collections::HashMap::from([(
|
||||||
|
"x-rustfs-internal-compression".to_string(),
|
||||||
|
"zstd".to_string(),
|
||||||
|
)]));
|
||||||
|
let mut minio_alias = base.clone();
|
||||||
|
minio_alias.user_defined = std::sync::Arc::new(std::collections::HashMap::from([(
|
||||||
|
"X-MINIO-INTERNAL-COMPRESSION".to_string(),
|
||||||
|
"zstd".to_string(),
|
||||||
|
)]));
|
||||||
|
|
||||||
|
let (_, idx) = resolve_latest_object_info_candidates(
|
||||||
|
vec![
|
||||||
|
LatestObjectInfoCandidate {
|
||||||
|
info: Some(rustfs_alias),
|
||||||
|
idx: 0,
|
||||||
|
err: None,
|
||||||
|
},
|
||||||
|
LatestObjectInfoCandidate {
|
||||||
|
info: Some(minio_alias),
|
||||||
|
idx: 1,
|
||||||
|
err: None,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"bucket",
|
||||||
|
"object",
|
||||||
|
&ObjectOptions::default(),
|
||||||
|
)
|
||||||
|
.expect("same-value internal aliases should resolve");
|
||||||
|
assert_eq!(idx, 1);
|
||||||
|
|
||||||
|
let mut dual_alias = base.clone();
|
||||||
|
dual_alias.user_defined = std::sync::Arc::new(std::collections::HashMap::from([
|
||||||
|
("x-rustfs-internal-compression".to_string(), "zstd".to_string()),
|
||||||
|
("x-minio-internal-compression".to_string(), "zstd".to_string()),
|
||||||
|
]));
|
||||||
|
let mut single_alias = base;
|
||||||
|
single_alias.user_defined = std::sync::Arc::new(std::collections::HashMap::from([(
|
||||||
"x-rustfs-internal-compression".to_string(),
|
"x-rustfs-internal-compression".to_string(),
|
||||||
"zstd".to_string(),
|
"zstd".to_string(),
|
||||||
)]));
|
)]));
|
||||||
|
|
||||||
assert_equal_time_identity_conflict(base, divergent);
|
let (_, idx) = resolve_latest_object_info_candidates(
|
||||||
|
vec![
|
||||||
|
LatestObjectInfoCandidate {
|
||||||
|
info: Some(dual_alias),
|
||||||
|
idx: 0,
|
||||||
|
err: None,
|
||||||
|
},
|
||||||
|
LatestObjectInfoCandidate {
|
||||||
|
info: Some(single_alias),
|
||||||
|
idx: 1,
|
||||||
|
err: None,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"bucket",
|
||||||
|
"object",
|
||||||
|
&ObjectOptions::default(),
|
||||||
|
)
|
||||||
|
.expect("dual-key and single-key internal metadata should resolve");
|
||||||
|
assert_eq!(idx, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn resolve_latest_object_info_candidates_rejects_different_internal_metadata_alias_values() {
|
||||||
|
let base = object_info_with_identity(10, false, Uuid::from_u128(1), Some("etag-a".to_string()));
|
||||||
|
let mut rustfs_alias = base.clone();
|
||||||
|
rustfs_alias.user_defined = std::sync::Arc::new(std::collections::HashMap::from([(
|
||||||
|
"x-rustfs-internal-compression".to_string(),
|
||||||
|
"zstd".to_string(),
|
||||||
|
)]));
|
||||||
|
let mut minio_alias = base;
|
||||||
|
minio_alias.user_defined = std::sync::Arc::new(std::collections::HashMap::from([(
|
||||||
|
"x-minio-internal-compression".to_string(),
|
||||||
|
"snappy".to_string(),
|
||||||
|
)]));
|
||||||
|
|
||||||
|
assert_equal_time_identity_conflict(rustfs_alias, minio_alias);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn resolve_latest_object_info_candidates_rejects_conflicting_internal_metadata_aliases_in_one_candidate() {
|
||||||
|
let base = object_info_with_identity(10, false, Uuid::from_u128(1), Some("etag-a".to_string()));
|
||||||
|
let mut first = base.clone();
|
||||||
|
first.user_defined = std::sync::Arc::new(std::collections::HashMap::from([
|
||||||
|
("x-rustfs-internal-compression".to_string(), "zstd".to_string()),
|
||||||
|
("x-minio-internal-compression".to_string(), "snappy".to_string()),
|
||||||
|
]));
|
||||||
|
let mut second = base;
|
||||||
|
second.user_defined = first.user_defined.clone();
|
||||||
|
|
||||||
|
assert_equal_time_identity_conflict(first, second);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -12,8 +12,11 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::error::{Error, Result, StorageError, is_err_object_not_found, is_err_version_not_found};
|
use crate::error::{Error, Result, StorageError, is_err_object_not_found, is_err_version_not_found};
|
||||||
use crate::object_api::{ObjectInfo, ObjectOptions};
|
use crate::object_api::{ObjectInfo, ObjectOptions};
|
||||||
|
use rustfs_utils::http::metadata_compat::strip_internal_prefix;
|
||||||
use rustfs_utils::path::decode_dir_object;
|
use rustfs_utils::path::decode_dir_object;
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
@@ -151,6 +154,45 @@ fn same_transition_identity(left: &ObjectInfo, right: &ObjectInfo) -> bool {
|
|||||||
&& left.transitioned_object.status == right.transitioned_object.status
|
&& left.transitioned_object.status == right.transitioned_object.status
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq)]
|
||||||
|
struct LatestUserDefinedIdentity {
|
||||||
|
internal: HashMap<String, String>,
|
||||||
|
other: HashMap<String, String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn normalize_user_defined_identity(user_defined: &HashMap<String, String>) -> Option<LatestUserDefinedIdentity> {
|
||||||
|
let mut identity = LatestUserDefinedIdentity {
|
||||||
|
internal: HashMap::with_capacity(user_defined.len()),
|
||||||
|
other: HashMap::with_capacity(user_defined.len()),
|
||||||
|
};
|
||||||
|
|
||||||
|
for (key, value) in user_defined {
|
||||||
|
if let Some(suffix) = strip_internal_prefix(key) {
|
||||||
|
if identity
|
||||||
|
.internal
|
||||||
|
.insert(suffix, value.clone())
|
||||||
|
.is_some_and(|previous| previous != *value)
|
||||||
|
{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
identity.other.insert(key.clone(), value.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(identity)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn same_user_defined_identity(left: &ObjectInfo, right: &ObjectInfo) -> bool {
|
||||||
|
match (
|
||||||
|
normalize_user_defined_identity(&left.user_defined),
|
||||||
|
normalize_user_defined_identity(&right.user_defined),
|
||||||
|
) {
|
||||||
|
(Some(left), Some(right)) => left == right,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Pool-specific erasure geometry is intentionally excluded: `get_object_info`
|
/// Pool-specific erasure geometry is intentionally excluded: `get_object_info`
|
||||||
/// returns each pool's own `data_blocks`/`parity_blocks`, so those values can
|
/// returns each pool's own `data_blocks`/`parity_blocks`, so those values can
|
||||||
/// differ for the same object version while the selected winner still carries
|
/// differ for the same object version while the selected winner still carries
|
||||||
@@ -167,7 +209,7 @@ fn same_latest_object_info_identity(left: &ObjectInfo, right: &ObjectInfo) -> bo
|
|||||||
&& left.size == right.size
|
&& left.size == right.size
|
||||||
&& left.actual_size == right.actual_size
|
&& left.actual_size == right.actual_size
|
||||||
&& left.is_dir == right.is_dir
|
&& left.is_dir == right.is_dir
|
||||||
&& left.user_defined == right.user_defined
|
&& same_user_defined_identity(left, right)
|
||||||
&& left.user_tags == right.user_tags
|
&& left.user_tags == right.user_tags
|
||||||
&& left.version_id == right.version_id
|
&& left.version_id == right.version_id
|
||||||
&& left.data_dir == right.data_dir
|
&& left.data_dir == right.data_dir
|
||||||
|
|||||||
Reference in New Issue
Block a user