From d3aef2c1a5102087fefd8594d1eabeffa58da9ad Mon Sep 17 00:00:00 2001 From: Glauber Costa Date: Wed, 14 Oct 2015 12:38:37 +0200 Subject: [PATCH] database: support clear snapshot This allows for us to delete an existing snapshot. It works at the column family level, and removing it from the list of keyspace snapshots needs to happen only when all CFs are processed. Therefore, that is provided as a separate operation. The filesystem code is a bit ugly: it can be made better by making our file lister more generic. First step would be to call it walker, not lister... For now, we'll use the fact that there are mostly two levels in the snapshot hierarchy to our advantage, and avoid a full recursion - using the same lambda for all calls would require us to provide a separate class to handle the state, that's part of making this generic. Signed-off-by: Glauber Costa --- database.cc | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++ database.hh | 1 + 2 files changed, 60 insertions(+) diff --git a/database.cc b/database.cc index 6300f7c77d..f7f8588896 100644 --- a/database.cc +++ b/database.cc @@ -1754,6 +1754,65 @@ future<> column_family::snapshot(sstring name) { }); } +enum class missing { no, yes }; +static missing +file_missing(future<> f) { + try { + f.get(); + return missing::no; + } catch (std::system_error& e) { + if (e.code() != std::error_code(ENOENT, std::system_category())) { + throw; + } + return missing::yes; + } +} + +future<> column_family::clear_snapshot(sstring tag) { + sstring jsondir = _config.datadir + "/snapshots/"; + sstring parent = _config.datadir; + if (!tag.empty()) { + jsondir += tag; + parent += "/snapshots/"; + } + + lister::dir_entry_types dir_and_files = { directory_entry_type::regular, directory_entry_type::directory }; + return lister::scan_dir(jsondir, dir_and_files, [this, curr_dir = jsondir, dir_and_files, tag] (directory_entry de) { + // FIXME: We really need a better directory walker. This should eventually be part of the seastar infrastructure. + // It's hard to write this in a fully recursive manner because we need to keep information about the parent directory, + // so we can remove the file. For now, we'll take advantage of the fact that we will at most visit 2 levels and keep + // it ugly but simple. + auto recurse = make_ready_future<>(); + if (de.type == directory_entry_type::directory) { + // Should only recurse when tag is empty, meaning delete all snapshots + if (!tag.empty()) { + throw std::runtime_error(sprint("Unexpected directory %s found at %s! Aborting", de.name, curr_dir)); + } + auto newdir = curr_dir + "/" + de.name; + recurse = lister::scan_dir(newdir, dir_and_files, [this, curr_dir = newdir] (directory_entry de) { + return remove_file(curr_dir + "/" + de.name); + }); + } + return recurse.then([fname = curr_dir + "/" + de.name] { + return remove_file(fname); + }); + }).then_wrapped([jsondir] (future<> f) { + // Fine if directory does not exist. If it did, we delete it + if (file_missing(std::move(f)) == missing::no) { + return remove_file(jsondir); + } + return make_ready_future<>(); + }).then([parent] { + return sync_directory(parent).then_wrapped([] (future<> f) { + // Should always exist for empty tags, but may not exist for a single tag if we never took + // snapshots. We will check this here just to mask out the exception, without silencing + // unexpected ones. + file_missing(std::move(f)); + return make_ready_future<>(); + }); + }); +} + future<> column_family::flush() { // FIXME: this will synchronously wait for this write to finish, but doesn't guarantee // anything about previous writes. diff --git a/database.hh b/database.hh index 0d62fa0ced..29aa1af875 100644 --- a/database.hh +++ b/database.hh @@ -220,6 +220,7 @@ public: future<> compact_sstables(sstables::compaction_descriptor descriptor); future<> snapshot(sstring name); + future<> clear_snapshot(sstring name); const bool incremental_backups_enabled() const { return _config.enable_incremental_backups;