-
Notifications
You must be signed in to change notification settings - Fork 183
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add a test for an invalid cache block #1139
Changes from all commits
a1096c2
dee9afa
c32be2e
5624745
1afe45b
fb62bbd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,23 +1,104 @@ | ||
use crate::common::cache::CacheTestWrapper; | ||
use crate::common::fuse::create_fuse_session; | ||
use crate::common::fuse::s3_session::create_crt_client; | ||
use crate::common::s3::{get_express_bucket, get_standard_bucket, get_test_bucket_and_prefix}; | ||
use mountpoint_s3::data_cache::{DataCache, DiskDataCache, DiskDataCacheConfig, ExpressDataCache}; | ||
use crate::common::s3::{get_test_bucket, get_test_prefix}; | ||
|
||
use mountpoint_s3::data_cache::{DataCache, DiskDataCache, DiskDataCacheConfig}; | ||
use mountpoint_s3::prefetch::caching_prefetch; | ||
use mountpoint_s3_client::S3CrtClient; | ||
|
||
use fuser::BackgroundSession; | ||
use rand::{Rng, RngCore, SeedableRng}; | ||
use rand_chacha::ChaChaRng; | ||
use std::fs; | ||
use std::time::Duration; | ||
use tempfile::TempDir; | ||
use test_case::test_case; | ||
|
||
#[cfg(all(feature = "s3_tests", feature = "s3express_tests"))] | ||
use crate::common::s3::{get_express_bucket, get_standard_bucket}; | ||
#[cfg(all(feature = "s3_tests", feature = "s3express_tests"))] | ||
use mountpoint_s3::data_cache::{build_prefix, get_s3_key, BlockIndex, ExpressDataCache}; | ||
#[cfg(all(feature = "s3_tests", feature = "s3express_tests"))] | ||
use mountpoint_s3::object::ObjectId; | ||
#[cfg(all(feature = "s3_tests", feature = "s3express_tests"))] | ||
use mountpoint_s3_client::types::{PutObjectSingleParams, UploadChecksum}; | ||
#[cfg(all(feature = "s3_tests", feature = "s3express_tests"))] | ||
use mountpoint_s3_client::ObjectClient; | ||
#[cfg(all(feature = "s3_tests", feature = "s3express_tests"))] | ||
use mountpoint_s3_crt::checksums::crc32c; | ||
|
||
const CACHE_BLOCK_SIZE: u64 = 1024 * 1024; | ||
const CLIENT_PART_SIZE: usize = 8 * 1024 * 1024; | ||
|
||
/// A test that checks that an invalid block may not be served from the cache | ||
#[tokio::test] | ||
#[cfg(all(feature = "s3_tests", feature = "s3express_tests"))] | ||
async fn express_invalid_block_read() { | ||
let bucket = get_standard_bucket(); | ||
let cache_bucket = get_express_bucket(); | ||
let prefix = get_test_prefix("express_invalid_block_read"); | ||
|
||
// Mount the bucket | ||
let client = create_crt_client(CLIENT_PART_SIZE, CLIENT_PART_SIZE); | ||
let cache = CacheTestWrapper::new(ExpressDataCache::new( | ||
client.clone(), | ||
Default::default(), | ||
&bucket, | ||
&cache_bucket, | ||
)); | ||
let (mount_point, _session) = mount_bucket(client.clone(), cache.clone(), &bucket, &prefix); | ||
|
||
// Put an object to the mounted bucket | ||
let object_key = generate_unprefixed_key(&prefix, "key", 100); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why pass the prefix then? can't you subtract the prefix's length from 100, if that's what you want? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually can't we just use a fixed key here? And leave the other test where we generate the key intact? |
||
let full_object_key = format!("{prefix}{object_key}"); | ||
let object_data = "object_data"; | ||
let result = client | ||
.put_object_single(&bucket, &full_object_key, &Default::default(), object_data) | ||
.await | ||
.expect("put object must succeed"); | ||
let object_etag = result.etag.into_inner(); | ||
|
||
// Read data twice, expect cache hits and no errors | ||
let path = mount_point.path().join(&object_key); | ||
|
||
let put_block_count = cache.put_block_count(); | ||
let read = fs::read(&path).expect("read should succeed"); | ||
assert_eq!(read, object_data.as_bytes()); | ||
cache.wait_for_put(Duration::from_secs(10), put_block_count); | ||
|
||
let read = fs::read(&path).expect("read should succeed"); | ||
assert_eq!(read, object_data.as_bytes()); | ||
|
||
assert_eq!(cache.get_block_invalid_count(), 0, "no invalid blocks yet"); | ||
assert!(cache.get_block_hit_count() > 0, "reads should result in a cache hit"); | ||
|
||
// Corrupt the cache block by replacing it with an object holding no metadata | ||
let object_id = get_object_id(&prefix, &object_key, &object_etag); | ||
let block_key = get_express_cache_block_key(&bucket, &object_id, 0); | ||
let corrupted_block = "corrupted_block"; | ||
let checksum = crc32c::checksum(corrupted_block.as_bytes()); | ||
let put_object_params = PutObjectSingleParams::default().checksum(Some(UploadChecksum::Crc32c(checksum))); | ||
client | ||
.put_object_single(&cache_bucket, &block_key, &put_object_params, corrupted_block) | ||
.await | ||
.expect("put object must succeed"); | ||
|
||
// Expect a successfull read from the source bucket. We expect cache errors being recorded because of the corrupted block. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. tiny nit: successful |
||
let path = mount_point.path().join(&object_key); | ||
let read = fs::read(&path).expect("read should succeed"); | ||
assert_eq!(read, object_data.as_bytes()); | ||
assert!( | ||
cache.get_block_invalid_count() > 0, | ||
"read should result in cache errors" | ||
); | ||
} | ||
|
||
#[test_case("key", 100, 1024; "simple")] | ||
#[test_case("£", 100, 1024; "non-ascii key")] | ||
#[test_case("key", 1024, 1024; "long key")] | ||
#[test_case("key", 100, 1024 * 1024; "big file")] | ||
#[cfg(all(feature = "s3_tests", feature = "s3express_tests"))] | ||
fn express_cache_write_read(key_suffix: &str, key_size: usize, object_size: usize) { | ||
let client = create_crt_client(CLIENT_PART_SIZE, CLIENT_PART_SIZE); | ||
let bucket_name = get_standard_bucket(); | ||
|
@@ -26,6 +107,7 @@ fn express_cache_write_read(key_suffix: &str, key_size: usize, object_size: usiz | |
|
||
cache_write_read_base( | ||
client, | ||
&bucket_name, | ||
key_suffix, | ||
key_size, | ||
object_size, | ||
|
@@ -38,6 +120,7 @@ fn express_cache_write_read(key_suffix: &str, key_size: usize, object_size: usiz | |
#[test_case("£", 100, 1024; "non-ascii key")] | ||
#[test_case("key", 1024, 1024; "long key")] | ||
#[test_case("key", 100, 1024 * 1024; "big file")] | ||
#[cfg(feature = "s3_tests")] | ||
fn disk_cache_write_read(key_suffix: &str, key_size: usize, object_size: usize) { | ||
let cache_dir = tempfile::tempdir().unwrap(); | ||
let cache_config = DiskDataCacheConfig { | ||
|
@@ -48,8 +131,10 @@ fn disk_cache_write_read(key_suffix: &str, key_size: usize, object_size: usize) | |
|
||
let client = create_crt_client(CLIENT_PART_SIZE, CLIENT_PART_SIZE); | ||
|
||
let bucket_name = get_test_bucket(); | ||
cache_write_read_base( | ||
client, | ||
&bucket_name, | ||
key_suffix, | ||
key_size, | ||
object_size, | ||
|
@@ -60,6 +145,7 @@ fn disk_cache_write_read(key_suffix: &str, key_size: usize, object_size: usize) | |
|
||
fn cache_write_read_base<Cache>( | ||
client: S3CrtClient, | ||
bucket: &str, | ||
key_suffix: &str, | ||
key_size: usize, | ||
object_size: usize, | ||
|
@@ -68,24 +154,14 @@ fn cache_write_read_base<Cache>( | |
) where | ||
Cache: DataCache + Send + Sync + 'static, | ||
{ | ||
let (bucket, prefix) = get_test_bucket_and_prefix(test_name); | ||
let prefix = get_test_prefix(test_name); | ||
|
||
// Mount a bucket | ||
let mount_point = tempfile::tempdir().unwrap(); | ||
let runtime = client.event_loop_group(); | ||
let cache = CacheTestWrapper::new(cache); | ||
let prefetcher = caching_prefetch(cache.clone(), runtime, Default::default()); | ||
let _session = create_fuse_session( | ||
client, | ||
prefetcher, | ||
&bucket, | ||
&prefix, | ||
mount_point.path(), | ||
Default::default(), | ||
); | ||
let (mount_point, _session) = mount_bucket(client, cache.clone(), bucket, &prefix); | ||
|
||
// Write an object, no caching happens yet | ||
let key = get_object_key(&prefix, key_suffix, key_size); | ||
let key = generate_unprefixed_key(&prefix, key_suffix, key_size); | ||
let path = mount_point.path().join(&key); | ||
let written = random_binary_data(object_size); | ||
fs::write(&path, &written).expect("write should succeed"); | ||
|
@@ -119,7 +195,8 @@ fn random_binary_data(size_in_bytes: usize) -> Vec<u8> { | |
} | ||
|
||
/// Creates a random key which has a size of at least `min_size_in_bytes` | ||
fn get_object_key(key_prefix: &str, key_suffix: &str, min_size_in_bytes: usize) -> String { | ||
/// The `key_prefix` is not included in the return value. | ||
fn generate_unprefixed_key(key_prefix: &str, key_suffix: &str, min_size_in_bytes: usize) -> String { | ||
let random_suffix: u64 = rand::thread_rng().gen(); | ||
let last_key_part = format!("{key_suffix}{random_suffix}"); // part of the key after all the "/" | ||
let full_key = format!("{key_prefix}{last_key_part}"); | ||
|
@@ -128,3 +205,32 @@ fn get_object_key(key_prefix: &str, key_suffix: &str, min_size_in_bytes: usize) | |
let padding = "0".repeat(padding_size); | ||
format!("{last_key_part}{padding}") | ||
} | ||
|
||
fn mount_bucket<Cache>(client: S3CrtClient, cache: Cache, bucket: &str, prefix: &str) -> (TempDir, BackgroundSession) | ||
where | ||
Cache: DataCache + Send + Sync + 'static, | ||
{ | ||
let mount_point = tempfile::tempdir().unwrap(); | ||
let runtime = client.event_loop_group(); | ||
let prefetcher = caching_prefetch(cache, runtime, Default::default()); | ||
let session = create_fuse_session( | ||
client, | ||
prefetcher, | ||
bucket, | ||
prefix, | ||
mount_point.path(), | ||
Default::default(), | ||
); | ||
(mount_point, session) | ||
} | ||
|
||
#[cfg(all(feature = "s3_tests", feature = "s3express_tests"))] | ||
fn get_object_id(prefix: &str, key: &str, etag: &str) -> ObjectId { | ||
ObjectId::new(format!("{prefix}{key}"), etag.into()) | ||
} | ||
|
||
#[cfg(all(feature = "s3_tests", feature = "s3express_tests"))] | ||
fn get_express_cache_block_key(bucket: &str, cache_key: &ObjectId, block_idx: BlockIndex) -> String { | ||
let block_key_prefix = build_prefix(bucket, CACHE_BLOCK_SIZE); | ||
get_s3_key(&block_key_prefix, cache_key, block_idx) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
#[cfg(all(feature = "s3_tests", feature = "s3express_tests"))] | ||
#[cfg(feature = "s3_tests")] | ||
mod cache_test; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is redundant now. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (not blocking) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have to keep it to make #[cfg(feature = "s3_tests")]
use crate::common::fuse::s3_session::create_crt_client; |
||
mod consistency_test; | ||
mod fork_test; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not blocking: we should consider moving (some of) the
use
into the functions that require them (to avoid all thecfg
).