mirror of
https://github.com/scylladb/scylladb.git
synced 2026-06-05 14:33:08 +00:00
The host_id_based_hinted_handoff feature is now guaranteed to be enabled on all supported upgrade paths. Move it to the deprecated features list (still advertised via gossip for compatibility) and remove the feature checks from the hint manager startup.
287 lines
10 KiB
C++
287 lines
10 KiB
C++
/*
|
|
* SPDX-License-Identifier: LicenseRef-ScyllaDB-Source-Available-1.1
|
|
*
|
|
* Copyright (C) 2020-present ScyllaDB
|
|
*/
|
|
|
|
#include <seastar/core/sstring.hh>
|
|
#include <seastar/core/seastar.hh>
|
|
#include <seastar/core/smp.hh>
|
|
#include "db/schema_features.hh"
|
|
#include "utils/log.hh"
|
|
#include "gms/feature.hh"
|
|
#include "gms/feature_service.hh"
|
|
#include "db/system_keyspace.hh"
|
|
#include <boost/algorithm/string/classification.hpp>
|
|
#include <boost/algorithm/string/split.hpp>
|
|
#include "gms/gossiper.hh"
|
|
#include "gms/i_endpoint_state_change_subscriber.hh"
|
|
#include "utils/assert.hh"
|
|
#include "utils/error_injection.hh"
|
|
#include "service/storage_service.hh"
|
|
|
|
namespace gms {
|
|
|
|
static logging::logger logger("features");
|
|
|
|
static const char* enable_test_feature_error_injection_name = "features_enable_test_feature";
|
|
static const char* enable_test_feature_as_deprecated_error_injection_name = "features_enable_test_feature_as_deprecated";
|
|
|
|
static bool is_test_only_feature_deprecated() {
|
|
return utils::get_local_injector().enter(enable_test_feature_as_deprecated_error_injection_name);
|
|
}
|
|
|
|
bool is_test_only_feature_enabled() {
|
|
return utils::get_local_injector().enter(enable_test_feature_error_injection_name)
|
|
|| is_test_only_feature_deprecated();
|
|
}
|
|
|
|
feature_service::feature_service(feature_config cfg) : _config(cfg) {
|
|
#ifdef SCYLLA_ENABLE_ERROR_INJECTION
|
|
initialize_suppressed_features_set();
|
|
#endif
|
|
|
|
if (is_test_only_feature_deprecated()) {
|
|
// Assume it's enabled
|
|
test_only_feature.enable();
|
|
unregister_feature(test_only_feature);
|
|
}
|
|
}
|
|
|
|
future<> feature_service::stop() {
|
|
return make_ready_future<>();
|
|
}
|
|
|
|
#ifdef SCYLLA_ENABLE_ERROR_INJECTION
|
|
void feature_service::initialize_suppressed_features_set() {
|
|
if (const auto features_list = utils::get_local_injector().inject_parameter<std::string_view>("suppress_features"); features_list) {
|
|
boost::split(_suppressed_features, *features_list, boost::is_any_of(";"));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void feature_service::register_feature(feature& f) {
|
|
auto i = _registered_features.emplace(f.name(), f);
|
|
SCYLLA_ASSERT(i.second);
|
|
}
|
|
|
|
void feature_service::unregister_feature(feature& f) {
|
|
_registered_features.erase(f.name());
|
|
}
|
|
|
|
|
|
std::set<std::string_view> feature_service::supported_feature_set() const {
|
|
std::set<std::string_view> features = {
|
|
// Deprecated features - sent to other nodes via gossip, but assumed true in the code
|
|
"RANGE_TOMBSTONES"sv,
|
|
"LARGE_PARTITIONS"sv,
|
|
"COUNTERS"sv,
|
|
"DIGEST_MULTIPARTITION_READ"sv,
|
|
"CORRECT_COUNTER_ORDER"sv,
|
|
"SCHEMA_TABLES_V3"sv,
|
|
"CORRECT_NON_COMPOUND_RANGE_TOMBSTONES"sv,
|
|
"WRITE_FAILURE_REPLY"sv,
|
|
"XXHASH"sv,
|
|
"ROLES"sv,
|
|
"LA_SSTABLE_FORMAT"sv,
|
|
"STREAM_WITH_RPC_STREAM"sv,
|
|
"MATERIALIZED_VIEWS"sv,
|
|
"INDEXES"sv,
|
|
"ROW_LEVEL_REPAIR"sv,
|
|
"TRUNCATION_TABLE"sv,
|
|
"CORRECT_STATIC_COMPACT_IN_MC"sv,
|
|
"UNBOUNDED_RANGE_TOMBSTONES"sv,
|
|
"MC_SSTABLE_FORMAT"sv,
|
|
"COMPUTED_COLUMNS"sv,
|
|
"SCHEMA_COMMITLOG"sv,
|
|
"MD_SSTABLE_FORMAT"sv,
|
|
"ME_SSTABLE_FORMAT"sv,
|
|
"VIEW_VIRTUAL_COLUMNS"sv,
|
|
"DIGEST_INSENSITIVE_TO_EXPIRY"sv,
|
|
"CDC"sv,
|
|
"NONFROZEN_UDTS"sv,
|
|
"HINTED_HANDOFF_SEPARATE_CONNECTION"sv,
|
|
"LWT"sv,
|
|
"PER_TABLE_PARTITIONERS"sv,
|
|
"PER_TABLE_CACHING"sv,
|
|
"DIGEST_FOR_NULL_VALUES"sv,
|
|
"CORRECT_IDX_TOKEN_IN_SECONDARY_INDEX"sv,
|
|
"UUID_SSTABLE_IDENTIFIERS"sv,
|
|
"GROUP0_SCHEMA_VERSIONING"sv,
|
|
"VIEW_BUILD_STATUS_ON_GROUP0"sv,
|
|
"CDC_GENERATIONS_V2"sv,
|
|
"HOST_ID_BASED_HINTED_HANDOFF"sv,
|
|
};
|
|
|
|
if (is_test_only_feature_deprecated()) {
|
|
features.insert(test_only_feature.name());
|
|
}
|
|
|
|
for (auto& [name, f_ref] : _registered_features) {
|
|
features.insert(name);
|
|
}
|
|
|
|
for (const sstring& s : _config.disabled_features) {
|
|
features.erase(s);
|
|
}
|
|
|
|
#ifdef SCYLLA_ENABLE_ERROR_INJECTION
|
|
for (auto& sf: _suppressed_features) {
|
|
features.erase(sf);
|
|
}
|
|
#endif
|
|
|
|
return features;
|
|
}
|
|
|
|
const std::unordered_map<sstring, std::reference_wrapper<feature>>& feature_service::registered_features() const {
|
|
return _registered_features;
|
|
}
|
|
|
|
feature::feature(feature_service& service, std::string_view name, bool enabled)
|
|
: _service(&service)
|
|
, _name(name)
|
|
, _enabled(enabled) {
|
|
_service->register_feature(*this);
|
|
}
|
|
|
|
feature::~feature() {
|
|
if (_service) {
|
|
_service->unregister_feature(*this);
|
|
}
|
|
}
|
|
|
|
feature& feature::operator=(feature&& other) {
|
|
_service->unregister_feature(*this);
|
|
_service = std::exchange(other._service, nullptr);
|
|
_name = other._name;
|
|
_enabled = other._enabled;
|
|
_s = std::move(other._s);
|
|
_service->register_feature(*this);
|
|
return *this;
|
|
}
|
|
|
|
void feature::enable() {
|
|
if (!_enabled) {
|
|
if (this_shard_id() == 0) {
|
|
logger.info("Feature {} is enabled", name());
|
|
}
|
|
_enabled = true;
|
|
_s();
|
|
}
|
|
}
|
|
|
|
db::schema_features feature_service::cluster_schema_features() const {
|
|
db::schema_features f;
|
|
f.set<db::schema_feature::DIGEST_INSENSITIVE_TO_EXPIRY>();
|
|
f.set<db::schema_feature::COMPUTED_COLUMNS>();
|
|
f.set_if<db::schema_feature::SCYLLA_KEYSPACES>(keyspace_storage_options);
|
|
f.set_if<db::schema_feature::SCYLLA_KEYSPACES>(tablets);
|
|
f.set_if<db::schema_feature::SCYLLA_AGGREGATES>(aggregate_storage_options);
|
|
f.set_if<db::schema_feature::TABLE_DIGEST_INSENSITIVE_TO_EXPIRY>(table_digest_insensitive_to_expiry);
|
|
f.set<db::schema_feature::GROUP0_SCHEMA_VERSIONING>();
|
|
f.set_if<db::schema_feature::IN_MEMORY_TABLES>(bool(in_memory_tables));
|
|
f.set_if<db::schema_feature::TABLET_OPTIONS>(bool(tablet_options));
|
|
f.set_if<db::schema_feature::KEYSPACE_MULTI_RF_CHANGE>(bool(keyspace_multi_rf_change));
|
|
return f;
|
|
}
|
|
|
|
std::set<sstring> feature_service::to_feature_set(sstring features_string) {
|
|
std::set<sstring> features;
|
|
boost::split(features, features_string, boost::is_any_of(","));
|
|
features.erase("");
|
|
return features;
|
|
}
|
|
|
|
future<> feature_service::on_system_tables_loaded(db::system_keyspace& sys_ks) {
|
|
return enable_features_on_startup(sys_ks);
|
|
}
|
|
|
|
future<> feature_service::enable_features_on_startup(db::system_keyspace& sys_ks) {
|
|
std::set<sstring> features_to_enable;
|
|
std::set<sstring> persisted_features;
|
|
std::set<sstring> persisted_unsafe_to_disable_features;
|
|
|
|
auto topo_features = co_await sys_ks.load_topology_features_state();
|
|
if (topo_features) {
|
|
persisted_unsafe_to_disable_features = topo_features->calculate_not_yet_enabled_features();
|
|
persisted_features = std::move(topo_features->enabled_features);
|
|
} else {
|
|
persisted_features = co_await sys_ks.load_local_enabled_features();
|
|
}
|
|
|
|
check_features(persisted_features, persisted_unsafe_to_disable_features);
|
|
|
|
for (auto&& f : persisted_features) {
|
|
logger.debug("Enabling persisted feature '{}'", f);
|
|
if (_registered_features.contains(sstring(f))) {
|
|
features_to_enable.insert(std::move(f));
|
|
}
|
|
}
|
|
|
|
co_await container().invoke_on_all([&features_to_enable] (auto& srv) -> future<> {
|
|
auto feat = features_to_enable | std::ranges::to<std::set<std::string_view>>();
|
|
co_await srv.enable(std::move(feat));
|
|
});
|
|
}
|
|
|
|
void feature_service::check_features(const std::set<sstring>& enabled_features,
|
|
const std::set<sstring>& unsafe_to_disable_features) {
|
|
|
|
if (enabled_features.empty() && unsafe_to_disable_features.empty()) {
|
|
return;
|
|
}
|
|
|
|
const auto known_features = supported_feature_set();
|
|
for (auto&& f : enabled_features) {
|
|
const bool is_registered_feat = _registered_features.contains(sstring(f));
|
|
if (!known_features.contains(f)) {
|
|
if (is_registered_feat) {
|
|
throw unsupported_feature_exception(format(
|
|
"Feature '{}' was previously enabled in the cluster but its support is disabled by this node. "
|
|
"Set the corresponding configuration option to enable the support for the feature.", f));
|
|
} else {
|
|
throw unsupported_feature_exception(format("Unknown feature '{}' was previously enabled in the cluster. "
|
|
" That means this node is performing a prohibited downgrade procedure"
|
|
" and should not be allowed to boot.", f));
|
|
}
|
|
}
|
|
// If a feature is not in `registered_features` but still in `known_features` list
|
|
// that means the feature name is used for backward compatibility and should be implicitly
|
|
// enabled in the code by default, so just skip it.
|
|
}
|
|
|
|
// With raft cluster features, it is also unsafe to disable support for features
|
|
// that are supported by everybody but not enabled yet. There is a possibility
|
|
// that the feature became enabled and this node didn't notice it.
|
|
for (auto&& f : unsafe_to_disable_features) {
|
|
const bool is_registered_feat = _registered_features.contains(sstring(f));
|
|
if (!known_features.contains(f)) {
|
|
if (is_registered_feat) {
|
|
throw unsupported_feature_exception(format(
|
|
"Feature '{}' was previously supported by all nodes in the cluster. It is unknown whether "
|
|
"the feature became enabled or not, therefore it is not safe for this node to boot. "
|
|
"Set the corresponding configuration option to enable the support for the feature.", f));
|
|
} else {
|
|
throw unsupported_feature_exception(format(
|
|
"Unknown feature '{}' was previously supported by all nodes in the cluster. "
|
|
"That means this node is performing a prohibited downgrade procedure "
|
|
"and should not be allowed to boot.", f));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
future<> feature_service::enable(std::set<std::string_view> list) {
|
|
// `gms::feature::enable` should be run within a seastar thread context
|
|
return seastar::async([this, list = std::move(list)] {
|
|
for (gms::feature& f : _registered_features | std::views::values) {
|
|
if (list.contains(f.name())) {
|
|
f.enable();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
} // namespace gms
|