Skip to content

Commit cf26b1c

Browse files
committed
cluster: add conditional requests test to cloudcheck self-test
1 parent f6ddb89 commit cf26b1c

File tree

3 files changed

+164
-12
lines changed

3 files changed

+164
-12
lines changed

src/v/cluster/self_test/cloudcheck.cc

Lines changed: 152 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,6 @@ ss::future<std::vector<self_test_result>> cloudcheck::run_benchmarks() {
108108
const auto bucket = cloud_storage_clients::bucket_name{
109109
cloud_storage::configuration::get_bucket_config()().value()};
110110

111-
const auto self_test_prefix = cloud_storage_clients::object_key{
112-
"self-test/"};
113-
114111
const auto uuid = cloud_storage_clients::object_key{
115112
ss::sstring{uuid_t::create()}};
116113
const auto self_test_key = cloud_storage_clients::object_key{
@@ -209,6 +206,13 @@ ss::future<std::vector<self_test_result>> cloudcheck::run_benchmarks() {
209206
&cloudcheck::verify_deletes, bucket, num_default_objects);
210207
results.push_back(std::move(deletes_test_result.test_result));
211208

209+
// Test Puts and Gets with conditional headers (`If-None-Match`,
210+
// `If-Match`).
211+
auto conditional_puts_and_gets_test_result = co_await do_run_test(
212+
&cloudcheck::verify_conditional_puts_and_gets, bucket);
213+
results.push_back(
214+
std::move(conditional_puts_and_gets_test_result.test_result));
215+
212216
co_return results;
213217
}
214218

@@ -461,8 +465,10 @@ ss::future<cloudcheck::verify_deletes_result> cloudcheck::verify_deletes(
461465
}
462466

463467
std::vector<cloud_storage_clients::object_key> keys(num_objects);
464-
std::generate(keys.begin(), keys.end(), []() {
465-
return cloud_storage_clients::object_key{ss::sstring{uuid_t::create()}};
468+
std::generate(keys.begin(), keys.end(), [this]() {
469+
const auto uuid = cloud_storage_clients::object_key{
470+
ss::sstring{uuid_t::create()}};
471+
return cloud_storage_clients::object_key{self_test_prefix / uuid};
466472
});
467473

468474
for (const auto& key : keys) {
@@ -495,4 +501,145 @@ ss::future<cloudcheck::verify_deletes_result> cloudcheck::verify_deletes(
495501
co_return result;
496502
}
497503

504+
ss::future<cloudcheck::verify_upload_result>
505+
cloudcheck::verify_conditional_puts_and_gets(
506+
cloud_storage_clients::bucket_name bucket) {
507+
auto result = self_test_result{
508+
.name = _opts.name,
509+
.info = "Conditional Puts and Gets",
510+
.test_type = "cloud"};
511+
512+
if (_cancelled) {
513+
result.warning = "Run was manually cancelled.";
514+
co_return result;
515+
}
516+
517+
const auto uuid = cloud_storage_clients::object_key{
518+
ss::sstring{uuid_t::create()}};
519+
auto key = cloud_storage_clients::object_key{self_test_prefix / uuid};
520+
auto payload = make_random_payload();
521+
522+
try {
523+
ss::sstring etag = {};
524+
// Attempt the first upload - expect it to succeed.
525+
{
526+
auto rtc = retry_chain_node(_opts.timeout, _opts.backoff, &_rtc);
527+
cloud_storage::upload_request upload_request = make_upload_request(
528+
bucket, key, payload.copy(), rtc);
529+
upload_request.etag = &etag;
530+
const cloud_storage::upload_result upload_result
531+
= co_await _cloud_storage_api.local().upload_object(
532+
std::move(upload_request));
533+
switch (upload_result) {
534+
case cloud_storage::upload_result::success:
535+
break;
536+
case cloud_storage::upload_result::timedout:
537+
[[fallthrough]];
538+
case cloud_storage::upload_result::failed:
539+
[[fallthrough]];
540+
case cloud_storage::upload_result::precondition_failed:
541+
[[fallthrough]];
542+
case cloud_storage::upload_result::cancelled:
543+
result.error = "Failed to upload to cloud storage.";
544+
break;
545+
}
546+
}
547+
548+
// Make a conditional read with the recorded etag - expect it to
549+
// succeed.
550+
{
551+
iobuf download_payload;
552+
auto rtc = retry_chain_node(_opts.timeout, _opts.backoff, &_rtc);
553+
cloud_storage::download_request download_request
554+
= make_download_request(
555+
bucket, key, std::ref(download_payload), rtc);
556+
download_request.set_download_if_matches(etag);
557+
const cloud_storage::download_result download_result
558+
= co_await _cloud_storage_api.local().download_object(
559+
std::move(download_request));
560+
561+
switch (download_result) {
562+
case cloud_storage::download_result::success:
563+
break;
564+
case cloud_storage::download_result::timedout:
565+
[[fallthrough]];
566+
case cloud_storage::download_result::failed:
567+
[[fallthrough]];
568+
case cloud_storage::download_result::precondition_failed:
569+
[[fallthrough]];
570+
case cloud_storage::download_result::notfound:
571+
result.error = "Failed to download from cloud storage using "
572+
"recorded ETag and If-Match header.";
573+
break;
574+
}
575+
}
576+
577+
// Make a conditional read with an invalid etag - expect it to fail.
578+
{
579+
iobuf download_payload;
580+
auto rtc = retry_chain_node(_opts.timeout, _opts.backoff, &_rtc);
581+
cloud_storage::download_request download_request
582+
= make_download_request(
583+
bucket, key, std::ref(download_payload), rtc);
584+
download_request.set_download_if_matches("DEADBEEF");
585+
const cloud_storage::download_result download_result
586+
= co_await _cloud_storage_api.local().download_object(
587+
std::move(download_request));
588+
589+
switch (download_result) {
590+
case cloud_storage::download_result::precondition_failed:
591+
break;
592+
case cloud_storage::download_result::success:
593+
result.error
594+
= "Failed to respect conditional read from cloud storage.";
595+
break;
596+
case cloud_storage::download_result::timedout:
597+
[[fallthrough]];
598+
case cloud_storage::download_result::failed:
599+
[[fallthrough]];
600+
case cloud_storage::download_result::notfound:
601+
result.error = "Failed to download from cloud storage.";
602+
break;
603+
}
604+
}
605+
606+
// Attempt the second upload, with If-None-Match header - expect it to
607+
// fail.
608+
{
609+
auto rtc = retry_chain_node(_opts.timeout, _opts.backoff, &_rtc);
610+
cloud_storage::upload_request upload_request = make_upload_request(
611+
bucket, key, payload.copy(), rtc);
612+
upload_request.set_upload_if_not_exists();
613+
const cloud_storage::upload_result upload_result
614+
= co_await _cloud_storage_api.local().upload_object(
615+
std::move(upload_request));
616+
617+
switch (upload_result) {
618+
case cloud_storage::upload_result::precondition_failed:
619+
break;
620+
case cloud_storage::upload_result::success:
621+
[[fallthrough]];
622+
case cloud_storage::upload_result::timedout:
623+
[[fallthrough]];
624+
case cloud_storage::upload_result::failed:
625+
[[fallthrough]];
626+
case cloud_storage::upload_result::cancelled:
627+
result.error = "Failed to respect If-None-Match header.";
628+
break;
629+
}
630+
}
631+
632+
// Clean-up the uploaded file.
633+
{
634+
auto rtc = retry_chain_node(_opts.timeout, _opts.backoff, &_rtc);
635+
std::ignore = co_await _cloud_storage_api.local().delete_object(
636+
bucket, key, rtc);
637+
}
638+
} catch (const std::exception& e) {
639+
result.error = e.what();
640+
}
641+
642+
co_return result;
643+
}
644+
498645
} // namespace cluster::self_test

src/v/cluster/self_test/cloudcheck.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,12 @@ class cloudcheck {
142142
cloud_storage_clients::bucket_name bucket,
143143
size_t num_objects = num_default_objects);
144144

145+
// Verify that conditional requests (both Put: write operation, using the
146+
// `If-None-Match` header and Get: read operation, using the `If-Match`
147+
// header) to cloud storage work.
148+
ss::future<verify_upload_result>
149+
verify_conditional_puts_and_gets(cloud_storage_clients::bucket_name bucket);
150+
145151
private:
146152
static constexpr size_t num_default_objects = 5;
147153
bool _cancelled{false};

tests/rptest/tests/self_test_test.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -83,14 +83,13 @@ def test_self_test(self):
8383

8484
cloud_results = [r for r in reports if r['test_type'] == 'cloud']
8585

86-
read_tests = ['List', 'Head', 'Get']
87-
write_tests = ['Put', 'Delete', 'Plural Delete']
86+
cloudcheck_tests = [
87+
'List', 'Head', 'Get', 'Put', 'Delete', 'Plural Delete',
88+
'Conditional Puts and Gets'
89+
]
8890

89-
num_expected_cloud_storage_read_tests = num_nodes * len(read_tests)
90-
num_expected_cloud_storage_write_tests = num_nodes * len(write_tests)
91-
assert len(
92-
cloud_results
93-
) == num_expected_cloud_storage_write_tests + num_expected_cloud_storage_read_tests
91+
num_expected_cloud_storage_tests = num_nodes * len(cloudcheck_tests)
92+
assert len(cloud_results) == num_expected_cloud_storage_tests
9493

9594
# Ensure no other result sets exist
9695
assert len(disk_results) + len(network_results) + len(

0 commit comments

Comments
 (0)