Skip to content

Commit 1b59d90

Browse files
Merge pull request #13867 from vbotbuildovich/backport-pr-13740-v23.2.x-999
[v23.2.x] cloud_storage: serialize uint64 to topic manifest
2 parents 8a233d7 + 5cc8008 commit 1b59d90

File tree

2 files changed

+118
-2
lines changed

2 files changed

+118
-2
lines changed

src/v/cloud_storage/tests/topic_manifest_test.cc

+76
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@
2626
#include <boost/test/unit_test.hpp>
2727

2828
#include <chrono>
29+
#include <limits>
30+
#include <ratio>
31+
#include <sstream>
2932
#include <string>
3033
#include <string_view>
3134
#include <system_error>
@@ -112,6 +115,22 @@ static constexpr std::string_view wrong_compaction_strategy = R"json({
112115
"retention_duration": 36000000000
113116
})json";
114117

118+
static constexpr std::string_view negative_properties_manifest = R"json({
119+
"version": 1,
120+
"namespace": "negative-test-namespace",
121+
"topic": "full-test-topic",
122+
"partition_count": 64,
123+
"replication_factor": 6,
124+
"revision_id": 1,
125+
"compression": "snappy",
126+
"cleanup_policy_bitflags": "compact,delete",
127+
"compaction_strategy": "offset",
128+
"timestamp_type": "LogAppendTime",
129+
"segment_size": -1234,
130+
"retention_bytes": -42342,
131+
"retention_duration": -36000000000
132+
})json";
133+
115134
inline ss::input_stream<char> make_manifest_stream(std::string_view json) {
116135
iobuf i;
117136
i.append(json.data(), json.size());
@@ -224,6 +243,46 @@ SEASTAR_THREAD_TEST_CASE(full_config_update_all_fields_correct) {
224243
std::chrono::milliseconds(36000000000));
225244
}
226245

246+
SEASTAR_THREAD_TEST_CASE(topic_manifest_min_serialization) {
247+
manifest_topic_configuration min_cfg{cfg};
248+
min_cfg.properties.retention_bytes = tristate<size_t>(
249+
std::numeric_limits<size_t>::min());
250+
min_cfg.properties.retention_duration = tristate<std::chrono::milliseconds>(
251+
std::chrono::milliseconds::min());
252+
min_cfg.properties.segment_size = std::make_optional(
253+
std::numeric_limits<size_t>::min());
254+
topic_manifest m(min_cfg, model::initial_revision_id{0});
255+
auto [is, size] = m.serialize().get();
256+
iobuf buf;
257+
auto os = make_iobuf_ref_output_stream(buf);
258+
ss::copy(is, os).get();
259+
260+
auto rstr = make_iobuf_input_stream(std::move(buf));
261+
topic_manifest restored;
262+
restored.update(std::move(rstr)).get();
263+
BOOST_REQUIRE(m == restored);
264+
}
265+
266+
SEASTAR_THREAD_TEST_CASE(topic_manifest_max_serialization) {
267+
manifest_topic_configuration max_cfg{cfg};
268+
max_cfg.properties.retention_bytes = tristate<size_t>(
269+
std::numeric_limits<size_t>::max());
270+
max_cfg.properties.retention_duration = tristate<std::chrono::milliseconds>(
271+
std::chrono::milliseconds::max());
272+
max_cfg.properties.segment_size = std::make_optional(
273+
std::numeric_limits<size_t>::max());
274+
topic_manifest m(max_cfg, model::initial_revision_id{0});
275+
auto [is, size] = m.serialize().get();
276+
iobuf buf;
277+
auto os = make_iobuf_ref_output_stream(buf);
278+
ss::copy(is, os).get();
279+
280+
auto rstr = make_iobuf_input_stream(std::move(buf));
281+
topic_manifest restored;
282+
restored.update(std::move(rstr)).get();
283+
BOOST_REQUIRE(m == restored);
284+
}
285+
227286
SEASTAR_THREAD_TEST_CASE(missing_required_fields_throws) {
228287
topic_manifest m;
229288
BOOST_REQUIRE_EXCEPTION(
@@ -294,3 +353,20 @@ SEASTAR_THREAD_TEST_CASE(update_non_empty_manifest) {
294353

295354
BOOST_REQUIRE(m == restored);
296355
}
356+
357+
SEASTAR_THREAD_TEST_CASE(test_negative_property_manifest) {
358+
topic_manifest m(cfg, model::initial_revision_id(0));
359+
m.update(make_manifest_stream(negative_properties_manifest)).get();
360+
auto tp_cfg = m.get_topic_config();
361+
BOOST_REQUIRE(tp_cfg.has_value());
362+
BOOST_REQUIRE_EQUAL(64, tp_cfg->partition_count);
363+
BOOST_REQUIRE_EQUAL(6, tp_cfg->replication_factor);
364+
auto tp_props = tp_cfg->properties;
365+
BOOST_REQUIRE(tp_props.retention_duration.has_optional_value());
366+
BOOST_REQUIRE_EQUAL(
367+
tp_props.retention_duration.value().count(), -36000000000);
368+
369+
// The usigned types that were passed in negative values shouldn't be set.
370+
BOOST_REQUIRE(tp_props.retention_bytes.is_disabled());
371+
BOOST_REQUIRE(!tp_props.segment_size.has_value());
372+
}

src/v/cloud_storage/topic_manifest.cc

+42-2
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,46 @@ struct topic_manifest_handler
9494
}
9595
}
9696

97+
bool Int(int i) { return Int64(i); }
98+
99+
bool Int64(int64_t i) {
100+
if (i >= 0) {
101+
// Should only be called when negative, but doesn't hurt to just
102+
// defer to the unsigned variant.
103+
return Uint64(i);
104+
}
105+
switch (_state) {
106+
case state::expect_value:
107+
if (_key == "version") {
108+
_version = i;
109+
} else if (_key == "partition_count") {
110+
_partition_count = i;
111+
} else if (_key == "replication_factor") {
112+
_replication_factor = i;
113+
} else if (_key == "revision_id") {
114+
_revision_id = model::initial_revision_id{i};
115+
} else if (_key == "segment_size") {
116+
// NOTE: segment size and retention bytes are unsigned, but
117+
// older versions of Redpanda could serialize them as negative.
118+
// Just leave them empty.
119+
_properties.segment_size = std::nullopt;
120+
} else if (_key == "retention_bytes") {
121+
_properties.retention_bytes = tristate<size_t>{};
122+
} else if (_key == "retention_duration") {
123+
_properties.retention_duration
124+
= tristate<std::chrono::milliseconds>(
125+
std::chrono::milliseconds(i));
126+
} else {
127+
return false;
128+
}
129+
_state = state::expect_key;
130+
return true;
131+
case state::expect_manifest_start:
132+
case state::expect_key:
133+
return false;
134+
}
135+
}
136+
97137
bool Uint(unsigned u) { return Uint64(u); }
98138

99139
bool Uint64(uint64_t u) {
@@ -375,7 +415,7 @@ void topic_manifest::serialize(std::ostream& out) const {
375415
}
376416
w.Key("segment_size");
377417
if (_topic_config->properties.segment_size.has_value()) {
378-
w.Int64(*_topic_config->properties.segment_size);
418+
w.Uint64(*_topic_config->properties.segment_size);
379419
} else {
380420
w.Null();
381421
}
@@ -388,7 +428,7 @@ void topic_manifest::serialize(std::ostream& out) const {
388428
if (!_topic_config->properties.retention_bytes.is_disabled()) {
389429
w.Key("retention_bytes");
390430
if (_topic_config->properties.retention_bytes.has_optional_value()) {
391-
w.Int64(_topic_config->properties.retention_bytes.value());
431+
w.Uint64(_topic_config->properties.retention_bytes.value());
392432
} else {
393433
w.Null();
394434
}

0 commit comments

Comments
 (0)