This series converts the synchronous `effective_replication_map::get_range_addresses` to async by calling the replication strategy async entry point with the same name, as its callers are already async or can be made so easily. To allow it to yield and work on a coherent view of the token_metadata / topology / replication_map, let the callers of this patch hold a effective_replication_map per keyspace and pass it down to the (now asynchronous) functions that use it (making affected storage_service methods static where possible if they no longer depend on the storage_service instance). Also, the repeated calls to everywhere_replication_strategy::calculate_natural_endpoints are optimized in this series by introducing a virtual abstract_replication_strategy::has_static_natural_endpoints predicate that is true for local_strategy and everywhere_replication_strategy, and is false otherwise. With it, functions repeatedly calling calculate_natural_endpoints in a loop, for every token, will call it only once since it will return the same result every time anyhow. Refs #11005 Doesn't fix the issue as the large allocation still remains until we make change dht::token_range_vector chunked (chunked_vector cannot be used as is at the moment since we require the ability to push also to the front when unwrapping) Closes #11009 * github.com:scylladb/scylladb: effective_replication_map: make get_range_addresses asynchronous range_streamer: add_ranges and friends: get erm as param storage_service: get_new_source_ranges: get erm as param storage_service: get_changed_ranges_for_leaving: get erm as param storage_service: get_ranges_for_endpoint: get erm as param repair: use get_non_local_strategy_keyspaces_erms database: add get_non_local_strategy_keyspaces_erms database: add get_non_local_strategy_keyspaces storage_service: coroutinize update_pending_ranges effective_replication_map: add get_replication_strategy effective_replication_map: get_range_addresses: use the precalculated replication_map abstract_replication_strategy: get_pending_address_ranges: prevent extra vector copies abstract_replication_strategy: reindent utils: sequenced_set: expose set and `contains` method abstract_replication_strategy: calculate_natural_endpoints: return endpoint_set utils: sequenced_set: templatize VectorType utils: sanitize sequenced_set utils: sequenced_set: delete mutable get_vector method
1462 lines
64 KiB
C++
1462 lines
64 KiB
C++
/*
|
|
* Copyright (C) 2015-present ScyllaDB
|
|
*/
|
|
|
|
/*
|
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
*/
|
|
|
|
#include "storage_service.hh"
|
|
#include "api/api-doc/storage_service.json.hh"
|
|
#include "db/config.hh"
|
|
#include "db/schema_tables.hh"
|
|
#include "utils/hash.hh"
|
|
#include <optional>
|
|
#include <sstream>
|
|
#include <time.h>
|
|
#include <algorithm>
|
|
#include <boost/range/adaptor/map.hpp>
|
|
#include <boost/range/adaptor/filtered.hpp>
|
|
#include <boost/algorithm/string/trim_all.hpp>
|
|
#include <boost/algorithm/string/case_conv.hpp>
|
|
#include <boost/functional/hash.hpp>
|
|
#include "service/storage_service.hh"
|
|
#include "service/load_meter.hh"
|
|
#include "db/commitlog/commitlog.hh"
|
|
#include "gms/gossiper.hh"
|
|
#include "db/system_keyspace.hh"
|
|
#include <seastar/http/exception.hh>
|
|
#include <seastar/core/coroutine.hh>
|
|
#include <seastar/coroutine/parallel_for_each.hh>
|
|
#include "repair/row_level.hh"
|
|
#include "locator/snitch_base.hh"
|
|
#include "column_family.hh"
|
|
#include "log.hh"
|
|
#include "release.hh"
|
|
#include "compaction/compaction_manager.hh"
|
|
#include "sstables/sstables.hh"
|
|
#include "replica/database.hh"
|
|
#include "db/extensions.hh"
|
|
#include "db/snapshot-ctl.hh"
|
|
#include "transport/controller.hh"
|
|
#include "thrift/controller.hh"
|
|
#include "locator/token_metadata.hh"
|
|
#include "cdc/generation_service.hh"
|
|
#include "service/storage_proxy.hh"
|
|
#include "locator/abstract_replication_strategy.hh"
|
|
#include "sstables_loader.hh"
|
|
#include "db/view/view_builder.hh"
|
|
|
|
extern logging::logger apilog;
|
|
|
|
namespace api {
|
|
|
|
const locator::token_metadata& http_context::get_token_metadata() {
|
|
return *shared_token_metadata.local().get();
|
|
}
|
|
|
|
namespace ss = httpd::storage_service_json;
|
|
using namespace json;
|
|
|
|
sstring validate_keyspace(http_context& ctx, sstring ks_name) {
|
|
if (ctx.db.local().has_keyspace(ks_name)) {
|
|
return ks_name;
|
|
}
|
|
throw bad_param_exception(replica::no_such_keyspace(ks_name).what());
|
|
}
|
|
|
|
sstring validate_keyspace(http_context& ctx, const parameters& param) {
|
|
return validate_keyspace(ctx, param["keyspace"]);
|
|
}
|
|
|
|
// splits a request parameter assumed to hold a comma-separated list of table names
|
|
// verify that the tables are found, otherwise a bad_param_exception exception is thrown
|
|
// containing the description of the respective no_such_column_family error.
|
|
std::vector<sstring> parse_tables(const sstring& ks_name, http_context& ctx, sstring value) {
|
|
if (value.empty()) {
|
|
return map_keys(ctx.db.local().find_keyspace(ks_name).metadata().get()->cf_meta_data());
|
|
}
|
|
std::vector<sstring> names = split(value, ",");
|
|
try {
|
|
for (const auto& table_name : names) {
|
|
ctx.db.local().find_column_family(ks_name, table_name);
|
|
}
|
|
} catch (const replica::no_such_column_family& e) {
|
|
throw bad_param_exception(e.what());
|
|
}
|
|
return names;
|
|
}
|
|
|
|
std::vector<sstring> parse_tables(const sstring& ks_name, http_context& ctx, const std::unordered_map<sstring, sstring>& query_params, sstring param_name) {
|
|
auto it = query_params.find(param_name);
|
|
if (it == query_params.end()) {
|
|
return {};
|
|
}
|
|
return parse_tables(ks_name, ctx, it->second);
|
|
}
|
|
|
|
static ss::token_range token_range_endpoints_to_json(const dht::token_range_endpoints& d) {
|
|
ss::token_range r;
|
|
r.start_token = d._start_token;
|
|
r.end_token = d._end_token;
|
|
r.endpoints = d._endpoints;
|
|
r.rpc_endpoints = d._rpc_endpoints;
|
|
for (auto det : d._endpoint_details) {
|
|
ss::endpoint_detail ed;
|
|
ed.host = boost::lexical_cast<std::string>(det._host);
|
|
ed.datacenter = det._datacenter;
|
|
if (det._rack != "") {
|
|
ed.rack = det._rack;
|
|
}
|
|
r.endpoint_details.push(ed);
|
|
}
|
|
return r;
|
|
}
|
|
|
|
using ks_cf_func = std::function<future<json::json_return_type>(http_context&, std::unique_ptr<request>, sstring, std::vector<sstring>)>;
|
|
|
|
static auto wrap_ks_cf(http_context &ctx, ks_cf_func f) {
|
|
return [&ctx, f = std::move(f)](std::unique_ptr<request> req) {
|
|
auto keyspace = validate_keyspace(ctx, req->param);
|
|
auto column_families = parse_tables(keyspace, ctx, req->query_parameters, "cf");
|
|
if (column_families.empty()) {
|
|
column_families = map_keys(ctx.db.local().find_keyspace(keyspace).metadata().get()->cf_meta_data());
|
|
}
|
|
return f(ctx, std::move(req), std::move(keyspace), std::move(column_families));
|
|
};
|
|
}
|
|
|
|
seastar::future<json::json_return_type> run_toppartitions_query(db::toppartitions_query& q, http_context &ctx, bool legacy_request) {
|
|
namespace cf = httpd::column_family_json;
|
|
return q.scatter().then([&q, legacy_request] {
|
|
return sleep(q.duration()).then([&q, legacy_request] {
|
|
return q.gather(q.capacity()).then([&q, legacy_request] (auto topk_results) {
|
|
apilog.debug("toppartitions query: processing results");
|
|
cf::toppartitions_query_results results;
|
|
|
|
results.read_cardinality = topk_results.read.size();
|
|
results.write_cardinality = topk_results.write.size();
|
|
|
|
for (auto& d: topk_results.read.top(q.list_size())) {
|
|
cf::toppartitions_record r;
|
|
r.partition = (legacy_request ? "" : "(" + d.item.schema->ks_name() + ":" + d.item.schema->cf_name() + ") ") + sstring(d.item);
|
|
r.count = d.count;
|
|
r.error = d.error;
|
|
results.read.push(r);
|
|
}
|
|
for (auto& d: topk_results.write.top(q.list_size())) {
|
|
cf::toppartitions_record r;
|
|
r.partition = (legacy_request ? "" : "(" + d.item.schema->ks_name() + ":" + d.item.schema->cf_name() + ") ") + sstring(d.item);
|
|
r.count = d.count;
|
|
r.error = d.error;
|
|
results.write.push(r);
|
|
}
|
|
return make_ready_future<json::json_return_type>(results);
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
future<json::json_return_type> set_tables_autocompaction(http_context& ctx, const sstring &keyspace, std::vector<sstring> tables, bool enabled) {
|
|
if (tables.empty()) {
|
|
tables = map_keys(ctx.db.local().find_keyspace(keyspace).metadata().get()->cf_meta_data());
|
|
}
|
|
|
|
apilog.info("set_tables_autocompaction: enabled={} keyspace={} tables={}", enabled, keyspace, tables);
|
|
return do_with(keyspace, std::move(tables), [&ctx, enabled] (const sstring &keyspace, const std::vector<sstring>& tables) {
|
|
return ctx.db.invoke_on(0, [&ctx, &keyspace, &tables, enabled] (replica::database& db) {
|
|
auto g = replica::database::autocompaction_toggle_guard(db);
|
|
return ctx.db.invoke_on_all([&keyspace, &tables, enabled] (replica::database& db) {
|
|
return parallel_for_each(tables, [&db, &keyspace, enabled] (const sstring& table) {
|
|
replica::column_family& cf = db.find_column_family(keyspace, table);
|
|
if (enabled) {
|
|
cf.enable_auto_compaction();
|
|
} else {
|
|
return cf.disable_auto_compaction();
|
|
}
|
|
return make_ready_future<>();
|
|
});
|
|
}).finally([g = std::move(g)] {});
|
|
});
|
|
}).then([] {
|
|
return make_ready_future<json::json_return_type>(json_void());
|
|
});
|
|
}
|
|
|
|
void set_transport_controller(http_context& ctx, routes& r, cql_transport::controller& ctl) {
|
|
ss::start_native_transport.set(r, [&ctl](std::unique_ptr<request> req) {
|
|
return smp::submit_to(0, [&] {
|
|
return ctl.start_server();
|
|
}).then([] {
|
|
return make_ready_future<json::json_return_type>(json_void());
|
|
});
|
|
});
|
|
|
|
ss::stop_native_transport.set(r, [&ctl](std::unique_ptr<request> req) {
|
|
return smp::submit_to(0, [&] {
|
|
return ctl.request_stop_server();
|
|
}).then([] {
|
|
return make_ready_future<json::json_return_type>(json_void());
|
|
});
|
|
});
|
|
|
|
ss::is_native_transport_running.set(r, [&ctl] (std::unique_ptr<request> req) {
|
|
return smp::submit_to(0, [&] {
|
|
return !ctl.listen_addresses().empty();
|
|
}).then([] (bool running) {
|
|
return make_ready_future<json::json_return_type>(running);
|
|
});
|
|
});
|
|
}
|
|
|
|
void unset_transport_controller(http_context& ctx, routes& r) {
|
|
ss::start_native_transport.unset(r);
|
|
ss::stop_native_transport.unset(r);
|
|
ss::is_native_transport_running.unset(r);
|
|
}
|
|
|
|
void set_rpc_controller(http_context& ctx, routes& r, thrift_controller& ctl) {
|
|
ss::stop_rpc_server.set(r, [&ctl](std::unique_ptr<request> req) {
|
|
return smp::submit_to(0, [&] {
|
|
return ctl.request_stop_server();
|
|
}).then([] {
|
|
return make_ready_future<json::json_return_type>(json_void());
|
|
});
|
|
});
|
|
|
|
ss::start_rpc_server.set(r, [&ctl](std::unique_ptr<request> req) {
|
|
return smp::submit_to(0, [&] {
|
|
return ctl.start_server();
|
|
}).then([] {
|
|
return make_ready_future<json::json_return_type>(json_void());
|
|
});
|
|
});
|
|
|
|
ss::is_rpc_server_running.set(r, [&ctl] (std::unique_ptr<request> req) {
|
|
return smp::submit_to(0, [&] {
|
|
return !ctl.listen_addresses().empty();
|
|
}).then([] (bool running) {
|
|
return make_ready_future<json::json_return_type>(running);
|
|
});
|
|
});
|
|
}
|
|
|
|
void unset_rpc_controller(http_context& ctx, routes& r) {
|
|
ss::stop_rpc_server.unset(r);
|
|
ss::start_rpc_server.unset(r);
|
|
ss::is_rpc_server_running.unset(r);
|
|
}
|
|
|
|
void set_repair(http_context& ctx, routes& r, sharded<repair_service>& repair) {
|
|
ss::repair_async.set(r, [&ctx, &repair](std::unique_ptr<request> req) {
|
|
static std::vector<sstring> options = {"primaryRange", "parallelism", "incremental",
|
|
"jobThreads", "ranges", "columnFamilies", "dataCenters", "hosts", "ignore_nodes", "trace",
|
|
"startToken", "endToken" };
|
|
std::unordered_map<sstring, sstring> options_map;
|
|
for (auto o : options) {
|
|
auto s = req->get_query_param(o);
|
|
if (s != "") {
|
|
options_map[o] = s;
|
|
}
|
|
}
|
|
|
|
// The repair process is asynchronous: repair_start only starts it and
|
|
// returns immediately, not waiting for the repair to finish. The user
|
|
// then has other mechanisms to track the ongoing repair's progress,
|
|
// or stop it.
|
|
return repair_start(repair, validate_keyspace(ctx, req->param),
|
|
options_map).then([] (int i) {
|
|
return make_ready_future<json::json_return_type>(i);
|
|
});
|
|
});
|
|
|
|
ss::get_active_repair_async.set(r, [&repair] (std::unique_ptr<request> req) {
|
|
return repair.local().get_active_repairs().then([] (std::vector<int> res) {
|
|
return make_ready_future<json::json_return_type>(res);
|
|
});
|
|
});
|
|
|
|
ss::repair_async_status.set(r, [&repair] (std::unique_ptr<request> req) {
|
|
return repair.local().get_status(boost::lexical_cast<int>( req->get_query_param("id")))
|
|
.then_wrapped([] (future<repair_status>&& fut) {
|
|
ss::ns_repair_async_status::return_type_wrapper res;
|
|
try {
|
|
res = fut.get0();
|
|
} catch(std::runtime_error& e) {
|
|
throw httpd::bad_param_exception(e.what());
|
|
}
|
|
return make_ready_future<json::json_return_type>(json::json_return_type(res));
|
|
});
|
|
});
|
|
|
|
ss::repair_await_completion.set(r, [&repair] (std::unique_ptr<request> req) {
|
|
int id;
|
|
using clock = std::chrono::steady_clock;
|
|
clock::time_point expire;
|
|
try {
|
|
id = boost::lexical_cast<int>(req->get_query_param("id"));
|
|
// If timeout is not provided, it means no timeout.
|
|
sstring s = req->get_query_param("timeout");
|
|
int64_t timeout = s.empty() ? int64_t(-1) : boost::lexical_cast<int64_t>(s);
|
|
if (timeout < 0 && timeout != -1) {
|
|
return make_exception_future<json::json_return_type>(
|
|
httpd::bad_param_exception("timeout can only be -1 (means no timeout) or non negative integer"));
|
|
}
|
|
if (timeout < 0) {
|
|
expire = clock::time_point::max();
|
|
} else {
|
|
expire = clock::now() + std::chrono::seconds(timeout);
|
|
}
|
|
} catch (std::exception& e) {
|
|
return make_exception_future<json::json_return_type>(httpd::bad_param_exception(e.what()));
|
|
}
|
|
return repair.local().await_completion(id, expire)
|
|
.then_wrapped([] (future<repair_status>&& fut) {
|
|
ss::ns_repair_async_status::return_type_wrapper res;
|
|
try {
|
|
res = fut.get0();
|
|
} catch (std::exception& e) {
|
|
return make_exception_future<json::json_return_type>(httpd::bad_param_exception(e.what()));
|
|
}
|
|
return make_ready_future<json::json_return_type>(json::json_return_type(res));
|
|
});
|
|
});
|
|
|
|
ss::force_terminate_all_repair_sessions.set(r, [&repair] (std::unique_ptr<request> req) {
|
|
return repair.local().abort_all().then([] {
|
|
return make_ready_future<json::json_return_type>(json_void());
|
|
});
|
|
});
|
|
|
|
ss::force_terminate_all_repair_sessions_new.set(r, [&repair] (std::unique_ptr<request> req) {
|
|
return repair.local().abort_all().then([] {
|
|
return make_ready_future<json::json_return_type>(json_void());
|
|
});
|
|
});
|
|
|
|
}
|
|
|
|
void unset_repair(http_context& ctx, routes& r) {
|
|
ss::repair_async.unset(r);
|
|
ss::get_active_repair_async.unset(r);
|
|
ss::repair_async_status.unset(r);
|
|
ss::repair_await_completion.unset(r);
|
|
ss::force_terminate_all_repair_sessions.unset(r);
|
|
ss::force_terminate_all_repair_sessions_new.unset(r);
|
|
}
|
|
|
|
void set_sstables_loader(http_context& ctx, routes& r, sharded<sstables_loader>& sst_loader) {
|
|
ss::load_new_ss_tables.set(r, [&ctx, &sst_loader](std::unique_ptr<request> req) {
|
|
auto ks = validate_keyspace(ctx, req->param);
|
|
auto cf = req->get_query_param("cf");
|
|
auto stream = req->get_query_param("load_and_stream");
|
|
auto primary_replica = req->get_query_param("primary_replica_only");
|
|
boost::algorithm::to_lower(stream);
|
|
boost::algorithm::to_lower(primary_replica);
|
|
bool load_and_stream = stream == "true" || stream == "1";
|
|
bool primary_replica_only = primary_replica == "true" || primary_replica == "1";
|
|
// No need to add the keyspace, since all we want is to avoid always sending this to the same
|
|
// CPU. Even then I am being overzealous here. This is not something that happens all the time.
|
|
auto coordinator = std::hash<sstring>()(cf) % smp::count;
|
|
return sst_loader.invoke_on(coordinator,
|
|
[ks = std::move(ks), cf = std::move(cf),
|
|
load_and_stream, primary_replica_only] (sstables_loader& loader) {
|
|
return loader.load_new_sstables(ks, cf, load_and_stream, primary_replica_only);
|
|
}).then_wrapped([] (auto&& f) {
|
|
if (f.failed()) {
|
|
auto msg = fmt::format("Failed to load new sstables: {}", f.get_exception());
|
|
return make_exception_future<json::json_return_type>(httpd::server_error_exception(msg));
|
|
}
|
|
return make_ready_future<json::json_return_type>(json_void());
|
|
});
|
|
});
|
|
}
|
|
|
|
void unset_sstables_loader(http_context& ctx, routes& r) {
|
|
ss::load_new_ss_tables.unset(r);
|
|
}
|
|
|
|
void set_view_builder(http_context& ctx, routes& r, sharded<db::view::view_builder>& vb) {
|
|
ss::view_build_statuses.set(r, [&ctx, &vb] (std::unique_ptr<request> req) {
|
|
auto keyspace = validate_keyspace(ctx, req->param);
|
|
auto view = req->param["view"];
|
|
return vb.local().view_build_statuses(std::move(keyspace), std::move(view)).then([] (std::unordered_map<sstring, sstring> status) {
|
|
std::vector<storage_service_json::mapper> res;
|
|
return make_ready_future<json::json_return_type>(map_to_key_value(std::move(status), res));
|
|
});
|
|
});
|
|
|
|
}
|
|
|
|
void unset_view_builder(http_context& ctx, routes& r) {
|
|
ss::view_build_statuses.unset(r);
|
|
}
|
|
|
|
static future<json::json_return_type> describe_ring_as_json(sharded<service::storage_service>& ss, sstring keyspace) {
|
|
co_return json::json_return_type(stream_range_as_array(co_await ss.local().describe_ring(keyspace), token_range_endpoints_to_json));
|
|
}
|
|
|
|
void set_storage_service(http_context& ctx, routes& r, sharded<service::storage_service>& ss, gms::gossiper& g, sharded<cdc::generation_service>& cdc_gs, sharded<db::system_keyspace>& sys_ks) {
|
|
ss::local_hostid.set(r, [&ctx](std::unique_ptr<request> req) {
|
|
auto id = ctx.db.local().get_config().host_id;
|
|
return make_ready_future<json::json_return_type>(id.to_sstring());
|
|
});
|
|
|
|
ss::get_tokens.set(r, [&ctx] (std::unique_ptr<request> req) {
|
|
return make_ready_future<json::json_return_type>(stream_range_as_array(ctx.get_token_metadata().sorted_tokens(), [](const dht::token& i) {
|
|
return boost::lexical_cast<std::string>(i);
|
|
}));
|
|
});
|
|
|
|
ss::get_node_tokens.set(r, [&ctx] (std::unique_ptr<request> req) {
|
|
gms::inet_address addr(req->param["endpoint"]);
|
|
return make_ready_future<json::json_return_type>(stream_range_as_array(ctx.get_token_metadata().get_tokens(addr), [](const dht::token& i) {
|
|
return boost::lexical_cast<std::string>(i);
|
|
}));
|
|
});
|
|
|
|
ss::get_commitlog.set(r, [&ctx](const_req req) {
|
|
return ctx.db.local().commitlog()->active_config().commit_log_location;
|
|
});
|
|
|
|
ss::get_token_endpoint.set(r, [&ss] (std::unique_ptr<request> req) {
|
|
return make_ready_future<json::json_return_type>(stream_range_as_array(ss.local().get_token_to_endpoint_map(), [](const auto& i) {
|
|
storage_service_json::mapper val;
|
|
val.key = boost::lexical_cast<std::string>(i.first);
|
|
val.value = boost::lexical_cast<std::string>(i.second);
|
|
return val;
|
|
}));
|
|
});
|
|
|
|
ss::toppartitions_generic.set(r, [&ctx] (std::unique_ptr<request> req) {
|
|
bool filters_provided = false;
|
|
|
|
std::unordered_set<std::tuple<sstring, sstring>, utils::tuple_hash> table_filters {};
|
|
if (req->query_parameters.contains("table_filters")) {
|
|
filters_provided = true;
|
|
auto filters = req->get_query_param("table_filters");
|
|
std::stringstream ss { filters };
|
|
std::string filter;
|
|
while (!filters.empty() && ss.good()) {
|
|
std::getline(ss, filter, ',');
|
|
table_filters.emplace(parse_fully_qualified_cf_name(filter));
|
|
}
|
|
}
|
|
|
|
std::unordered_set<sstring> keyspace_filters {};
|
|
if (req->query_parameters.contains("keyspace_filters")) {
|
|
filters_provided = true;
|
|
auto filters = req->get_query_param("keyspace_filters");
|
|
std::stringstream ss { filters };
|
|
std::string filter;
|
|
while (!filters.empty() && ss.good()) {
|
|
std::getline(ss, filter, ',');
|
|
keyspace_filters.emplace(std::move(filter));
|
|
}
|
|
}
|
|
|
|
// when the query is empty return immediately
|
|
if (filters_provided && table_filters.empty() && keyspace_filters.empty()) {
|
|
apilog.debug("toppartitions query: processing results");
|
|
httpd::column_family_json::toppartitions_query_results results;
|
|
|
|
results.read_cardinality = 0;
|
|
results.write_cardinality = 0;
|
|
|
|
return make_ready_future<json::json_return_type>(results);
|
|
}
|
|
|
|
api::req_param<std::chrono::milliseconds, unsigned> duration{*req, "duration", 1000ms};
|
|
api::req_param<unsigned> capacity(*req, "capacity", 256);
|
|
api::req_param<unsigned> list_size(*req, "list_size", 10);
|
|
|
|
apilog.info("toppartitions query: #table_filters={} #keyspace_filters={} duration={} list_size={} capacity={}",
|
|
!table_filters.empty() ? std::to_string(table_filters.size()) : "all", !keyspace_filters.empty() ? std::to_string(keyspace_filters.size()) : "all", duration.param, list_size.param, capacity.param);
|
|
|
|
return seastar::do_with(db::toppartitions_query(ctx.db, std::move(table_filters), std::move(keyspace_filters), duration.value, list_size, capacity), [&ctx] (db::toppartitions_query& q) {
|
|
return run_toppartitions_query(q, ctx);
|
|
});
|
|
});
|
|
|
|
ss::get_leaving_nodes.set(r, [&ctx](const_req req) {
|
|
return container_to_vec(ctx.get_token_metadata().get_leaving_endpoints());
|
|
});
|
|
|
|
ss::get_moving_nodes.set(r, [](const_req req) {
|
|
std::unordered_set<sstring> addr;
|
|
return container_to_vec(addr);
|
|
});
|
|
|
|
ss::get_joining_nodes.set(r, [&ctx](const_req req) {
|
|
auto points = ctx.get_token_metadata().get_bootstrap_tokens();
|
|
std::unordered_set<sstring> addr;
|
|
for (auto i: points) {
|
|
addr.insert(boost::lexical_cast<std::string>(i.second));
|
|
}
|
|
return container_to_vec(addr);
|
|
});
|
|
|
|
ss::get_release_version.set(r, [&ss](const_req req) {
|
|
return ss.local().get_release_version();
|
|
});
|
|
|
|
ss::get_scylla_release_version.set(r, [](const_req req) {
|
|
return scylla_version();
|
|
});
|
|
ss::get_schema_version.set(r, [&ss](const_req req) {
|
|
return ss.local().get_schema_version();
|
|
});
|
|
|
|
ss::get_all_data_file_locations.set(r, [&ctx](const_req req) {
|
|
return container_to_vec(ctx.db.local().get_config().data_file_directories());
|
|
});
|
|
|
|
ss::get_saved_caches_location.set(r, [&ctx](const_req req) {
|
|
return ctx.db.local().get_config().saved_caches_directory();
|
|
});
|
|
|
|
ss::get_range_to_endpoint_map.set(r, [&ctx, &ss](std::unique_ptr<request> req) -> future<json::json_return_type> {
|
|
auto keyspace = validate_keyspace(ctx, req->param);
|
|
std::vector<ss::maplist_mapper> res;
|
|
co_return stream_range_as_array(co_await ss.local().get_range_to_address_map(keyspace),
|
|
[](const std::pair<dht::token_range, inet_address_vector_replica_set>& entry){
|
|
ss::maplist_mapper m;
|
|
if (entry.first.start()) {
|
|
m.key.push(entry.first.start().value().value().to_sstring());
|
|
} else {
|
|
m.key.push("");
|
|
}
|
|
if (entry.first.end()) {
|
|
m.key.push(entry.first.end().value().value().to_sstring());
|
|
} else {
|
|
m.key.push("");
|
|
}
|
|
for (const gms::inet_address& address : entry.second) {
|
|
m.value.push(address.to_sstring());
|
|
}
|
|
return m;
|
|
});
|
|
});
|
|
|
|
ss::get_pending_range_to_endpoint_map.set(r, [&ctx](std::unique_ptr<request> req) {
|
|
//TBD
|
|
unimplemented();
|
|
auto keyspace = validate_keyspace(ctx, req->param);
|
|
std::vector<ss::maplist_mapper> res;
|
|
return make_ready_future<json::json_return_type>(res);
|
|
});
|
|
|
|
ss::describe_any_ring.set(r, [&ctx, &ss](std::unique_ptr<request> req) {
|
|
// Find an arbitrary non-system keyspace.
|
|
auto keyspaces = ctx.db.local().get_non_local_strategy_keyspaces();
|
|
if (keyspaces.empty()) {
|
|
throw std::runtime_error("No keyspace provided and no non system kespace exist");
|
|
}
|
|
auto ks = keyspaces[0];
|
|
return describe_ring_as_json(ss, ks);
|
|
});
|
|
|
|
ss::describe_ring.set(r, [&ctx, &ss](std::unique_ptr<request> req) {
|
|
return describe_ring_as_json(ss, validate_keyspace(ctx, req->param));
|
|
});
|
|
|
|
ss::get_host_id_map.set(r, [&ctx](const_req req) {
|
|
std::vector<ss::mapper> res;
|
|
return map_to_key_value(ctx.get_token_metadata().get_endpoint_to_host_id_map_for_reading(), res);
|
|
});
|
|
|
|
ss::get_load.set(r, [&ctx](std::unique_ptr<request> req) {
|
|
return get_cf_stats(ctx, &replica::column_family_stats::live_disk_space_used);
|
|
});
|
|
|
|
ss::get_load_map.set(r, [&ctx] (std::unique_ptr<request> req) {
|
|
return ctx.lmeter.get_load_map().then([] (auto&& load_map) {
|
|
std::vector<ss::map_string_double> res;
|
|
for (auto i : load_map) {
|
|
ss::map_string_double val;
|
|
val.key = i.first;
|
|
val.value = i.second;
|
|
res.push_back(val);
|
|
}
|
|
return make_ready_future<json::json_return_type>(res);
|
|
});
|
|
});
|
|
|
|
ss::get_current_generation_number.set(r, [&g](std::unique_ptr<request> req) {
|
|
gms::inet_address ep(utils::fb_utilities::get_broadcast_address());
|
|
return g.get_current_generation_number(ep).then([](int res) {
|
|
return make_ready_future<json::json_return_type>(res);
|
|
});
|
|
});
|
|
|
|
ss::get_natural_endpoints.set(r, [&ctx, &ss](const_req req) {
|
|
auto keyspace = validate_keyspace(ctx, req.param);
|
|
return container_to_vec(ss.local().get_natural_endpoints(keyspace, req.get_query_param("cf"),
|
|
req.get_query_param("key")));
|
|
});
|
|
|
|
ss::cdc_streams_check_and_repair.set(r, [&ctx, &cdc_gs] (std::unique_ptr<request> req) {
|
|
if (!cdc_gs.local_is_initialized()) {
|
|
throw std::runtime_error("get_cdc_generation_service: not initialized yet");
|
|
}
|
|
return cdc_gs.local().check_and_repair_cdc_streams().then([] {
|
|
return make_ready_future<json::json_return_type>(json_void());
|
|
});
|
|
});
|
|
|
|
ss::force_keyspace_compaction.set(r, [&ctx](std::unique_ptr<request> req) {
|
|
auto keyspace = validate_keyspace(ctx, req->param);
|
|
auto column_families = parse_tables(keyspace, ctx, req->query_parameters, "cf");
|
|
if (column_families.empty()) {
|
|
column_families = map_keys(ctx.db.local().find_keyspace(keyspace).metadata().get()->cf_meta_data());
|
|
}
|
|
return ctx.db.invoke_on_all([keyspace, column_families] (replica::database& db) -> future<> {
|
|
auto table_ids = boost::copy_range<std::vector<table_id>>(column_families | boost::adaptors::transformed([&] (auto& cf_name) {
|
|
return db.find_uuid(keyspace, cf_name);
|
|
}));
|
|
// major compact smaller tables first, to increase chances of success if low on space.
|
|
std::ranges::sort(table_ids, std::less<>(), [&] (const table_id& id) {
|
|
return db.find_column_family(id).get_stats().live_disk_space_used;
|
|
});
|
|
// as a table can be dropped during loop below, let's find it before issuing major compaction request.
|
|
for (auto& id : table_ids) {
|
|
co_await db.find_column_family(id).compact_all_sstables();
|
|
}
|
|
co_return;
|
|
}).then([]{
|
|
return make_ready_future<json::json_return_type>(json_void());
|
|
});
|
|
});
|
|
|
|
ss::force_keyspace_cleanup.set(r, [&ctx, &ss](std::unique_ptr<request> req) {
|
|
auto keyspace = validate_keyspace(ctx, req->param);
|
|
auto column_families = parse_tables(keyspace, ctx, req->query_parameters, "cf");
|
|
if (column_families.empty()) {
|
|
column_families = map_keys(ctx.db.local().find_keyspace(keyspace).metadata().get()->cf_meta_data());
|
|
}
|
|
return ss.local().is_cleanup_allowed(keyspace).then([&ctx, keyspace,
|
|
column_families = std::move(column_families)] (bool is_cleanup_allowed) mutable {
|
|
if (!is_cleanup_allowed) {
|
|
return make_exception_future<json::json_return_type>(
|
|
std::runtime_error("Can not perform cleanup operation when topology changes"));
|
|
}
|
|
return ctx.db.invoke_on_all([keyspace, column_families] (replica::database& db) -> future<> {
|
|
auto table_ids = boost::copy_range<std::vector<table_id>>(column_families | boost::adaptors::transformed([&] (auto& table_name) {
|
|
return db.find_uuid(keyspace, table_name);
|
|
}));
|
|
// cleanup smaller tables first, to increase chances of success if low on space.
|
|
std::ranges::sort(table_ids, std::less<>(), [&] (const table_id& id) {
|
|
return db.find_column_family(id).get_stats().live_disk_space_used;
|
|
});
|
|
auto& cm = db.get_compaction_manager();
|
|
auto owned_ranges_ptr = compaction::make_owned_ranges_ptr(db.get_keyspace_local_ranges(keyspace));
|
|
// as a table can be dropped during loop below, let's find it before issuing the cleanup request.
|
|
for (auto& id : table_ids) {
|
|
replica::table& t = db.find_column_family(id);
|
|
co_await cm.perform_cleanup(owned_ranges_ptr, t.as_table_state());
|
|
}
|
|
co_return;
|
|
}).then([]{
|
|
return make_ready_future<json::json_return_type>(0);
|
|
});
|
|
});
|
|
});
|
|
|
|
ss::perform_keyspace_offstrategy_compaction.set(r, wrap_ks_cf(ctx, [] (http_context& ctx, std::unique_ptr<request> req, sstring keyspace, std::vector<sstring> tables) -> future<json::json_return_type> {
|
|
co_return co_await ctx.db.map_reduce0([&keyspace, &tables] (replica::database& db) -> future<bool> {
|
|
bool needed = false;
|
|
for (const auto& table : tables) {
|
|
auto& t = db.find_column_family(keyspace, table);
|
|
needed |= co_await t.perform_offstrategy_compaction();
|
|
}
|
|
co_return needed;
|
|
}, false, std::plus<bool>());
|
|
}));
|
|
|
|
ss::upgrade_sstables.set(r, wrap_ks_cf(ctx, [] (http_context& ctx, std::unique_ptr<request> req, sstring keyspace, std::vector<sstring> column_families) {
|
|
bool exclude_current_version = req_param<bool>(*req, "exclude_current_version", false);
|
|
|
|
return ctx.db.invoke_on_all([=] (replica::database& db) {
|
|
auto owned_ranges_ptr = compaction::make_owned_ranges_ptr(db.get_keyspace_local_ranges(keyspace));
|
|
return do_for_each(column_families, [=, &db](sstring cfname) {
|
|
auto& cm = db.get_compaction_manager();
|
|
auto& cf = db.find_column_family(keyspace, cfname);
|
|
return cm.perform_sstable_upgrade(owned_ranges_ptr, cf.as_table_state(), exclude_current_version);
|
|
});
|
|
}).then([]{
|
|
return make_ready_future<json::json_return_type>(0);
|
|
});
|
|
}));
|
|
|
|
ss::force_keyspace_flush.set(r, [&ctx](std::unique_ptr<request> req) -> future<json::json_return_type> {
|
|
auto keyspace = validate_keyspace(ctx, req->param);
|
|
auto column_families = parse_tables(keyspace, ctx, req->query_parameters, "cf");
|
|
auto& db = ctx.db;
|
|
if (column_families.empty()) {
|
|
co_await replica::database::flush_keyspace_on_all_shards(db, keyspace);
|
|
} else {
|
|
co_await replica::database::flush_tables_on_all_shards(db, keyspace, std::move(column_families));
|
|
}
|
|
co_return json_void();
|
|
});
|
|
|
|
|
|
ss::decommission.set(r, [&ss](std::unique_ptr<request> req) {
|
|
return ss.local().decommission().then([] {
|
|
return make_ready_future<json::json_return_type>(json_void());
|
|
});
|
|
});
|
|
|
|
ss::move.set(r, [&ss] (std::unique_ptr<request> req) {
|
|
auto new_token = req->get_query_param("new_token");
|
|
return ss.local().move(new_token).then([] {
|
|
return make_ready_future<json::json_return_type>(json_void());
|
|
});
|
|
});
|
|
|
|
ss::remove_node.set(r, [&ss](std::unique_ptr<request> req) {
|
|
auto host_id = req->get_query_param("host_id");
|
|
std::vector<sstring> ignore_nodes_strs= split(req->get_query_param("ignore_nodes"), ",");
|
|
auto ignore_nodes = std::list<gms::inet_address>();
|
|
for (std::string n : ignore_nodes_strs) {
|
|
try {
|
|
std::replace(n.begin(), n.end(), '\"', ' ');
|
|
std::replace(n.begin(), n.end(), '\'', ' ');
|
|
boost::trim_all(n);
|
|
if (!n.empty()) {
|
|
auto node = gms::inet_address(n);
|
|
ignore_nodes.push_back(node);
|
|
}
|
|
} catch (...) {
|
|
throw std::runtime_error(format("Failed to parse ignore_nodes parameter: ignore_nodes={}, node={}", ignore_nodes_strs, n));
|
|
}
|
|
}
|
|
return ss.local().removenode(host_id, std::move(ignore_nodes)).then([] {
|
|
return make_ready_future<json::json_return_type>(json_void());
|
|
});
|
|
});
|
|
|
|
ss::get_removal_status.set(r, [&ss](std::unique_ptr<request> req) {
|
|
return ss.local().get_removal_status().then([] (auto status) {
|
|
return make_ready_future<json::json_return_type>(status);
|
|
});
|
|
});
|
|
|
|
ss::force_remove_completion.set(r, [&ss](std::unique_ptr<request> req) {
|
|
return ss.local().force_remove_completion().then([] {
|
|
return make_ready_future<json::json_return_type>(json_void());
|
|
});
|
|
});
|
|
|
|
ss::set_logging_level.set(r, [](std::unique_ptr<request> req) {
|
|
//TBD
|
|
unimplemented();
|
|
auto class_qualifier = req->get_query_param("class_qualifier");
|
|
auto level = req->get_query_param("level");
|
|
return make_ready_future<json::json_return_type>(json_void());
|
|
});
|
|
|
|
ss::get_logging_levels.set(r, [](std::unique_ptr<request> req) {
|
|
std::vector<ss::mapper> res;
|
|
for (auto i : logging::logger_registry().get_all_logger_names()) {
|
|
ss::mapper log;
|
|
log.key = i;
|
|
log.value = logging::level_name(logging::logger_registry().get_logger_level(i));
|
|
res.push_back(log);
|
|
}
|
|
return make_ready_future<json::json_return_type>(res);
|
|
});
|
|
|
|
ss::get_operation_mode.set(r, [&ss](std::unique_ptr<request> req) {
|
|
return ss.local().get_operation_mode().then([] (auto mode) {
|
|
return make_ready_future<json::json_return_type>(format("{}", mode));
|
|
});
|
|
});
|
|
|
|
ss::is_starting.set(r, [&ss](std::unique_ptr<request> req) {
|
|
return ss.local().get_operation_mode().then([] (auto mode) {
|
|
return make_ready_future<json::json_return_type>(mode <= service::storage_service::mode::STARTING);
|
|
});
|
|
});
|
|
|
|
ss::get_drain_progress.set(r, [&ctx](std::unique_ptr<request> req) {
|
|
return ctx.db.map_reduce(adder<replica::database::drain_progress>(), [] (auto& db) {
|
|
return db.get_drain_progress();
|
|
}).then([] (auto&& progress) {
|
|
auto progress_str = format("Drained {}/{} ColumnFamilies", progress.remaining_cfs, progress.total_cfs);
|
|
return make_ready_future<json::json_return_type>(std::move(progress_str));
|
|
});
|
|
});
|
|
|
|
ss::drain.set(r, [&ss](std::unique_ptr<request> req) {
|
|
return ss.local().drain().then([] {
|
|
return make_ready_future<json::json_return_type>(json_void());
|
|
});
|
|
});
|
|
ss::truncate.set(r, [&ctx](std::unique_ptr<request> req) {
|
|
//TBD
|
|
unimplemented();
|
|
auto keyspace = validate_keyspace(ctx, req->param);
|
|
auto column_family = req->get_query_param("cf");
|
|
return make_ready_future<json::json_return_type>(json_void());
|
|
});
|
|
|
|
ss::get_keyspaces.set(r, [&ctx](const_req req) {
|
|
auto type = req.get_query_param("type");
|
|
if (type == "user") {
|
|
return ctx.db.local().get_user_keyspaces();
|
|
} else if (type == "non_local_strategy") {
|
|
return ctx.db.local().get_non_local_strategy_keyspaces();
|
|
}
|
|
return map_keys(ctx.db.local().get_keyspaces());
|
|
});
|
|
|
|
ss::update_snitch.set(r, [](std::unique_ptr<request> req) {
|
|
locator::snitch_config cfg;
|
|
cfg.name = req->get_query_param("ep_snitch_class_name");
|
|
return locator::i_endpoint_snitch::reset_snitch(cfg).then([] {
|
|
return make_ready_future<json::json_return_type>(json_void());
|
|
});
|
|
});
|
|
|
|
ss::stop_gossiping.set(r, [&ss](std::unique_ptr<request> req) {
|
|
return ss.local().stop_gossiping().then([] {
|
|
return make_ready_future<json::json_return_type>(json_void());
|
|
});
|
|
});
|
|
|
|
ss::start_gossiping.set(r, [&ss](std::unique_ptr<request> req) {
|
|
return ss.local().start_gossiping().then([] {
|
|
return make_ready_future<json::json_return_type>(json_void());
|
|
});
|
|
});
|
|
|
|
ss::is_gossip_running.set(r, [&ss](std::unique_ptr<request> req) {
|
|
return ss.local().is_gossip_running().then([] (bool running){
|
|
return make_ready_future<json::json_return_type>(running);
|
|
});
|
|
});
|
|
|
|
|
|
ss::stop_daemon.set(r, [](std::unique_ptr<request> req) {
|
|
//TBD
|
|
unimplemented();
|
|
return make_ready_future<json::json_return_type>(json_void());
|
|
});
|
|
|
|
ss::is_initialized.set(r, [&ss, &g](std::unique_ptr<request> req) {
|
|
return ss.local().get_operation_mode().then([&g] (auto mode) {
|
|
bool is_initialized = mode >= service::storage_service::mode::STARTING;
|
|
if (mode == service::storage_service::mode::NORMAL) {
|
|
is_initialized = g.is_enabled();
|
|
}
|
|
return make_ready_future<json::json_return_type>(is_initialized);
|
|
});
|
|
});
|
|
|
|
ss::join_ring.set(r, [](std::unique_ptr<request> req) {
|
|
return make_ready_future<json::json_return_type>(json_void());
|
|
});
|
|
|
|
ss::is_joined.set(r, [&ss] (std::unique_ptr<request> req) {
|
|
return ss.local().get_operation_mode().then([] (auto mode) {
|
|
return make_ready_future<json::json_return_type>(mode >= service::storage_service::mode::JOINING);
|
|
});
|
|
});
|
|
|
|
ss::set_stream_throughput_mb_per_sec.set(r, [](std::unique_ptr<request> req) {
|
|
//TBD
|
|
unimplemented();
|
|
auto value = req->get_query_param("value");
|
|
return make_ready_future<json::json_return_type>(json_void());
|
|
});
|
|
|
|
ss::get_stream_throughput_mb_per_sec.set(r, [](std::unique_ptr<request> req) {
|
|
//TBD
|
|
unimplemented();
|
|
return make_ready_future<json::json_return_type>(0);
|
|
});
|
|
|
|
ss::get_compaction_throughput_mb_per_sec.set(r, [&ctx](std::unique_ptr<request> req) {
|
|
int value = ctx.db.local().get_config().compaction_throughput_mb_per_sec();
|
|
return make_ready_future<json::json_return_type>(value);
|
|
});
|
|
|
|
ss::set_compaction_throughput_mb_per_sec.set(r, [](std::unique_ptr<request> req) {
|
|
//TBD
|
|
unimplemented();
|
|
auto value = req->get_query_param("value");
|
|
return make_ready_future<json::json_return_type>(json_void());
|
|
});
|
|
|
|
ss::is_incremental_backups_enabled.set(r, [&ctx](std::unique_ptr<request> req) {
|
|
// If this is issued in parallel with an ongoing change, we may see values not agreeing.
|
|
// Reissuing is asking for trouble, so we will just return true upon seeing any true value.
|
|
return ctx.db.map_reduce(adder<bool>(), [] (replica::database& db) {
|
|
for (auto& pair: db.get_keyspaces()) {
|
|
auto& ks = pair.second;
|
|
if (ks.incremental_backups_enabled()) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}).then([] (bool val) {
|
|
return make_ready_future<json::json_return_type>(val);
|
|
});
|
|
});
|
|
|
|
ss::set_incremental_backups_enabled.set(r, [&ctx](std::unique_ptr<request> req) {
|
|
auto val_str = req->get_query_param("value");
|
|
bool value = (val_str == "True") || (val_str == "true") || (val_str == "1");
|
|
return ctx.db.invoke_on_all([value] (replica::database& db) {
|
|
db.set_enable_incremental_backups(value);
|
|
|
|
// Change both KS and CF, so they are in sync
|
|
for (auto& pair: db.get_keyspaces()) {
|
|
auto& ks = pair.second;
|
|
ks.set_incremental_backups(value);
|
|
}
|
|
|
|
for (auto& pair: db.get_column_families()) {
|
|
auto cf_ptr = pair.second;
|
|
cf_ptr->set_incremental_backups(value);
|
|
}
|
|
}).then([] {
|
|
return make_ready_future<json::json_return_type>(json_void());
|
|
});
|
|
});
|
|
|
|
ss::rebuild.set(r, [&ss](std::unique_ptr<request> req) {
|
|
auto source_dc = req->get_query_param("source_dc");
|
|
return ss.local().rebuild(std::move(source_dc)).then([] {
|
|
return make_ready_future<json::json_return_type>(json_void());
|
|
});
|
|
});
|
|
|
|
ss::bulk_load.set(r, [](std::unique_ptr<request> req) {
|
|
//TBD
|
|
unimplemented();
|
|
auto path = req->param["path"];
|
|
return make_ready_future<json::json_return_type>(json_void());
|
|
});
|
|
|
|
ss::bulk_load_async.set(r, [](std::unique_ptr<request> req) {
|
|
//TBD
|
|
unimplemented();
|
|
auto path = req->param["path"];
|
|
return make_ready_future<json::json_return_type>(json_void());
|
|
});
|
|
|
|
ss::reschedule_failed_deletions.set(r, [](std::unique_ptr<request> req) {
|
|
//TBD
|
|
unimplemented();
|
|
return make_ready_future<json::json_return_type>(json_void());
|
|
});
|
|
|
|
ss::sample_key_range.set(r, [](std::unique_ptr<request> req) {
|
|
//TBD
|
|
unimplemented();
|
|
std::vector<sstring> res;
|
|
return make_ready_future<json::json_return_type>(res);
|
|
});
|
|
|
|
ss::reset_local_schema.set(r, [&sys_ks](std::unique_ptr<request> req) {
|
|
// FIXME: We should truncate schema tables if more than one node in the cluster.
|
|
auto& sp = service::get_storage_proxy();
|
|
auto& fs = sp.local().features();
|
|
return db::schema_tables::recalculate_schema_version(sys_ks, sp, fs).then([] {
|
|
return make_ready_future<json::json_return_type>(json_void());
|
|
});
|
|
});
|
|
|
|
ss::set_trace_probability.set(r, [](std::unique_ptr<request> req) {
|
|
auto probability = req->get_query_param("probability");
|
|
return futurize_invoke([probability] {
|
|
double real_prob = std::stod(probability.c_str());
|
|
return tracing::tracing::tracing_instance().invoke_on_all([real_prob] (auto& local_tracing) {
|
|
local_tracing.set_trace_probability(real_prob);
|
|
}).then([] {
|
|
return make_ready_future<json::json_return_type>(json_void());
|
|
});
|
|
}).then_wrapped([probability] (auto&& f) {
|
|
try {
|
|
f.get();
|
|
return make_ready_future<json::json_return_type>(json_void());
|
|
} catch (std::out_of_range& e) {
|
|
throw httpd::bad_param_exception(e.what());
|
|
} catch (std::invalid_argument&){
|
|
throw httpd::bad_param_exception(format("Bad format in a probability value: \"{}\"", probability.c_str()));
|
|
}
|
|
});
|
|
});
|
|
|
|
ss::get_trace_probability.set(r, [](std::unique_ptr<request> req) {
|
|
return make_ready_future<json::json_return_type>(tracing::tracing::get_local_tracing_instance().get_trace_probability());
|
|
});
|
|
|
|
ss::get_slow_query_info.set(r, [](const_req req) {
|
|
ss::slow_query_info res;
|
|
res.enable = tracing::tracing::get_local_tracing_instance().slow_query_tracing_enabled();
|
|
res.ttl = tracing::tracing::get_local_tracing_instance().slow_query_record_ttl().count() ;
|
|
res.threshold = tracing::tracing::get_local_tracing_instance().slow_query_threshold().count();
|
|
res.fast = tracing::tracing::get_local_tracing_instance().ignore_trace_events_enabled();
|
|
return res;
|
|
});
|
|
|
|
ss::set_slow_query.set(r, [](std::unique_ptr<request> req) {
|
|
auto enable = req->get_query_param("enable");
|
|
auto ttl = req->get_query_param("ttl");
|
|
auto threshold = req->get_query_param("threshold");
|
|
auto fast = req->get_query_param("fast");
|
|
try {
|
|
return tracing::tracing::tracing_instance().invoke_on_all([enable, ttl, threshold, fast] (auto& local_tracing) {
|
|
if (threshold != "") {
|
|
local_tracing.set_slow_query_threshold(std::chrono::microseconds(std::stol(threshold.c_str())));
|
|
}
|
|
if (ttl != "") {
|
|
local_tracing.set_slow_query_record_ttl(std::chrono::seconds(std::stol(ttl.c_str())));
|
|
}
|
|
if (enable != "") {
|
|
local_tracing.set_slow_query_enabled(strcasecmp(enable.c_str(), "true") == 0);
|
|
}
|
|
if (fast != "") {
|
|
local_tracing.set_ignore_trace_events(strcasecmp(fast.c_str(), "true") == 0);
|
|
}
|
|
}).then([] {
|
|
return make_ready_future<json::json_return_type>(json_void());
|
|
});
|
|
} catch (...) {
|
|
throw httpd::bad_param_exception(format("Bad format value: "));
|
|
}
|
|
});
|
|
|
|
ss::enable_auto_compaction.set(r, [&ctx](std::unique_ptr<request> req) {
|
|
auto keyspace = validate_keyspace(ctx, req->param);
|
|
auto tables = parse_tables(keyspace, ctx, req->query_parameters, "cf");
|
|
|
|
return set_tables_autocompaction(ctx, keyspace, tables, true);
|
|
});
|
|
|
|
ss::disable_auto_compaction.set(r, [&ctx](std::unique_ptr<request> req) {
|
|
auto keyspace = validate_keyspace(ctx, req->param);
|
|
auto tables = parse_tables(keyspace, ctx, req->query_parameters, "cf");
|
|
|
|
return set_tables_autocompaction(ctx, keyspace, tables, false);
|
|
});
|
|
|
|
ss::deliver_hints.set(r, [](std::unique_ptr<request> req) {
|
|
//TBD
|
|
unimplemented();
|
|
auto host = req->get_query_param("host");
|
|
return make_ready_future<json::json_return_type>(json_void());
|
|
});
|
|
|
|
ss::get_cluster_name.set(r, [&g](const_req req) {
|
|
return g.get_cluster_name();
|
|
});
|
|
|
|
ss::get_partitioner_name.set(r, [&g](const_req req) {
|
|
return g.get_partitioner_name();
|
|
});
|
|
|
|
ss::get_tombstone_warn_threshold.set(r, [](std::unique_ptr<request> req) {
|
|
//TBD
|
|
unimplemented();
|
|
return make_ready_future<json::json_return_type>(0);
|
|
});
|
|
|
|
ss::set_tombstone_warn_threshold.set(r, [](std::unique_ptr<request> req) {
|
|
//TBD
|
|
unimplemented();
|
|
auto debug_threshold = req->get_query_param("debug_threshold");
|
|
return make_ready_future<json::json_return_type>(json_void());
|
|
});
|
|
|
|
ss::get_tombstone_failure_threshold.set(r, [](std::unique_ptr<request> req) {
|
|
//TBD
|
|
unimplemented();
|
|
return make_ready_future<json::json_return_type>(0);
|
|
});
|
|
|
|
ss::set_tombstone_failure_threshold.set(r, [](std::unique_ptr<request> req) {
|
|
//TBD
|
|
unimplemented();
|
|
auto debug_threshold = req->get_query_param("debug_threshold");
|
|
return make_ready_future<json::json_return_type>(json_void());
|
|
});
|
|
|
|
ss::get_batch_size_failure_threshold.set(r, [](std::unique_ptr<request> req) {
|
|
//TBD
|
|
unimplemented();
|
|
return make_ready_future<json::json_return_type>(0);
|
|
});
|
|
|
|
ss::set_batch_size_failure_threshold.set(r, [](std::unique_ptr<request> req) {
|
|
//TBD
|
|
unimplemented();
|
|
auto threshold = req->get_query_param("threshold");
|
|
return make_ready_future<json::json_return_type>(json_void());
|
|
});
|
|
|
|
ss::set_hinted_handoff_throttle_in_kb.set(r, [](std::unique_ptr<request> req) {
|
|
//TBD
|
|
unimplemented();
|
|
auto debug_threshold = req->get_query_param("throttle");
|
|
return make_ready_future<json::json_return_type>(json_void());
|
|
});
|
|
|
|
ss::get_metrics_load.set(r, [&ctx](std::unique_ptr<request> req) {
|
|
return get_cf_stats(ctx, &replica::column_family_stats::live_disk_space_used);
|
|
});
|
|
|
|
ss::get_exceptions.set(r, [&ss](const_req req) {
|
|
return ss.local().get_exception_count();
|
|
});
|
|
|
|
ss::get_total_hints_in_progress.set(r, [](std::unique_ptr<request> req) {
|
|
//TBD
|
|
unimplemented();
|
|
return make_ready_future<json::json_return_type>(0);
|
|
});
|
|
|
|
ss::get_total_hints.set(r, [](std::unique_ptr<request> req) {
|
|
//TBD
|
|
unimplemented();
|
|
return make_ready_future<json::json_return_type>(0);
|
|
});
|
|
|
|
ss::get_ownership.set(r, [&ss] (std::unique_ptr<request> req) {
|
|
return ss.local().get_ownership().then([] (auto&& ownership) {
|
|
std::vector<storage_service_json::mapper> res;
|
|
return make_ready_future<json::json_return_type>(map_to_key_value(ownership, res));
|
|
});
|
|
});
|
|
|
|
ss::get_effective_ownership.set(r, [&ctx, &ss] (std::unique_ptr<request> req) {
|
|
auto keyspace_name = req->param["keyspace"] == "null" ? "" : validate_keyspace(ctx, req->param);
|
|
return ss.local().effective_ownership(keyspace_name).then([] (auto&& ownership) {
|
|
std::vector<storage_service_json::mapper> res;
|
|
return make_ready_future<json::json_return_type>(map_to_key_value(ownership, res));
|
|
});
|
|
});
|
|
|
|
ss::sstable_info.set(r, [&ctx] (std::unique_ptr<request> req) {
|
|
auto ks = api::req_param<sstring>(*req, "keyspace", {}).value;
|
|
auto cf = api::req_param<sstring>(*req, "cf", {}).value;
|
|
|
|
// The size of this vector is bound by ks::cf. I.e. it is as most Nks + Ncf long
|
|
// which is not small, but not huge either.
|
|
using table_sstables_list = std::vector<ss::table_sstables>;
|
|
|
|
return do_with(table_sstables_list{}, [ks, cf, &ctx](table_sstables_list& dst) {
|
|
return ctx.db.map_reduce([&dst](table_sstables_list&& res) {
|
|
for (auto&& t : res) {
|
|
auto i = std::find_if(dst.begin(), dst.end(), [&t](const ss::table_sstables& t2) {
|
|
return t.keyspace() == t2.keyspace() && t.table() == t2.table();
|
|
});
|
|
if (i == dst.end()) {
|
|
dst.emplace_back(std::move(t));
|
|
continue;
|
|
}
|
|
auto& ssd = i->sstables;
|
|
for (auto&& sd : t.sstables._elements) {
|
|
auto j = std::find_if(ssd._elements.begin(), ssd._elements.end(), [&sd](const ss::sstable& s) {
|
|
return s.generation() == sd.generation();
|
|
});
|
|
if (j == ssd._elements.end()) {
|
|
i->sstables.push(std::move(sd));
|
|
}
|
|
}
|
|
}
|
|
}, [ks, cf](const replica::database& db) {
|
|
// see above
|
|
table_sstables_list res;
|
|
|
|
auto& ext = db.get_config().extensions();
|
|
|
|
for (auto& t : db.get_column_families() | boost::adaptors::map_values) {
|
|
auto& schema = t->schema();
|
|
if ((ks.empty() || ks == schema->ks_name()) && (cf.empty() || cf == schema->cf_name())) {
|
|
// at most Nsstables long
|
|
ss::table_sstables tst;
|
|
tst.keyspace = schema->ks_name();
|
|
tst.table = schema->cf_name();
|
|
|
|
for (auto sstables = t->get_sstables_including_compacted_undeleted(); auto sstable : *sstables) {
|
|
auto ts = db_clock::to_time_t(sstable->data_file_write_time());
|
|
::tm t;
|
|
::gmtime_r(&ts, &t);
|
|
|
|
ss::sstable info;
|
|
|
|
info.timestamp = t;
|
|
info.generation = sstables::generation_value(sstable->generation());
|
|
info.level = sstable->get_sstable_level();
|
|
info.size = sstable->bytes_on_disk();
|
|
info.data_size = sstable->ondisk_data_size();
|
|
info.index_size = sstable->index_size();
|
|
info.filter_size = sstable->filter_size();
|
|
info.version = sstable->get_version();
|
|
|
|
if (sstable->has_component(sstables::component_type::CompressionInfo)) {
|
|
auto& c = sstable->get_compression();
|
|
auto cp = sstables::get_sstable_compressor(c);
|
|
|
|
ss::named_maps nm;
|
|
nm.group = "compression_parameters";
|
|
for (auto& p : cp->options()) {
|
|
ss::mapper e;
|
|
e.key = p.first;
|
|
e.value = p.second;
|
|
nm.attributes.push(std::move(e));
|
|
}
|
|
if (!cp->options().contains(compression_parameters::SSTABLE_COMPRESSION)) {
|
|
ss::mapper e;
|
|
e.key = compression_parameters::SSTABLE_COMPRESSION;
|
|
e.value = cp->name();
|
|
nm.attributes.push(std::move(e));
|
|
}
|
|
info.extended_properties.push(std::move(nm));
|
|
}
|
|
|
|
sstables::file_io_extension::attr_value_map map;
|
|
|
|
for (auto* ep : ext.sstable_file_io_extensions()) {
|
|
map.merge(ep->get_attributes(*sstable));
|
|
}
|
|
|
|
for (auto& p : map) {
|
|
struct {
|
|
const sstring& key;
|
|
ss::sstable& info;
|
|
void operator()(const std::map<sstring, sstring>& map) const {
|
|
ss::named_maps nm;
|
|
nm.group = key;
|
|
for (auto& p : map) {
|
|
ss::mapper e;
|
|
e.key = p.first;
|
|
e.value = p.second;
|
|
nm.attributes.push(std::move(e));
|
|
}
|
|
info.extended_properties.push(std::move(nm));
|
|
}
|
|
void operator()(const sstring& value) const {
|
|
ss::mapper e;
|
|
e.key = key;
|
|
e.value = value;
|
|
info.properties.push(std::move(e));
|
|
}
|
|
} v{p.first, info};
|
|
|
|
std::visit(v, p.second);
|
|
}
|
|
|
|
tst.sstables.push(std::move(info));
|
|
}
|
|
res.emplace_back(std::move(tst));
|
|
}
|
|
}
|
|
std::sort(res.begin(), res.end(), [](const ss::table_sstables& t1, const ss::table_sstables& t2) {
|
|
return t1.keyspace() < t2.keyspace() || (t1.keyspace() == t2.keyspace() && t1.table() < t2.table());
|
|
});
|
|
return res;
|
|
}).then([&dst] {
|
|
return make_ready_future<json::json_return_type>(stream_object(dst));
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
enum class scrub_status {
|
|
successful = 0,
|
|
aborted,
|
|
unable_to_cancel, // Not used in Scylla, included to ensure compability with nodetool api.
|
|
validation_errors,
|
|
};
|
|
|
|
void set_snapshot(http_context& ctx, routes& r, sharded<db::snapshot_ctl>& snap_ctl) {
|
|
ss::get_snapshot_details.set(r, [&snap_ctl](std::unique_ptr<request> req) {
|
|
return snap_ctl.local().get_snapshot_details().then([] (std::unordered_map<sstring, std::vector<db::snapshot_ctl::snapshot_details>>&& result) {
|
|
std::function<future<>(output_stream<char>&&)> f = [result = std::move(result)](output_stream<char>&& s) {
|
|
return do_with(output_stream<char>(std::move(s)), true, [&result] (output_stream<char>& s, bool& first){
|
|
return s.write("[").then([&s, &first, &result] {
|
|
return do_for_each(result, [&s, &first](std::tuple<sstring, std::vector<db::snapshot_ctl::snapshot_details>>&& map){
|
|
return do_with(ss::snapshots(), [&s, &first, &map](ss::snapshots& all_snapshots) {
|
|
all_snapshots.key = std::get<0>(map);
|
|
future<> f = first ? make_ready_future<>() : s.write(", ");
|
|
first = false;
|
|
std::vector<ss::snapshot> snapshot;
|
|
for (auto& cf: std::get<1>(map)) {
|
|
ss::snapshot snp;
|
|
snp.ks = cf.ks;
|
|
snp.cf = cf.cf;
|
|
snp.live = cf.live;
|
|
snp.total = cf.total;
|
|
snapshot.push_back(std::move(snp));
|
|
}
|
|
all_snapshots.value = std::move(snapshot);
|
|
return f.then([&s, &all_snapshots] {
|
|
return all_snapshots.write(s);
|
|
});
|
|
});
|
|
});
|
|
}).then([&s] {
|
|
return s.write("]").then([&s] {
|
|
return s.close();
|
|
});
|
|
});
|
|
});
|
|
};
|
|
|
|
return make_ready_future<json::json_return_type>(std::move(f));
|
|
});
|
|
});
|
|
|
|
ss::take_snapshot.set(r, [&snap_ctl](std::unique_ptr<request> req) -> future<json::json_return_type> {
|
|
apilog.info("take_snapshot: {}", req->query_parameters);
|
|
auto tag = req->get_query_param("tag");
|
|
auto column_families = split(req->get_query_param("cf"), ",");
|
|
auto sfopt = req->get_query_param("sf");
|
|
auto sf = db::snapshot_ctl::skip_flush(strcasecmp(sfopt.c_str(), "true") == 0);
|
|
|
|
std::vector<sstring> keynames = split(req->get_query_param("kn"), ",");
|
|
try {
|
|
if (column_families.empty()) {
|
|
co_await snap_ctl.local().take_snapshot(tag, keynames, sf);
|
|
} else {
|
|
if (keynames.empty()) {
|
|
throw httpd::bad_param_exception("The keyspace of column families must be specified");
|
|
}
|
|
if (keynames.size() > 1) {
|
|
throw httpd::bad_param_exception("Only one keyspace allowed when specifying a column family");
|
|
}
|
|
co_await snap_ctl.local().take_column_family_snapshot(keynames[0], column_families, tag, sf);
|
|
}
|
|
co_return json_void();
|
|
} catch (...) {
|
|
apilog.error("take_snapshot failed: {}", std::current_exception());
|
|
throw;
|
|
}
|
|
});
|
|
|
|
ss::del_snapshot.set(r, [&snap_ctl](std::unique_ptr<request> req) -> future<json::json_return_type> {
|
|
apilog.info("del_snapshot: {}", req->query_parameters);
|
|
auto tag = req->get_query_param("tag");
|
|
auto column_family = req->get_query_param("cf");
|
|
|
|
std::vector<sstring> keynames = split(req->get_query_param("kn"), ",");
|
|
try {
|
|
co_await snap_ctl.local().clear_snapshot(tag, keynames, column_family);
|
|
co_return json_void();
|
|
} catch (...) {
|
|
apilog.error("del_snapshot failed: {}", std::current_exception());
|
|
throw;
|
|
}
|
|
});
|
|
|
|
ss::true_snapshots_size.set(r, [&snap_ctl](std::unique_ptr<request> req) {
|
|
return snap_ctl.local().true_snapshots_size().then([] (int64_t size) {
|
|
return make_ready_future<json::json_return_type>(size);
|
|
});
|
|
});
|
|
|
|
ss::scrub.set(r, [&ctx, &snap_ctl] (std::unique_ptr<request> req) {
|
|
auto rp = req_params({
|
|
{"keyspace", {mandatory::yes}},
|
|
{"cf", {""}},
|
|
{"scrub_mode", {}},
|
|
{"skip_corrupted", {}},
|
|
{"disable_snapshot", {}},
|
|
{"quarantine_mode", {}},
|
|
});
|
|
rp.process(*req);
|
|
auto keyspace = validate_keyspace(ctx, *rp.get("keyspace"));
|
|
auto column_families = parse_tables(keyspace, ctx, *rp.get("cf"));
|
|
auto scrub_mode_opt = rp.get("scrub_mode");
|
|
auto scrub_mode = sstables::compaction_type_options::scrub::mode::abort;
|
|
|
|
if (!scrub_mode_opt) {
|
|
const auto skip_corrupted = rp.get_as<bool>("skip_corrupted").value_or(false);
|
|
|
|
if (skip_corrupted) {
|
|
scrub_mode = sstables::compaction_type_options::scrub::mode::skip;
|
|
}
|
|
} else {
|
|
auto scrub_mode_str = *scrub_mode_opt;
|
|
if (scrub_mode_str == "ABORT") {
|
|
scrub_mode = sstables::compaction_type_options::scrub::mode::abort;
|
|
} else if (scrub_mode_str == "SKIP") {
|
|
scrub_mode = sstables::compaction_type_options::scrub::mode::skip;
|
|
} else if (scrub_mode_str == "SEGREGATE") {
|
|
scrub_mode = sstables::compaction_type_options::scrub::mode::segregate;
|
|
} else if (scrub_mode_str == "VALIDATE") {
|
|
scrub_mode = sstables::compaction_type_options::scrub::mode::validate;
|
|
} else {
|
|
throw httpd::bad_param_exception(fmt::format("Unknown argument for 'scrub_mode' parameter: {}", scrub_mode_str));
|
|
}
|
|
}
|
|
|
|
auto f = make_ready_future<>();
|
|
if (!req_param<bool>(*req, "disable_snapshot", false)) {
|
|
auto tag = format("pre-scrub-{:d}", db_clock::now().time_since_epoch().count());
|
|
f = parallel_for_each(column_families, [&snap_ctl, keyspace, tag](sstring cf) {
|
|
return snap_ctl.local().take_column_family_snapshot(keyspace, cf, tag, db::snapshot_ctl::skip_flush::no, db::snapshot_ctl::allow_view_snapshots::yes);
|
|
});
|
|
}
|
|
|
|
sstables::compaction_type_options::scrub opts = {
|
|
.operation_mode = scrub_mode,
|
|
};
|
|
const sstring quarantine_mode_str = req_param<sstring>(*req, "quarantine_mode", "INCLUDE");
|
|
if (quarantine_mode_str == "INCLUDE") {
|
|
opts.quarantine_operation_mode = sstables::compaction_type_options::scrub::quarantine_mode::include;
|
|
} else if (quarantine_mode_str == "EXCLUDE") {
|
|
opts.quarantine_operation_mode = sstables::compaction_type_options::scrub::quarantine_mode::exclude;
|
|
} else if (quarantine_mode_str == "ONLY") {
|
|
opts.quarantine_operation_mode = sstables::compaction_type_options::scrub::quarantine_mode::only;
|
|
} else {
|
|
throw httpd::bad_param_exception(fmt::format("Unknown argument for 'quarantine_mode' parameter: {}", quarantine_mode_str));
|
|
}
|
|
|
|
const auto& reduce_compaction_stats = [] (const compaction_manager::compaction_stats_opt& lhs, const compaction_manager::compaction_stats_opt& rhs) {
|
|
sstables::compaction_stats stats{};
|
|
stats += lhs.value();
|
|
stats += rhs.value();
|
|
return stats;
|
|
};
|
|
|
|
return f.then([&ctx, keyspace, column_families, opts, &reduce_compaction_stats] {
|
|
return ctx.db.map_reduce0([=] (replica::database& db) {
|
|
return map_reduce(column_families, [=, &db] (sstring cfname) {
|
|
auto& cm = db.get_compaction_manager();
|
|
auto& cf = db.find_column_family(keyspace, cfname);
|
|
return cm.perform_sstable_scrub(cf.as_table_state(), opts);
|
|
}, std::make_optional(sstables::compaction_stats{}), reduce_compaction_stats);
|
|
}, std::make_optional(sstables::compaction_stats{}), reduce_compaction_stats);
|
|
}).then_wrapped([] (auto f) {
|
|
if (f.failed()) {
|
|
auto ex = f.get_exception();
|
|
if (try_catch<sstables::compaction_aborted_exception>(ex)) {
|
|
return make_ready_future<json::json_return_type>(static_cast<int>(scrub_status::aborted));
|
|
} else {
|
|
return make_exception_future<json::json_return_type>(std::move(ex));
|
|
}
|
|
} else if (f.get()->validation_errors) {
|
|
return make_ready_future<json::json_return_type>(static_cast<int>(scrub_status::validation_errors));
|
|
} else {
|
|
return make_ready_future<json::json_return_type>(static_cast<int>(scrub_status::successful));
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
void unset_snapshot(http_context& ctx, routes& r) {
|
|
ss::get_snapshot_details.unset(r);
|
|
ss::take_snapshot.unset(r);
|
|
ss::del_snapshot.unset(r);
|
|
ss::true_snapshots_size.unset(r);
|
|
ss::scrub.unset(r);
|
|
}
|
|
|
|
}
|