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:
Avi Kivity
2021-04-12 17:34:43 +03:00
47 changed files with 2505 additions and 36 deletions

View File

@@ -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) {

View File

@@ -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({
});
});
}

View File

@@ -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(); });

View 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);
});
}
};
}