Merge 'test: add test_sstable_clone_preserves_staging_state' from Benny Halevy

Add a test that verifies filesystem_storage::clone preserves the sstable
state: an sstable in staging is cloned to a new generation, the clone is
re-loaded from the staging directory, and its state is asserted to still
be staging.

The change proves that https://scylladb.atlassian.net/browse/SCYLLADB-1205
is invalid, and can be closed.

* No functional change and no backport needed

Closes scylladb/scylladb#29209

* github.com:scylladb/scylladb:
  test: add test_sstable_clone_preserves_staging_state
  test: derive sstable state from directory in test_env::make_sstable
  sstables: log debug message in filesystem_storage::clone
This commit is contained in:
Pavel Emelyanov
2026-04-07 17:02:04 +03:00
3 changed files with 51 additions and 1 deletions

View File

@@ -449,6 +449,7 @@ future<> filesystem_storage::snapshot(const sstable& sst, sstring name) const {
co_await create_links_common(sst, snapshot_dir.native(), sst._generation, link_mode::default_mode);
}
future<> filesystem_storage::clone(const sstable& sst, generation_type gen, bool leave_unsealed) const {
sstlog.debug("Cloning {} dir={} generation={} leave_unsealed={}", sst.get_filename(), _dir.path().native(), gen, leave_unsealed);
co_await create_links_common(sst, _dir.path().native(), std::move(gen), leave_unsealed ? link_mode::leave_unsealed : link_mode::default_mode);
}

View File

@@ -13,6 +13,8 @@
#include "utils/lister.hh"
#include "test/lib/tmpdir.hh"
#include "test/lib/sstable_test_env.hh"
#include "test/lib/sstable_utils.hh"
#include "test/lib/simple_schema.hh"
#include "sstable_test.hh"
using namespace sstables;
@@ -140,3 +142,37 @@ SEASTAR_THREAD_TEST_CASE(test_sstable_move_exists_failure) {
dst_sst->close_files().get();
BOOST_REQUIRE_THROW(test(src_sst).move_to_new_dir(new_dir, gen_2).get(), malformed_sstable_exception);
}
// Test that filesystem_storage::clone preserves the input sstable state.
// An sstable in staging is cloned and re-loaded; the re-loaded clone should still
// be in staging (i.e. state() == staging and requires_view_building() == true).
SEASTAR_THREAD_TEST_CASE(test_sstable_clone_preserves_staging_state) {
auto scf = make_sstable_compressor_factory_for_tests_in_thread();
test_env env({}, *scf);
auto stop_env = defer([&env] { env.stop().get(); });
simple_schema ss;
auto schema = ss.schema();
// Create an sstable in normal state.
auto sst = make_sstable_containing(env.make_sst_factory(schema), {ss.new_mutation("key1")});
// Move it to staging state.
sst->change_state(sstable_state::staging).get();
BOOST_REQUIRE(sst->state() == sstable_state::staging);
// Clone the staging sstable to a new generation. The clone should land in the
// same (staging) directory as the source.
auto clone_gen = env.new_generation();
sst->clone(clone_gen).get();
// Load the cloned sstable from the staging directory. We use the storage
// prefix of the source (which is the staging sub-directory) so that the
// loader can find the files, mirroring what distributed_loader does.
auto staging_dir = sst->get_storage().prefix();
auto cloned_sst = env.reusable_sst(schema, staging_dir, clone_gen).get();
// Assert that the cloned sstable preserves the staging state.
BOOST_REQUIRE(cloned_sst->state() == sstable_state::staging);
BOOST_REQUIRE(cloned_sst->requires_view_building());
}

View File

@@ -23,6 +23,7 @@
#include "utils/assert.hh"
#include "utils/overloaded_functor.hh"
#include <boost/program_options.hpp>
#include <filesystem>
#include <iostream>
#include <fmt/ranges.h>
#include <seastar/util/defer.hh>
@@ -390,12 +391,24 @@ test_env::make_sstable(schema_ptr schema, sstring dir, sstables::generation_type
size_t buffer_size, db_clock::time_point now) {
// FIXME -- most of the callers work with _impl->dir's path, so
// test_env can initialize the .dir/.prefix only once, when constructed
sstables::sstable_state state = sstables::sstable_state::normal;
auto filename = std::filesystem::path(dir).filename().native();
if (filename == sstables::staging_dir) {
state = sstables::sstable_state::staging;
} else if (filename == sstables::upload_dir) {
state = sstables::sstable_state::upload;
} else if (filename == sstables::quarantine_dir) {
state = sstables::sstable_state::quarantine;
}
if (state != sstables::sstable_state::normal) {
dir = std::filesystem::path(dir).parent_path().native();
}
auto storage = _impl->storage;
std::visit(overloaded_functor {
[&dir] (data_dictionary::storage_options::local& o) { o.dir = dir; },
[&schema] (data_dictionary::storage_options::object_storage& o) { o.location = schema->id(); },
}, storage.value);
return _impl->mgr.make_sstable(std::move(schema), storage, generation, sstables::sstable_state::normal, v, f, now, default_io_error_handler_gen(), buffer_size);
return _impl->mgr.make_sstable(std::move(schema), storage, generation, state, v, f, now, default_io_error_handler_gen(), buffer_size);
}
shared_sstable