Before this commit, any attempt to create, alter, attach, or drop the default service level would result in a syntax error whose error message was unclear: ``` cqlsh> attach service level default to cassandra; SyntaxException: line 1:21 no viable alternative at input 'default' ``` The error stems from the grammar not being able to parse `default` as a correct service level name. To fix that, we cover it manually. This way, the grammar accepts it and we can process it in Scylla. The reason why we'd like to cover the default service level is that it's an actual service level that the user should reference. Getting a syntax error is not what should happen. Hence this fix. We validate the input and if the given role is really the default service level, we reject the query and provide an informative error message. Two validation tests are provided. Fixes scylladb/scylladb#26699 Closes scylladb/scylladb#27162
71 lines
2.8 KiB
C++
71 lines
2.8 KiB
C++
/*
|
|
* Copyright (C) 2021-present ScyllaDB
|
|
*/
|
|
|
|
/*
|
|
* SPDX-License-Identifier: LicenseRef-ScyllaDB-Source-Available-1.0
|
|
*/
|
|
|
|
#include "seastarx.hh"
|
|
#include "auth/service.hh"
|
|
#include "cql3/statements/attach_service_level_statement.hh"
|
|
#include "service/qos/service_level_controller.hh"
|
|
#include "exceptions/exceptions.hh"
|
|
#include "transport/messages/result_message.hh"
|
|
#include "service/client_state.hh"
|
|
#include "service/query_state.hh"
|
|
#include "cql3/query_processor.hh"
|
|
|
|
namespace cql3 {
|
|
|
|
namespace statements {
|
|
|
|
attach_service_level_statement::attach_service_level_statement(sstring service_level, sstring role_name) :
|
|
_service_level(service_level), _role_name(role_name) {
|
|
}
|
|
|
|
bool attach_service_level_statement::needs_guard(query_processor& qp, service::query_state& state) const {
|
|
return !auth::legacy_mode(qp) || state.get_service_level_controller().is_v2();
|
|
}
|
|
|
|
std::unique_ptr<cql3::statements::prepared_statement>
|
|
cql3::statements::attach_service_level_statement::prepare(
|
|
data_dictionary::database db, cql_stats &stats) {
|
|
return std::make_unique<prepared_statement>(audit_info(), ::make_shared<attach_service_level_statement>(*this));
|
|
}
|
|
|
|
future<> attach_service_level_statement::check_access(query_processor& qp, const service::client_state &state) const {
|
|
return state.ensure_has_permission(auth::command_desc{.permission = auth::permission::AUTHORIZE, .resource = auth::root_service_level_resource()});
|
|
}
|
|
|
|
future<::shared_ptr<cql_transport::messages::result_message>>
|
|
attach_service_level_statement::execute(query_processor& qp,
|
|
service::query_state &state,
|
|
const query_options &,
|
|
std::optional<service::group0_guard> guard) const {
|
|
if (_service_level == qos::service_level_controller::default_service_level_name) {
|
|
sstring reason = seastar::format("The default service level, {}, cannot be "
|
|
"attached to a role. If you want to detach an attached service level, "
|
|
"use the DETACH SERVICE LEVEL statement",
|
|
qos::service_level_controller::default_service_level_name);
|
|
throw exceptions::invalid_request_exception(std::move(reason));
|
|
}
|
|
|
|
auto sli = co_await state.get_service_level_controller().get_distributed_service_level(_service_level);
|
|
if (sli.empty()) {
|
|
throw qos::nonexistant_service_level_exception(_service_level);
|
|
}
|
|
|
|
auto& as = *state.get_client_state().get_auth_service();
|
|
auto& sl = state.get_service_level_controller();
|
|
service::group0_batch mc{std::move(guard)};
|
|
co_await auth::set_attribute(as, _role_name, "service_level", _service_level, mc);
|
|
co_await sl.commit_mutations(std::move(mc));
|
|
|
|
using void_result_msg = cql_transport::messages::result_message::void_message;
|
|
using result_msg = cql_transport::messages::result_message;
|
|
co_return ::static_pointer_cast<result_msg>(make_shared<void_result_msg>());
|
|
}
|
|
}
|
|
}
|