Merge 'Introduce service levels' from Piotr Sarna
This series introduces service level syntax borrowed from https://docs.scylladb.com/using-scylla/workload-prioritization/ , but without workload prioritization itself - just for the sake of using identical syntax to provide different parameters later. The new parameters may include: * per-service-level timeouts * oltp/olap declaration, which may change the way Scylla treats long requests - e.g. time them out (the oltp way) or keep them sustained with empty pages (the olap way) Refs #7617 Closes #7867 * github.com:scylladb/scylla: transport: initialize query state with service level controller main: add initializing service level data accessor service: make enable_shared_from_this inheritance public cql3: add SERVICE LEVEL syntax (without an underscore) unit test: Add unit test for per user sla syntax cql: Add support for service level cql queries auth: Add service_level resource for supporting in authorization of cql service_level cql: Support accessing service_level_controller from query state instantiate and initialize the service_level_controller qos: Add a standard implementation for service level data accessor qos: add waiting for the updater future service/qos: adding service level controller service_levels: Add documentation for distributed tables service/qos: adding service level table to the distributed keyspace service/qos: add common definitions auth: add support for role attributes
This commit is contained in:
@@ -50,6 +50,13 @@ BOOST_AUTO_TEST_CASE(root_of) {
|
||||
|
||||
const auto rv = auth::role_resource_view(rr);
|
||||
BOOST_REQUIRE(!rv.role());
|
||||
|
||||
//
|
||||
// service_level
|
||||
//
|
||||
|
||||
const auto slr = auth::resource(auth::resource_kind::service_level);
|
||||
BOOST_REQUIRE_EQUAL(slr.kind(), auth::resource_kind::service_level);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(data) {
|
||||
@@ -76,6 +83,11 @@ BOOST_AUTO_TEST_CASE(role) {
|
||||
BOOST_REQUIRE_EQUAL(*v.role(), "joe");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(service_level) {
|
||||
const auto r = auth::make_service_level_resource();
|
||||
BOOST_REQUIRE_EQUAL(r.kind(), auth::resource_kind::service_level);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(from_name) {
|
||||
//
|
||||
// data
|
||||
@@ -104,6 +116,15 @@ BOOST_AUTO_TEST_CASE(from_name) {
|
||||
|
||||
BOOST_REQUIRE_THROW(auth::parse_resource("roles/joe/smith"), auth::invalid_resource_name);
|
||||
|
||||
//
|
||||
//service_level
|
||||
//
|
||||
|
||||
const auto slr1 = auth::parse_resource("service_levels");
|
||||
BOOST_REQUIRE_EQUAL(slr1, auth::root_service_level_resource());
|
||||
|
||||
BOOST_REQUIRE_THROW(auth::parse_resource("service_levels/low_priority"), auth::invalid_resource_name);
|
||||
|
||||
//
|
||||
// Generic errors.
|
||||
//
|
||||
@@ -127,6 +148,12 @@ BOOST_AUTO_TEST_CASE(name) {
|
||||
|
||||
BOOST_REQUIRE_EQUAL(auth::root_role_resource().name(), "roles");
|
||||
BOOST_REQUIRE_EQUAL(auth::make_role_resource("joe").name(), "roles/joe");
|
||||
|
||||
//
|
||||
// service_level
|
||||
//
|
||||
|
||||
BOOST_REQUIRE_EQUAL(auth::root_service_level_resource().name(), "service_levels");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(parent) {
|
||||
@@ -162,6 +189,12 @@ BOOST_AUTO_TEST_CASE(output) {
|
||||
|
||||
BOOST_REQUIRE_EQUAL(format("{}", auth::root_role_resource()), "<all roles>");
|
||||
BOOST_REQUIRE_EQUAL(format("{}", auth::make_role_resource("joe")), "<role joe>");
|
||||
|
||||
//
|
||||
// service_level
|
||||
//
|
||||
|
||||
BOOST_REQUIRE_EQUAL(format("{}", auth::root_service_level_resource()), "<all service levels>");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(expand) {
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
#include <regex>
|
||||
#include "gms/feature.hh"
|
||||
#include "db/query_context.hh"
|
||||
#include "service/qos/qos_common.hh"
|
||||
|
||||
using namespace std::literals::chrono_literals;
|
||||
|
||||
@@ -4881,3 +4882,81 @@ SEASTAR_THREAD_TEST_CASE(test_query_unselected_columns) {
|
||||
}
|
||||
}, std::move(cfg), thread_attributes{.sched_group = statement_sched_group}).get();
|
||||
}
|
||||
|
||||
SEASTAR_TEST_CASE(test_user_based_sla_queries) {
|
||||
return do_with_cql_env_thread([] (cql_test_env& e) {
|
||||
// test create service level with defaults
|
||||
e.execute_cql("CREATE SERVICE_LEVEL sl_1;").get();
|
||||
auto msg = e.execute_cql("LIST SERVICE_LEVEL sl_1;").get0();
|
||||
assert_that(msg).is_rows().with_rows({
|
||||
{utf8_type->decompose("sl_1")},
|
||||
});
|
||||
e.execute_cql("CREATE SERVICE_LEVEL sl_2;").get();
|
||||
//drop service levels
|
||||
e.execute_cql("DROP SERVICE_LEVEL sl_1;").get();
|
||||
msg = e.execute_cql("LIST ALL SERVICE_LEVELS;").get0();
|
||||
assert_that(msg).is_rows().with_rows({
|
||||
{utf8_type->decompose("sl_2")},
|
||||
});
|
||||
|
||||
// validate exceptions (illegal requests)
|
||||
BOOST_REQUIRE_THROW(e.execute_cql("DROP SERVICE_LEVEL sl_1;").get(), qos::nonexistant_service_level_exception);
|
||||
e.execute_cql("DROP SERVICE_LEVEL IF EXISTS sl_1;").get();
|
||||
|
||||
BOOST_REQUIRE_THROW(e.execute_cql("CREATE SERVICE_LEVEL sl_2;").get(), exceptions::invalid_request_exception);
|
||||
BOOST_REQUIRE_THROW(e.execute_cql("CREATE SERVICE_LEVEL sl_2;").get(), exceptions::invalid_request_exception);
|
||||
e.execute_cql("CREATE SERVICE_LEVEL IF NOT EXISTS sl_2;").get();
|
||||
|
||||
// test attach role
|
||||
e.execute_cql("ATTACH SERVICE_LEVEL sl_2 TO tester").get();
|
||||
msg = e.execute_cql("LIST ATTACHED SERVICE_LEVEL OF tester;").get0();
|
||||
assert_that(msg).is_rows().with_rows({
|
||||
{utf8_type->decompose("tester"), utf8_type->decompose("sl_2")},
|
||||
});
|
||||
msg = e.execute_cql("LIST ALL ATTACHED SERVICE_LEVELS;").get0();
|
||||
assert_that(msg).is_rows().with_rows({
|
||||
{utf8_type->decompose("tester"), utf8_type->decompose("sl_2")},
|
||||
});
|
||||
|
||||
// test attachment illegal request
|
||||
BOOST_REQUIRE_THROW(e.execute_cql("ATTACH SERVICE_LEVEL sl_2 TO tester2;").get(), auth::nonexistant_role);
|
||||
BOOST_REQUIRE_THROW(e.execute_cql("ATTACH SERVICE_LEVEL sl_1 TO tester;").get(), qos::nonexistant_service_level_exception);
|
||||
BOOST_CHECK(true);
|
||||
// tests detaching service levels
|
||||
e.execute_cql("CREATE ROLE tester2;").get();
|
||||
e.execute_cql("CREATE SERVICE_LEVEL sl_1;").get();
|
||||
e.execute_cql("ATTACH SERVICE_LEVEL sl_1 TO tester2;").get();
|
||||
e.execute_cql("DETACH SERVICE_LEVEL FROM tester;").get();
|
||||
msg = e.execute_cql("LIST ATTACHED SERVICE_LEVEL OF tester2;").get0();
|
||||
assert_that(msg).is_rows().with_rows({
|
||||
{utf8_type->decompose("tester2"), utf8_type->decompose("sl_1")},
|
||||
});
|
||||
BOOST_CHECK(true);
|
||||
msg = e.execute_cql("LIST ATTACHED SERVICE_LEVEL OF tester;").get0();
|
||||
assert_that(msg).is_rows().with_rows({
|
||||
});
|
||||
BOOST_CHECK(true);
|
||||
msg = e.execute_cql("LIST ALL ATTACHED SERVICE_LEVELS;").get0();
|
||||
assert_that(msg).is_rows().with_rows({
|
||||
{utf8_type->decompose("tester2"), utf8_type->decompose("sl_1")},
|
||||
});
|
||||
BOOST_CHECK(true);
|
||||
//test implicit detach when removing role
|
||||
e.execute_cql("DROP ROLE tester2;").get();
|
||||
msg = e.execute_cql("LIST ALL ATTACHED SERVICE_LEVELS;").get0();
|
||||
assert_that(msg).is_rows().with_rows({
|
||||
});
|
||||
BOOST_CHECK(true);
|
||||
e.execute_cql("ATTACH SERVICE_LEVEL sl_1 TO tester;").get();
|
||||
msg = e.execute_cql("LIST ALL ATTACHED SERVICE_LEVELS;").get0();
|
||||
assert_that(msg).is_rows().with_rows({
|
||||
{utf8_type->decompose("tester"), utf8_type->decompose("sl_1")},
|
||||
});
|
||||
BOOST_CHECK(true);
|
||||
//test implicit detach when removing service level
|
||||
e.execute_cql("DROP SERVICE_LEVEL sl_1;").get();
|
||||
msg = e.execute_cql("LIST ALL ATTACHED SERVICE_LEVELS;").get0();
|
||||
assert_that(msg).is_rows().with_rows({
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
#include "test/lib/reader_permit.hh"
|
||||
#include "db/query_context.hh"
|
||||
#include "test/lib/test_services.hh"
|
||||
#include "unit_test_service_levels_accessor.hh"
|
||||
#include "db/view/view_builder.hh"
|
||||
#include "db/view/node_view_update_backlog.hh"
|
||||
#include "distributed_loader.hh"
|
||||
@@ -123,6 +124,7 @@ private:
|
||||
sharded<db::view::view_update_generator>& _view_update_generator;
|
||||
sharded<service::migration_notifier>& _mnotifier;
|
||||
sharded<cdc::generation_service>& _cdc_generation_service;
|
||||
sharded<qos::service_level_controller>& _sl_controller;
|
||||
private:
|
||||
struct core_local_state {
|
||||
service::client_state client_state;
|
||||
@@ -143,7 +145,7 @@ private:
|
||||
if (_db.local().has_keyspace(ks_name)) {
|
||||
_core_local.local().client_state.set_keyspace(_db.local(), ks_name);
|
||||
}
|
||||
return ::make_shared<service::query_state>(_core_local.local().client_state, empty_service_permit());
|
||||
return ::make_shared<service::query_state>(_core_local.local().client_state, empty_service_permit(), _sl_controller.local());
|
||||
}
|
||||
public:
|
||||
single_node_cql_env(
|
||||
@@ -154,7 +156,8 @@ public:
|
||||
sharded<db::view::view_builder>& view_builder,
|
||||
sharded<db::view::view_update_generator>& view_update_generator,
|
||||
sharded<service::migration_notifier>& mnotifier,
|
||||
sharded<cdc::generation_service>& cdc_generation_service)
|
||||
sharded<cdc::generation_service>& cdc_generation_service,
|
||||
sharded<qos::service_level_controller> &sl_controller)
|
||||
: _feature_service(feature_service)
|
||||
, _db(db)
|
||||
, _qp(qp)
|
||||
@@ -163,6 +166,7 @@ public:
|
||||
, _view_update_generator(view_update_generator)
|
||||
, _mnotifier(mnotifier)
|
||||
, _cdc_generation_service(cdc_generation_service)
|
||||
, _sl_controller(sl_controller)
|
||||
{ }
|
||||
|
||||
virtual future<::shared_ptr<cql_transport::messages::result_message>> execute_cql(sstring_view text) override {
|
||||
@@ -438,15 +442,27 @@ public:
|
||||
mm_notif.start().get();
|
||||
auto stop_mm_notify = defer([&mm_notif] { mm_notif.stop().get(); });
|
||||
|
||||
sharded<auth::service> auth_service;
|
||||
|
||||
set_abort_on_internal_error(true);
|
||||
const gms::inet_address listen("127.0.0.1");
|
||||
auto sys_dist_ks = seastar::sharded<db::system_distributed_keyspace>();
|
||||
auto sl_controller = sharded<qos::service_level_controller>();
|
||||
sl_controller.start(std::ref(auth_service), qos::service_level_options{}).get();
|
||||
auto stop_sl_controller = defer([&sl_controller] { sl_controller.stop().get(); });
|
||||
sl_controller.invoke_on_all(&qos::service_level_controller::start).get();
|
||||
sl_controller.invoke_on_all([&sys_dist_ks, &sl_controller] (qos::service_level_controller& service) {
|
||||
qos::service_level_controller::service_level_distributed_data_accessor_ptr service_level_data_accessor =
|
||||
::static_pointer_cast<qos::service_level_controller::service_level_distributed_data_accessor>(
|
||||
make_shared<qos::unit_test_service_levels_accessor>(sl_controller,sys_dist_ks));
|
||||
return service.set_distributed_data_accessor(std::move(service_level_data_accessor));
|
||||
}).get();
|
||||
|
||||
sharded<netw::messaging_service> ms;
|
||||
// don't start listening so tests can be run in parallel
|
||||
ms.start(listen, std::move(7000)).get();
|
||||
auto stop_ms = defer([&ms] { ms.stop().get(); });
|
||||
|
||||
sharded<auth::service> auth_service;
|
||||
// Normally the auth server is already stopped in here,
|
||||
// but if there is an initialization failure we have to
|
||||
// make sure to stop it now or ~sharded will assert.
|
||||
@@ -454,7 +470,6 @@ public:
|
||||
auth_service.stop().get();
|
||||
});
|
||||
|
||||
auto sys_dist_ks = seastar::sharded<db::system_distributed_keyspace>();
|
||||
auto stop_sys_dist_ks = defer([&sys_dist_ks] { sys_dist_ks.stop().get(); });
|
||||
|
||||
gms::feature_config fcfg = gms::feature_config_from_db_config(*cfg, cfg_in.disabled_features);
|
||||
@@ -529,7 +544,7 @@ public:
|
||||
|
||||
sharded<cql3::query_processor> qp;
|
||||
cql3::query_processor::memory_config qp_mcfg = {memory::stats().total_memory() / 256, memory::stats().total_memory() / 2560};
|
||||
qp.start(std::ref(proxy), std::ref(db), std::ref(mm_notif), std::ref(mm), qp_mcfg, std::ref(cql_config)).get();
|
||||
qp.start(std::ref(proxy), std::ref(db), std::ref(mm_notif), std::ref(mm), qp_mcfg, std::ref(cql_config), std::ref(sl_controller)).get();
|
||||
auto stop_qp = defer([&qp] { qp.stop().get(); });
|
||||
|
||||
// In main.cc we call db::system_keyspace::setup which calls
|
||||
@@ -643,7 +658,7 @@ public:
|
||||
// The default user may already exist if this `cql_test_env` is starting with previously populated data.
|
||||
}
|
||||
|
||||
single_node_cql_env env(feature_service, db, qp, auth_service, view_builder, view_update_generator, mm_notif, cdc_generation_service);
|
||||
single_node_cql_env env(feature_service, db, qp, auth_service, view_builder, view_update_generator, mm_notif, cdc_generation_service, std::ref(sl_controller));
|
||||
env.start().get();
|
||||
auto stop_env = defer([&env] { env.stop().get(); });
|
||||
|
||||
|
||||
62
test/lib/unit_test_service_levels_accessor.hh
Normal file
62
test/lib/unit_test_service_levels_accessor.hh
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (C) 2021 ScyllaDB
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is part of Scylla.
|
||||
*
|
||||
* Scylla is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Scylla is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Scylla. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "service/qos/service_level_controller.hh"
|
||||
#include "service/qos/qos_common.hh"
|
||||
#include "db/system_distributed_keyspace.hh"
|
||||
#pragma once
|
||||
|
||||
namespace qos {
|
||||
|
||||
/**
|
||||
* This class is a helper for unit testing. It implements the service level distributed
|
||||
* accessor interface in order to be used in the unit testing environment. The advantage
|
||||
* of this class over the standard implementation is that it makes sure that updates are
|
||||
* Immediately propagated to the underlying service level controller.
|
||||
*/
|
||||
class unit_test_service_levels_accessor : public service_level_controller::service_level_distributed_data_accessor {
|
||||
sharded<service_level_controller> &_sl_controller;
|
||||
sharded<db::system_distributed_keyspace> &_sys_dist_ks;
|
||||
public:
|
||||
unit_test_service_levels_accessor(sharded<service_level_controller>& sl_controller, sharded<db::system_distributed_keyspace> &sys_dist_ks)
|
||||
: _sl_controller(sl_controller)
|
||||
, _sys_dist_ks(sys_dist_ks)
|
||||
{}
|
||||
virtual future<qos::service_levels_info> get_service_levels() const {
|
||||
return _sys_dist_ks.local().get_service_levels();
|
||||
}
|
||||
virtual future<qos::service_levels_info> get_service_level(sstring service_level_name) const {
|
||||
return _sys_dist_ks.local().get_service_level(service_level_name);
|
||||
}
|
||||
virtual future<> set_service_level(sstring service_level_name, qos::service_level_options slo) const {
|
||||
return _sys_dist_ks.local().set_service_level(service_level_name, slo).then([this] () {
|
||||
return _sl_controller.invoke_on_all(&service_level_controller::update_service_levels_from_distributed_data);
|
||||
});
|
||||
|
||||
}
|
||||
virtual future<> drop_service_level(sstring service_level_name) const {
|
||||
return _sys_dist_ks.local().drop_service_level(service_level_name).then([this] () {
|
||||
return _sl_controller.invoke_on_all(&service_level_controller::update_service_levels_from_distributed_data);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user