Skip to content

Commit 2b04ff8

Browse files
committed
cloud_storage/tests: use better containers in s3_imposter
Saw an instance where we failed to allocate ~800KB because one of the containers in s3_imposters grew to be large. It isn't unreasonable to expect a test to bombard s3_imposter with requests, especially when trying to stress certain subsystems. This replaces the containers in s3_imposter with chunked_vector and absl::btree_multimap, which are more aligned with best practices of our codebase.
1 parent 545b890 commit 2b04ff8

File tree

13 files changed

+107
-97
lines changed

13 files changed

+107
-97
lines changed

src/v/cloud_io/tests/BUILD

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,11 @@ redpanda_test_cc_library(
3434
"//src/v/base",
3535
"//src/v/cloud_storage_clients",
3636
"//src/v/config",
37+
"//src/v/container:chunked_vector",
3738
"//src/v/http:utils",
3839
"//src/v/http/tests:utils",
3940
"//src/v/utils:uuid",
41+
"@abseil-cpp//absl/container:btree",
4042
"@abseil-cpp//absl/container:flat_hash_set",
4143
"@seastar",
4244
],

src/v/cloud_io/tests/s3_imposter.cc

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ uint16_t unit_test_httpd_port_number() { return 4442; }
3939
namespace {
4040

4141
using expectation_map_t
42-
= std::map<ss::sstring, s3_imposter_fixture::expectation>;
42+
= absl::btree_map<ss::sstring, s3_imposter_fixture::expectation>;
4343

4444
// Takes the input map of keys to expectations and returns a stringified XML
4545
// corresponding to the appropriate S3 response.
@@ -49,7 +49,7 @@ ss::sstring list_objects_resp(
4949
ss::sstring delimiter,
5050
std::optional<size_t> max_keys_opt,
5151
std::optional<ss::sstring> continuation_token_opt) {
52-
std::map<ss::sstring, size_t> content_key_to_size;
52+
absl::btree_map<ss::sstring, size_t> content_key_to_size;
5353
std::set<ss::sstring> common_prefixes;
5454
// Filter by prefix and group by the substring between the prefix and first
5555
// delimiter.
@@ -165,7 +165,7 @@ uint64_t string_view_to_ul(std::string_view sv) {
165165

166166
struct s3_imposter_fixture::content_handler {
167167
content_handler(
168-
const std::vector<s3_imposter_fixture::expectation>& exp,
168+
const chunked_vector<s3_imposter_fixture::expectation>& exp,
169169
s3_imposter_fixture& imp,
170170
std::optional<absl::flat_hash_set<ss::sstring>> headers_to_store
171171
= std::nullopt)
@@ -176,13 +176,13 @@ struct s3_imposter_fixture::content_handler {
176176
}
177177
}
178178

179-
void insert(const std::vector<s3_imposter_fixture::expectation>& list) {
179+
void insert(const chunked_vector<s3_imposter_fixture::expectation>& list) {
180180
for (const auto& exp : list) {
181181
expectations.insert(std::make_pair(exp.url, exp));
182182
}
183183
}
184184

185-
void remove(const std::vector<ss::sstring>& urls) {
185+
void remove(const chunked_vector<ss::sstring>& urls) {
186186
for (const auto& u : urls) {
187187
expectations.erase(u);
188188
}
@@ -474,15 +474,14 @@ uint16_t s3_imposter_fixture::httpd_port_number() {
474474
return unit_test_httpd_port_number();
475475
}
476476

477-
const std::vector<http_test_utils::request_info>&
477+
const chunked_vector<http_test_utils::request_info>&
478478
s3_imposter_fixture::get_requests() const {
479479
return _requests;
480480
}
481481

482-
std::vector<http_test_utils::request_info> s3_imposter_fixture::get_requests(
482+
chunked_vector<http_test_utils::request_info> s3_imposter_fixture::get_requests(
483483
s3_imposter_fixture::req_pred_t predicate) const {
484-
std::vector<http_test_utils::request_info> matching_requests;
485-
matching_requests.reserve(_requests.size());
484+
chunked_vector<http_test_utils::request_info> matching_requests;
486485
std::copy_if(
487486
_requests.cbegin(),
488487
_requests.cend(),
@@ -491,13 +490,13 @@ std::vector<http_test_utils::request_info> s3_imposter_fixture::get_requests(
491490
return matching_requests;
492491
}
493492

494-
const std::multimap<ss::sstring, http_test_utils::request_info>&
493+
const absl::btree_multimap<ss::sstring, http_test_utils::request_info>&
495494
s3_imposter_fixture::get_targets() const {
496495
return _targets;
497496
}
498497

499498
void s3_imposter_fixture::set_expectations_and_listen(
500-
std::vector<s3_imposter_fixture::expectation> expectations,
499+
chunked_vector<s3_imposter_fixture::expectation> expectations,
501500
std::optional<absl::flat_hash_set<ss::sstring>> headers_to_store,
502501
std::set<ss::sstring> content_type_overrides) {
503502
const ss::sstring url_prefix = "/" + url_base();
@@ -522,7 +521,7 @@ void s3_imposter_fixture::set_expectations_and_listen(
522521
}
523522

524523
void s3_imposter_fixture::add_expectations(
525-
std::vector<s3_imposter_fixture::expectation> expectations) {
524+
chunked_vector<s3_imposter_fixture::expectation> expectations) {
526525
vassert(_content_handler != nullptr, "Imposter is not initialized");
527526
const ss::sstring url_prefix = "/" + url_base();
528527
for (auto& expectation : expectations) {
@@ -532,7 +531,8 @@ void s3_imposter_fixture::add_expectations(
532531
_content_handler->insert(expectations);
533532
}
534533

535-
void s3_imposter_fixture::remove_expectations(std::vector<ss::sstring> urls) {
534+
void s3_imposter_fixture::remove_expectations(
535+
chunked_vector<ss::sstring> urls) {
536536
vassert(_content_handler != nullptr, "Imposter is not initialized");
537537
const ss::sstring url_prefix = "/" + url_base();
538538
for (auto& url : urls) {
@@ -561,7 +561,7 @@ ss::sstring s3_imposter_fixture::url_base() const {
561561

562562
void s3_imposter_fixture::set_routes(
563563
ss::httpd::routes& r,
564-
const std::vector<s3_imposter_fixture::expectation>& expectations,
564+
const chunked_vector<s3_imposter_fixture::expectation>& expectations,
565565
std::optional<absl::flat_hash_set<ss::sstring>> headers_to_store,
566566
std::set<ss::sstring> content_type_overrides) {
567567
using namespace ss::httpd;
@@ -626,9 +626,9 @@ cloud_storage_clients::http_byte_range parse_byte_header(std::string_view s) {
626626
string_view_to_ul(bytes_value.substr(split_at + 1)));
627627
}
628628

629-
std::vector<cloud_storage_clients::object_key>
629+
chunked_vector<cloud_storage_clients::object_key>
630630
keys_from_delete_objects_request(const http_test_utils::request_info& req) {
631-
std::vector<cloud_storage_clients::object_key> keys;
631+
chunked_vector<cloud_storage_clients::object_key> keys;
632632

633633
auto buffer_stream = std::istringstream{std::string{req.content}};
634634
boost::property_tree::ptree tree;
@@ -643,9 +643,10 @@ keys_from_delete_objects_request(const http_test_utils::request_info& req) {
643643
return keys;
644644
}
645645

646-
std::vector<std::pair<ss::sstring, cloud_storage_clients::object_key>>
646+
chunked_vector<std::pair<ss::sstring, cloud_storage_clients::object_key>>
647647
keys_from_batch_delete_request(const http_test_utils::request_info& req) {
648-
std::vector<std::pair<ss::sstring, cloud_storage_clients::object_key>> keys;
648+
chunked_vector<std::pair<ss::sstring, cloud_storage_clients::object_key>>
649+
keys;
649650
auto buffer_stream = std::istringstream{std::string{req.content}};
650651

651652
// crudely iterate over request lines, stripping out object keys

src/v/cloud_io/tests/s3_imposter.h

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@
1010

1111
#pragma once
1212

13+
#include "absl/container/btree_map.h"
1314
#include "absl/container/flat_hash_set.h"
1415
#include "base/seastarx.h"
1516
#include "cloud_storage_clients/client.h"
1617
#include "config/configuration.h"
18+
#include "container/chunked_vector.h"
1719
#include "http/tests/registered_urls.h"
1820
#include "utils/uuid.h"
1921

@@ -22,9 +24,6 @@
2224
#include <seastar/core/sstring.hh>
2325
#include <seastar/http/httpd.hh>
2426

25-
#include <map>
26-
#include <vector>
27-
2827
inline cloud_storage_clients::bucket_name random_test_bucket_name() {
2928
return cloud_storage_clients::bucket_name{
3029
"test-bucket-" + ss::sstring{uuid_t::create()}};
@@ -81,30 +80,30 @@ class s3_imposter_fixture {
8180
/// to null but there was PUT call that sent some data, subsequent GET call
8281
/// will retrieve this data.
8382
void set_expectations_and_listen(
84-
std::vector<expectation> expectations,
83+
chunked_vector<expectation> expectations,
8584
std::optional<absl::flat_hash_set<ss::sstring>> headers_to_store
8685
= std::nullopt,
8786
std::set<ss::sstring> content_type_overrides = {});
8887

8988
/// Update expectations for the REST API.
90-
void add_expectations(std::vector<expectation> expectations);
91-
void remove_expectations(std::vector<ss::sstring> urls);
89+
void add_expectations(chunked_vector<expectation> expectations);
90+
void remove_expectations(chunked_vector<ss::sstring> urls);
9291

9392
/// Get object from S3 or nullopt if it doesn't exist
9493
std::optional<ss::sstring> get_object(const ss::sstring& url) const;
9594

9695
/// Access all http requests ordered by time
97-
const std::vector<http_test_utils::request_info>& get_requests() const;
96+
const chunked_vector<http_test_utils::request_info>& get_requests() const;
9897

9998
using req_pred_t
10099
= std::function<bool(const http_test_utils::request_info&)>;
101100

102101
/// Access http requests matching the given predicate
103-
std::vector<http_test_utils::request_info>
102+
chunked_vector<http_test_utils::request_info>
104103
get_requests(req_pred_t predicate) const;
105104

106105
/// Access all http requests ordered by target url
107-
const std::multimap<ss::sstring, http_test_utils::request_info>&
106+
const absl::btree_multimap<ss::sstring, http_test_utils::request_info>&
108107
get_targets() const;
109108

110109
cloud_storage_clients::s3_configuration get_configuration();
@@ -127,7 +126,7 @@ class s3_imposter_fixture {
127126
private:
128127
void set_routes(
129128
ss::httpd::routes& r,
130-
const std::vector<expectation>& expectations,
129+
const chunked_vector<expectation>& expectations,
131130
std::optional<absl::flat_hash_set<ss::sstring>> headers_to_store
132131
= std::nullopt,
133132
std::set<ss::sstring> content_type_overrides = {});
@@ -140,16 +139,16 @@ class s3_imposter_fixture {
140139
ss::shared_ptr<content_handler> _content_handler;
141140
std::unique_ptr<ss::httpd::handler_base> _handler;
142141
/// Contains saved requests
143-
std::vector<http_test_utils::request_info> _requests;
142+
chunked_vector<http_test_utils::request_info> _requests;
144143
/// Contains all accessed target urls
145-
std::multimap<ss::sstring, http_test_utils::request_info> _targets;
144+
absl::btree_multimap<ss::sstring, http_test_utils::request_info> _targets;
146145

147146
/// Whether or not to search through expectations for content when handling
148147
/// a list GET request.
149148
bool _search_on_get_list{true};
150149

151-
std::vector<req_pred_t> _fail_request_if;
152-
std::vector<http_test_utils::response> _failure_response;
150+
chunked_vector<req_pred_t> _fail_request_if;
151+
chunked_vector<http_test_utils::response> _failure_response;
153152
};
154153

155154
class enable_cloud_storage_fixture {
@@ -163,8 +162,8 @@ class enable_cloud_storage_fixture {
163162

164163
cloud_storage_clients::http_byte_range parse_byte_header(std::string_view s);
165164

166-
std::vector<cloud_storage_clients::object_key>
165+
chunked_vector<cloud_storage_clients::object_key>
167166
keys_from_delete_objects_request(const http_test_utils::request_info&);
168167

169-
std::vector<std::pair<ss::sstring, cloud_storage_clients::object_key>>
168+
chunked_vector<std::pair<ss::sstring, cloud_storage_clients::object_key>>
170169
keys_from_batch_delete_request(const http_test_utils::request_info&);

src/v/cloud_storage/tests/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ redpanda_test_cc_library(
4343
"//src/v/cloud_storage:types",
4444
"//src/v/cluster",
4545
"//src/v/cluster/cloud_metadata/tests:manual_mixin",
46+
"//src/v/container:chunked_vector",
4647
"//src/v/json",
4748
"//src/v/kafka/server/tests:kafka_test_utils",
4849
"//src/v/model",

src/v/cloud_storage/tests/async_manifest_view_test.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ class async_manifest_view_fixture
244244
}
245245
}
246246

247-
void listen() { set_expectations_and_listen(_expectations); }
247+
void listen() { set_expectations_and_listen(std::move(_expectations)); }
248248

249249
void collect_segments_to(std::vector<segment_meta>& meta) {
250250
all_segments = std::ref(meta);
@@ -330,7 +330,7 @@ class async_manifest_view_fixture
330330
retry_chain_logger ctxlog;
331331
partition_probe probe;
332332
async_manifest_view view;
333-
std::vector<expectation> _expectations;
333+
chunked_vector<expectation> _expectations;
334334
std::vector<model::offset> spillover_start_offsets;
335335
model::offset _last_spillover_offset;
336336
std::optional<std::reference_wrapper<std::vector<segment_meta>>>

src/v/cloud_storage/tests/remote_partition_test.cc

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -421,7 +421,7 @@ FIXTURE_TEST(test_overlapping_segments, cloud_storage_fixture) {
421421
.url = prefixed_partition_manifest_json_path(
422422
manifest.get_ntp(), manifest.get_revision_id()),
423423
.body = body};
424-
set_expectations_and_listen(expectations);
424+
set_expectations_and_listen(std::move(expectations));
425425
BOOST_REQUIRE(check_scan(*this, kafka::offset(0), 9));
426426
}
427427

@@ -2306,7 +2306,7 @@ FIXTURE_TEST(test_out_of_range_query, cloud_storage_fixture) {
23062306
cloud_storage::partition_manifest manifest(manifest_ntp, manifest_revision);
23072307

23082308
auto expectations = make_imposter_expectations(manifest, segments);
2309-
set_expectations_and_listen(expectations);
2309+
set_expectations_and_listen(std::move(expectations));
23102310

23112311
// Advance start offset as-if archiver did apply retention but didn't
23122312
// run GC yet (the clean offset is not updated).
@@ -2326,11 +2326,11 @@ FIXTURE_TEST(test_out_of_range_query, cloud_storage_fixture) {
23262326
ostr.str());
23272327

23282328
auto manifest_url = manifest.get_manifest_path(path_provider)().string();
2329-
remove_expectations({manifest_url});
2330-
add_expectations({
2331-
cloud_storage_fixture::expectation{
2332-
.url = manifest_url, .body = serialize_manifest(manifest)},
2333-
});
2329+
remove_expectations(chunked_vector<ss::sstring>::single(manifest_url));
2330+
add_expectations(
2331+
chunked_vector<cloud_storage_fixture::expectation>::single(
2332+
cloud_storage_fixture::expectation{
2333+
.url = manifest_url, .body = serialize_manifest(manifest)}));
23342334

23352335
auto base = segments[0].base_offset;
23362336
auto max = segments[segments.size() - 1].max_offset;
@@ -2376,7 +2376,7 @@ FIXTURE_TEST(test_out_of_range_spillover_query, cloud_storage_fixture) {
23762376
cloud_storage::partition_manifest manifest(manifest_ntp, manifest_revision);
23772377

23782378
auto expectations = make_imposter_expectations(manifest, segments);
2379-
set_expectations_and_listen(expectations);
2379+
set_expectations_and_listen(std::move(expectations));
23802380

23812381
for (int i = 0; i < 2; i++) {
23822382
spillover_manifest spm(manifest_ntp, manifest_revision);
@@ -2400,10 +2400,12 @@ FIXTURE_TEST(test_out_of_range_spillover_query, cloud_storage_fixture) {
24002400

24012401
auto s_data = spm.serialize().get();
24022402
auto buf = s_data.stream.read_exactly(s_data.size_bytes).get();
2403-
add_expectations({cloud_storage_fixture::expectation{
2404-
.url = spm.get_manifest_path(path_provider)().string(),
2405-
.body = ss::sstring(buf.begin(), buf.end()),
2406-
}});
2403+
add_expectations(
2404+
chunked_vector<cloud_storage_fixture::expectation>::single(
2405+
cloud_storage_fixture::expectation{
2406+
.url = spm.get_manifest_path(path_provider)().string(),
2407+
.body = ss::sstring(buf.begin(), buf.end()),
2408+
}));
24072409
}
24082410

24092411
// Advance start offset as-if archiver did apply retention but didn't
@@ -2430,11 +2432,11 @@ FIXTURE_TEST(test_out_of_range_spillover_query, cloud_storage_fixture) {
24302432
ostr.str());
24312433

24322434
auto manifest_url = manifest.get_manifest_path(path_provider)().string();
2433-
remove_expectations({manifest_url});
2434-
add_expectations({
2435-
cloud_storage_fixture::expectation{
2436-
.url = manifest_url, .body = serialize_manifest(manifest)},
2437-
});
2435+
remove_expectations(chunked_vector<ss::sstring>::single(manifest_url));
2436+
add_expectations(
2437+
chunked_vector<cloud_storage_fixture::expectation>::single(
2438+
cloud_storage_fixture::expectation{
2439+
.url = manifest_url, .body = serialize_manifest(manifest)}));
24382440

24392441
auto base = segments[0].base_offset;
24402442
auto max = segments[segments.size() - 1].max_offset;
@@ -2517,11 +2519,11 @@ FIXTURE_TEST(test_out_of_range_spillover_query, cloud_storage_fixture) {
25172519
manifest.get_manifest_path(path_provider),
25182520
ostr.str());
25192521

2520-
remove_expectations({manifest_url});
2521-
add_expectations({
2522-
cloud_storage_fixture::expectation{
2523-
.url = manifest_url, .body = serialize_manifest(manifest)},
2524-
});
2522+
remove_expectations(chunked_vector<ss::sstring>::single(manifest_url));
2523+
add_expectations(
2524+
chunked_vector<cloud_storage_fixture::expectation>::single(
2525+
cloud_storage_fixture::expectation{
2526+
.url = manifest_url, .body = serialize_manifest(manifest)}));
25252527

25262528
// Still can't query from the base offset.
25272529
BOOST_REQUIRE_EXCEPTION(

src/v/cloud_storage/tests/remote_test.cc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -835,7 +835,7 @@ TEST_P(all_types_remote_fixture, test_delete_objects_multiple_batches) {
835835
auto result
836836
= remote.local().delete_objects(bucket_name, to_delete, fib).get();
837837
ASSERT_EQ(cloud_storage::upload_result::success, result);
838-
auto requests = get_requests();
838+
const auto& requests = get_requests();
839839
ASSERT_EQ(requests.size(), 3);
840840

841841
std::vector<cloud_storage_clients::object_key> deleted_keys;
@@ -875,7 +875,7 @@ TEST_P(
875875
auto result
876876
= remote.local().delete_objects(bucket_name, to_delete, fib).get();
877877
ASSERT_EQ(cloud_storage::upload_result::failed, result);
878-
auto requests = get_requests();
878+
const auto& requests = get_requests();
879879
ASSERT_EQ(requests.size(), 3);
880880

881881
std::vector<cloud_storage_clients::object_key> deleted_keys;
@@ -1354,7 +1354,7 @@ TEST_P(all_types_remote_fixture, test_get_object) {
13541354
.payload = buf})
13551355
.get();
13561356

1357-
const auto requests = get_requests();
1357+
const auto& requests = get_requests();
13581358
ASSERT_EQ(requests.size(), 2);
13591359

13601360
const auto last_request = requests.back();

0 commit comments

Comments
 (0)