From a3d07a468fa8716fc30f5afe1fc287b40e018bec Mon Sep 17 00:00:00 2001 From: Ernest Zaslavsky Date: Wed, 29 Apr 2026 09:50:51 +0300 Subject: [PATCH] test: verify sstable stream sink writes to object storage Add test_stream_sink_write_local, test_stream_sink_write_s3, and test_stream_sink_write_gs that exercise sstable_stream_sink_impl:: output() with local filesystem and S3/GCS backends respectively. This is the code path used by stream_blob during tablet migration streaming. The local variant ensures the fix did not regress filesystem storage. The S3/GCS variants would throw std::logic_error before the fix. --- test/boost/file_stream_test.cc | 61 ++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/test/boost/file_stream_test.cc b/test/boost/file_stream_test.cc index 4bfff9e5c0..5a25889b70 100644 --- a/test/boost/file_stream_test.cc +++ b/test/boost/file_stream_test.cc @@ -12,8 +12,17 @@ #include "test/lib/log.hh" #include "test/lib/sstable_utils.hh" #include "sstables/exceptions.hh" +#include "sstables/sstables.hh" +#include "utils/overloaded_functor.hh" +#include "schema/schema_builder.hh" +#include "test/lib/random_utils.hh" +#include "test/lib/sstable_test_env.hh" +#include "test/lib/test_utils.hh" +#include "test/lib/gcs_fixture.hh" #include +#include +#include #include #include #include @@ -533,3 +542,55 @@ SEASTAR_THREAD_TEST_CASE(test_sstable_stream_digest_mismatched_compressed) { SEASTAR_THREAD_TEST_CASE(test_sstable_stream_digest_mismatched_uncompressed) { test_sstable_stream(compress_sstable::no, corrupt_digest_component, "Digest mismatch"); } + +// Exercises the sstable_stream_sink_impl::output() path with local and object storage. +// Before the fix, output() used open_file() which for S3 returns a readable_file +// (read-only). Writing through it threw std::logic_error("unsupported operation +// on s3 readable file"), breaking tablet migration streaming entirely. +static future<> test_stream_sink_write(sstables::test_env_config cfg) { + return sstables::test_env::do_with_async([](sstables::test_env& env) { + using namespace sstables; + + auto s = schema_builder(this_smp_shard_count(), "ks", "cf") + .with_column("pk", utf8_type, column_kind::partition_key) + .with_column("v", utf8_type) + .build(); + + auto version = get_highest_sstable_version(); + auto format = sstable::format_types::big; + auto generation = env.new_generation(); + auto& mgr = env.manager(); + auto s_opts = env.get_storage_options(); + + std::visit(overloaded_functor { + [&env] (data_dictionary::storage_options::local& o) { o.dir = env.tempdir().path().native(); }, + [&s] (data_dictionary::storage_options::object_storage& o) { o.location = s->id(); }, + }, s_opts.value); + + auto toc_basename = sstable::component_basename( + s->ks_name(), s->cf_name(), version, generation, format, component_type::TOC); + + auto sink = create_stream_sink(s, mgr, s_opts, sstable_state::normal, toc_basename, {}); + + // output() would succeed before the fix (wrapping the read-only file into a stream), + // but the write below would throw std::logic_error("unsupported operation on s3 readable file"). + auto out = sink->output(file_open_options{}, file_output_stream_options{}).get(); + + auto data = tests::random::get_bytes(4096); + out.write(reinterpret_cast(data.data()), data.size()).get(); + out.flush().get(); + out.close().get(); + }, std::move(cfg)); +} + +SEASTAR_TEST_CASE(test_stream_sink_write_local) { + return test_stream_sink_write(sstables::test_env_config{}); +} + +SEASTAR_TEST_CASE(test_stream_sink_write_s3, *boost::unit_test::precondition(tests::has_scylla_test_env)) { + return test_stream_sink_write(sstables::test_env_config{ .storage = make_test_object_storage_options("S3") }); +} + +SEASTAR_FIXTURE_TEST_CASE(test_stream_sink_write_gs, gcs_fixture, *tests::check_run_test_decorator("ENABLE_GCP_STORAGE_TEST", true)) { + return test_stream_sink_write(sstables::test_env_config{ .storage = make_test_object_storage_options("GS") }); +}