Compare commits
53 Commits
copilot/fi
...
copilot/re
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bb1ab98fc9 | ||
|
|
bdbc47a333 | ||
|
|
5407b6b43d | ||
|
|
8582156257 | ||
|
|
866c96f536 | ||
|
|
367633270a | ||
|
|
e97a504775 | ||
|
|
a5c971d21c | ||
|
|
a0809f0032 | ||
|
|
bb6e41f97a | ||
|
|
4df6b51ac2 | ||
|
|
0c8730ba05 | ||
|
|
bc2e83bc1f | ||
|
|
f4c3d5c1b7 | ||
|
|
e54abde3e8 | ||
|
|
9696ee64d0 | ||
|
|
8dd69f02a8 | ||
|
|
d000fa3335 | ||
|
|
4e289e8e6a | ||
|
|
9d2f7c3f52 | ||
|
|
e3e81a9a7a | ||
|
|
b82f92b439 | ||
|
|
f00e00fde0 | ||
|
|
b0727d3f2a | ||
|
|
4169bdb7a6 | ||
|
|
c5580399a8 | ||
|
|
1d42770936 | ||
|
|
d287b054b9 | ||
|
|
4f803aad22 | ||
|
|
a54bf50290 | ||
|
|
06dd3b2e64 | ||
|
|
6163fedd2e | ||
|
|
67f1c6d36c | ||
|
|
669286b1d6 | ||
|
|
b9199e8b24 | ||
|
|
1ff7f5941b | ||
|
|
3b70154f0a | ||
|
|
6ae72ed134 | ||
|
|
a191503ddf | ||
|
|
619bf3ac4b | ||
|
|
62802b119b | ||
|
|
323e5cd171 | ||
|
|
dd461e0472 | ||
|
|
0c9b2e5332 | ||
|
|
b29c42adce | ||
|
|
ea3dc0b0de | ||
|
|
2a6bef96d6 | ||
|
|
19da1cb656 | ||
|
|
2cf1ca43b5 | ||
|
|
642f468c59 | ||
|
|
bd7c87731b | ||
|
|
4c667e87ec | ||
|
|
aacf883a8b |
109
UNIMPLEMENTED_ENUM_ANALYSIS.md
Normal file
109
UNIMPLEMENTED_ENUM_ANALYSIS.md
Normal file
@@ -0,0 +1,109 @@
|
||||
# Analysis of unimplemented::cause Enum Values
|
||||
|
||||
This document provides an analysis of the `unimplemented::cause` enum values after cleanup.
|
||||
|
||||
## Removed Unused Enum Values (20 values removed)
|
||||
|
||||
The following enum values had **zero usages** in the codebase and have been removed:
|
||||
|
||||
- `LWT` - Lightweight transactions
|
||||
- `PAGING` - Query result paging
|
||||
- `AUTH` - Authentication
|
||||
- `PERMISSIONS` - Permission checking
|
||||
- `COUNTERS` - Counter columns
|
||||
- `MIGRATIONS` - Schema migrations
|
||||
- `GOSSIP` - Gossip protocol
|
||||
- `TOKEN_RESTRICTION` - Token-based restrictions
|
||||
- `LEGACY_COMPOSITE_KEYS` - Legacy composite key handling
|
||||
- `COLLECTION_RANGE_TOMBSTONES` - Collection range tombstones
|
||||
- `RANGE_DELETES` - Range deletion operations
|
||||
- `COMPRESSION` - Compression features
|
||||
- `NONATOMIC` - Non-atomic operations
|
||||
- `CONSISTENCY` - Consistency level handling
|
||||
- `WRAP_AROUND` - Token wrap-around handling
|
||||
- `STORAGE_SERVICE` - Storage service operations
|
||||
- `SCHEMA_CHANGE` - Schema change operations
|
||||
- `MIXED_CF` - Mixed column family operations
|
||||
- `SSTABLE_FORMAT_M` - SSTable format M
|
||||
|
||||
## Remaining Enum Values (8 values kept)
|
||||
|
||||
### 1. `API` (4 usages)
|
||||
**Impact**: REST API features that are not fully implemented.
|
||||
|
||||
**Usages**:
|
||||
- `api/column_family.cc:1052` - Fails when `split_output` parameter is used in major compaction
|
||||
- `api/compaction_manager.cc:100,146,216` - Warns when force_user_defined_compaction or related operations are called
|
||||
|
||||
**User Impact**: Some REST API endpoints for compaction management are stubs and will warn or fail.
|
||||
|
||||
### 2. `INDEXES` (6 usages)
|
||||
**Impact**: Secondary index features not fully supported.
|
||||
|
||||
**Usages**:
|
||||
- `api/column_family.cc:433,440,449,456` - Warns about index-related operations
|
||||
- `cql3/restrictions/statement_restrictions.cc:1158` - Fails when attempting filtering on collection columns without proper indexing
|
||||
- `cql3/statements/update_statement.cc:149` - Warns about index operations
|
||||
|
||||
**User Impact**: Some advanced secondary index features (especially filtering on collections) are not available.
|
||||
|
||||
### 3. `TRIGGERS` (2 usages)
|
||||
**Impact**: Trigger support is not implemented.
|
||||
|
||||
**Usages**:
|
||||
- `db/schema_tables.cc:2017` - Warns when loading trigger metadata from schema tables
|
||||
- `service/storage_proxy.cc:4166` - Warns when processing trigger-related operations
|
||||
|
||||
**User Impact**: Cassandra triggers (stored procedures that execute on data changes) are not supported.
|
||||
|
||||
### 4. `METRICS` (1 usage)
|
||||
**Impact**: Some query processor metrics are not collected.
|
||||
|
||||
**Usages**:
|
||||
- `cql3/query_processor.cc:585` - Warns about missing metrics implementation
|
||||
|
||||
**User Impact**: Minor - some internal metrics may not be available.
|
||||
|
||||
### 5. `VALIDATION` (4 usages)
|
||||
**Impact**: Schema validation checks are partially implemented.
|
||||
|
||||
**Usages**:
|
||||
- `cql3/functions/token_fct.hh:38` - Warns about validation in token functions
|
||||
- `cql3/statements/drop_keyspace_statement.cc:40` - Warns when dropping keyspace
|
||||
- `cql3/statements/truncate_statement.cc:87` - Warns when truncating table
|
||||
- `service/migration_manager.cc:750` - Warns during schema migrations
|
||||
|
||||
**User Impact**: Some schema validation checks are skipped (with warnings logged).
|
||||
|
||||
### 6. `REVERSED` (1 usage)
|
||||
**Impact**: Reversed type support in CQL protocol.
|
||||
|
||||
**Usages**:
|
||||
- `transport/server.cc:2085` - Fails when trying to use reversed types in CQL protocol
|
||||
|
||||
**User Impact**: Reversed types are not supported in the CQL protocol implementation.
|
||||
|
||||
### 7. `HINT` (1 usage)
|
||||
**Impact**: Hint replaying is not implemented.
|
||||
|
||||
**Usages**:
|
||||
- `db/batchlog_manager.cc:251` - Warns when attempting to replay hints
|
||||
|
||||
**User Impact**: Cassandra hints (temporary storage of writes when nodes are down) are not supported.
|
||||
|
||||
### 8. `SUPER` (2 usages)
|
||||
**Impact**: Super column families are not supported.
|
||||
|
||||
**Usages**:
|
||||
- `db/legacy_schema_migrator.cc:157` - Fails when encountering super column family in legacy schema
|
||||
- `db/schema_tables.cc:2288` - Fails when encountering super column family in schema tables
|
||||
|
||||
**User Impact**: Super column families (legacy Cassandra feature) will cause errors if encountered in legacy data or schema migrations.
|
||||
|
||||
## Summary
|
||||
|
||||
- **Removed**: 20 unused enum values (76% reduction)
|
||||
- **Kept**: 8 actively used enum values (24% remaining)
|
||||
- **Total lines removed**: ~40 lines from enum definition and switch statement
|
||||
|
||||
The remaining enum values represent actual unimplemented features that users may encounter, with varying impacts ranging from warnings (TRIGGERS, METRICS, VALIDATION, HINT) to failures (API split_output, INDEXES on collections, REVERSED types, SUPER tables).
|
||||
@@ -9,6 +9,7 @@ target_sources(scylla_auth
|
||||
allow_all_authorizer.cc
|
||||
authenticated_user.cc
|
||||
authenticator.cc
|
||||
cache.cc
|
||||
certificate_authenticator.cc
|
||||
common.cc
|
||||
default_authorizer.cc
|
||||
|
||||
@@ -23,6 +23,7 @@ static const class_registrator<
|
||||
cql3::query_processor&,
|
||||
::service::raft_group0_client&,
|
||||
::service::migration_manager&,
|
||||
cache&,
|
||||
utils::alien_worker&> registration("org.apache.cassandra.auth.AllowAllAuthenticator");
|
||||
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
#include "auth/authenticated_user.hh"
|
||||
#include "auth/authenticator.hh"
|
||||
#include "auth/cache.hh"
|
||||
#include "auth/common.hh"
|
||||
#include "utils/alien_worker.hh"
|
||||
|
||||
@@ -29,7 +30,7 @@ extern const std::string_view allow_all_authenticator_name;
|
||||
|
||||
class allow_all_authenticator final : public authenticator {
|
||||
public:
|
||||
allow_all_authenticator(cql3::query_processor&, ::service::raft_group0_client&, ::service::migration_manager&, utils::alien_worker&) {
|
||||
allow_all_authenticator(cql3::query_processor&, ::service::raft_group0_client&, ::service::migration_manager&, cache&, utils::alien_worker&) {
|
||||
}
|
||||
|
||||
virtual future<> start() override {
|
||||
|
||||
180
auth/cache.cc
Normal file
180
auth/cache.cc
Normal file
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* Copyright (C) 2017-present ScyllaDB
|
||||
*/
|
||||
|
||||
/*
|
||||
* SPDX-License-Identifier: LicenseRef-ScyllaDB-Source-Available-1.0
|
||||
*/
|
||||
|
||||
#include "auth/cache.hh"
|
||||
#include "auth/common.hh"
|
||||
#include "auth/roles-metadata.hh"
|
||||
#include "cql3/query_processor.hh"
|
||||
#include "cql3/untyped_result_set.hh"
|
||||
#include "db/consistency_level_type.hh"
|
||||
#include "db/system_keyspace.hh"
|
||||
#include "schema/schema.hh"
|
||||
#include <iterator>
|
||||
#include <seastar/coroutine/maybe_yield.hh>
|
||||
#include <seastar/core/format.hh>
|
||||
|
||||
namespace auth {
|
||||
|
||||
logging::logger logger("auth-cache");
|
||||
|
||||
cache::cache(cql3::query_processor& qp) noexcept
|
||||
: _current_version(0)
|
||||
, _qp(qp) {
|
||||
}
|
||||
|
||||
lw_shared_ptr<const cache::role_record> cache::get(const role_name_t& role) const noexcept {
|
||||
auto it = _roles.find(role);
|
||||
if (it == _roles.end()) {
|
||||
return {};
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
future<lw_shared_ptr<cache::role_record>> cache::fetch_role(const role_name_t& role) const {
|
||||
auto rec = make_lw_shared<role_record>();
|
||||
rec->version = _current_version;
|
||||
|
||||
auto fetch = [this, &role](const sstring& q) {
|
||||
return _qp.execute_internal(q, db::consistency_level::LOCAL_ONE,
|
||||
internal_distributed_query_state(), {role},
|
||||
cql3::query_processor::cache_internal::yes);
|
||||
};
|
||||
// roles
|
||||
{
|
||||
static const sstring q = format("SELECT * FROM {}.{} WHERE role = ?", db::system_keyspace::NAME, meta::roles_table::name);
|
||||
auto rs = co_await fetch(q);
|
||||
if (!rs->empty()) {
|
||||
auto& r = rs->one();
|
||||
rec->is_superuser = r.get_or<bool>("is_superuser", false);
|
||||
rec->can_login = r.get_or<bool>("can_login", false);
|
||||
rec->salted_hash = r.get_or<sstring>("salted_hash", "");
|
||||
if (r.has("member_of")) {
|
||||
auto mo = r.get_set<sstring>("member_of");
|
||||
rec->member_of.insert(
|
||||
std::make_move_iterator(mo.begin()),
|
||||
std::make_move_iterator(mo.end()));
|
||||
}
|
||||
} else {
|
||||
// role got deleted
|
||||
co_return nullptr;
|
||||
}
|
||||
}
|
||||
// members
|
||||
{
|
||||
static const sstring q = format("SELECT role, member FROM {}.{} WHERE role = ?", db::system_keyspace::NAME, ROLE_MEMBERS_CF);
|
||||
auto rs = co_await fetch(q);
|
||||
for (const auto& r : *rs) {
|
||||
rec->members.insert(r.get_as<sstring>("member"));
|
||||
co_await coroutine::maybe_yield();
|
||||
}
|
||||
}
|
||||
// attributes
|
||||
{
|
||||
static const sstring q = format("SELECT role, name, value FROM {}.{} WHERE role = ?", db::system_keyspace::NAME, ROLE_ATTRIBUTES_CF);
|
||||
auto rs = co_await fetch(q);
|
||||
for (const auto& r : *rs) {
|
||||
rec->attributes[r.get_as<sstring>("name")] =
|
||||
r.get_as<sstring>("value");
|
||||
co_await coroutine::maybe_yield();
|
||||
}
|
||||
}
|
||||
// permissions
|
||||
{
|
||||
static const sstring q = format("SELECT role, resource, permissions FROM {}.{} WHERE role = ?", db::system_keyspace::NAME, PERMISSIONS_CF);
|
||||
auto rs = co_await fetch(q);
|
||||
for (const auto& r : *rs) {
|
||||
auto resource = r.get_as<sstring>("resource");
|
||||
auto perms_strings = r.get_set<sstring>("permissions");
|
||||
std::unordered_set<sstring> perms_set(perms_strings.begin(), perms_strings.end());
|
||||
auto pset = permissions::from_strings(perms_set);
|
||||
rec->permissions[std::move(resource)] = std::move(pset);
|
||||
co_await coroutine::maybe_yield();
|
||||
}
|
||||
}
|
||||
co_return rec;
|
||||
}
|
||||
|
||||
future<> cache::prune_all() noexcept {
|
||||
for (auto it = _roles.begin(); it != _roles.end(); ) {
|
||||
if (it->second->version != _current_version) {
|
||||
_roles.erase(it++);
|
||||
co_await coroutine::maybe_yield();
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
co_return;
|
||||
}
|
||||
|
||||
future<> cache::load_all() {
|
||||
if (legacy_mode(_qp)) {
|
||||
co_return;
|
||||
}
|
||||
SCYLLA_ASSERT(this_shard_id() == 0);
|
||||
++_current_version;
|
||||
|
||||
logger.info("Loading all roles");
|
||||
const uint32_t page_size = 128;
|
||||
auto loader = [this](const cql3::untyped_result_set::row& r) -> future<stop_iteration> {
|
||||
const auto name = r.get_as<sstring>("role");
|
||||
auto role = co_await fetch_role(name);
|
||||
if (role) {
|
||||
_roles[name] = role;
|
||||
}
|
||||
co_return stop_iteration::no;
|
||||
};
|
||||
co_await _qp.query_internal(format("SELECT * FROM {}.{}",
|
||||
db::system_keyspace::NAME, meta::roles_table::name),
|
||||
db::consistency_level::LOCAL_ONE, {}, page_size, loader);
|
||||
|
||||
co_await prune_all();
|
||||
for (const auto& [name, role] : _roles) {
|
||||
co_await distribute_role(name, role);
|
||||
}
|
||||
co_await container().invoke_on_others([this](cache& c) -> future<> {
|
||||
c._current_version = _current_version;
|
||||
co_await c.prune_all();
|
||||
});
|
||||
}
|
||||
|
||||
future<> cache::load_roles(std::unordered_set<role_name_t> roles) {
|
||||
if (legacy_mode(_qp)) {
|
||||
co_return;
|
||||
}
|
||||
for (const auto& name : roles) {
|
||||
logger.info("Loading role {}", name);
|
||||
auto role = co_await fetch_role(name);
|
||||
if (role) {
|
||||
_roles[name] = role;
|
||||
} else {
|
||||
_roles.erase(name);
|
||||
}
|
||||
co_await distribute_role(name, role);
|
||||
}
|
||||
}
|
||||
|
||||
future<> cache::distribute_role(const role_name_t& name, lw_shared_ptr<role_record> role) {
|
||||
auto role_ptr = role.get();
|
||||
co_await container().invoke_on_others([&name, role_ptr](cache& c) {
|
||||
if (!role_ptr) {
|
||||
c._roles.erase(name);
|
||||
return;
|
||||
}
|
||||
auto role_copy = make_lw_shared<role_record>(*role_ptr);
|
||||
c._roles[name] = std::move(role_copy);
|
||||
});
|
||||
}
|
||||
|
||||
bool cache::includes_table(const table_id& id) noexcept {
|
||||
return id == db::system_keyspace::roles()->id()
|
||||
|| id == db::system_keyspace::role_members()->id()
|
||||
|| id == db::system_keyspace::role_attributes()->id()
|
||||
|| id == db::system_keyspace::role_permissions()->id();
|
||||
}
|
||||
|
||||
} // namespace auth
|
||||
61
auth/cache.hh
Normal file
61
auth/cache.hh
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (C) 2025-present ScyllaDB
|
||||
*/
|
||||
|
||||
/*
|
||||
* SPDX-License-Identifier: LicenseRef-ScyllaDB-Source-Available-1.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <unordered_set>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <seastar/core/sstring.hh>
|
||||
#include <seastar/core/future.hh>
|
||||
#include <seastar/core/sharded.hh>
|
||||
#include <seastar/core/shared_ptr.hh>
|
||||
|
||||
#include <absl/container/flat_hash_map.h>
|
||||
|
||||
#include "auth/permission.hh"
|
||||
#include "auth/common.hh"
|
||||
|
||||
namespace cql3 { class query_processor; }
|
||||
|
||||
namespace auth {
|
||||
|
||||
class cache : public peering_sharded_service<cache> {
|
||||
public:
|
||||
using role_name_t = sstring;
|
||||
using version_tag_t = char;
|
||||
|
||||
struct role_record {
|
||||
bool can_login = false;
|
||||
bool is_superuser = false;
|
||||
std::unordered_set<role_name_t> member_of;
|
||||
std::unordered_set<role_name_t> members;
|
||||
sstring salted_hash;
|
||||
std::unordered_map<sstring, sstring> attributes;
|
||||
std::unordered_map<sstring, permission_set> permissions;
|
||||
version_tag_t version; // used for seamless cache reloads
|
||||
};
|
||||
|
||||
explicit cache(cql3::query_processor& qp) noexcept;
|
||||
lw_shared_ptr<const role_record> get(const role_name_t& role) const noexcept;
|
||||
future<> load_all();
|
||||
future<> load_roles(std::unordered_set<role_name_t> roles);
|
||||
static bool includes_table(const table_id&) noexcept;
|
||||
|
||||
private:
|
||||
using roles_map = absl::flat_hash_map<role_name_t, lw_shared_ptr<role_record>>;
|
||||
roles_map _roles;
|
||||
version_tag_t _current_version;
|
||||
cql3::query_processor& _qp;
|
||||
|
||||
future<lw_shared_ptr<role_record>> fetch_role(const role_name_t& role) const;
|
||||
future<> prune_all() noexcept;
|
||||
future<> distribute_role(const role_name_t& name, const lw_shared_ptr<role_record> role);
|
||||
};
|
||||
|
||||
} // namespace auth
|
||||
@@ -48,6 +48,10 @@ extern constinit const std::string_view AUTH_PACKAGE_NAME;
|
||||
|
||||
} // namespace meta
|
||||
|
||||
constexpr std::string_view PERMISSIONS_CF = "role_permissions";
|
||||
constexpr std::string_view ROLE_MEMBERS_CF = "role_members";
|
||||
constexpr std::string_view ROLE_ATTRIBUTES_CF = "role_attributes";
|
||||
|
||||
// This is a helper to check whether auth-v2 is on.
|
||||
bool legacy_mode(cql3::query_processor& qp);
|
||||
|
||||
|
||||
@@ -37,7 +37,6 @@ std::string_view default_authorizer::qualified_java_name() const {
|
||||
static constexpr std::string_view ROLE_NAME = "role";
|
||||
static constexpr std::string_view RESOURCE_NAME = "resource";
|
||||
static constexpr std::string_view PERMISSIONS_NAME = "permissions";
|
||||
static constexpr std::string_view PERMISSIONS_CF = "role_permissions";
|
||||
|
||||
static logging::logger alogger("default_authorizer");
|
||||
|
||||
|
||||
@@ -83,17 +83,18 @@ static const class_registrator<
|
||||
ldap_role_manager,
|
||||
cql3::query_processor&,
|
||||
::service::raft_group0_client&,
|
||||
::service::migration_manager&> registration(ldap_role_manager_full_name);
|
||||
::service::migration_manager&,
|
||||
cache&> registration(ldap_role_manager_full_name);
|
||||
|
||||
ldap_role_manager::ldap_role_manager(
|
||||
std::string_view query_template, std::string_view target_attr, std::string_view bind_name, std::string_view bind_password,
|
||||
cql3::query_processor& qp, ::service::raft_group0_client& rg0c, ::service::migration_manager& mm)
|
||||
: _std_mgr(qp, rg0c, mm), _group0_client(rg0c), _query_template(query_template), _target_attr(target_attr), _bind_name(bind_name)
|
||||
cql3::query_processor& qp, ::service::raft_group0_client& rg0c, ::service::migration_manager& mm, cache& cache)
|
||||
: _std_mgr(qp, rg0c, mm, cache), _group0_client(rg0c), _query_template(query_template), _target_attr(target_attr), _bind_name(bind_name)
|
||||
, _bind_password(bind_password)
|
||||
, _connection_factory(bind(std::mem_fn(&ldap_role_manager::reconnect), std::ref(*this))) {
|
||||
}
|
||||
|
||||
ldap_role_manager::ldap_role_manager(cql3::query_processor& qp, ::service::raft_group0_client& rg0c, ::service::migration_manager& mm)
|
||||
ldap_role_manager::ldap_role_manager(cql3::query_processor& qp, ::service::raft_group0_client& rg0c, ::service::migration_manager& mm, cache& cache)
|
||||
: ldap_role_manager(
|
||||
qp.db().get_config().ldap_url_template(),
|
||||
qp.db().get_config().ldap_attr_role(),
|
||||
@@ -101,7 +102,8 @@ ldap_role_manager::ldap_role_manager(cql3::query_processor& qp, ::service::raft_
|
||||
qp.db().get_config().ldap_bind_passwd(),
|
||||
qp,
|
||||
rg0c,
|
||||
mm) {
|
||||
mm,
|
||||
cache) {
|
||||
}
|
||||
|
||||
std::string_view ldap_role_manager::qualified_java_name() const noexcept {
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
#include "ent/ldap/ldap_connection.hh"
|
||||
#include "standard_role_manager.hh"
|
||||
#include "auth/cache.hh"
|
||||
|
||||
namespace auth {
|
||||
|
||||
@@ -43,12 +44,13 @@ class ldap_role_manager : public role_manager {
|
||||
std::string_view bind_password, ///< LDAP bind credentials.
|
||||
cql3::query_processor& qp, ///< Passed to standard_role_manager.
|
||||
::service::raft_group0_client& rg0c, ///< Passed to standard_role_manager.
|
||||
::service::migration_manager& mm ///< Passed to standard_role_manager.
|
||||
::service::migration_manager& mm, ///< Passed to standard_role_manager.
|
||||
cache& cache ///< Passed to standard_role_manager.
|
||||
);
|
||||
|
||||
/// Retrieves LDAP configuration entries from qp and invokes the other constructor. Required by
|
||||
/// class_registrator<role_manager>.
|
||||
ldap_role_manager(cql3::query_processor& qp, ::service::raft_group0_client& rg0c, ::service::migration_manager& mm);
|
||||
ldap_role_manager(cql3::query_processor& qp, ::service::raft_group0_client& rg0c, ::service::migration_manager& mm, cache& cache);
|
||||
|
||||
/// Thrown when query-template parsing fails.
|
||||
struct url_error : public std::runtime_error {
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <seastar/core/future.hh>
|
||||
#include <stdexcept>
|
||||
#include <string_view>
|
||||
#include "auth/cache.hh"
|
||||
#include "cql3/description.hh"
|
||||
#include "utils/class_registrator.hh"
|
||||
|
||||
@@ -23,7 +24,8 @@ static const class_registrator<
|
||||
maintenance_socket_role_manager,
|
||||
cql3::query_processor&,
|
||||
::service::raft_group0_client&,
|
||||
::service::migration_manager&> registration(sstring{maintenance_socket_role_manager_name});
|
||||
::service::migration_manager&,
|
||||
cache&> registration(sstring{maintenance_socket_role_manager_name});
|
||||
|
||||
|
||||
std::string_view maintenance_socket_role_manager::qualified_java_name() const noexcept {
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "auth/cache.hh"
|
||||
#include "auth/resource.hh"
|
||||
#include "auth/role_manager.hh"
|
||||
#include <seastar/core/future.hh>
|
||||
@@ -29,7 +30,7 @@ extern const std::string_view maintenance_socket_role_manager_name;
|
||||
// system_auth keyspace, which may be not yet created when the maintenance socket starts listening.
|
||||
class maintenance_socket_role_manager final : public role_manager {
|
||||
public:
|
||||
maintenance_socket_role_manager(cql3::query_processor&, ::service::raft_group0_client&, ::service::migration_manager&) {}
|
||||
maintenance_socket_role_manager(cql3::query_processor&, ::service::raft_group0_client&, ::service::migration_manager&, cache&) {}
|
||||
|
||||
virtual std::string_view qualified_java_name() const noexcept override;
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@ static const class_registrator<
|
||||
cql3::query_processor&,
|
||||
::service::raft_group0_client&,
|
||||
::service::migration_manager&,
|
||||
cache&,
|
||||
utils::alien_worker&> password_auth_reg("org.apache.cassandra.auth.PasswordAuthenticator");
|
||||
|
||||
static thread_local auto rng_for_salt = std::default_random_engine(std::random_device{}());
|
||||
@@ -63,10 +64,11 @@ std::string password_authenticator::default_superuser(const db::config& cfg) {
|
||||
password_authenticator::~password_authenticator() {
|
||||
}
|
||||
|
||||
password_authenticator::password_authenticator(cql3::query_processor& qp, ::service::raft_group0_client& g0, ::service::migration_manager& mm, utils::alien_worker& hashing_worker)
|
||||
password_authenticator::password_authenticator(cql3::query_processor& qp, ::service::raft_group0_client& g0, ::service::migration_manager& mm, cache& cache, utils::alien_worker& hashing_worker)
|
||||
: _qp(qp)
|
||||
, _group0_client(g0)
|
||||
, _migration_manager(mm)
|
||||
, _cache(cache)
|
||||
, _stopped(make_ready_future<>())
|
||||
, _superuser(default_superuser(qp.db().get_config()))
|
||||
, _hashing_worker(hashing_worker)
|
||||
@@ -315,11 +317,20 @@ future<authenticated_user> password_authenticator::authenticate(
|
||||
const sstring password = credentials.at(PASSWORD_KEY);
|
||||
|
||||
try {
|
||||
const std::optional<sstring> salted_hash = co_await get_password_hash(username);
|
||||
if (!salted_hash) {
|
||||
throw exceptions::authentication_exception("Username and/or password are incorrect");
|
||||
std::optional<sstring> salted_hash;
|
||||
if (legacy_mode(_qp)) {
|
||||
salted_hash = co_await get_password_hash(username);
|
||||
if (!salted_hash) {
|
||||
throw exceptions::authentication_exception("Username and/or password are incorrect");
|
||||
}
|
||||
} else {
|
||||
auto role = _cache.get(username);
|
||||
if (!role || role->salted_hash.empty()) {
|
||||
throw exceptions::authentication_exception("Username and/or password are incorrect");
|
||||
}
|
||||
salted_hash = role->salted_hash;
|
||||
}
|
||||
const bool password_match = co_await _hashing_worker.submit<bool>([password = std::move(password), salted_hash = std::move(salted_hash)]{
|
||||
const bool password_match = co_await _hashing_worker.submit<bool>([password = std::move(password), salted_hash] {
|
||||
return passwords::check(password, *salted_hash);
|
||||
});
|
||||
if (!password_match) {
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "db/consistency_level_type.hh"
|
||||
#include "auth/authenticator.hh"
|
||||
#include "auth/passwords.hh"
|
||||
#include "auth/cache.hh"
|
||||
#include "service/raft/raft_group0_client.hh"
|
||||
#include "utils/alien_worker.hh"
|
||||
|
||||
@@ -41,6 +42,7 @@ class password_authenticator : public authenticator {
|
||||
cql3::query_processor& _qp;
|
||||
::service::raft_group0_client& _group0_client;
|
||||
::service::migration_manager& _migration_manager;
|
||||
cache& _cache;
|
||||
future<> _stopped;
|
||||
abort_source _as;
|
||||
std::string _superuser; // default superuser name from the config (may or may not be present in roles table)
|
||||
@@ -53,7 +55,7 @@ public:
|
||||
static db::consistency_level consistency_for_user(std::string_view role_name);
|
||||
static std::string default_superuser(const db::config&);
|
||||
|
||||
password_authenticator(cql3::query_processor&, ::service::raft_group0_client&, ::service::migration_manager&, utils::alien_worker&);
|
||||
password_authenticator(cql3::query_processor&, ::service::raft_group0_client&, ::service::migration_manager&, cache&, utils::alien_worker&);
|
||||
|
||||
~password_authenticator();
|
||||
|
||||
|
||||
@@ -35,9 +35,10 @@ static const class_registrator<
|
||||
cql3::query_processor&,
|
||||
::service::raft_group0_client&,
|
||||
::service::migration_manager&,
|
||||
cache&,
|
||||
utils::alien_worker&> saslauthd_auth_reg("com.scylladb.auth.SaslauthdAuthenticator");
|
||||
|
||||
saslauthd_authenticator::saslauthd_authenticator(cql3::query_processor& qp, ::service::raft_group0_client&, ::service::migration_manager&, utils::alien_worker&)
|
||||
saslauthd_authenticator::saslauthd_authenticator(cql3::query_processor& qp, ::service::raft_group0_client&, ::service::migration_manager&, cache&, utils::alien_worker&)
|
||||
: _socket_path(qp.db().get_config().saslauthd_socket_path())
|
||||
{}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "auth/authenticator.hh"
|
||||
#include "auth/cache.hh"
|
||||
#include "utils/alien_worker.hh"
|
||||
|
||||
namespace cql3 {
|
||||
@@ -29,7 +30,7 @@ namespace auth {
|
||||
class saslauthd_authenticator : public authenticator {
|
||||
sstring _socket_path; ///< Path to the domain socket on which saslauthd is listening.
|
||||
public:
|
||||
saslauthd_authenticator(cql3::query_processor&, ::service::raft_group0_client&, ::service::migration_manager&, utils::alien_worker&);
|
||||
saslauthd_authenticator(cql3::query_processor&, ::service::raft_group0_client&, ::service::migration_manager&, cache&,utils::alien_worker&);
|
||||
|
||||
future<> start() override;
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <chrono>
|
||||
|
||||
#include <seastar/core/future-util.hh>
|
||||
#include <seastar/core/shard_id.hh>
|
||||
#include <seastar/core/sharded.hh>
|
||||
#include <seastar/core/shared_ptr.hh>
|
||||
|
||||
@@ -157,6 +158,7 @@ static future<> validate_role_exists(const service& ser, std::string_view role_n
|
||||
|
||||
service::service(
|
||||
utils::loading_cache_config c,
|
||||
cache& cache,
|
||||
cql3::query_processor& qp,
|
||||
::service::raft_group0_client& g0,
|
||||
::service::migration_notifier& mn,
|
||||
@@ -166,6 +168,7 @@ service::service(
|
||||
maintenance_socket_enabled used_by_maintenance_socket)
|
||||
: _loading_cache_config(std::move(c))
|
||||
, _permissions_cache(nullptr)
|
||||
, _cache(cache)
|
||||
, _qp(qp)
|
||||
, _group0_client(g0)
|
||||
, _mnotifier(mn)
|
||||
@@ -188,15 +191,17 @@ service::service(
|
||||
::service::migration_manager& mm,
|
||||
const service_config& sc,
|
||||
maintenance_socket_enabled used_by_maintenance_socket,
|
||||
cache& cache,
|
||||
utils::alien_worker& hashing_worker)
|
||||
: service(
|
||||
std::move(c),
|
||||
cache,
|
||||
qp,
|
||||
g0,
|
||||
mn,
|
||||
create_object<authorizer>(sc.authorizer_java_name, qp, g0, mm),
|
||||
create_object<authenticator>(sc.authenticator_java_name, qp, g0, mm, hashing_worker),
|
||||
create_object<role_manager>(sc.role_manager_java_name, qp, g0, mm),
|
||||
create_object<authenticator>(sc.authenticator_java_name, qp, g0, mm, cache, hashing_worker),
|
||||
create_object<role_manager>(sc.role_manager_java_name, qp, g0, mm, cache),
|
||||
used_by_maintenance_socket) {
|
||||
}
|
||||
|
||||
@@ -232,6 +237,9 @@ future<> service::start(::service::migration_manager& mm, db::system_keyspace& s
|
||||
auto auth_version = co_await sys_ks.get_auth_version();
|
||||
// version is set in query processor to be easily available in various places we call auth::legacy_mode check.
|
||||
_qp.auth_version = auth_version;
|
||||
if (this_shard_id() == 0) {
|
||||
co_await _cache.load_all();
|
||||
}
|
||||
if (!_used_by_maintenance_socket) {
|
||||
// this legacy keyspace is only used by cqlsh
|
||||
// it's needed when executing `list roles` or `list users`
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "auth/authorizer.hh"
|
||||
#include "auth/permission.hh"
|
||||
#include "auth/permissions_cache.hh"
|
||||
#include "auth/cache.hh"
|
||||
#include "auth/role_manager.hh"
|
||||
#include "auth/common.hh"
|
||||
#include "cql3/description.hh"
|
||||
@@ -77,6 +78,7 @@ public:
|
||||
class service final : public seastar::peering_sharded_service<service> {
|
||||
utils::loading_cache_config _loading_cache_config;
|
||||
std::unique_ptr<permissions_cache> _permissions_cache;
|
||||
cache& _cache;
|
||||
|
||||
cql3::query_processor& _qp;
|
||||
|
||||
@@ -107,6 +109,7 @@ class service final : public seastar::peering_sharded_service<service> {
|
||||
public:
|
||||
service(
|
||||
utils::loading_cache_config,
|
||||
cache& cache,
|
||||
cql3::query_processor&,
|
||||
::service::raft_group0_client&,
|
||||
::service::migration_notifier&,
|
||||
@@ -128,6 +131,7 @@ public:
|
||||
::service::migration_manager&,
|
||||
const service_config&,
|
||||
maintenance_socket_enabled,
|
||||
cache&,
|
||||
utils::alien_worker&);
|
||||
|
||||
future<> start(::service::migration_manager&, db::system_keyspace&);
|
||||
|
||||
@@ -41,21 +41,6 @@
|
||||
|
||||
namespace auth {
|
||||
|
||||
namespace meta {
|
||||
|
||||
namespace role_members_table {
|
||||
|
||||
constexpr std::string_view name{"role_members" , 12};
|
||||
|
||||
}
|
||||
|
||||
namespace role_attributes_table {
|
||||
|
||||
constexpr std::string_view name{"role_attributes", 15};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static logging::logger log("standard_role_manager");
|
||||
|
||||
@@ -64,7 +49,8 @@ static const class_registrator<
|
||||
standard_role_manager,
|
||||
cql3::query_processor&,
|
||||
::service::raft_group0_client&,
|
||||
::service::migration_manager&> registration("org.apache.cassandra.auth.CassandraRoleManager");
|
||||
::service::migration_manager&,
|
||||
cache&> registration("org.apache.cassandra.auth.CassandraRoleManager");
|
||||
|
||||
struct record final {
|
||||
sstring name;
|
||||
@@ -121,10 +107,11 @@ static bool has_can_login(const cql3::untyped_result_set_row& row) {
|
||||
return row.has("can_login") && !(boolean_type->deserialize(row.get_blob_unfragmented("can_login")).is_null());
|
||||
}
|
||||
|
||||
standard_role_manager::standard_role_manager(cql3::query_processor& qp, ::service::raft_group0_client& g0, ::service::migration_manager& mm)
|
||||
standard_role_manager::standard_role_manager(cql3::query_processor& qp, ::service::raft_group0_client& g0, ::service::migration_manager& mm, cache& cache)
|
||||
: _qp(qp)
|
||||
, _group0_client(g0)
|
||||
, _migration_manager(mm)
|
||||
, _cache(cache)
|
||||
, _stopped(make_ready_future<>())
|
||||
, _superuser(password_authenticator::default_superuser(qp.db().get_config()))
|
||||
{}
|
||||
@@ -136,7 +123,7 @@ std::string_view standard_role_manager::qualified_java_name() const noexcept {
|
||||
const resource_set& standard_role_manager::protected_resources() const {
|
||||
static const resource_set resources({
|
||||
make_data_resource(meta::legacy::AUTH_KS, meta::roles_table::name),
|
||||
make_data_resource(meta::legacy::AUTH_KS, meta::role_members_table::name)});
|
||||
make_data_resource(meta::legacy::AUTH_KS, ROLE_MEMBERS_CF)});
|
||||
|
||||
return resources;
|
||||
}
|
||||
@@ -160,7 +147,7 @@ future<> standard_role_manager::create_legacy_metadata_tables_if_missing() const
|
||||
" PRIMARY KEY (role, member)"
|
||||
")",
|
||||
meta::legacy::AUTH_KS,
|
||||
meta::role_members_table::name);
|
||||
ROLE_MEMBERS_CF);
|
||||
static const sstring create_role_attributes_query = seastar::format(
|
||||
"CREATE TABLE {}.{} ("
|
||||
" role text,"
|
||||
@@ -169,7 +156,7 @@ future<> standard_role_manager::create_legacy_metadata_tables_if_missing() const
|
||||
" PRIMARY KEY(role, name)"
|
||||
")",
|
||||
meta::legacy::AUTH_KS,
|
||||
meta::role_attributes_table::name);
|
||||
ROLE_ATTRIBUTES_CF);
|
||||
return when_all_succeed(
|
||||
create_legacy_metadata_table_if_missing(
|
||||
meta::roles_table::name,
|
||||
@@ -177,12 +164,12 @@ future<> standard_role_manager::create_legacy_metadata_tables_if_missing() const
|
||||
create_roles_query,
|
||||
_migration_manager),
|
||||
create_legacy_metadata_table_if_missing(
|
||||
meta::role_members_table::name,
|
||||
ROLE_MEMBERS_CF,
|
||||
_qp,
|
||||
create_role_members_query,
|
||||
_migration_manager),
|
||||
create_legacy_metadata_table_if_missing(
|
||||
meta::role_attributes_table::name,
|
||||
ROLE_ATTRIBUTES_CF,
|
||||
_qp,
|
||||
create_role_attributes_query,
|
||||
_migration_manager)).discard_result();
|
||||
@@ -429,7 +416,7 @@ future<> standard_role_manager::drop(std::string_view role_name, ::service::grou
|
||||
const auto revoke_from_members = [this, role_name, &mc] () -> future<> {
|
||||
const sstring query = seastar::format("SELECT member FROM {}.{} WHERE role = ?",
|
||||
get_auth_ks_name(_qp),
|
||||
meta::role_members_table::name);
|
||||
ROLE_MEMBERS_CF);
|
||||
const auto members = co_await _qp.execute_internal(
|
||||
query,
|
||||
consistency_for_role(role_name),
|
||||
@@ -461,7 +448,7 @@ future<> standard_role_manager::drop(std::string_view role_name, ::service::grou
|
||||
const auto remove_attributes_of = [this, role_name, &mc] () -> future<> {
|
||||
const sstring query = seastar::format("DELETE FROM {}.{} WHERE role = ?",
|
||||
get_auth_ks_name(_qp),
|
||||
meta::role_attributes_table::name);
|
||||
ROLE_ATTRIBUTES_CF);
|
||||
if (legacy_mode(_qp)) {
|
||||
co_await _qp.execute_internal(query, {sstring(role_name)},
|
||||
cql3::query_processor::cache_internal::yes).discard_result();
|
||||
@@ -517,7 +504,7 @@ standard_role_manager::legacy_modify_membership(
|
||||
case membership_change::add: {
|
||||
const sstring insert_query = seastar::format("INSERT INTO {}.{} (role, member) VALUES (?, ?)",
|
||||
get_auth_ks_name(_qp),
|
||||
meta::role_members_table::name);
|
||||
ROLE_MEMBERS_CF);
|
||||
co_return co_await _qp.execute_internal(
|
||||
insert_query,
|
||||
consistency_for_role(role_name),
|
||||
@@ -529,7 +516,7 @@ standard_role_manager::legacy_modify_membership(
|
||||
case membership_change::remove: {
|
||||
const sstring delete_query = seastar::format("DELETE FROM {}.{} WHERE role = ? AND member = ?",
|
||||
get_auth_ks_name(_qp),
|
||||
meta::role_members_table::name);
|
||||
ROLE_MEMBERS_CF);
|
||||
co_return co_await _qp.execute_internal(
|
||||
delete_query,
|
||||
consistency_for_role(role_name),
|
||||
@@ -567,12 +554,12 @@ standard_role_manager::modify_membership(
|
||||
case membership_change::add:
|
||||
modify_role_members = seastar::format("INSERT INTO {}.{} (role, member) VALUES (?, ?)",
|
||||
get_auth_ks_name(_qp),
|
||||
meta::role_members_table::name);
|
||||
ROLE_MEMBERS_CF);
|
||||
break;
|
||||
case membership_change::remove:
|
||||
modify_role_members = seastar::format("DELETE FROM {}.{} WHERE role = ? AND member = ?",
|
||||
get_auth_ks_name(_qp),
|
||||
meta::role_members_table::name);
|
||||
ROLE_MEMBERS_CF);
|
||||
break;
|
||||
default:
|
||||
on_internal_error(log, format("unknown membership_change value: {}", int(ch)));
|
||||
@@ -666,7 +653,7 @@ future<role_set> standard_role_manager::query_granted(std::string_view grantee_n
|
||||
future<role_to_directly_granted_map> standard_role_manager::query_all_directly_granted(::service::query_state& qs) {
|
||||
const sstring query = seastar::format("SELECT * FROM {}.{}",
|
||||
get_auth_ks_name(_qp),
|
||||
meta::role_members_table::name);
|
||||
ROLE_MEMBERS_CF);
|
||||
|
||||
const auto results = co_await _qp.execute_internal(
|
||||
query,
|
||||
@@ -731,15 +718,21 @@ future<bool> standard_role_manager::is_superuser(std::string_view role_name) {
|
||||
}
|
||||
|
||||
future<bool> standard_role_manager::can_login(std::string_view role_name) {
|
||||
return require_record(_qp, role_name).then([](record r) {
|
||||
return r.can_login;
|
||||
});
|
||||
if (legacy_mode(_qp)) {
|
||||
const auto r = co_await require_record(_qp, role_name);
|
||||
co_return r.can_login;
|
||||
}
|
||||
auto role = _cache.get(sstring(role_name));
|
||||
if (!role) {
|
||||
throw nonexistant_role(role_name);
|
||||
}
|
||||
co_return role->can_login;
|
||||
}
|
||||
|
||||
future<std::optional<sstring>> standard_role_manager::get_attribute(std::string_view role_name, std::string_view attribute_name, ::service::query_state& qs) {
|
||||
const sstring query = seastar::format("SELECT name, value FROM {}.{} WHERE role = ? AND name = ?",
|
||||
get_auth_ks_name(_qp),
|
||||
meta::role_attributes_table::name);
|
||||
ROLE_ATTRIBUTES_CF);
|
||||
const auto result_set = co_await _qp.execute_internal(query, db::consistency_level::ONE, qs, {sstring(role_name), sstring(attribute_name)}, cql3::query_processor::cache_internal::yes);
|
||||
if (!result_set->empty()) {
|
||||
const cql3::untyped_result_set_row &row = result_set->one();
|
||||
@@ -770,7 +763,7 @@ future<> standard_role_manager::set_attribute(std::string_view role_name, std::s
|
||||
}
|
||||
const sstring query = seastar::format("INSERT INTO {}.{} (role, name, value) VALUES (?, ?, ?)",
|
||||
get_auth_ks_name(_qp),
|
||||
meta::role_attributes_table::name);
|
||||
ROLE_ATTRIBUTES_CF);
|
||||
if (legacy_mode(_qp)) {
|
||||
co_await _qp.execute_internal(query, {sstring(role_name), sstring(attribute_name), sstring(attribute_value)}, cql3::query_processor::cache_internal::yes).discard_result();
|
||||
} else {
|
||||
@@ -785,7 +778,7 @@ future<> standard_role_manager::remove_attribute(std::string_view role_name, std
|
||||
}
|
||||
const sstring query = seastar::format("DELETE FROM {}.{} WHERE role = ? AND name = ?",
|
||||
get_auth_ks_name(_qp),
|
||||
meta::role_attributes_table::name);
|
||||
ROLE_ATTRIBUTES_CF);
|
||||
if (legacy_mode(_qp)) {
|
||||
co_await _qp.execute_internal(query, {sstring(role_name), sstring(attribute_name)}, cql3::query_processor::cache_internal::yes).discard_result();
|
||||
} else {
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#include "auth/common.hh"
|
||||
#include "auth/role_manager.hh"
|
||||
#include "auth/cache.hh"
|
||||
|
||||
#include <string_view>
|
||||
|
||||
@@ -36,13 +37,14 @@ class standard_role_manager final : public role_manager {
|
||||
cql3::query_processor& _qp;
|
||||
::service::raft_group0_client& _group0_client;
|
||||
::service::migration_manager& _migration_manager;
|
||||
cache& _cache;
|
||||
future<> _stopped;
|
||||
abort_source _as;
|
||||
std::string _superuser;
|
||||
shared_promise<> _superuser_created_promise;
|
||||
|
||||
public:
|
||||
standard_role_manager(cql3::query_processor&, ::service::raft_group0_client&, ::service::migration_manager&);
|
||||
standard_role_manager(cql3::query_processor&, ::service::raft_group0_client&, ::service::migration_manager&, cache&);
|
||||
|
||||
virtual std::string_view qualified_java_name() const noexcept override;
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "auth/authorizer.hh"
|
||||
#include "auth/default_authorizer.hh"
|
||||
#include "auth/password_authenticator.hh"
|
||||
#include "auth/cache.hh"
|
||||
#include "auth/permission.hh"
|
||||
#include "service/raft/raft_group0_client.hh"
|
||||
#include "utils/class_registrator.hh"
|
||||
@@ -37,8 +38,8 @@ class transitional_authenticator : public authenticator {
|
||||
public:
|
||||
static const sstring PASSWORD_AUTHENTICATOR_NAME;
|
||||
|
||||
transitional_authenticator(cql3::query_processor& qp, ::service::raft_group0_client& g0, ::service::migration_manager& mm, utils::alien_worker& hashing_worker)
|
||||
: transitional_authenticator(std::make_unique<password_authenticator>(qp, g0, mm, hashing_worker)) {
|
||||
transitional_authenticator(cql3::query_processor& qp, ::service::raft_group0_client& g0, ::service::migration_manager& mm, cache& cache, utils::alien_worker& hashing_worker)
|
||||
: transitional_authenticator(std::make_unique<password_authenticator>(qp, g0, mm, cache, hashing_worker)) {
|
||||
}
|
||||
transitional_authenticator(std::unique_ptr<authenticator> a)
|
||||
: _authenticator(std::move(a)) {
|
||||
@@ -240,6 +241,7 @@ static const class_registrator<
|
||||
cql3::query_processor&,
|
||||
::service::raft_group0_client&,
|
||||
::service::migration_manager&,
|
||||
auth::cache&,
|
||||
utils::alien_worker&> transitional_authenticator_reg(auth::PACKAGE_NAME + "TransitionalAuthenticator");
|
||||
|
||||
static const class_registrator<
|
||||
|
||||
@@ -445,6 +445,7 @@ ldap_tests = set([
|
||||
scylla_tests = set([
|
||||
'test/boost/combined_tests',
|
||||
'test/boost/UUID_test',
|
||||
'test/boost/url_parse_test',
|
||||
'test/boost/advanced_rpc_compressor_test',
|
||||
'test/boost/allocation_strategy_test',
|
||||
'test/boost/alternator_unit_test',
|
||||
@@ -1195,6 +1196,7 @@ scylla_core = (['message/messaging_service.cc',
|
||||
'auth/allow_all_authorizer.cc',
|
||||
'auth/authenticated_user.cc',
|
||||
'auth/authenticator.cc',
|
||||
'auth/cache.cc',
|
||||
'auth/common.cc',
|
||||
'auth/default_authorizer.cc',
|
||||
'auth/resource.cc',
|
||||
@@ -1646,6 +1648,7 @@ deps['test/boost/bytes_ostream_test'] = [
|
||||
]
|
||||
deps['test/boost/input_stream_test'] = ['test/boost/input_stream_test.cc']
|
||||
deps['test/boost/UUID_test'] = ['clocks-impl.cc', 'utils/UUID_gen.cc', 'test/boost/UUID_test.cc', 'utils/uuid.cc', 'utils/dynamic_bitset.cc', 'utils/hashers.cc', 'utils/on_internal_error.cc']
|
||||
deps['test/boost/url_parse_test'] = ['utils/http.cc', 'test/boost/url_parse_test.cc', ]
|
||||
deps['test/boost/murmur_hash_test'] = ['bytes.cc', 'utils/murmur_hash.cc', 'test/boost/murmur_hash_test.cc']
|
||||
deps['test/boost/allocation_strategy_test'] = ['test/boost/allocation_strategy_test.cc', 'utils/logalloc.cc', 'utils/dynamic_bitset.cc', 'utils/labels.cc']
|
||||
deps['test/boost/log_heap_test'] = ['test/boost/log_heap_test.cc']
|
||||
|
||||
12
cql3/Cql.g
12
cql3/Cql.g
@@ -575,6 +575,15 @@ usingTimeoutServiceLevelClauseObjective[std::unique_ptr<cql3::attributes::raw>&
|
||||
| serviceLevel sl_name=serviceLevelOrRoleName { attrs->service_level = std::move(sl_name); }
|
||||
;
|
||||
|
||||
usingTimeoutConcurrencyClause[std::unique_ptr<cql3::attributes::raw>& attrs]
|
||||
: K_USING usingTimeoutConcurrencyClauseObjective[attrs] ( K_AND usingTimeoutConcurrencyClauseObjective[attrs] )*
|
||||
;
|
||||
|
||||
usingTimeoutConcurrencyClauseObjective[std::unique_ptr<cql3::attributes::raw>& attrs]
|
||||
: K_TIMEOUT to=term { attrs->timeout = std::move(to); }
|
||||
| K_CONCURRENCY c=term { attrs->concurrency = std::move(c); }
|
||||
;
|
||||
|
||||
/**
|
||||
* UPDATE <CF>
|
||||
* USING TIMESTAMP <long>
|
||||
@@ -666,7 +675,7 @@ pruneMaterializedViewStatement returns [std::unique_ptr<raw::select_statement> e
|
||||
auto attrs = std::make_unique<cql3::attributes::raw>();
|
||||
expression wclause = conjunction{};
|
||||
}
|
||||
: K_PRUNE K_MATERIALIZED K_VIEW cf=columnFamilyName (K_WHERE w=whereClause { wclause = std::move(w); } )? ( usingClause[attrs] )?
|
||||
: K_PRUNE K_MATERIALIZED K_VIEW cf=columnFamilyName (K_WHERE w=whereClause { wclause = std::move(w); } )? ( usingTimeoutConcurrencyClause[attrs] )?
|
||||
{
|
||||
auto params = make_lw_shared<raw::select_statement::parameters>(std::move(orderings), is_distinct, allow_filtering, statement_subtype, bypass_cache);
|
||||
return std::make_unique<raw::select_statement>(std::move(cf), std::move(params),
|
||||
@@ -2370,6 +2379,7 @@ K_LIKE: L I K E;
|
||||
|
||||
K_TIMEOUT: T I M E O U T;
|
||||
K_PRUNE: P R U N E;
|
||||
K_CONCURRENCY: C O N C U R R E N C Y;
|
||||
|
||||
K_EXECUTE: E X E C U T E;
|
||||
|
||||
|
||||
@@ -20,19 +20,21 @@
|
||||
namespace cql3 {
|
||||
|
||||
std::unique_ptr<attributes> attributes::none() {
|
||||
return std::unique_ptr<attributes>{new attributes{{}, {}, {}, {}}};
|
||||
return std::unique_ptr<attributes>{new attributes{{}, {}, {}, {}, {}}};
|
||||
}
|
||||
|
||||
attributes::attributes(std::optional<cql3::expr::expression>&& timestamp,
|
||||
std::optional<cql3::expr::expression>&& time_to_live,
|
||||
std::optional<cql3::expr::expression>&& timeout,
|
||||
std::optional<sstring> service_level)
|
||||
std::optional<sstring> service_level,
|
||||
std::optional<cql3::expr::expression>&& concurrency)
|
||||
: _timestamp_unset_guard(timestamp)
|
||||
, _timestamp{std::move(timestamp)}
|
||||
, _time_to_live_unset_guard(time_to_live)
|
||||
, _time_to_live{std::move(time_to_live)}
|
||||
, _timeout{std::move(timeout)}
|
||||
, _service_level(std::move(service_level))
|
||||
, _concurrency{std::move(concurrency)}
|
||||
{ }
|
||||
|
||||
bool attributes::is_timestamp_set() const {
|
||||
@@ -51,6 +53,10 @@ bool attributes::is_service_level_set() const {
|
||||
return bool(_service_level);
|
||||
}
|
||||
|
||||
bool attributes::is_concurrency_set() const {
|
||||
return bool(_concurrency);
|
||||
}
|
||||
|
||||
int64_t attributes::get_timestamp(int64_t now, const query_options& options) {
|
||||
if (!_timestamp.has_value() || _timestamp_unset_guard.is_unset(options)) {
|
||||
return now;
|
||||
@@ -123,6 +129,27 @@ qos::service_level_options attributes::get_service_level(qos::service_level_cont
|
||||
return sl_controller.get_service_level(sl_name).slo;
|
||||
}
|
||||
|
||||
std::optional<int32_t> attributes::get_concurrency(const query_options& options) const {
|
||||
if (!_concurrency.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
cql3::raw_value concurrency_raw = expr::evaluate(*_concurrency, options);
|
||||
if (concurrency_raw.is_null()) {
|
||||
throw exceptions::invalid_request_exception("Invalid null value of concurrency");
|
||||
}
|
||||
int32_t concurrency;
|
||||
try {
|
||||
concurrency = concurrency_raw.view().validate_and_deserialize<int32_t>(*int32_type);
|
||||
} catch (marshal_exception& e) {
|
||||
throw exceptions::invalid_request_exception("Invalid concurrency value");
|
||||
}
|
||||
if (concurrency <= 0) {
|
||||
throw exceptions::invalid_request_exception("Concurrency must be a positive integer");
|
||||
}
|
||||
return concurrency;
|
||||
}
|
||||
|
||||
void attributes::fill_prepare_context(prepare_context& ctx) {
|
||||
if (_timestamp.has_value()) {
|
||||
expr::fill_prepare_context(*_timestamp, ctx);
|
||||
@@ -133,10 +160,13 @@ void attributes::fill_prepare_context(prepare_context& ctx) {
|
||||
if (_timeout.has_value()) {
|
||||
expr::fill_prepare_context(*_timeout, ctx);
|
||||
}
|
||||
if (_concurrency.has_value()) {
|
||||
expr::fill_prepare_context(*_concurrency, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<attributes> attributes::raw::prepare(data_dictionary::database db, const sstring& ks_name, const sstring& cf_name) const {
|
||||
std::optional<expr::expression> ts, ttl, to;
|
||||
std::optional<expr::expression> ts, ttl, to, conc;
|
||||
|
||||
if (timestamp.has_value()) {
|
||||
ts = prepare_expression(*timestamp, db, ks_name, nullptr, timestamp_receiver(ks_name, cf_name));
|
||||
@@ -153,7 +183,12 @@ std::unique_ptr<attributes> attributes::raw::prepare(data_dictionary::database d
|
||||
verify_no_aggregate_functions(*timeout, "USING clause");
|
||||
}
|
||||
|
||||
return std::unique_ptr<attributes>{new attributes{std::move(ts), std::move(ttl), std::move(to), std::move(service_level)}};
|
||||
if (concurrency.has_value()) {
|
||||
conc = prepare_expression(*concurrency, db, ks_name, nullptr, concurrency_receiver(ks_name, cf_name));
|
||||
verify_no_aggregate_functions(*concurrency, "USING clause");
|
||||
}
|
||||
|
||||
return std::unique_ptr<attributes>{new attributes{std::move(ts), std::move(ttl), std::move(to), std::move(service_level), std::move(conc)}};
|
||||
}
|
||||
|
||||
lw_shared_ptr<column_specification> attributes::raw::timestamp_receiver(const sstring& ks_name, const sstring& cf_name) const {
|
||||
@@ -168,4 +203,8 @@ lw_shared_ptr<column_specification> attributes::raw::timeout_receiver(const sstr
|
||||
return make_lw_shared<column_specification>(ks_name, cf_name, ::make_shared<column_identifier>("[timeout]", true), duration_type);
|
||||
}
|
||||
|
||||
lw_shared_ptr<column_specification> attributes::raw::concurrency_receiver(const sstring& ks_name, const sstring& cf_name) const {
|
||||
return make_lw_shared<column_specification>(ks_name, cf_name, ::make_shared<column_identifier>("[concurrency]", true), data_type_for<int32_t>());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -36,13 +36,15 @@ private:
|
||||
std::optional<cql3::expr::expression> _time_to_live;
|
||||
std::optional<cql3::expr::expression> _timeout;
|
||||
std::optional<sstring> _service_level;
|
||||
std::optional<cql3::expr::expression> _concurrency;
|
||||
public:
|
||||
static std::unique_ptr<attributes> none();
|
||||
private:
|
||||
attributes(std::optional<cql3::expr::expression>&& timestamp,
|
||||
std::optional<cql3::expr::expression>&& time_to_live,
|
||||
std::optional<cql3::expr::expression>&& timeout,
|
||||
std::optional<sstring> service_level);
|
||||
std::optional<sstring> service_level,
|
||||
std::optional<cql3::expr::expression>&& concurrency);
|
||||
public:
|
||||
bool is_timestamp_set() const;
|
||||
|
||||
@@ -52,6 +54,8 @@ public:
|
||||
|
||||
bool is_service_level_set() const;
|
||||
|
||||
bool is_concurrency_set() const;
|
||||
|
||||
int64_t get_timestamp(int64_t now, const query_options& options);
|
||||
|
||||
std::optional<int32_t> get_time_to_live(const query_options& options);
|
||||
@@ -60,6 +64,8 @@ public:
|
||||
|
||||
qos::service_level_options get_service_level(qos::service_level_controller& sl_controller) const;
|
||||
|
||||
std::optional<int32_t> get_concurrency(const query_options& options) const;
|
||||
|
||||
void fill_prepare_context(prepare_context& ctx);
|
||||
|
||||
class raw final {
|
||||
@@ -68,6 +74,7 @@ public:
|
||||
std::optional<cql3::expr::expression> time_to_live;
|
||||
std::optional<cql3::expr::expression> timeout;
|
||||
std::optional<sstring> service_level;
|
||||
std::optional<cql3::expr::expression> concurrency;
|
||||
|
||||
std::unique_ptr<attributes> prepare(data_dictionary::database db, const sstring& ks_name, const sstring& cf_name) const;
|
||||
private:
|
||||
@@ -76,6 +83,8 @@ public:
|
||||
lw_shared_ptr<column_specification> time_to_live_receiver(const sstring& ks_name, const sstring& cf_name) const;
|
||||
|
||||
lw_shared_ptr<column_specification> timeout_receiver(const sstring& ks_name, const sstring& cf_name) const;
|
||||
|
||||
lw_shared_ptr<column_specification> concurrency_receiver(const sstring& ks_name, const sstring& cf_name) const;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -186,10 +186,6 @@ void alter_table_statement::add_column(const query_options&, const schema& schem
|
||||
if (!schema.is_compound()) {
|
||||
throw exceptions::invalid_request_exception("Cannot use non-frozen collections with a non-composite PRIMARY KEY");
|
||||
}
|
||||
if (schema.is_super()) {
|
||||
throw exceptions::invalid_request_exception("Cannot use non-frozen collections with super column families");
|
||||
}
|
||||
|
||||
|
||||
// If there used to be a non-frozen collection column with the same name (that has been dropped),
|
||||
// we could still have some data using the old type, and so we can't allow adding a collection
|
||||
|
||||
@@ -279,11 +279,15 @@ std::vector<::shared_ptr<index_target>> create_index_statement::validate_while_e
|
||||
throw exceptions::invalid_request_exception(format("index names shouldn't be more than {:d} characters long (got \"{}\")", schema::NAME_LENGTH, _index_name.c_str()));
|
||||
}
|
||||
|
||||
try {
|
||||
db::view::validate_view_keyspace(db, keyspace());
|
||||
} catch (const std::exception& e) {
|
||||
// The type of the thrown exception is not specified, so we need to wrap it here.
|
||||
throw exceptions::invalid_request_exception(e.what());
|
||||
// Regular secondary indexes require rf-rack-validity.
|
||||
// Custom indexes need to validate this property themselves, if they need it.
|
||||
if (!_properties || !_properties->custom_class) {
|
||||
try {
|
||||
db::view::validate_view_keyspace(db, keyspace());
|
||||
} catch (const std::exception& e) {
|
||||
// The type of the thrown exception is not specified, so we need to wrap it here.
|
||||
throw exceptions::invalid_request_exception(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
validate_for_local_index(*schema);
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace cql3 {
|
||||
namespace statements {
|
||||
|
||||
static future<> delete_ghost_rows(dht::partition_range_vector partition_ranges, std::vector<query::clustering_range> clustering_bounds, view_ptr view,
|
||||
service::storage_proxy& proxy, service::query_state& state, const query_options& options, cql_stats& stats, db::timeout_clock::duration timeout_duration) {
|
||||
service::storage_proxy& proxy, service::query_state& state, const query_options& options, cql_stats& stats, db::timeout_clock::duration timeout_duration, size_t concurrency) {
|
||||
auto key_columns = std::ranges::to<std::vector<const column_definition*>>(
|
||||
view->all_columns()
|
||||
| std::views::filter([] (const column_definition& cdef) { return cdef.is_primary_key(); })
|
||||
@@ -35,7 +35,7 @@ static future<> delete_ghost_rows(dht::partition_range_vector partition_ranges,
|
||||
tracing::trace(state.get_trace_state(), "Deleting ghost rows from partition ranges {}", partition_ranges);
|
||||
|
||||
auto p = service::pager::query_pagers::ghost_row_deleting_pager(schema_ptr(view), selection, state,
|
||||
options, std::move(command), std::move(partition_ranges), stats, proxy, timeout_duration);
|
||||
options, std::move(command), std::move(partition_ranges), stats, proxy, timeout_duration, concurrency);
|
||||
|
||||
int32_t page_size = std::max(options.get_page_size(), 1000);
|
||||
auto now = gc_clock::now();
|
||||
@@ -62,7 +62,8 @@ future<::shared_ptr<cql_transport::messages::result_message>> prune_materialized
|
||||
auto timeout_duration = get_timeout(state.get_client_state(), options);
|
||||
dht::partition_range_vector key_ranges = _restrictions->get_partition_key_ranges(options);
|
||||
std::vector<query::clustering_range> clustering_bounds = _restrictions->get_clustering_bounds(options);
|
||||
return delete_ghost_rows(std::move(key_ranges), std::move(clustering_bounds), view_ptr(_schema), qp.proxy(), state, options, _stats, timeout_duration).then([] {
|
||||
size_t concurrency = _attrs->is_concurrency_set() ? _attrs->get_concurrency(options).value() : 1;
|
||||
return delete_ghost_rows(std::move(key_ranges), std::move(clustering_bounds), view_ptr(_schema), qp.proxy(), state, options, _stats, timeout_duration, concurrency).then([] {
|
||||
return make_ready_future<::shared_ptr<cql_transport::messages::result_message>>(::make_shared<cql_transport::messages::result_message::void_message>());
|
||||
});
|
||||
}
|
||||
|
||||
18
db/config.cc
18
db/config.cc
@@ -1172,6 +1172,17 @@ db::config::config(std::shared_ptr<db::extensions> exts)
|
||||
"* default_weight: (Default: 1 **) How many requests are handled during each turn of the RoundRobin.\n"
|
||||
"* weights: (Default: Keyspace: 1) Takes a list of keyspaces. It sets how many requests are handled during each turn of the RoundRobin, based on the request_scheduler_id.")
|
||||
/**
|
||||
* @Group Vector search settings
|
||||
* @GroupDescription Settings for configuring and tuning vector search functionality.
|
||||
*/
|
||||
, vector_store_primary_uri(this, "vector_store_primary_uri", liveness::LiveUpdate, value_status::Used, "",
|
||||
"A comma-separated list of primary vector store node URIs. These nodes are preferred for vector search operations.")
|
||||
, vector_store_secondary_uri(this, "vector_store_secondary_uri", liveness::LiveUpdate, value_status::Used, "",
|
||||
"A comma-separated list of secondary vector store node URIs. These nodes are used as a fallback when all primary nodes are unavailable, and are typically located in a different availability zone for high availability.")
|
||||
, vector_store_encryption_options(this, "vector_store_encryption_options", value_status::Used, {},
|
||||
"Options for encrypted connections to the vector store. These options are used for HTTPS URIs in `vector_store_primary_uri` and `vector_store_secondary_uri`. The available options are:\n"
|
||||
"* truststore: (Default: <not set, use system truststore>) Location of the truststore containing the trusted certificate for authenticating remote servers.")
|
||||
/**
|
||||
* @Group Security properties
|
||||
* @GroupDescription Server and client security settings.
|
||||
*/
|
||||
@@ -1459,13 +1470,6 @@ db::config::config(std::shared_ptr<db::extensions> exts)
|
||||
, alternator_max_expression_cache_entries_per_shard(this, "alternator_max_expression_cache_entries_per_shard", liveness::LiveUpdate, value_status::Used, 2000, "Maximum number of cached parsed request expressions, per shard.")
|
||||
, alternator_max_users_query_size_in_trace_output(this, "alternator_max_users_query_size_in_trace_output", liveness::LiveUpdate, value_status::Used, uint64_t(4096),
|
||||
"Maximum size of user's command in trace output (`alternator_op` entry). Larger traces will be truncated and have `<truncated>` message appended - which doesn't count to the maximum limit.")
|
||||
, vector_store_primary_uri(
|
||||
this, "vector_store_primary_uri", liveness::LiveUpdate, value_status::Used, "", "A comma-separated list of primary vector store node URIs. These nodes are preferred for vector search operations.")
|
||||
, vector_store_secondary_uri(this, "vector_store_secondary_uri", liveness::LiveUpdate, value_status::Used, "",
|
||||
"A comma-separated list of secondary vector store node URIs. These nodes are used as a fallback when all primary nodes are unavailable, and are typically located in a different availability zone for high availability.")
|
||||
, vector_store_encryption_options(this, "vector_store_encryption_options", value_status::Used, {},
|
||||
"Options for encrypted connections to the vector store. These options are used for HTTPS URIs in vector_store_primary_uri and vector_store_secondary_uri. The available options are:\n"
|
||||
"* truststore: (Default: <not set. use system truststore>) Location of the truststore containing the trusted certificate for authenticating remote servers.")
|
||||
, abort_on_ebadf(this, "abort_on_ebadf", value_status::Used, true, "Abort the server on incorrect file descriptor access. Throws exception when disabled.")
|
||||
, sanitizer_report_backtrace(this, "sanitizer_report_backtrace", value_status::Used, false,
|
||||
"In debug mode, report log-structured allocator sanitizer violations with a backtrace. Slow.")
|
||||
|
||||
@@ -344,6 +344,9 @@ public:
|
||||
named_value<sstring> request_scheduler;
|
||||
named_value<sstring> request_scheduler_id;
|
||||
named_value<string_map> request_scheduler_options;
|
||||
named_value<sstring> vector_store_primary_uri;
|
||||
named_value<sstring> vector_store_secondary_uri;
|
||||
named_value<string_map> vector_store_encryption_options;
|
||||
named_value<sstring> authenticator;
|
||||
named_value<sstring> internode_authenticator;
|
||||
named_value<sstring> authorizer;
|
||||
@@ -471,10 +474,6 @@ public:
|
||||
named_value<uint32_t> alternator_max_expression_cache_entries_per_shard;
|
||||
named_value<uint64_t> alternator_max_users_query_size_in_trace_output;
|
||||
|
||||
named_value<sstring> vector_store_primary_uri;
|
||||
named_value<sstring> vector_store_secondary_uri;
|
||||
named_value<string_map> vector_store_encryption_options;
|
||||
|
||||
named_value<bool> abort_on_ebadf;
|
||||
|
||||
named_value<bool> sanitizer_report_backtrace;
|
||||
|
||||
@@ -152,8 +152,8 @@ public:
|
||||
|
||||
builder.with_version(sm.digest());
|
||||
|
||||
cf_type cf = sstring_to_cf_type(td.get_or("type", sstring("standard")));
|
||||
if (cf == cf_type::super) {
|
||||
auto type_str = td.get_or("type", sstring("standard"));
|
||||
if (type_str == "Super") {
|
||||
fail(unimplemented::cause::SUPER);
|
||||
}
|
||||
|
||||
@@ -284,13 +284,8 @@ public:
|
||||
if (kind_str == "compact_value") {
|
||||
continue;
|
||||
}
|
||||
if (kind == column_kind::clustering_key) {
|
||||
if (cf == cf_type::super && component_index != 0) {
|
||||
continue;
|
||||
}
|
||||
if (cf != cf_type::super && !is_compound) {
|
||||
continue;
|
||||
}
|
||||
if (kind == column_kind::clustering_key && !is_compound) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -143,7 +143,7 @@ static computed_columns_map get_computed_columns(const schema_mutations& sm);
|
||||
|
||||
static std::vector<column_definition> create_columns_from_column_rows(
|
||||
const query::result_set& rows, const sstring& keyspace,
|
||||
const sstring& table, bool is_super, column_view_virtual is_view_virtual, const computed_columns_map& computed_columns,
|
||||
const sstring& table, column_view_virtual is_view_virtual, const computed_columns_map& computed_columns,
|
||||
const data_dictionary::user_types_storage& user_types);
|
||||
|
||||
|
||||
@@ -1804,9 +1804,6 @@ static schema_mutations make_table_mutations(schema_ptr table, api::timestamp_ty
|
||||
auto scylla_tables_mutation = make_scylla_tables_mutation(table, timestamp);
|
||||
|
||||
list_type_impl::native_type flags;
|
||||
if (table->is_super()) {
|
||||
flags.emplace_back("super");
|
||||
}
|
||||
if (table->is_dense()) {
|
||||
flags.emplace_back("dense");
|
||||
}
|
||||
@@ -2280,7 +2277,6 @@ schema_ptr create_table_from_mutations(const schema_ctxt& ctxt, schema_mutations
|
||||
auto id = table_id(table_row.get_nonnull<utils::UUID>("id"));
|
||||
schema_builder builder{ks_name, cf_name, id};
|
||||
|
||||
auto cf = cf_type::standard;
|
||||
auto is_dense = false;
|
||||
auto is_counter = false;
|
||||
auto is_compound = false;
|
||||
@@ -2289,7 +2285,6 @@ schema_ptr create_table_from_mutations(const schema_ctxt& ctxt, schema_mutations
|
||||
if (flags) {
|
||||
for (auto& s : *flags) {
|
||||
if (s == "super") {
|
||||
// cf = cf_type::super;
|
||||
fail(unimplemented::cause::SUPER);
|
||||
} else if (s == "dense") {
|
||||
is_dense = true;
|
||||
@@ -2305,9 +2300,7 @@ schema_ptr create_table_from_mutations(const schema_ctxt& ctxt, schema_mutations
|
||||
std::vector<column_definition> column_defs = create_columns_from_column_rows(
|
||||
query::result_set(sm.columns_mutation()),
|
||||
ks_name,
|
||||
cf_name,/*,
|
||||
fullRawComparator, */
|
||||
cf == cf_type::super,
|
||||
cf_name,
|
||||
column_view_virtual::no,
|
||||
computed_columns,
|
||||
user_types);
|
||||
@@ -2486,9 +2479,7 @@ static computed_columns_map get_computed_columns(const schema_mutations& sm) {
|
||||
static std::vector<column_definition> create_columns_from_column_rows(
|
||||
const query::result_set& rows,
|
||||
const sstring& keyspace,
|
||||
const sstring& table, /*,
|
||||
AbstractType<?> rawComparator, */
|
||||
bool is_super,
|
||||
const sstring& table,
|
||||
column_view_virtual is_view_virtual,
|
||||
const computed_columns_map& computed_columns,
|
||||
const data_dictionary::user_types_storage& user_types)
|
||||
@@ -2565,12 +2556,12 @@ static schema_builder prepare_view_schema_builder_from_mutations(const schema_ct
|
||||
}
|
||||
|
||||
auto computed_columns = get_computed_columns(sm);
|
||||
auto column_defs = create_columns_from_column_rows(query::result_set(sm.columns_mutation()), ks_name, cf_name, false, column_view_virtual::no, computed_columns, user_types);
|
||||
auto column_defs = create_columns_from_column_rows(query::result_set(sm.columns_mutation()), ks_name, cf_name, column_view_virtual::no, computed_columns, user_types);
|
||||
for (auto&& cdef : column_defs) {
|
||||
builder.with_column_ordered(cdef);
|
||||
}
|
||||
if (sm.view_virtual_columns_mutation()) {
|
||||
column_defs = create_columns_from_column_rows(query::result_set(*sm.view_virtual_columns_mutation()), ks_name, cf_name, false, column_view_virtual::yes, computed_columns, user_types);
|
||||
column_defs = create_columns_from_column_rows(query::result_set(*sm.view_virtual_columns_mutation()), ks_name, cf_name, column_view_virtual::yes, computed_columns, user_types);
|
||||
for (auto&& cdef : column_defs) {
|
||||
builder.with_column_ordered(cdef);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
#include "query/query-result-reader.hh"
|
||||
#include "replica/database_fwd.hh"
|
||||
#include "db/timeout_clock.hh"
|
||||
#include <seastar/core/future.hh>
|
||||
#include <seastar/core/gate.hh>
|
||||
|
||||
namespace service {
|
||||
class storage_proxy;
|
||||
@@ -25,8 +27,14 @@ class delete_ghost_rows_visitor {
|
||||
replica::table& _view_table;
|
||||
schema_ptr _base_schema;
|
||||
std::optional<partition_key> _view_pk;
|
||||
db::timeout_semaphore _concurrency_semaphore;
|
||||
seastar::gate _gate;
|
||||
std::exception_ptr& _ex;
|
||||
|
||||
public:
|
||||
delete_ghost_rows_visitor(service::storage_proxy& proxy, service::query_state& state, view_ptr view, db::timeout_clock::duration timeout_duration);
|
||||
delete_ghost_rows_visitor(service::storage_proxy& proxy, service::query_state& state, view_ptr view, db::timeout_clock::duration timeout_duration, size_t concurrency, std::exception_ptr& ex);
|
||||
delete_ghost_rows_visitor(delete_ghost_rows_visitor&&) = default;
|
||||
~delete_ghost_rows_visitor() noexcept;
|
||||
|
||||
void add_value(const column_definition& def, query::result_row_view::iterator_type& i) {
|
||||
}
|
||||
@@ -45,6 +53,9 @@ public:
|
||||
uint32_t accept_partition_end(const query::result_row_view& static_row) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
private:
|
||||
future<> do_accept_new_row(partition_key pk, clustering_key ck);
|
||||
};
|
||||
|
||||
} //namespace db::view
|
||||
|
||||
@@ -3597,7 +3597,7 @@ view_updating_consumer::view_updating_consumer(view_update_generator& gen, schem
|
||||
})
|
||||
{ }
|
||||
|
||||
delete_ghost_rows_visitor::delete_ghost_rows_visitor(service::storage_proxy& proxy, service::query_state& state, view_ptr view, db::timeout_clock::duration timeout_duration)
|
||||
delete_ghost_rows_visitor::delete_ghost_rows_visitor(service::storage_proxy& proxy, service::query_state& state, view_ptr view, db::timeout_clock::duration timeout_duration, size_t concurrency, std::exception_ptr& ex)
|
||||
: _proxy(proxy)
|
||||
, _state(state)
|
||||
, _timeout_duration(timeout_duration)
|
||||
@@ -3605,8 +3605,20 @@ delete_ghost_rows_visitor::delete_ghost_rows_visitor(service::storage_proxy& pro
|
||||
, _view_table(_proxy.get_db().local().find_column_family(view))
|
||||
, _base_schema(_proxy.get_db().local().find_schema(_view->view_info()->base_id()))
|
||||
, _view_pk()
|
||||
, _concurrency_semaphore(concurrency)
|
||||
, _ex(ex)
|
||||
{}
|
||||
|
||||
|
||||
delete_ghost_rows_visitor::~delete_ghost_rows_visitor() noexcept {
|
||||
try {
|
||||
_gate.close().get();
|
||||
} catch (...) {
|
||||
// Closing the gate should never throw, but if it does anyway, capture the exception.
|
||||
_ex = std::current_exception();
|
||||
}
|
||||
}
|
||||
|
||||
void delete_ghost_rows_visitor::accept_new_partition(const partition_key& key, uint32_t row_count) {
|
||||
SCYLLA_ASSERT(thread::running_in_thread());
|
||||
_view_pk = key;
|
||||
@@ -3614,7 +3626,18 @@ void delete_ghost_rows_visitor::accept_new_partition(const partition_key& key, u
|
||||
|
||||
// Assumes running in seastar::thread
|
||||
void delete_ghost_rows_visitor::accept_new_row(const clustering_key& ck, const query::result_row_view& static_row, const query::result_row_view& row) {
|
||||
auto view_exploded_pk = _view_pk->explode();
|
||||
auto units = get_units(_concurrency_semaphore, 1).get();
|
||||
(void)seastar::try_with_gate(_gate, [this, pk = _view_pk.value(), units = std::move(units), ck] () mutable {
|
||||
return do_accept_new_row(std::move(pk), std::move(ck)).then_wrapped([this, units = std::move(units)] (future<>&& f) mutable {
|
||||
if (f.failed()) {
|
||||
_ex = f.get_exception();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
future<> delete_ghost_rows_visitor::do_accept_new_row(partition_key pk, clustering_key ck) {
|
||||
auto view_exploded_pk = pk.explode();
|
||||
auto view_exploded_ck = ck.explode();
|
||||
std::vector<bytes> base_exploded_pk(_base_schema->partition_key_size());
|
||||
std::vector<bytes> base_exploded_ck(_base_schema->clustering_key_size());
|
||||
@@ -3649,17 +3672,17 @@ void delete_ghost_rows_visitor::accept_new_row(const clustering_key& ck, const q
|
||||
_proxy.get_max_result_size(partition_slice), query::tombstone_limit(_proxy.get_tombstone_limit()));
|
||||
auto timeout = db::timeout_clock::now() + _timeout_duration;
|
||||
service::storage_proxy::coordinator_query_options opts{timeout, _state.get_permit(), _state.get_client_state(), _state.get_trace_state()};
|
||||
auto base_qr = _proxy.query(_base_schema, command, std::move(partition_ranges), db::consistency_level::ALL, opts).get();
|
||||
auto base_qr = co_await _proxy.query(_base_schema, command, std::move(partition_ranges), db::consistency_level::ALL, opts);
|
||||
query::result& result = *base_qr.query_result;
|
||||
auto delete_ghost_row = [&]() {
|
||||
mutation m(_view, *_view_pk);
|
||||
auto delete_ghost_row = [&]() -> future<> {
|
||||
mutation m(_view, pk);
|
||||
auto& row = m.partition().clustered_row(*_view, ck);
|
||||
row.apply(tombstone(api::new_timestamp(), gc_clock::now()));
|
||||
timeout = db::timeout_clock::now() + _timeout_duration;
|
||||
_proxy.mutate({m}, db::consistency_level::ALL, timeout, _state.get_trace_state(), empty_service_permit(), db::allow_per_partition_rate_limit::no).get();
|
||||
return _proxy.mutate({m}, db::consistency_level::ALL, timeout, _state.get_trace_state(), empty_service_permit(), db::allow_per_partition_rate_limit::no);
|
||||
};
|
||||
if (result.row_count().value_or(0) == 0) {
|
||||
delete_ghost_row();
|
||||
co_await delete_ghost_row();
|
||||
} else if (!view_key_cols_not_in_base_key.empty()) {
|
||||
if (result.row_count().value_or(0) != 1) {
|
||||
on_internal_error(vlogger, format("Got multiple base rows corresponding to a single view row when pruning {}.{}", _view->ks_name(), _view->cf_name()));
|
||||
@@ -3669,7 +3692,7 @@ void delete_ghost_rows_visitor::accept_new_row(const clustering_key& ck, const q
|
||||
for (const auto& [col_def, col_val] : view_key_cols_not_in_base_key) {
|
||||
const data_value* base_val = base_row.get_data_value(col_def->name_as_text());
|
||||
if (!base_val || base_val->is_null() || col_val != base_val->serialize_nonnull()) {
|
||||
delete_ghost_row();
|
||||
co_await delete_ghost_row();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
1
dist/debian/debian/scylla-server.install
vendored
1
dist/debian/debian/scylla-server.install
vendored
@@ -2,7 +2,6 @@ etc/default/scylla-server
|
||||
etc/default/scylla-housekeeping
|
||||
etc/scylla.d/*.conf
|
||||
etc/bash_completion.d/nodetool-completion
|
||||
opt/scylladb/share/p11-kit/modules/*
|
||||
opt/scylladb/share/doc/scylla/*
|
||||
opt/scylladb/share/doc/scylla/licenses/
|
||||
usr/lib/systemd/system/*.timer
|
||||
|
||||
1
dist/redhat/scylla.spec
vendored
1
dist/redhat/scylla.spec
vendored
@@ -122,7 +122,6 @@ ln -sfT /etc/scylla /var/lib/scylla/conf
|
||||
%config(noreplace) %{_sysconfdir}/sysconfig/scylla-housekeeping
|
||||
%attr(0755,root,root) %dir %{_sysconfdir}/scylla.d
|
||||
%config(noreplace) %{_sysconfdir}/scylla.d/*.conf
|
||||
/opt/scylladb/share/p11-kit/modules/*
|
||||
/opt/scylladb/share/doc/scylla/*
|
||||
%{_unitdir}/scylla-fstrim.service
|
||||
%{_unitdir}/scylla-housekeeping-daily.service
|
||||
|
||||
@@ -1,6 +1,18 @@
|
||||
### a dictionary of redirections
|
||||
#old path: new path
|
||||
|
||||
# Move the diver information to another project
|
||||
|
||||
/stable/using-scylla/drivers/index.html: https://docs.scylladb.com/stable/drivers/index.html
|
||||
/stable/using-scylla/drivers/dynamo-drivers/index.html: https://docs.scylladb.com/stable/drivers/dynamo-drivers.html
|
||||
/stable/using-scylla/drivers/cql-drivers/index.html: https://docs.scylladb.com/stable/drivers/cql-drivers.html
|
||||
/stable/using-scylla/drivers/cql-drivers/scylla-python-driver.html: https://docs.scylladb.com/stable/drivers/cql-drivers.html
|
||||
/stable/using-scylla/drivers/cql-drivers/scylla-java-driver.html: https://docs.scylladb.com/stable/drivers/cql-drivers.html
|
||||
/stable/using-scylla/drivers/cql-drivers/scylla-go-driver.html: https://docs.scylladb.com/stable/drivers/cql-drivers.html
|
||||
/stable/using-scylla/drivers/cql-drivers/scylla-gocqlx-driver.html: https://docs.scylladb.com/stable/drivers/cql-drivers.html
|
||||
/stable/using-scylla/drivers/cql-drivers/scylla-cpp-driver.html: https://docs.scylladb.com/stable/drivers/cql-drivers.html
|
||||
/stable/using-scylla/drivers/cql-drivers/scylla-rust-driver.html: https://docs.scylladb.com/stable/drivers/cql-drivers.html
|
||||
|
||||
# Redirect 2025.1 upgrade guides that are not on master but were indexed by Google (404 reported)
|
||||
|
||||
/master/upgrade/upgrade-guides/upgrade-guide-from-2024.x-to-2025.1/upgrade-guide-from-2024.x-to-2025.1.html: https://docs.scylladb.com/manual/stable/upgrade/index.html
|
||||
|
||||
@@ -106,6 +106,15 @@ which is recommended in order to make the operation less heavyweight
|
||||
and allow for running multiple parallel pruning statements for non-overlapping
|
||||
token ranges.
|
||||
|
||||
By default, the PRUNE MATERIALIZED VIEW statement is relatively slow, only
|
||||
performing one base read or write at a time. This can be changed with the
|
||||
USING CONCURRENCY clause. If the clause is used, the concurrency of reads
|
||||
and writes from the base table will be allowed to increase up to the specified
|
||||
value. For example, to run the PRUNE with 100 parallel reads/writes, you can use:
|
||||
```cql
|
||||
PRUNE MATERIALIZED VIEW my_view WHERE v = 19 USING CONCURRENCY 100;
|
||||
```
|
||||
|
||||
## Synchronous materialized views
|
||||
|
||||
Usually, when a table with materialized views is updated, the update to the
|
||||
|
||||
@@ -37,7 +37,7 @@ Getting Started
|
||||
:id: "getting-started"
|
||||
:class: my-panel
|
||||
|
||||
* :doc:`ScyllaDB Drivers</using-scylla/drivers/index>`
|
||||
* `ScyllaDB Drivers <https://docs.scylladb.com/stable/drivers/index.html>`_
|
||||
* `Get Started Lesson on ScyllaDB University <https://university.scylladb.com/courses/scylla-essentials-overview/lessons/quick-wins-install-and-run-scylla/>`_
|
||||
* :doc:`CQL Reference </cql/index>`
|
||||
* :doc:`cqlsh - the CQL shell </cql/cqlsh/>`
|
||||
|
||||
@@ -35,7 +35,7 @@ Documentation Highlights
|
||||
* :doc:`Cluster Management Procedures </operating-scylla/procedures/cluster-management/index>`
|
||||
* :doc:`Upgrade ScyllaDB </upgrade/index>`
|
||||
* :doc:`CQL Reference </cql/index>`
|
||||
* :doc:`ScyllaDB Drivers </using-scylla/drivers/index>`
|
||||
* `ScyllaDB Drivers <https://docs.scylladb.com/stable/drivers/index.html>`_
|
||||
* :doc:`Features </features/index>`
|
||||
|
||||
ScyllaDB Support
|
||||
|
||||
@@ -172,7 +172,7 @@ For example:
|
||||
* `ScyllaDB Java Driver <https://github.com/scylladb/java-driver/tree/3.7.1-scylla/manual/compression>`_
|
||||
* `Go Driver <https://godoc.org/github.com/gocql/gocql#Compressor>`_
|
||||
|
||||
Refer to the :doc:`Drivers Page </using-scylla/drivers/index>` for more drivers.
|
||||
Refer to `ScyllaDB Drivers <https://docs.scylladb.com/stable/drivers/index.html>`_ for more drivers.
|
||||
|
||||
.. _internode-compression:
|
||||
|
||||
|
||||
@@ -206,7 +206,7 @@ This is 19% of the latency compared to no batching.
|
||||
Driver Guidelines
|
||||
-----------------
|
||||
|
||||
Use the :doc:`ScyllaDB drivers </using-scylla/drivers/index>` that are available for Java, Python, Go, and C/C++.
|
||||
Use the `ScyllaDB drivers <https://docs.scylladb.com/stable/drivers/index.html>`_ that are available for Java, Python, Go, and C/C++.
|
||||
They provide much better performance than third-party drivers because they are shard aware &emdash; they can route requests to the right CPU core (shard).
|
||||
When the driver starts, it gets the topology of the cluster and therefore it knows exactly which CPU core should get a request.
|
||||
Our latest shard-aware drivers also improve the efficiency of our Change Data Capture (CDC) feature.
|
||||
|
||||
@@ -121,7 +121,7 @@ Driver Compression
|
||||
|
||||
This refers to compressing traffic between the client and ScyllaDB.
|
||||
Verify your client driver is using compressed traffic when connected to ScyllaDB.
|
||||
As compression is driver settings dependent, please check your client driver manual or :doc:`ScyllaDB Drivers </using-scylla/drivers/index>`.
|
||||
As compression is driver settings dependent, please check your client driver manual. See `ScyllaDB Drivers <https://docs.scylladb.com/stable/drivers/index.html>`_.
|
||||
|
||||
|
||||
Connectivity
|
||||
@@ -130,7 +130,7 @@ Connectivity
|
||||
Drivers Settings
|
||||
================
|
||||
|
||||
* Use shard aware drivers wherever possible. :doc:`ScyllaDB Drivers </using-scylla/drivers/index>` (not third-party drivers) are shard aware.
|
||||
* Use shard aware drivers wherever possible. `ScyllaDB Drivers <https://docs.scylladb.com/stable/drivers/index.html>`_ (not third-party drivers) are shard aware.
|
||||
* Configure connection pool - open more connections (>3 per shard) and/Or more clients. See `this blog <https://www.scylladb.com/2019/11/20/maximizing-performance-via-concurrency-while-minimizing-timeouts-in-distributed-databases/>`_.
|
||||
|
||||
Management
|
||||
|
||||
@@ -25,8 +25,8 @@ Actions
|
||||
|
||||
If your cluster is having timeouts during overload, check first if you are not making the overload situation worse through retries, and pay attention to the following:
|
||||
|
||||
* Make sure the client retries only after the server has already timed out. Depending on the application this may mean increasing the client-side timeout or decreasing the server-side timeout. Client timeouts are configured by the driver, check your :doc:`driver documentation </using-scylla/drivers/index>` about parameters and defaults. For the server-side timeout, the ``/etc/scylla/scylla.yaml`` has request-specific timeout settings like ``read_request_timeout_in_ms`` and ``write_request_timeout_in_ms``
|
||||
* Make sure the client neither runs a speculative retry nor runs it very aggressively. Client-side speculative retry is configured by the driver, check your :doc:`driver documentation </using-scylla/drivers/index>` about parameters and defaults.
|
||||
* Make sure the client retries only after the server has already timed out. Depending on the application this may mean increasing the client-side timeout or decreasing the server-side timeout. Client timeouts are configured by the driver, check your `driver documentation <https://docs.scylladb.com/stable/drivers/index.html>`_ about parameters and defaults. For the server-side timeout, the ``/etc/scylla/scylla.yaml`` has request-specific timeout settings like ``read_request_timeout_in_ms`` and ``write_request_timeout_in_ms``
|
||||
* Make sure the client neither runs a speculative retry nor runs it very aggressively. Client-side speculative retry is configured by the driver, check your `driver documentation <https://docs.scylladb.com/stable/drivers/index.html>`_ about parameters and defaults.
|
||||
* Make sure the server neither runs speculative retry nor runs it based on percentiles (as those can fluctuate aggressively). Server-side speculative retries are a per-table setting that can be changed with the ALTER TABLE command. See the :ref:`documentation <speculative-retry-options>` for details.
|
||||
|
||||
|
||||
|
||||
@@ -9,9 +9,19 @@ To ensure a successful upgrade, follow
|
||||
the :doc:`documented upgrade procedures <upgrade-guides/index>` tested by
|
||||
ScyllaDB. This means that:
|
||||
|
||||
* You should perform the upgrades consecutively - to each successive X.Y
|
||||
version, **without skipping any major or minor version**, unless there is
|
||||
a documented upgrade procedure to bypass a version.
|
||||
* You should follow the upgrade policy:
|
||||
|
||||
* Starting with version **2025.4**, upgrades can skip minor versions as long
|
||||
as they remain within the same major version (for example, upgrading directly
|
||||
from 2025.1 → 2025.4 is supported).
|
||||
* For versions **prior to 2025.4**, upgrades must be performed consecutively—
|
||||
each successive X.Y version must be installed in order, **without skipping
|
||||
any major or minor version** (for example, upgrading directly from 2025.1 → 2025.3
|
||||
is not supported).
|
||||
* You cannot skip major versions. Upgrades must move from one major version to
|
||||
the next using the documented major-version upgrade path.
|
||||
* You should upgrade to a supported version of ScyllaDB.
|
||||
See `ScyllaDB Version Support <https://docs.scylladb.com/stable/versioning/version-support.html>`_.
|
||||
* Before you upgrade to the next version, the whole cluster (each node) must
|
||||
be upgraded to the previous version.
|
||||
* You cannot perform an upgrade by replacing the nodes in the cluster with new
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 28 KiB |
@@ -1,141 +0,0 @@
|
||||
=====================
|
||||
ScyllaDB CQL Drivers
|
||||
=====================
|
||||
|
||||
.. toctree::
|
||||
:titlesonly:
|
||||
:hidden:
|
||||
|
||||
scylla-python-driver
|
||||
scylla-java-driver
|
||||
scylla-go-driver
|
||||
scylla-gocqlx-driver
|
||||
scylla-cpp-driver
|
||||
scylla-rust-driver
|
||||
|
||||
ScyllaDB Drivers
|
||||
-----------------
|
||||
|
||||
The following ScyllaDB drivers are available:
|
||||
|
||||
* :doc:`Python Driver</using-scylla/drivers/cql-drivers/scylla-python-driver>`
|
||||
* :doc:`Java Driver </using-scylla/drivers/cql-drivers/scylla-java-driver>`
|
||||
* :doc:`Go Driver </using-scylla/drivers/cql-drivers/scylla-go-driver>`
|
||||
* :doc:`Go Extension </using-scylla/drivers/cql-drivers/scylla-gocqlx-driver>`
|
||||
* :doc:`C++ Driver </using-scylla/drivers/cql-drivers/scylla-cpp-driver>`
|
||||
* `CPP-over-Rust Driver <https://cpp-rust-driver.docs.scylladb.com/>`_
|
||||
* :doc:`Rust Driver </using-scylla/drivers/cql-drivers/scylla-rust-driver>`
|
||||
* `C# Driver <https://csharp-driver.docs.scylladb.com/>`_
|
||||
|
||||
We recommend using ScyllaDB drivers. All ScyllaDB drivers are shard-aware and provide additional
|
||||
benefits over third-party drivers.
|
||||
|
||||
ScyllaDB supports the CQL binary protocol version 3, so any Apache Cassandra/CQL driver that implements
|
||||
the same version works with ScyllaDB.
|
||||
|
||||
CDC Integration with ScyllaDB Drivers
|
||||
-------------------------------------------
|
||||
|
||||
The following table specifies which ScyllaDB drivers include a library for
|
||||
:doc:`CDC </features/cdc/cdc-intro>`.
|
||||
|
||||
.. list-table::
|
||||
:widths: 40 60
|
||||
:header-rows: 1
|
||||
|
||||
* - ScyllaDB Driver
|
||||
- CDC Connector
|
||||
* - :doc:`Python </using-scylla/drivers/cql-drivers/scylla-python-driver>`
|
||||
- |x|
|
||||
* - :doc:`Java </using-scylla/drivers/cql-drivers/scylla-java-driver>`
|
||||
- |v|
|
||||
* - :doc:`Go </using-scylla/drivers/cql-drivers/scylla-go-driver>`
|
||||
- |v|
|
||||
* - :doc:`Go Extension </using-scylla/drivers/cql-drivers/scylla-gocqlx-driver>`
|
||||
- |x|
|
||||
* - :doc:`C++ </using-scylla/drivers/cql-drivers/scylla-cpp-driver>`
|
||||
- |x|
|
||||
* - `CPP-over-Rust Driver <https://cpp-rust-driver.docs.scylladb.com/>`_
|
||||
- |x|
|
||||
* - :doc:`Rust </using-scylla/drivers/cql-drivers/scylla-rust-driver>`
|
||||
- |v|
|
||||
* - `C# Driver <https://csharp-driver.docs.scylladb.com/>`_
|
||||
- |x|
|
||||
|
||||
Support for Tablets
|
||||
-------------------------
|
||||
|
||||
The following table specifies which ScyllaDB drivers support
|
||||
:doc:`tablets </architecture/tablets>` and since which version.
|
||||
|
||||
.. list-table::
|
||||
:widths: 30 35 35
|
||||
:header-rows: 1
|
||||
|
||||
* - ScyllaDB Driver
|
||||
- Support for Tablets
|
||||
- Since Version
|
||||
* - :doc:`Python</using-scylla/drivers/cql-drivers/scylla-python-driver>`
|
||||
- |v|
|
||||
- 3.26.5
|
||||
* - :doc:`Java </using-scylla/drivers/cql-drivers/scylla-java-driver>`
|
||||
- |v|
|
||||
- 4.18.0 (Java Driver 4.x)
|
||||
|
||||
3.11.5.2 (Java Driver 3.x)
|
||||
* - :doc:`Go </using-scylla/drivers/cql-drivers/scylla-go-driver>`
|
||||
- |v|
|
||||
- 1.13.0
|
||||
* - :doc:`Go Extension </using-scylla/drivers/cql-drivers/scylla-gocqlx-driver>`
|
||||
- |x|
|
||||
- N/A
|
||||
* - :doc:`C++ </using-scylla/drivers/cql-drivers/scylla-cpp-driver>`
|
||||
- |x|
|
||||
- N/A
|
||||
* - `CPP-over-Rust Driver <https://cpp-rust-driver.docs.scylladb.com/>`_
|
||||
- |v|
|
||||
- All versions
|
||||
* - :doc:`Rust </using-scylla/drivers/cql-drivers/scylla-rust-driver>`
|
||||
- |v|
|
||||
- 0.13.0
|
||||
* - `C# Driver <https://csharp-driver.docs.scylladb.com/>`_
|
||||
- |v|
|
||||
- All versions
|
||||
|
||||
Driver Support Policy
|
||||
-------------------------------
|
||||
|
||||
We support the **two most recent minor releases** of our drivers.
|
||||
|
||||
* We test and validate the latest two minor versions.
|
||||
* We typically patch only the latest minor release.
|
||||
|
||||
We recommend staying up to date with the latest supported versions to receive
|
||||
updates and fixes.
|
||||
|
||||
At a minimum, upgrade your driver when upgrading to a new ScyllaDB version
|
||||
to ensure compatibility between the driver and the database.
|
||||
|
||||
Third-party Drivers
|
||||
----------------------
|
||||
|
||||
You can find the third-party driver documentation on the GitHub pages for each driver:
|
||||
|
||||
* `DataStax Java Driver <https://github.com/datastax/java-driver/>`_
|
||||
* `DataStax Python Driver <https://github.com/datastax/python-driver/>`_
|
||||
* `DataStax C# Driver <https://github.com/datastax/csharp-driver/>`_
|
||||
* `DataStax Ruby Driver <https://github.com/datastax/ruby-driver/>`_
|
||||
* `DataStax Node.js Driver <https://github.com/datastax/nodejs-driver/>`_
|
||||
* `DataStax C++ Driver <https://github.com/datastax/cpp-driver/>`_
|
||||
* `DataStax PHP Driver (Supported versions: 7.1) <https://github.com/datastax/php-driver>`_
|
||||
* `He4rt PHP Driver (Supported versions: 8.1 and 8.2) <https://github.com/he4rt/scylladb-php-driver/>`_
|
||||
* `Scala Phantom Project <https://github.com/outworkers/phantom>`_
|
||||
* `Xandra Elixir Driver <https://github.com/lexhide/xandra>`_
|
||||
* `Exandra Elixir Driver <https://github.com/vinniefranco/exandra>`_
|
||||
|
||||
Learn about ScyllaDB Drivers on ScyllaDB University
|
||||
----------------------------------------------------
|
||||
|
||||
The free `Using ScyllaDB Drivers course <https://university.scylladb.com/courses/using-scylla-drivers/>`_
|
||||
on ScyllaDB University covers the use of drivers in multiple languages to interact with a ScyllaDB
|
||||
cluster. The languages covered include Java, CPP, Rust, Golang, Python, Node.JS, Scala, and others.
|
||||
@@ -1,16 +0,0 @@
|
||||
===================
|
||||
ScyllaDB C++ Driver
|
||||
===================
|
||||
|
||||
The ScyllaDB C++ driver is a modern, feature-rich and **shard-aware** C/C++ client library for ScyllaDB using exclusively Cassandra’s binary protocol and Cassandra Query Language v3.
|
||||
This driver is forked from Datastax cpp-driver.
|
||||
|
||||
Read the `documentation <https://cpp-driver.docs.scylladb.com>`_ to get started or visit the Github project `ScyllaDB C++ driver <https://github.com/scylladb/cpp-driver>`_.
|
||||
|
||||
|
||||
More Information
|
||||
----------------
|
||||
|
||||
* `C++ Driver Documentation <https://cpp-driver.docs.scylladb.com>`_
|
||||
* `C/C++ Driver course at ScyllaDB University <https://university.scylladb.com/courses/using-scylla-drivers/lessons/cpp-driver-part-1/>`_
|
||||
* `Blog: A Shard-Aware ScyllaDB C/C++ Driver <https://www.scylladb.com/2021/03/18/a-shard-aware-scylla-c-c-driver/>`_
|
||||
@@ -1,28 +0,0 @@
|
||||
==================
|
||||
ScyllaDB Go Driver
|
||||
==================
|
||||
|
||||
The `ScyllaDB Go driver <https://github.com/scylladb/gocql>`_ is shard aware and contains extensions for a tokenAwareHostPolicy supported by ScyllaDB 2.3 and onwards.
|
||||
It is is a fork of the `GoCQL Driver <https://github.com/gocql/gocql>`_ but has been enhanced with capabilities that take advantage of ScyllaDB's unique architecture.
|
||||
Using this policy, the driver can select a connection to a particular shard based on the shard’s token.
|
||||
As a result, latency is significantly reduced because there is no need to pass data between the shards.
|
||||
|
||||
The protocol extension spec is `available here <https://github.com/scylladb/scylla/blob/master/docs/dev/protocol-extensions.md>`_.
|
||||
The ScyllaDB Go Driver is a drop-in replacement for gocql.
|
||||
As such, no code changes are needed to use this driver.
|
||||
All you need to do is rebuild using the ``replace`` directive in your ``mod`` file.
|
||||
|
||||
**To download and install the driver**, visit the `Github project <https://github.com/scylladb/gocql>`_.
|
||||
|
||||
|
||||
Using CDC with Go
|
||||
-----------------
|
||||
|
||||
When writing applications, you can now use our `Go Library <https://github.com/scylladb/scylla-cdc-go>`_ to simplify writing applications that read from ScyllaDB CDC.
|
||||
|
||||
More information
|
||||
----------------
|
||||
|
||||
* `ScyllaDB Gocql Driver project page on GitHub <https://github.com/scylladb/gocql>`_ - contains the source code as well as a readme and documentation files.
|
||||
* `ScyllaDB University: Golang and ScyllaDB <https://university.scylladb.com/courses/using-scylla-drivers/lessons/golang-and-scylla-part-1/>`_
|
||||
A three-part lesson with in-depth examples from executing a few basic CQL statements with a ScyllaDB cluster using the Gocql driver, to the different data types that you can use in your database tables and how to store these binary files in ScyllaDB with a simple Go application.
|
||||
@@ -1,16 +0,0 @@
|
||||
=========================
|
||||
ScyllaDB Gocql Extension
|
||||
=========================
|
||||
|
||||
The ScyllaDB Gocqlx is an extension to gocql that provides usability features.
|
||||
With gocqlx, you can bind the query parameters from maps and structs, use named query parameters (``:identifier``), and scan the query results into structs and slices.
|
||||
The driver includes a fluent and flexible CQL query builder and a database migrations module.
|
||||
|
||||
|
||||
|
||||
More information
|
||||
----------------
|
||||
|
||||
* `ScyllaDB Gocqlx Driver project page on GitHub <https://github.com/scylladb/gocqlx>`_ - contains the source code as well as a readme and documentation files.
|
||||
* `ScyllaDB University: Golang and ScyllaDB Part 3 – GoCQLX <https://university.scylladb.com/courses/using-scylla-drivers/lessons/golang-and-scylla-part-3-gocqlx/>`_ - part three of the Golang three-part course which focuses on how to create a sample Go application that executes a few basic CQL statements with a ScyllaDB cluster using the GoCQLX package
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
=====================
|
||||
ScyllaDB Java Driver
|
||||
=====================
|
||||
|
||||
ScyllaDB Java Driver is forked from `DataStax Java Driver <https://github.com/datastax/java-driver>`_ with enhanced capabilities, taking advantage of ScyllaDB's unique architecture.
|
||||
|
||||
The ScyllaDB Java driver is shard aware and contains extensions for a ``tokenAwareHostPolicy``.
|
||||
Using this policy, the driver can select a connection to a particular shard based on the shard’s token.
|
||||
As a result, latency is significantly reduced because there is no need to pass data between the shards.
|
||||
|
||||
Use the ScyllaDB Java driver for better compatibility and support for ScyllaDB with Java-based applications.
|
||||
|
||||
Read the `documentation <https://java-driver.docs.scylladb.com/>`_ to get started or visit the `Github project <https://github.com/scylladb/java-driver>`_.
|
||||
|
||||
The driver architecture is based on layers. At the bottom lies the driver core.
|
||||
This core handles everything related to the connections to a ScyllaDB cluster (for example, connection pool, discovering new nodes, etc.) and exposes a simple, relatively low-level API on top of which higher-level layers can be built.
|
||||
|
||||
The ScyllaDB Java Driver is a drop-in replacement for the DataStax Java Driver.
|
||||
As such, no code changes are needed to use this driver.
|
||||
|
||||
Using CDC with Java
|
||||
-------------------
|
||||
|
||||
When writing applications, you can now use our `Java Library <https://github.com/scylladb/scylla-cdc-java>`_ to simplify writing applications that read from ScyllaDB CDC.
|
||||
|
||||
More information
|
||||
----------------
|
||||
* `ScyllaDB Java Driver Docs <https://java-driver.docs.scylladb.com/>`_
|
||||
* `ScyllaDB Java Driver project page on GitHub <https://github.com/scylladb/java-driver/>`_ - Source Code
|
||||
* `ScyllaDB University: Coding with Java <https://university.scylladb.com/courses/using-scylla-drivers/lessons/coding-with-java-part-1/>`_ - a three-part lesson with in-depth examples from executing a few basic CQL statements with a ScyllaDB cluster using the Java driver, to the different data types that you can use in your database tables and how to store these binary files in ScyllaDB with a simple Java application.
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
======================
|
||||
ScyllaDB Python Driver
|
||||
======================
|
||||
|
||||
The ScyllaDB Python driver is shard aware and contains extensions for a ``tokenAwareHostPolicy``.
|
||||
Using this policy, the driver can select a connection to a particular shard based on the shard’s token.
|
||||
As a result, latency is significantly reduced because there is no need to pass data between the shards.
|
||||
|
||||
Read the `documentation <https://python-driver.docs.scylladb.com/>`_ to get started or visit the Github project `ScyllaDB Python driver <https://github.com/scylladb/python-driver/>`_.
|
||||
|
||||
As the ScyllaDB Python Driver is a drop-in replacement for DataStax Python Driver, no code changes are needed to use the driver.
|
||||
Use the ScyllaDB Python driver for better compatibility and support for ScyllaDB with Python-based applications.
|
||||
|
||||
|
||||
More information
|
||||
----------------
|
||||
|
||||
* `ScyllaDB Python Driver Documentation <https://python-driver.docs.scylladb.com/>`_
|
||||
* `ScyllaDB Python Driver on GitHub <https://github.com/scylladb/python-driver/>`_
|
||||
* `ScyllaDB University: Coding with Python <https://university.scylladb.com/courses/using-scylla-drivers/lessons/coding-with-python/>`_
|
||||
@@ -1,24 +0,0 @@
|
||||
=====================
|
||||
ScyllaDB Rust Driver
|
||||
=====================
|
||||
|
||||
The ScyllaDB Rust driver is a client-side, shard-aware driver written in pure Rust with a fully async API using Tokio.
|
||||
Optimized for ScyllaDB, the driver is also compatible with Apache Cassandra®.
|
||||
|
||||
|
||||
.. image:: ./images/monster-rust.png
|
||||
:width: 150pt
|
||||
|
||||
|
||||
**To download and install the driver**, visit the `Github project <https://github.com/scylladb/scylla-rust-driver>`_.
|
||||
|
||||
Read the `Documentation <https://rust-driver.docs.scylladb.com>`_.
|
||||
|
||||
Using CDC with Rust
|
||||
----------------------
|
||||
|
||||
When writing applications, you can use ScyllaDB's `Rust CDC Library <https://github.com/scylladb/scylla-cdc-rust>`_
|
||||
to simplify writing applications that read from ScyllaDB's CDC.
|
||||
|
||||
Use `Rust CDC Library <https://github.com/scylladb/scylla-cdc-rust>`_ to read
|
||||
:doc:`ScyllaDB's CDC </features/cdc/index>` update streams.
|
||||
@@ -1,9 +0,0 @@
|
||||
========================
|
||||
AWS DynamoDB Drivers
|
||||
========================
|
||||
|
||||
|
||||
|
||||
|
||||
ScyllaDB AWS DynamoDB Compatible API can be used with any AWS DynamoDB Driver.
|
||||
For a list of AWS AWS DynamoDB drivers see `here <https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GettingStarted.html>`_
|
||||
@@ -1,21 +0,0 @@
|
||||
================
|
||||
ScyllaDB Drivers
|
||||
================
|
||||
|
||||
.. toctree::
|
||||
:titlesonly:
|
||||
:hidden:
|
||||
|
||||
ScyllaDB CQL Drivers <cql-drivers/index>
|
||||
ScyllaDB DynamoDB Drivers <dynamo-drivers/index>
|
||||
|
||||
|
||||
|
||||
You can use ScyllaDB with:
|
||||
|
||||
* :doc:`Apache Cassandra CQL Compatible Drivers <cql-drivers/index>`
|
||||
* :doc:`Amazon DynamoDB Compatible API Drivers <dynamo-drivers/index>`
|
||||
|
||||
Additional drivers coming soon!
|
||||
|
||||
If you are looking for a ScyllaDB Integration Solution or a Connector, refer to :doc:`ScyllaDB Integrations </using-scylla/integrations/index>`.
|
||||
@@ -9,7 +9,7 @@ ScyllaDB for Developers
|
||||
Tutorials and Example Projects <https://docs.scylladb.com/stable/get-started/develop-with-scylladb/tutorials-example-projects.html>
|
||||
Learn to Use ScyllaDB <https://docs.scylladb.com/stable/get-started/learn-resources/index.html>
|
||||
ScyllaDB Alternator <alternator/index>
|
||||
ScyllaDB Drivers <drivers/index>
|
||||
ScyllaDB Drivers <https://docs.scylladb.com/stable/drivers/index.html>
|
||||
|
||||
|
||||
.. panel-box::
|
||||
@@ -26,7 +26,7 @@ ScyllaDB for Developers
|
||||
:id: "getting-started"
|
||||
:class: my-panel
|
||||
|
||||
* :doc:`ScyllaDB Drivers </using-scylla/drivers/index>` - ScyllaDB and third-party drivers for CQL and DynamoDB
|
||||
* `ScyllaDB Drivers <https://docs.scylladb.com/stable/drivers/index.html>`_ - ScyllaDB and third-party drivers for CQL and DynamoDB
|
||||
* :doc:`ScyllaDB Alternator </using-scylla/alternator/index>` - The Open Source DynamoDB-compatible API
|
||||
* :doc:`CQL Reference </cql/index>` - Reference for the Apache Cassandra Query Language (CQL) and its ScyllaDB extensions
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ ScyllaDB Integrations and Connectors
|
||||
:class: my-panel
|
||||
|
||||
|
||||
ScyllaDB is Apache Cassandra compatible at the CQL binary protocol level, and any driver which uses CQL will work with ScyllaDB (more :doc:`here </using-scylla/drivers/index>`).
|
||||
ScyllaDB is Apache Cassandra compatible at the CQL binary protocol level, and any driver which uses CQL will work with ScyllaDB. See `ScyllaDB Drivers <https://docs.scylladb.com/stable/drivers/index.html>`_.
|
||||
Any application which uses a CQL driver will work with ScyllaDB.
|
||||
|
||||
The list below contains links to integration projects using ScyllaDB with third-party projects.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Integrate ScyllaDB with Databricks
|
||||
==================================
|
||||
|
||||
ScyllaDB is Apache Cassandra compatible at the CQL binary protocol level, and any driver which uses CQL will work with ScyllaDB (more :doc:`here </using-scylla/drivers/index>`). Any application which uses a CQL driver will work with ScyllaDB, for example, Databricks Spark cluster.
|
||||
ScyllaDB is Apache Cassandra compatible at the CQL binary protocol level, and any driver which uses CQL will work with ScyllaDB. See `ScyllaDB Drivers <https://docs.scylladb.com/stable/drivers/index.html>`_. Any application which uses a CQL driver will work with ScyllaDB, for example, Databricks Spark cluster.
|
||||
|
||||
Resource list
|
||||
-------------
|
||||
|
||||
@@ -3,7 +3,7 @@ Integrate ScyllaDB with Elasticsearch
|
||||
=====================================
|
||||
|
||||
|
||||
ScyllaDB is Apache Cassandra compatible at the CQL binary protocol level, and any driver which uses CQL will work with ScyllaDB (more :doc:`here </using-scylla/drivers/index>`). Any application which uses a CQL driver will work with ScyllaDB.
|
||||
ScyllaDB is Apache Cassandra compatible at the CQL binary protocol level, and any driver which uses CQL will work with ScyllaDB. See `ScyllaDB Drivers <https://docs.scylladb.com/stable/drivers/index.html>`_. Any application which uses a CQL driver will work with ScyllaDB.
|
||||
|
||||
The list below contains integration projects using ScyllaDB with Elasticsearch. If you have tested your application with ScyllaDB and want to publish the results, contact us using the `community forum <https://forum.scylladb.com>`_.
|
||||
|
||||
|
||||
@@ -13,11 +13,11 @@ The Jaeger Query service offers a web-based UI and API for users to explore, vis
|
||||
Jaeger also supports integration with other observability tools like Prometheus and Grafana,
|
||||
making it a popular choice for monitoring modern distributed applications.
|
||||
|
||||
Jaeger Server `can also be run <https://github.com/jaegertracing/jaeger/tree/main/plugin/storage/scylladb>`_ with ScyllaDB as the storage backend, thanks to ScyllaDB's compatibility with Cassandra.
|
||||
Jaeger Server `can also be run <https://www.jaegertracing.io/docs/2.11/storage/cassandra/#compatible-backends>`_ with ScyllaDB as the storage backend, thanks to ScyllaDB's compatibility with Cassandra.
|
||||
As a drop-in replacement for Cassandra, ScyllaDB implements the same protocol and provides a high-performance,
|
||||
low-latency alternative. This compatibility allows Jaeger users to easily switch to ScyllaDB without making significant changes to their setup.
|
||||
|
||||
Using ScyllaDB as the storage backend for Jaeger Server can offer additional benefits,
|
||||
such as improved performance, scalability, and resource efficiency.
|
||||
This makes Jaeger even more effective for monitoring and troubleshooting distributed applications,
|
||||
especially in high-traffic, demanding environments where a high-performance storage solution is critical.
|
||||
especially in high-traffic, demanding environments where a high-performance storage solution is critical.
|
||||
|
||||
@@ -3,7 +3,7 @@ Integrate ScyllaDB with Spark
|
||||
=============================
|
||||
|
||||
|
||||
ScyllaDB is Apache Cassandra compatible at the CQL binary protocol level, and any driver which uses CQL will work with ScyllaDB (more :doc:`here </using-scylla/drivers/index>`). Any application which uses a CQL driver will work with ScyllaDB.
|
||||
ScyllaDB is Apache Cassandra compatible at the CQL binary protocol level, and any driver which uses CQL will work with ScyllaDB. See `ScyllaDB Drivers <https://docs.scylladb.com/stable/drivers/index.html>`_. Any application which uses a CQL driver will work with ScyllaDB.
|
||||
|
||||
The list below contains integration projects using ScyllaDB with Spark. If you have tested your application with ScyllaDB and want to publish the results, contact us using the `community forum <https://forum.scylladb.com>`_.
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "db/config.hh"
|
||||
#include "utils/log.hh"
|
||||
#include "utils/hash.hh"
|
||||
#include "utils/http.hh"
|
||||
#include "utils/rjson.hh"
|
||||
#include "utils/base64.hh"
|
||||
#include "utils/loading_cache.hh"
|
||||
@@ -267,7 +268,6 @@ std::tuple<std::string, std::string> azure_host::impl::parse_key(std::string_vie
|
||||
|
||||
std::tuple<std::string, std::string, unsigned> azure_host::impl::parse_vault(std::string_view vault) {
|
||||
static const boost::regex vault_name_re(R"([a-zA-Z0-9-]+)");
|
||||
static const boost::regex vault_endpoint_re(R"((https?)://([^/:]+)(?::(\d+))?)");
|
||||
|
||||
boost::smatch match;
|
||||
std::string tmp{vault};
|
||||
@@ -277,16 +277,12 @@ std::tuple<std::string, std::string, unsigned> azure_host::impl::parse_vault(std
|
||||
return {"https", fmt::format(AKV_HOST_TEMPLATE, vault), 443};
|
||||
}
|
||||
|
||||
if (boost::regex_match(tmp, match, vault_endpoint_re)) {
|
||||
std::string scheme = match[1];
|
||||
std::string host = match[2];
|
||||
std::string port_str = match[3];
|
||||
|
||||
unsigned port = (port_str.empty()) ? (scheme == "https" ? 443 : 80) : std::stoi(port_str);
|
||||
return {scheme, host, port};
|
||||
try {
|
||||
auto info = utils::http::parse_simple_url(tmp);
|
||||
return {info.scheme, info.host, info.port};
|
||||
} catch (...) {
|
||||
std::throw_with_nested(std::invalid_argument(fmt::format("Invalid vault '{}'. Must be either a name or an endpoint in format: http(s)://<host>[:port]", vault)));
|
||||
}
|
||||
|
||||
throw std::invalid_argument(fmt::format("Invalid vault '{}'. Must be either a name or an endpoint in format: http(s)://<host>[:port]", vault));
|
||||
}
|
||||
|
||||
future<shared_ptr<tls::certificate_credentials>> azure_host::impl::make_creds() {
|
||||
|
||||
@@ -816,6 +816,7 @@ public:
|
||||
future<data_sink> wrap_sink(const sstables::sstable& sst, sstables::component_type type, data_sink sink) override {
|
||||
switch (type) {
|
||||
case sstables::component_type::Scylla:
|
||||
case sstables::component_type::TemporaryScylla:
|
||||
case sstables::component_type::TemporaryTOC:
|
||||
case sstables::component_type::TOC:
|
||||
co_return sink;
|
||||
@@ -844,6 +845,7 @@ public:
|
||||
sstables::component_type type,
|
||||
data_source src) override {
|
||||
switch (type) {
|
||||
case sstables::component_type::TemporaryScylla:
|
||||
case sstables::component_type::Scylla:
|
||||
case sstables::component_type::TemporaryTOC:
|
||||
case sstables::component_type::TOC:
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
#include "encryption_exceptions.hh"
|
||||
#include "symmetric_key.hh"
|
||||
#include "utils.hh"
|
||||
#include "utils/exponential_backoff_retry.hh"
|
||||
#include "utils/hash.hh"
|
||||
#include "utils/loading_cache.hh"
|
||||
#include "utils/UUID.hh"
|
||||
@@ -163,6 +164,8 @@ private:
|
||||
shared_ptr<seastar::tls::certificate_credentials> _creds;
|
||||
std::unordered_map<bytes, shared_ptr<symmetric_key>> _cache;
|
||||
bool _initialized = false;
|
||||
|
||||
abort_source _as;
|
||||
};
|
||||
|
||||
template<typename T, typename C>
|
||||
@@ -251,24 +254,50 @@ future<rjson::value> encryption::gcp_host::impl::gcp_auth_post_with_retry(std::s
|
||||
|
||||
auto& creds = i->second;
|
||||
|
||||
int retries = 0;
|
||||
static constexpr auto max_retries = 10;
|
||||
|
||||
for (;;) {
|
||||
try {
|
||||
co_await creds.refresh(KMS_SCOPE, _certs);
|
||||
} catch (...) {
|
||||
std::throw_with_nested(permission_error("Error refreshing credentials"));
|
||||
exponential_backoff_retry exr(10ms, 10000ms);
|
||||
bool do_backoff = false;
|
||||
bool did_auth_retry = false;
|
||||
|
||||
for (int retry = 0; ; ++retry) {
|
||||
if (std::exchange(do_backoff, false)) {
|
||||
co_await exr.retry(_as);
|
||||
}
|
||||
|
||||
bool refreshing = true;
|
||||
|
||||
try {
|
||||
co_await creds.refresh(KMS_SCOPE, _certs);
|
||||
refreshing = false;
|
||||
|
||||
auto res = co_await send_request(uri, _certs, body, httpd::operation_type::POST, key_values({
|
||||
{ utils::gcp::AUTHORIZATION, utils::gcp::format_bearer(creds.token) },
|
||||
}));
|
||||
}), &_as);
|
||||
co_return res;
|
||||
} catch (httpd::unexpected_status_error& e) {
|
||||
gcp_log.debug("{}: Got unexpected response: {}", uri, e.status());
|
||||
if (e.status() == http::reply::status_type::unauthorized && retries++ < 3) {
|
||||
// refresh access token and retry.
|
||||
switch (e.status()) {
|
||||
default:
|
||||
if (http::reply::classify_status(e.status()) != http::reply::status_class::server_error) {
|
||||
break;
|
||||
}
|
||||
[[fallthrough]];
|
||||
case httpclient::reply_status::request_timeout:
|
||||
if (retry < max_retries) {
|
||||
// service unavailable etc -> backoff + retry
|
||||
do_backoff = true;
|
||||
did_auth_retry = false; // reset this, since we might cause expiration due to backoff (not really, but...)
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (refreshing) {
|
||||
std::throw_with_nested(permission_error("Error refreshing credentials"));
|
||||
}
|
||||
if (e.status() == http::reply::status_type::unauthorized && retry < max_retries && !did_auth_retry) {
|
||||
// refresh access token and retry. no backoff
|
||||
did_auth_retry = true;
|
||||
continue;
|
||||
}
|
||||
if (e.status() == http::reply::status_type::unauthorized) {
|
||||
@@ -322,6 +351,7 @@ future<> encryption::gcp_host::impl::init() {
|
||||
}
|
||||
|
||||
future<> encryption::gcp_host::impl::stop() {
|
||||
_as.request_abort();
|
||||
co_await _attr_cache.stop();
|
||||
co_await _id_cache.stop();
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
#include "utils/loading_cache.hh"
|
||||
#include "utils/UUID.hh"
|
||||
#include "utils/UUID_gen.hh"
|
||||
#include "utils/http.hh"
|
||||
#include "marshal_exception.hh"
|
||||
#include "db/config.hh"
|
||||
|
||||
@@ -322,17 +323,26 @@ future<> kmip_host::impl::connection::connect() {
|
||||
f = f.then([this, cred] {
|
||||
return cred->set_x509_trust_file(_options.truststore, seastar::tls::x509_crt_format::PEM);
|
||||
});
|
||||
} else {
|
||||
f = f.then([cred] {
|
||||
return cred->set_system_trust();
|
||||
});
|
||||
}
|
||||
return f.then([this, cred] {
|
||||
// TODO, find if we should do hostname verification
|
||||
// TODO: connect all failovers already?
|
||||
|
||||
auto i = _host.find_last_of(':');
|
||||
auto name = _host.substr(0, i);
|
||||
auto port = i != sstring::npos ? std::stoul(_host.substr(i + 1)) : kmip_port;
|
||||
// Use the URL parser to handle ipv6 etc proper.
|
||||
// Turn host arg into a URL.
|
||||
auto info = utils::http::parse_simple_url("kmip://" + _host);
|
||||
auto name = info.host;
|
||||
auto port = info.port != 80 ? info.port : kmip_port;
|
||||
|
||||
return seastar::net::dns::resolve_name(name).then([this, cred, port](seastar::net::inet_address addr) {
|
||||
return seastar::tls::connect(cred, seastar::ipv4_addr{addr, uint16_t(port)}).then([this](seastar::connected_socket s) {
|
||||
return seastar::net::dns::resolve_name(name).then([this, cred, port, name](seastar::net::inet_address addr) {
|
||||
kmip_log.debug("Try connect {}:{}", addr, port);
|
||||
// TODO: should we verify non-numeric hosts here? (opts.server_name)
|
||||
// Adding this might break existing users with half-baked certs.
|
||||
return seastar::tls::connect(cred, seastar::socket_address{addr, uint16_t(port)}).then([this](seastar::connected_socket s) {
|
||||
kmip_log.debug("Successfully connected {}", _host);
|
||||
// #998 Set keepalive to try avoiding connection going stale in between commands.
|
||||
s.set_keepalive_parameters(net::tcp_keepalive_params{60s, 60s, 10});
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#include "utils/exponential_backoff_retry.hh"
|
||||
#include "utils/hash.hh"
|
||||
#include "utils/loading_cache.hh"
|
||||
#include "utils/http.hh"
|
||||
#include "utils/UUID.hh"
|
||||
#include "utils/UUID_gen.hh"
|
||||
#include "utils/rjson.hh"
|
||||
@@ -151,15 +152,10 @@ public:
|
||||
{
|
||||
// check if we have an explicit endpoint set.
|
||||
if (!_options.endpoint.empty()) {
|
||||
static std::regex simple_url(R"foo((https?):\/\/(?:([\w\.]+)|\[([\w:]+)\]):?(\d+)?\/?)foo");
|
||||
std::transform(_options.endpoint.begin(), _options.endpoint.end(), _options.endpoint.begin(), ::tolower);
|
||||
std::smatch m;
|
||||
if (!std::regex_match(_options.endpoint, m, simple_url)) {
|
||||
throw std::invalid_argument(fmt::format("Could not parse URL: {}", _options.endpoint));
|
||||
}
|
||||
_options.https = m[1].str() == "https";
|
||||
_options.host = m[2].length() > 0 ? m[2].str() : m[3].str();
|
||||
_options.port = m[4].length() > 0 ? std::stoi(m[4].str()) : 0;
|
||||
auto info = utils::http::parse_simple_url(_options.endpoint);
|
||||
_options.https = info.is_https();
|
||||
_options.host = info.host;
|
||||
_options.port = info.port;
|
||||
}
|
||||
if (_options.endpoint.empty() && _options.host.empty() && _options.aws_region.empty() && !_options.aws_use_ec2_region) {
|
||||
throw std::invalid_argument("No AWS region or endpoint specified");
|
||||
|
||||
@@ -55,6 +55,7 @@ debian_base_packages=(
|
||||
librapidxml-dev
|
||||
libcrypto++-dev
|
||||
libxxhash-dev
|
||||
zlib1g-dev
|
||||
slapd
|
||||
ldap-utils
|
||||
libcpp-jwt-dev
|
||||
@@ -117,6 +118,7 @@ fedora_packages=(
|
||||
makeself
|
||||
libzstd-static libzstd-devel
|
||||
lz4-static lz4-devel
|
||||
zlib-ng-compat-devel
|
||||
rpm-build
|
||||
devscripts
|
||||
debhelper
|
||||
|
||||
13
install.sh
13
install.sh
@@ -157,6 +157,7 @@ adjust_bin() {
|
||||
export GNUTLS_SYSTEM_PRIORITY_FILE="\${GNUTLS_SYSTEM_PRIORITY_FILE-$prefix/libreloc/gnutls.config}"
|
||||
export LD_LIBRARY_PATH="$prefix/libreloc"
|
||||
export UBSAN_OPTIONS="${UBSAN_OPTIONS:+$UBSAN_OPTIONS:}suppressions=$prefix/libexec/ubsan-suppressions.supp"
|
||||
${p11_trust_paths:+export SCYLLA_P11_TRUST_PATHS="$p11_trust_paths"}
|
||||
exec -a "\$0" "$prefix/libexec/$bin" "\$@"
|
||||
EOF
|
||||
chmod 755 "$root/$prefix/bin/$bin"
|
||||
@@ -330,7 +331,6 @@ if ! $nonroot; then
|
||||
rsysconfdir=$(realpath -m "$root/$sysconfdir")
|
||||
rusr=$(realpath -m "$root/usr")
|
||||
rsystemd=$(realpath -m "$rusr/lib/systemd/system")
|
||||
rshare="$rprefix/share"
|
||||
rdoc="$rprefix/share/doc"
|
||||
rdata=$(realpath -m "$root/var/lib/scylla")
|
||||
rhkdata=$(realpath -m "$root/var/lib/scylla-housekeeping")
|
||||
@@ -338,7 +338,6 @@ else
|
||||
retc="$rprefix/etc"
|
||||
rsysconfdir="$rprefix/$sysconfdir"
|
||||
rsystemd="$HOME/.config/systemd/user"
|
||||
rshare="$rprefix/share"
|
||||
rdoc="$rprefix/share/doc"
|
||||
rdata="$rprefix"
|
||||
fi
|
||||
@@ -522,16 +521,6 @@ PRODUCT="$product"
|
||||
EOS
|
||||
chmod 644 "$rprefix"/scripts/scylla_product.py
|
||||
|
||||
install -d -m755 "$rshare"/p11-kit/modules
|
||||
cat << EOS > "$rshare"/p11-kit/modules/p11-kit-trust.module
|
||||
module: $prefix/libreloc/pkcs11/p11-kit-trust.so
|
||||
priority: 1
|
||||
trust-policy: yes
|
||||
x-trust-lookup: pkcs11:library-description=PKCS%2311%20Kit%20Trust%20Module
|
||||
disable-in: p11-kit-proxy
|
||||
x-init-reserved: paths=$p11_trust_paths
|
||||
EOS
|
||||
|
||||
if ! $nonroot && ! $without_systemd; then
|
||||
install -d -m755 "$retc"/systemd/system/scylla-server.service.d
|
||||
install -m644 dist/common/systemd/scylla-server.service.d/dependencies.conf -Dt "$retc"/systemd/system/scylla-server.service.d
|
||||
|
||||
56
main.cc
56
main.cc
@@ -10,6 +10,8 @@
|
||||
#include <functional>
|
||||
#include <fmt/ranges.h>
|
||||
|
||||
#include <gnutls/pkcs11.h>
|
||||
|
||||
#include <seastar/util/closeable.hh>
|
||||
#include <seastar/core/abort_source.hh>
|
||||
#include "db/view/view_building_worker.hh"
|
||||
@@ -118,15 +120,11 @@
|
||||
#include "message/dictionary_service.hh"
|
||||
#include "sstable_dict_autotrainer.hh"
|
||||
#include "utils/disk_space_monitor.hh"
|
||||
#include "auth/cache.hh"
|
||||
#include "utils/labels.hh"
|
||||
#include "tools/utils.hh"
|
||||
|
||||
|
||||
#define P11_KIT_FUTURE_UNSTABLE_API
|
||||
extern "C" {
|
||||
#include <p11-kit/p11-kit.h>
|
||||
}
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
#include <seastar/core/metrics_api.hh>
|
||||
#include <seastar/core/relabel_config.hh>
|
||||
@@ -708,14 +706,6 @@ To start the scylla server proper, simply invoke as: scylla server (or just scyl
|
||||
print_starting_message(ac, av, parsed_opts);
|
||||
}
|
||||
|
||||
// We have to override p11-kit config path before p11-kit initialization.
|
||||
// And the initialization will invoke on seastar initialization, so it has to
|
||||
// be before app.run()
|
||||
auto scylla_path = fs::read_symlink(fs::path("/proc/self/exe"));
|
||||
auto p11_modules = scylla_path.parent_path().parent_path().append("share/p11-kit/modules");
|
||||
auto p11_modules_str = p11_modules.string<char>();
|
||||
::p11_kit_override_system_files(NULL, NULL, p11_modules_str.c_str(), NULL, NULL);
|
||||
|
||||
sharded<locator::shared_token_metadata> token_metadata;
|
||||
sharded<locator::effective_replication_map_factory> erm_factory;
|
||||
sharded<service::migration_notifier> mm_notifier;
|
||||
@@ -727,6 +717,7 @@ To start the scylla server proper, simply invoke as: scylla server (or just scyl
|
||||
seastar::sharded<service::cache_hitrate_calculator> cf_cache_hitrate_calculator;
|
||||
service::load_meter load_meter;
|
||||
sharded<service::storage_proxy> proxy;
|
||||
sharded<auth::cache> auth_cache;
|
||||
sharded<service::storage_service> ss;
|
||||
sharded<service::migration_manager> mm;
|
||||
sharded<tasks::task_manager> task_manager;
|
||||
@@ -789,7 +780,7 @@ To start the scylla server proper, simply invoke as: scylla server (or just scyl
|
||||
return seastar::async([&app, cfg, ext, &disk_space_monitor_shard0, &cm, &sstm, &db, &qp, &bm, &proxy, &mapreduce_service, &mm, &mm_notifier, &ctx, &opts, &dirs,
|
||||
&prometheus_server, &cf_cache_hitrate_calculator, &load_meter, &feature_service, &gossiper, &snitch,
|
||||
&token_metadata, &erm_factory, &snapshot_ctl, &messaging, &sst_dir_semaphore, &raft_gr, &service_memory_limiter,
|
||||
&repair, &sst_loader, &ss, &lifecycle_notifier, &stream_manager, &task_manager, &rpc_dict_training_worker,
|
||||
&repair, &sst_loader, &auth_cache, &ss, &lifecycle_notifier, &stream_manager, &task_manager, &rpc_dict_training_worker,
|
||||
&hashing_worker, &vector_store_client] {
|
||||
try {
|
||||
if (opts.contains("relabel-config-file") && !opts["relabel-config-file"].as<sstring>().empty()) {
|
||||
@@ -1802,6 +1793,12 @@ To start the scylla server proper, simply invoke as: scylla server (or just scyl
|
||||
api::unset_server_stream_manager(ctx).get();
|
||||
});
|
||||
|
||||
checkpoint(stop_signal, "starting auth cache");
|
||||
auth_cache.start(std::ref(qp)).get();
|
||||
auto stop_auth_cache = defer_verbose_shutdown("auth cache", [&] {
|
||||
auth_cache.stop().get();
|
||||
});
|
||||
|
||||
checkpoint(stop_signal, "initializing storage service");
|
||||
debug::the_storage_service = &ss;
|
||||
ss.start(std::ref(stop_signal.as_sharded_abort_source()),
|
||||
@@ -1810,6 +1807,7 @@ To start the scylla server proper, simply invoke as: scylla server (or just scyl
|
||||
std::ref(messaging), std::ref(repair),
|
||||
std::ref(stream_manager), std::ref(lifecycle_notifier), std::ref(bm), std::ref(snitch),
|
||||
std::ref(tablet_allocator), std::ref(cdc_generation_service), std::ref(view_builder), std::ref(view_building_worker), std::ref(qp), std::ref(sl_controller),
|
||||
std::ref(auth_cache),
|
||||
std::ref(tsm), std::ref(vbsm), std::ref(task_manager), std::ref(gossip_address_map),
|
||||
compression_dict_updated_callback,
|
||||
only_on_shard0(&*disk_space_monitor_shard0)
|
||||
@@ -1825,11 +1823,6 @@ To start the scylla server proper, simply invoke as: scylla server (or just scyl
|
||||
ss.stop().get();
|
||||
});
|
||||
|
||||
api::set_server_storage_service(ctx, ss, group0_client).get();
|
||||
auto stop_ss_api = defer_verbose_shutdown("storage service API", [&ctx] {
|
||||
api::unset_server_storage_service(ctx).get();
|
||||
});
|
||||
|
||||
checkpoint(stop_signal, "initializing query processor remote part");
|
||||
// TODO: do this together with proxy.start_remote(...)
|
||||
qp.invoke_on_all(&cql3::query_processor::start_remote, std::ref(mm), std::ref(mapreduce_service),
|
||||
@@ -2070,7 +2063,7 @@ To start the scylla server proper, simply invoke as: scylla server (or just scyl
|
||||
maintenance_auth_config.authenticator_java_name = sstring{auth::allow_all_authenticator_name};
|
||||
maintenance_auth_config.role_manager_java_name = sstring{auth::maintenance_socket_role_manager_name};
|
||||
|
||||
maintenance_auth_service.start(perm_cache_config, std::ref(qp), std::ref(group0_client), std::ref(mm_notifier), std::ref(mm), maintenance_auth_config, maintenance_socket_enabled::yes, std::ref(hashing_worker)).get();
|
||||
maintenance_auth_service.start(perm_cache_config, std::ref(qp), std::ref(group0_client), std::ref(mm_notifier), std::ref(mm), maintenance_auth_config, maintenance_socket_enabled::yes, std::ref(auth_cache), std::ref(hashing_worker)).get();
|
||||
|
||||
cql_maintenance_server_ctl.emplace(maintenance_auth_service, mm_notifier, gossiper, qp, service_memory_limiter, sl_controller, lifecycle_notifier, *cfg, maintenance_cql_sg_stats_key, maintenance_socket_enabled::yes, dbcfg.statement_scheduling_group);
|
||||
|
||||
@@ -2184,6 +2177,11 @@ To start the scylla server proper, simply invoke as: scylla server (or just scyl
|
||||
// This will also disable migration manager schema pulls if needed.
|
||||
group0_service.setup_group0_if_exist(sys_ks.local(), ss.local(), qp.local(), mm.local()).get();
|
||||
|
||||
api::set_server_storage_service(ctx, ss, group0_client).get();
|
||||
auto stop_ss_api = defer_verbose_shutdown("storage service API", [&ctx] {
|
||||
api::unset_server_storage_service(ctx).get();
|
||||
});
|
||||
|
||||
with_scheduling_group(maintenance_scheduling_group, [&] {
|
||||
return messaging.invoke_on_all([&] (auto& ms) {
|
||||
return ms.start_listen(token_metadata.local(), [&gossiper] (gms::inet_address ip) {
|
||||
@@ -2341,7 +2339,7 @@ To start the scylla server proper, simply invoke as: scylla server (or just scyl
|
||||
auth_config.authenticator_java_name = qualified_authenticator_name;
|
||||
auth_config.role_manager_java_name = qualified_role_manager_name;
|
||||
|
||||
auth_service.start(std::move(perm_cache_config), std::ref(qp), std::ref(group0_client), std::ref(mm_notifier), std::ref(mm), auth_config, maintenance_socket_enabled::no, std::ref(hashing_worker)).get();
|
||||
auth_service.start(std::move(perm_cache_config), std::ref(qp), std::ref(group0_client), std::ref(mm_notifier), std::ref(mm), auth_config, maintenance_socket_enabled::no, std::ref(auth_cache), std::ref(hashing_worker)).get();
|
||||
|
||||
std::any stop_auth_service;
|
||||
// Has to be called after node joined the cluster (join_cluster())
|
||||
@@ -2687,13 +2685,15 @@ int main(int ac, char** av) {
|
||||
// #3583 - need to potentially ensure this for tools as well, since at least
|
||||
// sstable* might need crypto libraries.
|
||||
auto scylla_path = fs::read_symlink(fs::path("/proc/self/exe")); // could just be argv[0] I guess...
|
||||
auto p11_modules = scylla_path.parent_path().parent_path().append("share/p11-kit/modules");
|
||||
// Note: must be in scope for application lifetime. p11_kit_override_system_files does _not_
|
||||
// copy input strings.
|
||||
auto p11_modules_str = p11_modules.string<char>();
|
||||
// #3392 only do this if we are actually packaged and the path exists.
|
||||
if (fs::exists(p11_modules)) {
|
||||
::p11_kit_override_system_files(NULL, NULL, p11_modules_str.c_str(), NULL, NULL);
|
||||
auto p11_trust_paths_from_env = std::getenv("SCYLLA_P11_TRUST_PATHS");
|
||||
auto trust_module_path = scylla_path.parent_path().parent_path().append("libreloc/pkcs11/p11-kit-trust.so");
|
||||
if (fs::exists(trust_module_path) && p11_trust_paths_from_env) {
|
||||
gnutls_pkcs11_init(GNUTLS_PKCS11_FLAG_MANUAL, nullptr);
|
||||
auto trust_config = fmt::format("p11-kit:paths={} trusted=yes", p11_trust_paths_from_env);
|
||||
auto ret = gnutls_pkcs11_add_provider(trust_module_path.string().c_str(), trust_config.c_str());
|
||||
if (ret != GNUTLS_E_SUCCESS) {
|
||||
startlog.warn("Could not initialize p11-kit trust module: {}\n", gnutls_strerror(ret));
|
||||
}
|
||||
}
|
||||
|
||||
return main_func(ac, av);
|
||||
|
||||
@@ -2329,11 +2329,7 @@ future<gc_clock::time_point> repair_service::repair_tablet(gms::gossip_address_m
|
||||
auto duration = std::chrono::duration<float>(std::chrono::steady_clock::now()- start);
|
||||
rlogger.info("repair[{}]: Finished tablet repair for table={}.{} range={} duration={} replicas={} global_tablet_id={} flush_time={}",
|
||||
id.uuid(), keyspace_name, table_name, range, duration, replicas, gid, flush_time);
|
||||
if (!flush_time.has_value()) {
|
||||
throw std::runtime_error(format("Batchlog reply failed for table={}.{} range={} replicas={} global_tablet_id={}",
|
||||
id.uuid(), keyspace_name, table_name, range, replicas, gid));
|
||||
}
|
||||
co_return flush_time.value();
|
||||
co_return flush_time;
|
||||
}
|
||||
|
||||
tasks::is_user_task repair::tablet_repair_task_impl::is_user_task() const noexcept {
|
||||
@@ -2410,9 +2406,11 @@ future<> repair::tablet_repair_task_impl::run() {
|
||||
});
|
||||
|
||||
auto parent_shard = this_shard_id();
|
||||
std::vector<std::optional<gc_clock::time_point>> flush_times(smp::count, gc_clock::time_point{});
|
||||
rs.container().invoke_on_all([&idx, &flush_times, id, metas = _metas, parent_data, reason = _reason, tables = _tables, sched_info = sched_info, ranges_parallelism = _ranges_parallelism, parent_shard, topo_guard = _topo_guard, skip_flush = _skip_flush] (repair_service& rs) -> future<> {
|
||||
auto flush_time = _flush_time;
|
||||
auto res = rs.container().map_reduce0([&idx, id, metas = _metas, parent_data, reason = _reason, tables = _tables, sched_info = sched_info, ranges_parallelism = _ranges_parallelism, parent_shard, topo_guard = _topo_guard, skip_flush = _skip_flush] (repair_service& rs) -> future<std::pair<gc_clock::time_point, bool>> {
|
||||
std::exception_ptr error;
|
||||
gc_clock::time_point shard_flush_time;
|
||||
bool flush_failed = false;
|
||||
for (auto& m : metas) {
|
||||
if (m.master_shard_id != this_shard_id()) {
|
||||
continue;
|
||||
@@ -2466,27 +2464,24 @@ future<> repair::tablet_repair_task_impl::run() {
|
||||
error = std::move(ep);
|
||||
}
|
||||
}
|
||||
auto current = flush_times[this_shard_id()];
|
||||
if ((needs_flush_before_repair &&!hints_batchlog_flushed) || !current.has_value()) {
|
||||
flush_times[this_shard_id()] = std::nullopt;
|
||||
} else {
|
||||
auto time = task->get_flush_time();
|
||||
flush_times[this_shard_id()] = current == gc_clock::time_point() ? time : std::min(current.value(), time);
|
||||
}
|
||||
auto time = task->get_flush_time();
|
||||
shard_flush_time = shard_flush_time == gc_clock::time_point() ? time : std::min(shard_flush_time, time);
|
||||
flush_failed = flush_failed || (needs_flush_before_repair && !hints_batchlog_flushed);
|
||||
}
|
||||
if (error) {
|
||||
co_await coroutine::return_exception_ptr(std::move(error));
|
||||
}
|
||||
co_return std::make_pair(shard_flush_time, flush_failed);
|
||||
}, std::make_pair<gc_clock::time_point, bool>(std::move(flush_time), false), [] (const auto& p1, const auto& p2) {
|
||||
auto& [time1, failed1] = p1;
|
||||
auto& [time2, failed2] = p2;
|
||||
auto flush_time = time1 == gc_clock::time_point() ? time2 :
|
||||
(time2 == gc_clock::time_point() ? time1 : std::min(time1, time2));
|
||||
auto failed = failed1 || failed2;
|
||||
return std::make_pair(flush_time, failed);
|
||||
}).get();
|
||||
for (auto& time : flush_times) {
|
||||
if (!time.has_value()) {
|
||||
_flush_time = std::nullopt;
|
||||
break;
|
||||
}
|
||||
if (time != gc_clock::time_point()) {
|
||||
_flush_time = _flush_time == gc_clock::time_point() ? time : std::min(_flush_time.value(), time.value());
|
||||
}
|
||||
}
|
||||
_flush_time = res.first;
|
||||
_should_flush_and_flush_failed = res.second;
|
||||
auto duration = std::chrono::duration<float>(std::chrono::steady_clock::now() - start_time);
|
||||
rlogger.info("repair[{}]: Finished user-requested repair for tablet keyspace={} tables={} repair_id={} tablets_repaired={} duration={}",
|
||||
id.uuid(), _keyspace, _tables, id.id, _metas.size(), duration);
|
||||
|
||||
@@ -2529,7 +2529,7 @@ future<repair_update_system_table_response> repair_service::repair_update_system
|
||||
}
|
||||
}
|
||||
if (req.range.end()) {
|
||||
if (!req.range.end()->is_inclusive()) {
|
||||
if (!req.range.end()->is_inclusive() && req.range.end()->value() != dht::maximum_token()) {
|
||||
is_valid_range = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,7 +112,8 @@ private:
|
||||
optimized_optional<abort_source::subscription> _abort_subscription;
|
||||
std::optional<int> _ranges_parallelism;
|
||||
size_t _metas_size = 0;
|
||||
std::optional<gc_clock::time_point> _flush_time = gc_clock::time_point();
|
||||
gc_clock::time_point _flush_time = gc_clock::time_point();
|
||||
bool _should_flush_and_flush_failed = false;
|
||||
service::frozen_topology_guard _topo_guard;
|
||||
bool _skip_flush;
|
||||
public:
|
||||
@@ -134,7 +135,12 @@ public:
|
||||
return tasks::is_abortable(!_abort_subscription);
|
||||
}
|
||||
|
||||
std::optional<gc_clock::time_point> get_flush_time() const { return _flush_time; }
|
||||
gc_clock::time_point get_flush_time() const {
|
||||
if (_should_flush_and_flush_failed) {
|
||||
throw std::runtime_error(fmt::format("Flush is needed for repair {} with parent {}, but failed", id(), _parent_id));
|
||||
}
|
||||
return _flush_time;
|
||||
}
|
||||
|
||||
tasks::is_user_task is_user_task() const noexcept override;
|
||||
virtual future<> release_resources() noexcept override;
|
||||
|
||||
@@ -3704,7 +3704,7 @@ future<utils::chunked_vector<temporary_buffer<char>>> database::sample_data_file
|
||||
}), std::ref(state));
|
||||
|
||||
// [1, 2, 3, 0] --> [0, 1, 3, 6]
|
||||
std::exclusive_scan(global_offset.begin(), global_offset.end(), global_offset.begin(), 0, std::plus());
|
||||
std::exclusive_scan(global_offset.begin(), global_offset.end(), global_offset.begin(), uint64_t(0), std::plus());
|
||||
|
||||
// We can't generate random non-negative integers smaller than 0,
|
||||
// so let's just deal with the `total_chunks == 0` case with an early return.
|
||||
|
||||
@@ -134,14 +134,11 @@ bool is_compatible(column_kind k1, column_kind k2);
|
||||
|
||||
enum class cf_type : uint8_t {
|
||||
standard,
|
||||
super,
|
||||
};
|
||||
|
||||
inline sstring cf_type_to_sstring(cf_type t) {
|
||||
if (t == cf_type::standard) {
|
||||
return "Standard";
|
||||
} else if (t == cf_type::super) {
|
||||
return "Super";
|
||||
}
|
||||
throw std::invalid_argument(format("unknown type: {:d}\n", uint8_t(t)));
|
||||
}
|
||||
@@ -149,8 +146,6 @@ inline sstring cf_type_to_sstring(cf_type t) {
|
||||
inline cf_type sstring_to_cf_type(sstring name) {
|
||||
if (name == "Standard") {
|
||||
return cf_type::standard;
|
||||
} else if (name == "Super") {
|
||||
return cf_type::super;
|
||||
}
|
||||
throw std::invalid_argument(format("unknown type: {}\n", name));
|
||||
}
|
||||
@@ -688,13 +683,13 @@ public:
|
||||
}
|
||||
|
||||
bool is_cql3_table() const {
|
||||
return !is_super() && !is_dense() && is_compound();
|
||||
return !is_dense() && is_compound();
|
||||
}
|
||||
bool is_compact_table() const {
|
||||
return !is_cql3_table();
|
||||
}
|
||||
bool is_static_compact_table() const {
|
||||
return !is_super() && !is_dense() && !is_compound();
|
||||
return !is_dense() && !is_compound();
|
||||
}
|
||||
|
||||
const table_id& id() const {
|
||||
@@ -711,10 +706,6 @@ public:
|
||||
return _raw._type;
|
||||
}
|
||||
|
||||
bool is_super() const {
|
||||
return _raw._type == cf_type::super;
|
||||
}
|
||||
|
||||
gc_clock::duration gc_grace_seconds() const {
|
||||
auto seconds = std::chrono::seconds(_raw._props.gc_grace_seconds);
|
||||
return std::chrono::duration_cast<gc_clock::duration>(seconds);
|
||||
|
||||
@@ -301,6 +301,7 @@ protected:
|
||||
class ghost_row_deleting_query_pager : public service::pager::query_pager {
|
||||
service::storage_proxy& _proxy;
|
||||
db::timeout_clock::duration _timeout_duration;
|
||||
size_t _concurrency;
|
||||
public:
|
||||
ghost_row_deleting_query_pager(schema_ptr s, shared_ptr<const cql3::selection::selection> selection,
|
||||
service::query_state& state,
|
||||
@@ -309,10 +310,12 @@ public:
|
||||
dht::partition_range_vector ranges,
|
||||
cql3::cql_stats& stats,
|
||||
service::storage_proxy& proxy,
|
||||
db::timeout_clock::duration timeout_duration)
|
||||
db::timeout_clock::duration timeout_duration,
|
||||
size_t concurrency)
|
||||
: query_pager(proxy, s, selection, state, options, std::move(cmd), std::move(ranges), std::nullopt)
|
||||
, _proxy(proxy)
|
||||
, _timeout_duration(timeout_duration)
|
||||
, _concurrency(concurrency)
|
||||
{}
|
||||
virtual ~ghost_row_deleting_query_pager() {}
|
||||
|
||||
@@ -322,8 +325,12 @@ public:
|
||||
_query_read_repair_decision = qr.read_repair_decision;
|
||||
qr.query_result->ensure_counts();
|
||||
return seastar::async([this, query_result = std::move(qr.query_result), page_size, now] () mutable -> result<> {
|
||||
handle_result(db::view::delete_ghost_rows_visitor{_proxy, _state, view_ptr(_query_schema), _timeout_duration},
|
||||
std::exception_ptr ex;
|
||||
handle_result(db::view::delete_ghost_rows_visitor{_proxy, _state, view_ptr(_query_schema), _timeout_duration, _concurrency, ex},
|
||||
std::move(query_result), page_size, now);
|
||||
if (ex) {
|
||||
std::rethrow_exception(ex);
|
||||
}
|
||||
return bo::success();
|
||||
});
|
||||
}));
|
||||
@@ -503,7 +510,8 @@ std::unique_ptr<service::pager::query_pager> service::pager::query_pagers::pager
|
||||
dht::partition_range_vector ranges,
|
||||
cql3::cql_stats& stats,
|
||||
storage_proxy& proxy,
|
||||
db::timeout_clock::duration duration) {
|
||||
db::timeout_clock::duration duration,
|
||||
size_t concurrency) {
|
||||
return ::make_shared<ghost_row_deleting_query_pager>(std::move(s), std::move(selection), state,
|
||||
options, std::move(cmd), std::move(ranges), stats, proxy, duration);
|
||||
options, std::move(cmd), std::move(ranges), stats, proxy, duration, concurrency);
|
||||
}
|
||||
|
||||
@@ -47,7 +47,8 @@ public:
|
||||
dht::partition_range_vector,
|
||||
cql3::cql_stats& stats,
|
||||
storage_proxy& proxy,
|
||||
db::timeout_clock::duration timeout_duration);
|
||||
db::timeout_clock::duration timeout_duration,
|
||||
size_t concurrency);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
* SPDX-License-Identifier: LicenseRef-ScyllaDB-Source-Available-1.0
|
||||
*/
|
||||
#include "service/raft/group0_state_machine.hh"
|
||||
#include "auth/cache.hh"
|
||||
#include "db/schema_tables.hh"
|
||||
#include "mutation/atomic_cell.hh"
|
||||
#include "cql3/selection/selection.hh"
|
||||
@@ -174,6 +175,7 @@ future<> group0_state_machine::reload_modules(modules_to_reload modules) {
|
||||
bool update_service_levels_effective_cache = false;
|
||||
bool make_view_building_state_transition = false;
|
||||
std::unordered_set<table_id> update_cdc_streams;
|
||||
std::unordered_set<auth::cache::role_name_t> update_auth_cache_roles;
|
||||
|
||||
for (const auto& m : modules.entries) {
|
||||
if (m.table == db::system_keyspace::service_levels_v2()->id()) {
|
||||
@@ -197,6 +199,12 @@ future<> group0_state_machine::reload_modules(modules_to_reload modules) {
|
||||
const auto elements = m.pk.explode(*db::system_keyspace::cdc_streams_history());
|
||||
auto cdc_log_table_id = table_id(value_cast<utils::UUID>(uuid_type->deserialize_value(elements.front())));
|
||||
update_cdc_streams.insert(cdc_log_table_id);
|
||||
} else if (auth::cache::includes_table(m.table)) {
|
||||
auto schema = _ss.get_database().find_schema(m.table);
|
||||
const auto elements = m.pk.explode(*schema);
|
||||
auto role = value_cast<sstring>(schema->partition_key_type()->
|
||||
types().front()->deserialize(elements.front()));
|
||||
update_auth_cache_roles.insert(std::move(role));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,6 +217,9 @@ future<> group0_state_machine::reload_modules(modules_to_reload modules) {
|
||||
if (update_cdc_streams.size()) {
|
||||
co_await _ss.load_cdc_streams(std::move(update_cdc_streams));
|
||||
}
|
||||
if (update_auth_cache_roles.size()) {
|
||||
co_await _ss.auth_cache().load_roles(std::move(update_auth_cache_roles));
|
||||
}
|
||||
}
|
||||
|
||||
future<> group0_state_machine::merge_and_apply(group0_state_machine_merger& merger) {
|
||||
@@ -375,6 +386,7 @@ future<> group0_state_machine::load_snapshot(raft::snapshot_id id) {
|
||||
if (_feature_service.cdc_with_tablets) {
|
||||
co_await _ss.load_cdc_streams();
|
||||
}
|
||||
co_await _ss.auth_cache().load_all();
|
||||
_ss._topology_state_machine.event.broadcast();
|
||||
_ss._view_building_state_machine.event.broadcast();
|
||||
}
|
||||
@@ -443,6 +455,8 @@ future<> group0_state_machine::transfer_snapshot(raft::server_id from_id, raft::
|
||||
co_await mutate_locally(std::move(raft_snp->mutations), _sp);
|
||||
}
|
||||
|
||||
co_await _ss.auth_cache().load_all();
|
||||
|
||||
co_await _sp.mutate_locally({std::move(history_mut)}, nullptr);
|
||||
} catch (const abort_requested_exception&) {
|
||||
throw raft::request_aborted(fmt::format(
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "compaction/task_manager_module.hh"
|
||||
#include "gc_clock.hh"
|
||||
#include "raft/raft.hh"
|
||||
#include "auth/cache.hh"
|
||||
#include <ranges>
|
||||
#include <seastar/core/shard_id.hh>
|
||||
#include <seastar/core/sleep.hh>
|
||||
@@ -203,6 +204,7 @@ storage_service::storage_service(abort_source& abort_source,
|
||||
sharded<db::view::view_building_worker>& view_building_worker,
|
||||
cql3::query_processor& qp,
|
||||
sharded<qos::service_level_controller>& sl_controller,
|
||||
auth::cache& auth_cache,
|
||||
topology_state_machine& topology_state_machine,
|
||||
db::view::view_building_state_machine& view_building_state_machine,
|
||||
tasks::task_manager& tm,
|
||||
@@ -221,6 +223,7 @@ storage_service::storage_service(abort_source& abort_source,
|
||||
, _stream_manager(stream_manager)
|
||||
, _snitch(snitch)
|
||||
, _sl_controller(sl_controller)
|
||||
, _auth_cache(auth_cache)
|
||||
, _group0(nullptr)
|
||||
, _async_gate("storage_service")
|
||||
, _node_ops_abort_thread(node_ops_abort_thread())
|
||||
@@ -274,6 +277,10 @@ node_ops::task_manager_module& storage_service::get_node_ops_module() noexcept {
|
||||
return *_node_ops_module;
|
||||
}
|
||||
|
||||
auth::cache& storage_service::auth_cache() noexcept {
|
||||
return _auth_cache;
|
||||
}
|
||||
|
||||
enum class node_external_status {
|
||||
UNKNOWN = 0,
|
||||
STARTING = 1,
|
||||
@@ -708,11 +715,14 @@ future<> storage_service::topology_state_load(state_change_hint hint) {
|
||||
co_return;
|
||||
}
|
||||
|
||||
co_await _qp.container().invoke_on_all([] (cql3::query_processor& qp) {
|
||||
if (_qp.auth_version < db::system_keyspace::auth_version_t::v2) {
|
||||
// auth-v2 gets enabled when consistent topology changes are enabled
|
||||
// (see topology::upgrade_state_type::done above) as we use the same migration procedure
|
||||
qp.auth_version = db::system_keyspace::auth_version_t::v2;
|
||||
});
|
||||
co_await _qp.container().invoke_on_all([] (cql3::query_processor& qp) {
|
||||
qp.auth_version = db::system_keyspace::auth_version_t::v2;
|
||||
});
|
||||
co_await auth_cache().load_all();
|
||||
}
|
||||
|
||||
co_await _sl_controller.invoke_on_all([this] (qos::service_level_controller& sl_controller) {
|
||||
sl_controller.upgrade_to_v2(_qp, _group0->client());
|
||||
|
||||
@@ -114,6 +114,8 @@ namespace replica {
|
||||
class tablet_mutation_builder;
|
||||
}
|
||||
|
||||
namespace auth { class cache; }
|
||||
|
||||
namespace utils {
|
||||
class disk_space_monitor;
|
||||
}
|
||||
@@ -199,6 +201,7 @@ private:
|
||||
sharded<streaming::stream_manager>& _stream_manager;
|
||||
sharded<locator::snitch_ptr>& _snitch;
|
||||
sharded<qos::service_level_controller>& _sl_controller;
|
||||
auth::cache& _auth_cache;
|
||||
|
||||
// Engaged on shard 0 before `join_cluster`.
|
||||
service::raft_group0* _group0;
|
||||
@@ -265,6 +268,7 @@ public:
|
||||
sharded<db::view::view_building_worker>& view_building_worker,
|
||||
cql3::query_processor& qp,
|
||||
sharded<qos::service_level_controller>& sl_controller,
|
||||
auth::cache& auth_cache,
|
||||
topology_state_machine& topology_state_machine,
|
||||
db::view::view_building_state_machine& view_building_state_machine,
|
||||
tasks::task_manager& tm,
|
||||
@@ -998,6 +1002,8 @@ public:
|
||||
// update_both_cache_levels::no - update only effective service levels cache
|
||||
future<> update_service_levels_cache(qos::update_both_cache_levels update_only_effective_cache = qos::update_both_cache_levels::yes, qos::query_context ctx = qos::query_context::unspecified);
|
||||
|
||||
auth::cache& auth_cache() noexcept;
|
||||
|
||||
// Should be called whenever new compression dictionaries are published to system.dicts.
|
||||
// This is an arbitrary callback passed through the constructor,
|
||||
// but its intended usage is to set up the RPC connections to use the new dictionaries.
|
||||
|
||||
@@ -1643,25 +1643,27 @@ class topology_coordinator : public endpoint_lifecycle_subscriber {
|
||||
}
|
||||
break;
|
||||
case locator::tablet_transition_stage::cleanup_target:
|
||||
if (advance_in_background(gid, tablet_state.cleanup, "cleanup_target", [&] {
|
||||
if (!trinfo.pending_replica) {
|
||||
rtlogger.info("Tablet cleanup of {} skipped because no replicas pending", gid);
|
||||
return make_ready_future<>();
|
||||
}
|
||||
locator::tablet_replica dst = *trinfo.pending_replica;
|
||||
if (is_excluded(raft::server_id(dst.host.uuid()))) {
|
||||
rtlogger.info("Tablet cleanup of {} on {} skipped because node is excluded and doesn't need to revert migration", gid, dst);
|
||||
return make_ready_future<>();
|
||||
}
|
||||
rtlogger.info("Initiating tablet cleanup of {} on {} to revert migration", gid, dst);
|
||||
return do_with(gids, [this, dst] (const auto& gids) {
|
||||
return do_for_each(gids, [this, dst] (locator::global_tablet_id gid) {
|
||||
return ser::storage_service_rpc_verbs::send_tablet_cleanup(&_messaging,
|
||||
dst.host, _as, raft::server_id(dst.host.uuid()), gid);
|
||||
if (do_barrier()) {
|
||||
if (advance_in_background(gid, tablet_state.cleanup, "cleanup_target", [&] {
|
||||
if (!trinfo.pending_replica) {
|
||||
rtlogger.info("Tablet cleanup of {} skipped because no replicas pending", gid);
|
||||
return make_ready_future<>();
|
||||
}
|
||||
locator::tablet_replica dst = *trinfo.pending_replica;
|
||||
if (is_excluded(raft::server_id(dst.host.uuid()))) {
|
||||
rtlogger.info("Tablet cleanup of {} on {} skipped because node is excluded and doesn't need to revert migration", gid, dst);
|
||||
return make_ready_future<>();
|
||||
}
|
||||
rtlogger.info("Initiating tablet cleanup of {} on {} to revert migration", gid, dst);
|
||||
return do_with(gids, [this, dst] (const auto& gids) {
|
||||
return do_for_each(gids, [this, dst] (locator::global_tablet_id gid) {
|
||||
return ser::storage_service_rpc_verbs::send_tablet_cleanup(&_messaging,
|
||||
dst.host, _as, raft::server_id(dst.host.uuid()), gid);
|
||||
});
|
||||
});
|
||||
});
|
||||
})) {
|
||||
transition_to(locator::tablet_transition_stage::revert_migration);
|
||||
})) {
|
||||
transition_to(locator::tablet_transition_stage::revert_migration);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case locator::tablet_transition_stage::revert_migration:
|
||||
|
||||
@@ -27,6 +27,7 @@ enum class component_type {
|
||||
TemporaryTOC,
|
||||
TemporaryStatistics,
|
||||
Scylla,
|
||||
TemporaryScylla,
|
||||
Rows,
|
||||
Partitions,
|
||||
TemporaryHashes,
|
||||
@@ -76,6 +77,8 @@ struct fmt::formatter<sstables::component_type> : fmt::formatter<string_view> {
|
||||
return formatter<string_view>::format("TemporaryStatistics", ctx);
|
||||
case Scylla:
|
||||
return formatter<string_view>::format("Scylla", ctx);
|
||||
case TemporaryScylla:
|
||||
return formatter<string_view>::format("TemporaryScylla", ctx);
|
||||
case Partitions:
|
||||
return formatter<string_view>::format("Partitions", ctx);
|
||||
case Rows:
|
||||
|
||||
@@ -632,6 +632,10 @@ private:
|
||||
std::unique_ptr<file_writer> close_writer(std::unique_ptr<file_writer>& w);
|
||||
|
||||
void close_data_writer();
|
||||
void close_index_writer();
|
||||
void close_rows_writer();
|
||||
void close_partitions_writer();
|
||||
|
||||
void ensure_tombstone_is_written() {
|
||||
if (!_tombstone_written) {
|
||||
consume(tombstone());
|
||||
@@ -944,17 +948,16 @@ void writer::init_file_writers() {
|
||||
_sst._schema->get_compressor_params(),
|
||||
std::move(compressor)), _sst.get_filename());
|
||||
}
|
||||
|
||||
if (_sst.has_component(component_type::Index)) {
|
||||
out = _sst._storage->make_data_or_index_sink(_sst, component_type::Index).get();
|
||||
_index_writer = std::make_unique<file_writer>(output_stream<char>(std::move(out)), _sst.index_filename());
|
||||
_index_writer = std::make_unique<crc32_digest_file_writer>(std::move(out), _sst.sstable_buffer_size, _sst.index_filename());
|
||||
}
|
||||
if (_sst.has_component(component_type::Partitions) && _sst.has_component(component_type::Rows)) {
|
||||
out = _sst._storage->make_data_or_index_sink(_sst, component_type::Rows).get();
|
||||
_rows_writer = std::make_unique<file_writer>(output_stream<char>(std::move(out)), component_name(_sst, component_type::Rows));
|
||||
_rows_writer = std::make_unique<crc32_digest_file_writer>(std::move(out), _sst.sstable_buffer_size, component_name(_sst, component_type::Rows));
|
||||
_bti_row_index_writer = trie::bti_row_index_writer(*_rows_writer);
|
||||
out = _sst._storage->make_data_or_index_sink(_sst, component_type::Partitions).get();
|
||||
_partitions_writer = std::make_unique<file_writer>(output_stream<char>(std::move(out)), component_name(_sst, component_type::Partitions));
|
||||
_partitions_writer = std::make_unique<crc32_digest_file_writer>(std::move(out), _sst.sstable_buffer_size, component_name(_sst, component_type::Partitions));
|
||||
_bti_partition_index_writer = trie::bti_partition_index_writer(*_partitions_writer);
|
||||
}
|
||||
if (_delayed_filter) {
|
||||
@@ -982,6 +985,41 @@ void writer::close_data_writer() {
|
||||
}
|
||||
}
|
||||
|
||||
void writer::close_index_writer() {
|
||||
if (_index_writer) {
|
||||
auto writer = close_writer(_index_writer);
|
||||
auto chksum_wr = static_cast<crc32_digest_file_writer*>(writer.get());
|
||||
_sst.get_components_digests().index_digest = chksum_wr->full_checksum();
|
||||
}
|
||||
}
|
||||
|
||||
void writer::close_partitions_writer() {
|
||||
if (_partitions_writer) {
|
||||
_sst._partitions_db_footer = std::move(*_bti_partition_index_writer).finish(
|
||||
_sst.get_version(),
|
||||
_first_key.value(),
|
||||
_last_key.value());
|
||||
auto writer = close_writer(_partitions_writer);
|
||||
auto chksum_wr = static_cast<crc32_digest_file_writer*>(writer.get());
|
||||
_sst.get_components_digests().partitions_digest = chksum_wr->full_checksum();
|
||||
}
|
||||
}
|
||||
|
||||
void writer::close_rows_writer() {
|
||||
if (_rows_writer) {
|
||||
// Append some garbage padding to the file just to ensure that it's never empty.
|
||||
// (Otherwise it would be empty if the sstable contains only small partitions).
|
||||
// This is a hack to work around some bad interactions between zero-sized files
|
||||
// and object storage. (It seems that e.g. minio considers a zero-sized file
|
||||
// upload to be a no-op, which breaks some assumptions).
|
||||
uint32_t garbage = seastar::cpu_to_be(0x13371337);
|
||||
_rows_writer->write(reinterpret_cast<const char*>(&garbage), sizeof(garbage));
|
||||
auto writer = close_writer(_rows_writer);
|
||||
auto chksum_wr = static_cast<crc32_digest_file_writer*>(writer.get());
|
||||
_sst.get_components_digests().rows_digest = chksum_wr->full_checksum();
|
||||
}
|
||||
}
|
||||
|
||||
void writer::consume_new_partition(const dht::decorated_key& dk) {
|
||||
_c_stats.start_offset = _data_writer->offset();
|
||||
_prev_row_start = _data_writer->offset();
|
||||
@@ -1630,27 +1668,10 @@ void writer::consume_end_of_stream() {
|
||||
_collector.add_compression_ratio(_sst._components->compression.compressed_file_length(), _sst._components->compression.uncompressed_file_length());
|
||||
}
|
||||
|
||||
if (_index_writer) {
|
||||
close_writer(_index_writer);
|
||||
}
|
||||
close_index_writer();
|
||||
|
||||
if (_partitions_writer) {
|
||||
_sst._partitions_db_footer = std::move(*_bti_partition_index_writer).finish(
|
||||
_sst.get_version(),
|
||||
_first_key.value(),
|
||||
_last_key.value());
|
||||
close_writer(_partitions_writer);
|
||||
}
|
||||
if (_rows_writer) {
|
||||
// Append some garbage padding to the file just to ensure that it's never empty.
|
||||
// (Otherwise it would be empty if the sstable contains only small partitions).
|
||||
// This is a hack to work around some bad interactions between zero-sized files
|
||||
// and object storage. (It seems that e.g. minio considers a zero-sized file
|
||||
// upload to be a no-op, which breaks some assumptions).
|
||||
uint32_t garbage = seastar::cpu_to_be(0x13371337);
|
||||
_rows_writer->write(reinterpret_cast<const char*>(&garbage), sizeof(garbage));
|
||||
close_writer(_rows_writer);
|
||||
}
|
||||
close_partitions_writer();
|
||||
close_rows_writer();
|
||||
|
||||
if (_hashes_writer) {
|
||||
close_writer(_hashes_writer);
|
||||
|
||||
@@ -44,6 +44,7 @@ sstable_version_constants::component_map_t sstable_version_constants::create_com
|
||||
{ component_type::Filter, "Filter.db" },
|
||||
{ component_type::Statistics, "Statistics.db" },
|
||||
{ component_type::Scylla, "Scylla.db" },
|
||||
{ component_type::TemporaryScylla, "Scylla.db.tmp" },
|
||||
{ component_type::TemporaryTOC, TEMPORARY_TOC_SUFFIX },
|
||||
{ component_type::TemporaryStatistics, "Statistics.db.tmp" }
|
||||
};
|
||||
|
||||
@@ -956,16 +956,22 @@ future<file_writer> sstable::make_component_file_writer(component_type c, file_o
|
||||
});
|
||||
}
|
||||
|
||||
future<std::unique_ptr<crc32_digest_file_writer>> sstable::make_digests_component_file_writer(component_type c, file_output_stream_options options, open_flags oflags) noexcept {
|
||||
return _storage->make_component_sink(*this, c, oflags, std::move(options)).then([this, comp = component_name(*this, c)] (data_sink sink) mutable {
|
||||
return std::make_unique<crc32_digest_file_writer>(std::move(sink), sstable_buffer_size, comp);
|
||||
});
|
||||
}
|
||||
|
||||
void sstable::open_sstable(const sstring& origin) {
|
||||
_origin = origin;
|
||||
generate_toc();
|
||||
_storage->open(*this);
|
||||
}
|
||||
|
||||
void sstable::write_toc(file_writer w) {
|
||||
void sstable::write_toc(std::unique_ptr<crc32_digest_file_writer> w) {
|
||||
sstlog.debug("Writing TOC file {} ", toc_filename());
|
||||
|
||||
do_write_simple(std::move(w), [&] (version_types v, file_writer& w) {
|
||||
do_write_simple(*w, [&] (version_types v, file_writer& w) {
|
||||
for (auto&& key : _recognized_components) {
|
||||
// new line character is appended to the end of each component name.
|
||||
auto value = sstable_version_constants::get_component_map(v).at(key) + "\n";
|
||||
@@ -973,6 +979,8 @@ void sstable::write_toc(file_writer w) {
|
||||
write(v, w, b);
|
||||
}
|
||||
});
|
||||
|
||||
_components_digests.toc_digest = w->full_checksum();
|
||||
}
|
||||
|
||||
void sstable::write_crc(const checksum& c) {
|
||||
@@ -989,6 +997,7 @@ void sstable::write_digest(uint32_t full_checksum) {
|
||||
auto digest = to_sstring<bytes>(full_checksum);
|
||||
write(v, w, digest);
|
||||
}, buffer_size);
|
||||
_components_digests.data_digest = full_checksum;
|
||||
}
|
||||
|
||||
thread_local std::array<std::vector<int>, downsampling::BASE_SAMPLING_LEVEL> downsampling::_sample_pattern_cache;
|
||||
@@ -1045,7 +1054,7 @@ future<> sstable::read_simple(T& component) {
|
||||
});
|
||||
}
|
||||
|
||||
void sstable::do_write_simple(file_writer&& writer,
|
||||
void sstable::do_write_simple(file_writer& writer,
|
||||
noncopyable_function<void (version_types, file_writer&)> write_component) {
|
||||
write_component(_version, writer);
|
||||
_metadata_size_on_disk += writer.offset();
|
||||
@@ -1060,7 +1069,7 @@ void sstable::do_write_simple(component_type type,
|
||||
file_output_stream_options options;
|
||||
options.buffer_size = buffer_size;
|
||||
auto w = make_component_file_writer(type, std::move(options)).get();
|
||||
do_write_simple(std::move(w), std::move(write_component));
|
||||
do_write_simple(w, std::move(write_component));
|
||||
}
|
||||
|
||||
template <component_type Type, typename T>
|
||||
@@ -1070,10 +1079,30 @@ void sstable::write_simple(const T& component) {
|
||||
}, sstable_buffer_size);
|
||||
}
|
||||
|
||||
uint32_t sstable::do_write_simple_with_digest(component_type type,
|
||||
noncopyable_function<void (version_types version, file_writer& writer)> write_component, unsigned buffer_size) {
|
||||
auto file_path = filename(type);
|
||||
sstlog.debug("Writing {} file {}", sstable_version_constants::get_component_map(_version).at(type), file_path);
|
||||
|
||||
file_output_stream_options options;
|
||||
options.buffer_size = buffer_size;
|
||||
auto w = make_digests_component_file_writer(type, std::move(options)).get();
|
||||
do_write_simple(*w, std::move(write_component));
|
||||
return w->full_checksum();
|
||||
}
|
||||
|
||||
template <component_type Type, typename T>
|
||||
uint32_t sstable::write_simple_with_digest(const T& component) {
|
||||
return do_write_simple_with_digest(Type, [&component] (version_types v, file_writer& w) {
|
||||
write(v, w, component);
|
||||
}, sstable_buffer_size);
|
||||
}
|
||||
|
||||
template future<> sstable::read_simple<component_type::Filter>(sstables::filter& f);
|
||||
template void sstable::write_simple<component_type::Filter>(const sstables::filter& f);
|
||||
|
||||
template void sstable::write_simple<component_type::Summary>(const sstables::summary_ka&);
|
||||
template uint32_t sstable::write_simple_with_digest<component_type::Summary>(const sstables::summary_ka&);
|
||||
|
||||
future<> sstable::read_compression() {
|
||||
// FIXME: If there is no compression, we should expect a CRC file to be present.
|
||||
@@ -1092,7 +1121,8 @@ void sstable::write_compression() {
|
||||
return;
|
||||
}
|
||||
|
||||
write_simple<component_type::CompressionInfo>(_components->compression);
|
||||
uint32_t digest = write_simple_with_digest<component_type::CompressionInfo>(_components->compression);
|
||||
_components_digests.compression_digest = digest;
|
||||
}
|
||||
|
||||
void sstable::validate_partitioner() {
|
||||
@@ -1317,7 +1347,8 @@ future<> sstable::read_partitions_db_footer() {
|
||||
}
|
||||
|
||||
void sstable::write_statistics() {
|
||||
write_simple<component_type::Statistics>(_components->statistics);
|
||||
auto digest = write_simple_with_digest<component_type::Statistics>(_components->statistics);
|
||||
_components_digests.statistics_digest = digest;
|
||||
}
|
||||
|
||||
void sstable::mark_as_being_repaired(const service::session_id& id) {
|
||||
@@ -1342,10 +1373,23 @@ void sstable::rewrite_statistics() {
|
||||
|
||||
file_output_stream_options options;
|
||||
options.buffer_size = sstable_buffer_size;
|
||||
auto w = make_component_file_writer(component_type::TemporaryStatistics, std::move(options),
|
||||
auto w = make_digests_component_file_writer(component_type::TemporaryStatistics, std::move(options),
|
||||
open_flags::wo | open_flags::create | open_flags::truncate).get();
|
||||
write(_version, w, _components->statistics);
|
||||
w.close();
|
||||
write(_version, *w, _components->statistics);
|
||||
w->close();
|
||||
|
||||
// When rewriting statistics, we also need to update the scylla component
|
||||
// because it contains the digest of the statistics component.
|
||||
if (has_scylla_component()) {
|
||||
_components_digests.statistics_digest = w->full_checksum();
|
||||
_components->scylla_metadata->data.set<scylla_metadata_type::ComponentsDigests>(components_digests{_components_digests});
|
||||
sstlog.debug("Rewriting scylla component of sstable {}", get_filename());
|
||||
write_simple<component_type::TemporaryScylla>(*_components->scylla_metadata);
|
||||
|
||||
// rename() guarantees atomicity when renaming a file into place.
|
||||
sstable_write_io_check(rename_file, fmt::to_string(filename(component_type::TemporaryScylla)), fmt::to_string(filename(component_type::Scylla))).get();
|
||||
}
|
||||
|
||||
// rename() guarantees atomicity when renaming a file into place.
|
||||
sstable_write_io_check(rename_file, fmt::to_string(filename(component_type::TemporaryStatistics)), fmt::to_string(filename(component_type::Statistics))).get();
|
||||
}
|
||||
@@ -1539,7 +1583,8 @@ void sstable::write_filter() {
|
||||
|
||||
auto&& bs = f->bits();
|
||||
auto filter_ref = sstables::filter_ref(f->num_hashes(), bs.get_storage());
|
||||
write_simple<component_type::Filter>(filter_ref);
|
||||
uint32_t digest = write_simple_with_digest<component_type::Filter>(filter_ref);
|
||||
_components_digests.filter_digest = digest;
|
||||
}
|
||||
|
||||
void sstable::maybe_rebuild_filter_from_index(uint64_t num_partitions) {
|
||||
@@ -1998,6 +2043,8 @@ sstable::read_scylla_metadata() noexcept {
|
||||
}
|
||||
return read_simple<component_type::Scylla>(*_components->scylla_metadata).then([this] {
|
||||
_features = _components->scylla_metadata->get_features();
|
||||
_components_digests = _components->scylla_metadata->get_components_digests();
|
||||
_components->digest = _components_digests.data_digest;
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -2087,6 +2134,7 @@ sstable::write_scylla_metadata(shard_id shard, struct run_identifier identifier,
|
||||
sstable_schema.columns.elements.push_back(sstable_column_description{to_sstable_column_kind(col.kind), {col.name()}, {to_bytes(col.type->name())}});
|
||||
}
|
||||
_components->scylla_metadata->data.set<scylla_metadata_type::Schema>(std::move(sstable_schema));
|
||||
_components->scylla_metadata->data.set<scylla_metadata_type::ComponentsDigests>(components_digests(_components_digests));
|
||||
|
||||
write_simple<component_type::Scylla>(*_components->scylla_metadata);
|
||||
}
|
||||
@@ -3075,6 +3123,31 @@ void sstable::set_sstable_level(uint32_t new_level) {
|
||||
s.sstable_level = new_level;
|
||||
}
|
||||
|
||||
std::optional<uint32_t> sstable::get_component_digest(component_type c) const {
|
||||
switch (c) {
|
||||
case component_type::Index:
|
||||
return _components_digests.index_digest;
|
||||
case component_type::Summary:
|
||||
return _components_digests.summary_digest;
|
||||
case component_type::TOC:
|
||||
return _components_digests.toc_digest;
|
||||
case component_type::CompressionInfo:
|
||||
return _components_digests.compression_digest;
|
||||
case component_type::Filter:
|
||||
return _components_digests.filter_digest;
|
||||
case component_type::Partitions:
|
||||
return _components_digests.partitions_digest;
|
||||
case component_type::Rows:
|
||||
return _components_digests.rows_digest;
|
||||
case component_type::Data:
|
||||
return _components_digests.data_digest;
|
||||
case component_type::Statistics:
|
||||
return _components_digests.statistics_digest;
|
||||
default:
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
future<> sstable::mutate_sstable_level(uint32_t new_level) {
|
||||
if (!has_component(component_type::Statistics)) {
|
||||
return make_ready_future<>();
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "sstables/writer.hh"
|
||||
#include "version.hh"
|
||||
#include "shared_sstable.hh"
|
||||
#include "open_info.hh"
|
||||
@@ -627,6 +628,8 @@ private:
|
||||
// Total memory reclaimed so far from this sstable
|
||||
size_t _total_memory_reclaimed{0};
|
||||
bool _unlinked{false};
|
||||
|
||||
components_digests _components_digests;
|
||||
public:
|
||||
bool has_component(component_type f) const;
|
||||
sstables_manager& manager() { return _manager; }
|
||||
@@ -647,12 +650,18 @@ private:
|
||||
|
||||
template <component_type Type, typename T>
|
||||
void write_simple(const T& comp);
|
||||
void do_write_simple(file_writer&& writer,
|
||||
void do_write_simple(file_writer& writer,
|
||||
noncopyable_function<void (version_types, file_writer&)> write_component);
|
||||
void do_write_simple(component_type type,
|
||||
noncopyable_function<void (version_types version, file_writer& writer)> write_component,
|
||||
unsigned buffer_size);
|
||||
|
||||
template <component_type Type, typename T>
|
||||
uint32_t write_simple_with_digest(const T& comp);
|
||||
uint32_t do_write_simple_with_digest(component_type type,
|
||||
noncopyable_function<void (version_types version, file_writer& writer)> write_component,
|
||||
unsigned buffer_size);
|
||||
|
||||
void write_crc(const checksum& c);
|
||||
void write_digest(uint32_t full_checksum);
|
||||
|
||||
@@ -663,6 +672,9 @@ private:
|
||||
future<file_writer> make_component_file_writer(component_type c, file_output_stream_options options,
|
||||
open_flags oflags = open_flags::wo | open_flags::create | open_flags::exclusive) noexcept;
|
||||
|
||||
future<std::unique_ptr<crc32_digest_file_writer>> make_digests_component_file_writer(component_type c, file_output_stream_options options,
|
||||
open_flags oflags = open_flags::wo | open_flags::create | open_flags::exclusive) noexcept;
|
||||
|
||||
void generate_toc();
|
||||
void open_sstable(const sstring& origin);
|
||||
|
||||
@@ -693,7 +705,8 @@ private:
|
||||
future<> read_summary() noexcept;
|
||||
|
||||
void write_summary() {
|
||||
write_simple<component_type::Summary>(_components->summary);
|
||||
uint32_t digest = write_simple_with_digest<component_type::Summary>(_components->summary);
|
||||
_components_digests.summary_digest = digest;
|
||||
}
|
||||
|
||||
// To be called when we try to load an SSTable that lacks a Summary. Could
|
||||
@@ -823,7 +836,7 @@ private:
|
||||
|
||||
future<> open_or_create_data(open_flags oflags, file_open_options options = {}) noexcept;
|
||||
// runs in async context (called from storage::open)
|
||||
void write_toc(file_writer w);
|
||||
void write_toc(std::unique_ptr<crc32_digest_file_writer> w);
|
||||
static future<uint32_t> read_digest_from_file(file f);
|
||||
static future<lw_shared_ptr<checksum>> read_checksum_from_file(file f);
|
||||
public:
|
||||
@@ -1013,6 +1026,12 @@ public:
|
||||
return _components->digest;
|
||||
}
|
||||
|
||||
components_digests& get_components_digests() {
|
||||
return _components_digests;
|
||||
}
|
||||
|
||||
std::optional<uint32_t> get_component_digest(component_type c) const;
|
||||
|
||||
// Gets ratio of droppable tombstone. A tombstone is considered droppable here
|
||||
// for cells and tombstones expired before the time point "GC before", which
|
||||
// is the point before which expiring data can be purged.
|
||||
|
||||
@@ -204,13 +204,13 @@ void filesystem_storage::open(sstable& sst) {
|
||||
open_flags::create |
|
||||
open_flags::exclusive,
|
||||
options).get();
|
||||
auto w = file_writer(output_stream<char>(std::move(sink)), component_name(sst, component_type::TemporaryTOC));
|
||||
auto w = std::make_unique<crc32_digest_file_writer>(std::move(sink), sst.sstable_buffer_size, component_name(sst, component_type::TemporaryTOC));
|
||||
|
||||
bool toc_exists = file_exists(fmt::to_string(sst.filename(component_type::TOC))).get();
|
||||
if (toc_exists) {
|
||||
// TOC will exist at this point if write_components() was called with
|
||||
// the generation of a sstable that exists.
|
||||
w.close();
|
||||
w->close();
|
||||
remove_file(fmt::to_string(sst.filename(component_type::TemporaryTOC))).get();
|
||||
throw std::runtime_error(format("SSTable write failed due to existence of TOC file for generation {} of {}.{}", sst._generation, sst._schema->ks_name(), sst._schema->cf_name()));
|
||||
}
|
||||
@@ -670,15 +670,10 @@ void object_storage_base::open(sstable& sst) {
|
||||
sst.manager().sstables_registry().create_entry(owner(), status_creating, sst._state, std::move(desc)).get();
|
||||
|
||||
memory_data_sink_buffers bufs;
|
||||
sst.write_toc(
|
||||
file_writer(
|
||||
output_stream<char>(
|
||||
data_sink(
|
||||
std::make_unique<memory_data_sink>(bufs)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
auto out = data_sink(std::make_unique<memory_data_sink>(bufs));
|
||||
auto w = std::make_unique<crc32_digest_file_writer>(std::move(out), sst.sstable_buffer_size, component_name(sst, component_type::TOC));
|
||||
|
||||
sst.write_toc(std::move(w));
|
||||
put_object(make_object_name(sst, component_type::TOC), std::move(bufs)).get();
|
||||
}
|
||||
|
||||
|
||||
@@ -547,6 +547,7 @@ enum class scylla_metadata_type : uint32_t {
|
||||
ExtTimestampStats = 9,
|
||||
SSTableIdentifier = 10,
|
||||
Schema = 11,
|
||||
ComponentsDigests = 12,
|
||||
};
|
||||
|
||||
// UUID is used for uniqueness across nodes, such that an imported sstable
|
||||
@@ -573,6 +574,24 @@ struct sstable_identifier_type {
|
||||
auto describe_type(sstable_version_types v, Describer f) { return f(value); }
|
||||
};
|
||||
|
||||
// Component digests stored in scylla metadata to track integrity of individual components
|
||||
struct components_digests {
|
||||
std::optional<uint32_t> data_digest;
|
||||
std::optional<uint32_t> compression_digest;
|
||||
std::optional<uint32_t> filter_digest;
|
||||
std::optional<uint32_t> statistics_digest;
|
||||
std::optional<uint32_t> summary_digest;
|
||||
std::optional<uint32_t> index_digest;
|
||||
std::optional<uint32_t> toc_digest;
|
||||
std::optional<uint32_t> partitions_digest;
|
||||
std::optional<uint32_t> rows_digest;
|
||||
|
||||
template <typename Describer>
|
||||
auto describe_type(sstable_version_types v, Describer f) {
|
||||
return f(data_digest,compression_digest, filter_digest, statistics_digest, summary_digest, index_digest, toc_digest, partitions_digest, rows_digest);
|
||||
}
|
||||
};
|
||||
|
||||
// Types of large data statistics.
|
||||
//
|
||||
// Note: For extensibility, never reuse an identifier,
|
||||
@@ -656,7 +675,8 @@ struct scylla_metadata {
|
||||
disk_tagged_union_member<scylla_metadata_type, scylla_metadata_type::ScyllaVersion, scylla_version>,
|
||||
disk_tagged_union_member<scylla_metadata_type, scylla_metadata_type::ExtTimestampStats, ext_timestamp_stats>,
|
||||
disk_tagged_union_member<scylla_metadata_type, scylla_metadata_type::SSTableIdentifier, sstable_identifier>,
|
||||
disk_tagged_union_member<scylla_metadata_type, scylla_metadata_type::Schema, sstable_schema>
|
||||
disk_tagged_union_member<scylla_metadata_type, scylla_metadata_type::Schema, sstable_schema>,
|
||||
disk_tagged_union_member<scylla_metadata_type, scylla_metadata_type::ComponentsDigests, components_digests>
|
||||
> data;
|
||||
|
||||
sstable_enabled_features get_features() const {
|
||||
@@ -691,6 +711,13 @@ struct scylla_metadata {
|
||||
auto* sid = data.get<scylla_metadata_type::SSTableIdentifier, scylla_metadata::sstable_identifier>();
|
||||
return sid ? sid->value : sstable_id::create_null_id();
|
||||
}
|
||||
const components_digests get_components_digests() const {
|
||||
auto cd = data.get<scylla_metadata_type::ComponentsDigests, components_digests>();
|
||||
if (!cd) {
|
||||
return {};
|
||||
}
|
||||
return *cd;
|
||||
}
|
||||
|
||||
template <typename Describer>
|
||||
auto describe_type(sstable_version_types v, Describer f) { return f(data); }
|
||||
|
||||
@@ -65,7 +65,7 @@ serialized_size(sstable_version_types v, const T& object) {
|
||||
return size;
|
||||
}
|
||||
|
||||
template <typename ChecksumType>
|
||||
template <typename ChecksumType, bool calculate_chunk_checksums>
|
||||
requires ChecksumUtils<ChecksumType>
|
||||
class checksummed_file_data_sink_impl : public data_sink_impl {
|
||||
data_sink _out;
|
||||
@@ -92,7 +92,9 @@ public:
|
||||
|
||||
per_chunk_checksum = ChecksumType::checksum(per_chunk_checksum, buf.begin() + offset, size);
|
||||
_full_checksum = checksum_combine_or_feed<ChecksumType>(_full_checksum, per_chunk_checksum, buf.begin() + offset, size);
|
||||
_c.checksums.push_back(per_chunk_checksum);
|
||||
if constexpr (calculate_chunk_checksums) {
|
||||
_c.checksums.push_back(per_chunk_checksum);
|
||||
}
|
||||
}
|
||||
}
|
||||
return _out.put(std::move(bufs));
|
||||
@@ -112,29 +114,29 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
template <typename ChecksumType>
|
||||
template <typename ChecksumType, bool calculate_chunk_checksums>
|
||||
requires ChecksumUtils<ChecksumType>
|
||||
class checksummed_file_data_sink : public data_sink {
|
||||
public:
|
||||
checksummed_file_data_sink(data_sink out, struct checksum& cinfo, uint32_t& full_file_checksum)
|
||||
: data_sink(std::make_unique<checksummed_file_data_sink_impl<ChecksumType>>(std::move(out), cinfo, full_file_checksum)) {}
|
||||
: data_sink(std::make_unique<checksummed_file_data_sink_impl<ChecksumType, calculate_chunk_checksums>>(std::move(out), cinfo, full_file_checksum)) {}
|
||||
};
|
||||
|
||||
template <typename ChecksumType>
|
||||
template <typename ChecksumType, bool calculate_chunk_checksums>
|
||||
requires ChecksumUtils<ChecksumType>
|
||||
inline
|
||||
output_stream<char> make_checksummed_file_output_stream(data_sink out, struct checksum& cinfo, uint32_t& full_file_checksum) {
|
||||
return output_stream<char>(checksummed_file_data_sink<ChecksumType>(std::move(out), cinfo, full_file_checksum));
|
||||
return output_stream<char>(checksummed_file_data_sink<ChecksumType, calculate_chunk_checksums>(std::move(out), cinfo, full_file_checksum));
|
||||
}
|
||||
|
||||
template <typename ChecksumType>
|
||||
template <typename ChecksumType, bool calculate_chunk_checksums>
|
||||
requires ChecksumUtils<ChecksumType>
|
||||
class checksummed_file_writer : public file_writer {
|
||||
checksum _c;
|
||||
uint32_t _full_checksum;
|
||||
public:
|
||||
checksummed_file_writer(data_sink out, size_t buffer_size, component_name c)
|
||||
: file_writer(make_checksummed_file_output_stream<ChecksumType>(std::move(out), _c, _full_checksum), std::move(c))
|
||||
: file_writer(make_checksummed_file_output_stream<ChecksumType, calculate_chunk_checksums>(std::move(out), _c, _full_checksum), std::move(c))
|
||||
, _c(uint32_t(std::min(size_t(DEFAULT_CHUNK_SIZE), buffer_size)), {})
|
||||
, _full_checksum(ChecksumType::init_checksum()) {}
|
||||
|
||||
@@ -152,8 +154,10 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
using adler32_checksummed_file_writer = checksummed_file_writer<adler32_utils>;
|
||||
using crc32_checksummed_file_writer = checksummed_file_writer<crc32_utils>;
|
||||
using adler32_checksummed_file_writer = checksummed_file_writer<adler32_utils, true>;
|
||||
using crc32_checksummed_file_writer = checksummed_file_writer<crc32_utils, true>;
|
||||
|
||||
using crc32_digest_file_writer = checksummed_file_writer<crc32_utils, false>;
|
||||
|
||||
template <typename T, typename W>
|
||||
requires Writer<W>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
add_scylla_test(UUID_test
|
||||
KIND BOOST)
|
||||
add_scylla_test(url_parse_test
|
||||
KIND BOOST)
|
||||
add_scylla_test(advanced_rpc_compressor_test
|
||||
KIND SEASTAR)
|
||||
add_scylla_test(allocation_strategy_test
|
||||
|
||||
@@ -24,7 +24,7 @@ auto make_manager(cql_test_env& env) {
|
||||
std::default_delete<auth::standard_role_manager>()(m);
|
||||
};
|
||||
return std::unique_ptr<auth::standard_role_manager, decltype(stop_role_manager)>(
|
||||
new auth::standard_role_manager(env.local_qp(), env.get_raft_group0_client(), env.migration_manager().local()),
|
||||
new auth::standard_role_manager(env.local_qp(), env.get_raft_group0_client(), env.migration_manager().local(), env.auth_cache().local()),
|
||||
std::move(stop_role_manager));
|
||||
}
|
||||
|
||||
|
||||
@@ -2062,6 +2062,90 @@ SEASTAR_TEST_CASE(test_returning_failure_from_ghost_rows_deletion) {
|
||||
});
|
||||
}
|
||||
|
||||
// We can't verify that concurrency is actually used in a unit test, so check that the USING CONCURRENCY clause
|
||||
// at least still results in correct ghost row deletions.
|
||||
SEASTAR_TEST_CASE(test_deleting_ghost_rows_using_concurrency) {
|
||||
return do_with_cql_env_thread([] (auto& e) {
|
||||
cquery_nofail(e, "CREATE TABLE t (p int, c int, v int, PRIMARY KEY (p, c))");
|
||||
cquery_nofail(e, "CREATE MATERIALIZED VIEW tv AS SELECT v, p, c FROM t WHERE v IS NOT NULL AND c IS NOT NULL PRIMARY KEY (v, p, c);");
|
||||
for (int i = 0; i < 100; i++) {
|
||||
cquery_nofail(e, format("INSERT INTO t (p,c,v) VALUES ({},{},{})", i, i * 100, i % 10));
|
||||
}
|
||||
|
||||
std::vector<std::vector<bytes_opt>> expected_view_rows;
|
||||
for (int i = 0; i < 100; i++) {
|
||||
expected_view_rows.push_back({int32_type->decompose(i % 10), int32_type->decompose(i), int32_type->decompose(i * 100)});
|
||||
}
|
||||
auto inject_ghost_row = [&e] (int p, int c, int v) {
|
||||
e.db().invoke_on_all([p, c, v] (replica::database& db) {
|
||||
schema_ptr schema = db.find_schema("ks", "tv");
|
||||
replica::table& t = db.find_column_family(schema);
|
||||
mutation m(schema, partition_key::from_singular(*schema, v));
|
||||
auto& row = m.partition().clustered_row(*schema, clustering_key::from_exploded(*schema, {int32_type->decompose(p), int32_type->decompose(c)}));
|
||||
row.apply(row_marker{api::new_timestamp()});
|
||||
unsigned shard = t.shard_for_reads(m.token());
|
||||
if (shard == this_shard_id()) {
|
||||
t.apply(m);
|
||||
}
|
||||
}).get();
|
||||
};
|
||||
|
||||
inject_ghost_row(1, 100, 1111);
|
||||
eventually([&] {
|
||||
// The ghost row exists, but it can only be queried from the view, not from the base
|
||||
auto msg = cquery_nofail(e, "SELECT * FROM tv WHERE v = 1111;");
|
||||
assert_that(msg).is_rows().with_rows({
|
||||
{int32_type->decompose(1111), int32_type->decompose(1), int32_type->decompose(100)},
|
||||
});
|
||||
});
|
||||
|
||||
// Ghost row deletion is attempted for a single view partition
|
||||
cquery_nofail(e, "PRUNE MATERIALIZED VIEW tv WHERE v = 1111 USING CONCURRENCY 2");
|
||||
eventually([&] {
|
||||
// The ghost row is deleted
|
||||
auto msg = cquery_nofail(e, "SELECT * FROM tv where v = 1111;");
|
||||
assert_that(msg).is_rows().with_size(0);
|
||||
});
|
||||
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
inject_ghost_row(i, i * 100, (i + 1) % 10);
|
||||
}
|
||||
eventually([&] {
|
||||
auto msg = cquery_nofail(e, "SELECT * FROM tv;");
|
||||
assert_that(msg).is_rows().with_size(200);
|
||||
});
|
||||
|
||||
// Ghost row deletion is attempted for the whole table
|
||||
cquery_nofail(e, "PRUNE MATERIALIZED VIEW tv USING CONCURRENCY 3;");
|
||||
eventually([&] {
|
||||
// Ghost rows are deleted
|
||||
auto msg = cquery_nofail(e, "SELECT * FROM tv;");
|
||||
assert_that(msg).is_rows().with_rows_ignore_order(expected_view_rows);
|
||||
});
|
||||
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
inject_ghost_row(i, i * 100 + 1, (i + 2) % 10);
|
||||
}
|
||||
eventually([&] {
|
||||
auto msg = cquery_nofail(e, "SELECT * FROM tv;");
|
||||
assert_that(msg).is_rows().with_size(200);
|
||||
});
|
||||
|
||||
// Ghost row deletion is attempted with a parallelized table scan
|
||||
when_all(
|
||||
e.execute_cql("PRUNE MATERIALIZED VIEW tv WHERE token(v) >= -9223372036854775807 AND token(v) <= 0 USING CONCURRENCY 1"),
|
||||
e.execute_cql("PRUNE MATERIALIZED VIEW tv WHERE token(v) > 0 AND token(v) <= 10000000 USING CONCURRENCY 2"),
|
||||
e.execute_cql("PRUNE MATERIALIZED VIEW tv WHERE token(v) > 10000000 AND token(v) <= 20000000 USING CONCURRENCY 4"),
|
||||
e.execute_cql("PRUNE MATERIALIZED VIEW tv WHERE token(v) > 20000000 AND token(v) <= 30000000 USING CONCURRENCY 100"),
|
||||
e.execute_cql("PRUNE MATERIALIZED VIEW tv WHERE token(v) > 30000000 AND token(v) <= 9223372036854775807 USING CONCURRENCY 1000")
|
||||
).get();
|
||||
eventually([&] {
|
||||
// Ghost rows are deleted
|
||||
auto msg = cquery_nofail(e, "SELECT * FROM tv;");
|
||||
assert_that(msg).is_rows().with_rows_ignore_order(expected_view_rows);
|
||||
});
|
||||
});
|
||||
}
|
||||
// Reproducer for #18536.
|
||||
//
|
||||
// Paged index queries have been reported to cause reactor stalls on the
|
||||
|
||||
@@ -15,11 +15,14 @@
|
||||
#include <seastar/core/smp.hh>
|
||||
#include <seastar/util/closeable.hh>
|
||||
|
||||
#include "sstables/checksum_utils.hh"
|
||||
#include <seastar/util/short_streams.hh>
|
||||
#include "sstables/generation_type.hh"
|
||||
#include "sstables/sstables.hh"
|
||||
#include "sstables/key.hh"
|
||||
#include "sstables/open_info.hh"
|
||||
#include "sstables/version.hh"
|
||||
#include "test/lib/random_schema.hh"
|
||||
#include "test/lib/sstable_utils.hh"
|
||||
#include "test/lib/reader_concurrency_semaphore.hh"
|
||||
#include "test/lib/scylla_test_case.hh"
|
||||
@@ -32,6 +35,7 @@
|
||||
#include "partition_slice_builder.hh"
|
||||
#include "sstables/sstable_mutation_reader.hh"
|
||||
#include "sstables/binary_search.hh"
|
||||
#include "test/lib/random_utils.hh"
|
||||
|
||||
#include <boost/range/combine.hpp>
|
||||
|
||||
@@ -879,3 +883,101 @@ BOOST_AUTO_TEST_CASE(test_parse_path_bad) {
|
||||
BOOST_CHECK_THROW(parse_path(path), std::exception);
|
||||
}
|
||||
}
|
||||
|
||||
using compress_sstable = tests::random_schema_specification::compress_sstable;
|
||||
static future<> test_component_digest_persistence(component_type component, sstable::version_types version, compress_sstable compress = compress_sstable::no, bool rewrite_statistics = false) {
|
||||
return test_env::do_with_async([component, version, compress, rewrite_statistics] (test_env& env) mutable {
|
||||
auto random_spec = tests::make_random_schema_specification(
|
||||
"ks",
|
||||
std::uniform_int_distribution<size_t>(1, 4),
|
||||
std::uniform_int_distribution<size_t>(2, 4),
|
||||
std::uniform_int_distribution<size_t>(2, 8),
|
||||
std::uniform_int_distribution<size_t>(2, 8),
|
||||
compress);
|
||||
auto random_schema = tests::random_schema{tests::random::get_int<uint32_t>(), *random_spec};
|
||||
auto schema = random_schema.schema();
|
||||
|
||||
const auto muts = tests::generate_random_mutations(random_schema, 2).get();
|
||||
auto sst_original = make_sstable_containing(env.make_sstable(schema, version), muts);
|
||||
|
||||
auto& components = sstables::test(sst_original).get_components();
|
||||
bool has_component = components.find(component) != components.end();
|
||||
BOOST_REQUIRE(has_component);
|
||||
|
||||
auto toc_path = fmt::to_string(sst_original->toc_filename());
|
||||
auto entry_desc = sstables::parse_path(toc_path, schema->ks_name(), schema->cf_name());
|
||||
auto dir_path = std::filesystem::path(toc_path).parent_path().string();
|
||||
|
||||
std::optional<uint32_t> original_digest;
|
||||
if (rewrite_statistics) {
|
||||
original_digest = sst_original->get_component_digest(component);
|
||||
BOOST_REQUIRE(original_digest.has_value());
|
||||
|
||||
sst_original->mutate_sstable_level(10).get();
|
||||
|
||||
auto new_digest = sst_original->get_component_digest(component);
|
||||
BOOST_REQUIRE(new_digest.has_value());
|
||||
|
||||
BOOST_REQUIRE(original_digest.value() != new_digest.value());
|
||||
}
|
||||
|
||||
sst_original = nullptr;
|
||||
|
||||
auto sst_reopened = env.make_sstable(schema, dir_path, entry_desc.generation, entry_desc.version, entry_desc.format);
|
||||
sst_reopened->load(schema->get_sharder()).get();
|
||||
|
||||
auto loaded_digest = sst_reopened->get_component_digest(component);
|
||||
BOOST_REQUIRE(loaded_digest.has_value());
|
||||
|
||||
auto f = open_file_dma(sstables::test(sst_reopened).filename(component).native(), open_flags::ro).get();
|
||||
auto stream = make_file_input_stream(f);
|
||||
auto close_stream = deferred_close(stream);
|
||||
auto component_data = util::read_entire_stream_contiguous(stream).get();
|
||||
auto calculated_digest = crc32_utils::checksum(component_data.begin(), component_data.size());
|
||||
BOOST_REQUIRE_EQUAL(calculated_digest, loaded_digest.value());
|
||||
});
|
||||
}
|
||||
|
||||
SEASTAR_TEST_CASE(test_digest_persistence_index) {
|
||||
return test_component_digest_persistence(component_type::Index, sstable::version_types::me);
|
||||
}
|
||||
|
||||
SEASTAR_TEST_CASE(test_digest_persistence_partitions) {
|
||||
return test_component_digest_persistence(component_type::Partitions, sstable::version_types::ms);
|
||||
}
|
||||
|
||||
SEASTAR_TEST_CASE(test_digest_persistence_rows) {
|
||||
return test_component_digest_persistence(component_type::Rows, sstable::version_types::ms);
|
||||
}
|
||||
|
||||
SEASTAR_TEST_CASE(test_digest_persistence_summary) {
|
||||
return test_component_digest_persistence(component_type::Summary, sstable::version_types::me);
|
||||
}
|
||||
|
||||
SEASTAR_TEST_CASE(test_digest_persistence_filter) {
|
||||
return test_component_digest_persistence(component_type::Filter, sstable::version_types::me);
|
||||
}
|
||||
|
||||
SEASTAR_TEST_CASE(test_digest_persistence_compression) {
|
||||
return test_component_digest_persistence(component_type::CompressionInfo, sstable::version_types::me, compress_sstable::yes);
|
||||
}
|
||||
|
||||
SEASTAR_TEST_CASE(test_digest_persistence_toc) {
|
||||
return test_component_digest_persistence(component_type::TOC, sstable::version_types::me);
|
||||
}
|
||||
|
||||
SEASTAR_TEST_CASE(test_digest_persistence_statistics) {
|
||||
return test_component_digest_persistence(component_type::Statistics, sstable::version_types::me);
|
||||
}
|
||||
|
||||
SEASTAR_TEST_CASE(test_digest_persistence_statistics_rewrite) {
|
||||
return test_component_digest_persistence(component_type::Statistics, sstable::version_types::me, compress_sstable::no, true);
|
||||
}
|
||||
|
||||
SEASTAR_TEST_CASE(test_digest_persistence_data) {
|
||||
return test_component_digest_persistence(component_type::Data, sstable::version_types::me);
|
||||
}
|
||||
|
||||
SEASTAR_TEST_CASE(test_digest_persistence_data_compressed) {
|
||||
return test_component_digest_persistence(component_type::Data, sstable::version_types::me, compress_sstable::yes);
|
||||
}
|
||||
|
||||
65
test/boost/url_parse_test.cc
Normal file
65
test/boost/url_parse_test.cc
Normal file
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (C) 2025-present ScyllaDB
|
||||
*/
|
||||
|
||||
/*
|
||||
* SPDX-License-Identifier: LicenseRef-ScyllaDB-Source-Available-1.0
|
||||
*/
|
||||
|
||||
#define BOOST_TEST_MODULE core
|
||||
|
||||
#include <boost/test/unit_test.hpp>
|
||||
#include "utils/http.hh"
|
||||
|
||||
using namespace utils::http;
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_parse_ipv6) {
|
||||
|
||||
static const std::string ipv6_addr_str = "2001:db8:4006:812::200e";
|
||||
auto info = parse_simple_url("http://[" + ipv6_addr_str + "]:8080");
|
||||
|
||||
BOOST_CHECK_EQUAL(info.host, ipv6_addr_str);
|
||||
BOOST_CHECK_EQUAL(info.scheme, "http");
|
||||
BOOST_CHECK(!info.is_https());
|
||||
BOOST_CHECK_EQUAL(info.port, 8080);
|
||||
BOOST_CHECK_EQUAL(info.path, "");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_parse_kmip) {
|
||||
auto info = parse_simple_url("kmip://127.0.0.1");
|
||||
|
||||
BOOST_CHECK_EQUAL(info.host, "127.0.0.1");
|
||||
BOOST_CHECK_EQUAL(info.scheme, "kmip");
|
||||
BOOST_CHECK(!info.is_https());
|
||||
BOOST_CHECK_EQUAL(info.port, 80); // default
|
||||
BOOST_CHECK_EQUAL(info.path, "");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_parse_https) {
|
||||
auto info = parse_simple_url("https://127.0.0.1");
|
||||
|
||||
BOOST_CHECK_EQUAL(info.host, "127.0.0.1");
|
||||
BOOST_CHECK_EQUAL(info.scheme, "https");
|
||||
BOOST_CHECK(info.is_https());
|
||||
BOOST_CHECK_EQUAL(info.port, 443); // default
|
||||
|
||||
info = parse_simple_url("HTTPS://www.apa.org");
|
||||
|
||||
BOOST_CHECK_EQUAL(info.host, "www.apa.org");
|
||||
BOOST_CHECK_EQUAL(info.scheme, "HTTPS");
|
||||
BOOST_CHECK(info.is_https());
|
||||
BOOST_CHECK_EQUAL(info.port, 443); // default
|
||||
BOOST_CHECK_EQUAL(info.path, "");
|
||||
}
|
||||
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_parse_path) {
|
||||
auto info = parse_simple_url("https://127.0.0.1:333/ola/korv");
|
||||
|
||||
BOOST_CHECK_EQUAL(info.host, "127.0.0.1");
|
||||
BOOST_CHECK_EQUAL(info.scheme, "https");
|
||||
BOOST_CHECK(info.is_https());
|
||||
BOOST_CHECK_EQUAL(info.port, 333); // default
|
||||
BOOST_CHECK_EQUAL(info.path, "/ola/korv");
|
||||
}
|
||||
|
||||
@@ -135,17 +135,6 @@ async def s3_server(pytestconfig, tmpdir):
|
||||
finally:
|
||||
await server.stop()
|
||||
|
||||
def get_s3_resource(s3_server):
|
||||
"""Creates boto3.resource object that can be used to communicate to the given server"""
|
||||
return boto3.resource('s3',
|
||||
endpoint_url=f'http://{s3_server.address}:{s3_server.port}',
|
||||
aws_access_key_id=s3_server.acc_key,
|
||||
aws_secret_access_key=s3_server.secret_key,
|
||||
aws_session_token=None,
|
||||
config=boto3.session.Config(signature_version='s3v4'),
|
||||
verify=False
|
||||
)
|
||||
|
||||
class GSFront:
|
||||
def __init__(self, endpoint, bucket_name, credentials_file):
|
||||
self.endpoint = endpoint
|
||||
|
||||
@@ -61,7 +61,7 @@ async def test_coordinator_queue_management(manager: ManagerClient):
|
||||
|
||||
s = await manager.server_add(start=False)
|
||||
|
||||
tasks = [asyncio.create_task(manager.server_start(s.server_id, expected_error="request canceled because some required nodes are dead")),
|
||||
tasks = [asyncio.create_task(manager.server_start(s.server_id, expected_error="request canceled because some required nodes are dead|received notification of being banned from the cluster from")),
|
||||
asyncio.create_task(manager.decommission_node(servers[1].server_id, expected_error="Decommission failed. See earlier errors"))]
|
||||
|
||||
await wait_for_first_completed([
|
||||
|
||||
@@ -65,7 +65,7 @@ async def test_cannot_add_new_node(manager: ManagerClient, raft_op_timeout: int)
|
||||
manager.server_stop_gracefully(servers[4].server_id))
|
||||
|
||||
logger.info("starting a sixth node with no quorum")
|
||||
await manager.server_add(expected_error="raft operation [read_barrier] timed out, there is no raft quorum",
|
||||
await manager.server_add(expected_error="raft operation \\[read_barrier\\] timed out, there is no raft quorum",
|
||||
timeout=60)
|
||||
|
||||
logger.info("done")
|
||||
@@ -96,7 +96,7 @@ async def test_quorum_lost_during_node_join(manager: ManagerClient, raft_op_time
|
||||
logger.info("starting a fourth node")
|
||||
fourth_node_future = asyncio.create_task(manager.server_add(
|
||||
seeds=[servers[0].ip_addr],
|
||||
expected_error="raft operation [add_entry] timed out, there is no raft quorum",
|
||||
expected_error="raft operation \\[add_entry\\] timed out, there is no raft quorum",
|
||||
timeout=60))
|
||||
|
||||
logger.info(f"waiting for the leader node {servers[0]} to start handling the join request")
|
||||
@@ -141,7 +141,7 @@ async def test_quorum_lost_during_node_join_response_handler(manager: ManagerCli
|
||||
logger.info("starting a fourth node")
|
||||
fourth_node_future = asyncio.create_task(
|
||||
manager.server_start(servers[3].server_id,
|
||||
expected_error="raft operation [read_barrier] timed out, there is no raft quorum",
|
||||
expected_error="raft operation \\[read_barrier\\] timed out, there is no raft quorum",
|
||||
timeout=60))
|
||||
|
||||
logger.info(
|
||||
|
||||
@@ -339,3 +339,17 @@ async def test_repair_timtestamp_difference(manager):
|
||||
|
||||
logger.info("Checking timestamps after repair")
|
||||
check({host1: update2_timestamp, host2: update2_timestamp})
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_small_table_optimization_repair(manager):
|
||||
servers = await manager.servers_add(2, auto_rack_dc="dc1")
|
||||
|
||||
cql = manager.get_cql()
|
||||
|
||||
cql.execute("CREATE KEYSPACE ks WITH replication = {'class': 'NetworkTopologyStrategy', 'replication_factor': 2} AND TABLETS = {'enabled': false}")
|
||||
cql.execute("CREATE TABLE ks.tbl (pk int, ck int, PRIMARY KEY (pk, ck)) WITH tombstone_gc = {'mode': 'repair'}")
|
||||
|
||||
await manager.api.repair(servers[0].ip_addr, "ks", "tbl", small_table_optimization=True)
|
||||
|
||||
rows = await cql.run_async(f"SELECT * from system.repair_history")
|
||||
assert len(rows) == 1
|
||||
|
||||
63
test/cluster/test_rest_api_on_startup.py
Normal file
63
test/cluster/test_rest_api_on_startup.py
Normal file
@@ -0,0 +1,63 @@
|
||||
# Copyright (C) 2025-present ScyllaDB
|
||||
#
|
||||
# SPDX-License-Identifier: LicenseRef-ScyllaDB-Source-Available-1.0
|
||||
from test.pylib.manager_client import ManagerClient
|
||||
|
||||
import asyncio
|
||||
from datetime import datetime, timedelta
|
||||
import pytest
|
||||
import logging
|
||||
import time
|
||||
|
||||
from test.pylib.rest_client import HTTPError
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_rest_api_on_startup(request, manager: ManagerClient):
|
||||
|
||||
host = None
|
||||
stop_loop = False
|
||||
|
||||
# Asynchronously keep sending a REST API request in a loop.
|
||||
# This lets us verify that the server doesn't crash when it is
|
||||
# started or restarted while requests are ongoing.
|
||||
async def test_rest_api():
|
||||
timeout = 60 # seconds
|
||||
start_time = time.time()
|
||||
|
||||
while True:
|
||||
if time.time() - start_time > timeout:
|
||||
raise TimeoutError
|
||||
|
||||
try:
|
||||
logger.info(f"Sending raft_topology/reload request")
|
||||
result = await manager.api.client.post("/storage_service/raft_topology/reload", host=host)
|
||||
logger.info(f"Received result {result}")
|
||||
if stop_loop:
|
||||
return
|
||||
except Exception as ex:
|
||||
# Some errors are expected, for example:
|
||||
# - Initially, `host=None`, so `manager.api.client.post` fails
|
||||
# - Scylla returns 404 until the `/storage_service/raft_topology/reload` endpoint exists
|
||||
# - aiohttp raises ClientConnectorError when it cannot connect
|
||||
# This is okay. The important point is that ScyllaDB does not crash,
|
||||
# so the final request should succeed.
|
||||
pass
|
||||
|
||||
# Avoid spamming requests to prevent log flooding.
|
||||
# One request per millisecond should be sufficient to expose issues.
|
||||
await asyncio.sleep(0.001)
|
||||
|
||||
fut = asyncio.create_task(test_rest_api())
|
||||
logger.info("Starting server")
|
||||
server = await manager.server_add()
|
||||
host = server.ip_addr
|
||||
|
||||
logger.info("Restarting server")
|
||||
await manager.server_restart(server.server_id)
|
||||
|
||||
logger.info("Stopping the loop")
|
||||
stop_loop = True
|
||||
await fut
|
||||
|
||||
@@ -9,6 +9,7 @@ from cassandra.query import SimpleStatement, ConsistencyLevel
|
||||
from test.pylib.manager_client import ManagerClient
|
||||
from test.pylib.rest_client import HTTPError, read_barrier
|
||||
from test.pylib.tablets import get_tablet_replica, get_all_tablet_replicas, get_tablet_info
|
||||
from test.pylib.util import start_writes
|
||||
from test.cluster.conftest import skip_mode
|
||||
from test.cluster.util import wait_for_cql_and_get_hosts, new_test_keyspace, reconnect_driver, wait_for
|
||||
import time
|
||||
@@ -127,9 +128,10 @@ async def test_node_failure_during_tablet_migration(manager: ManagerClient, fail
|
||||
|
||||
await make_server("r1")
|
||||
await make_server("r2")
|
||||
await make_server("r3")
|
||||
cql = manager.get_cql()
|
||||
|
||||
async with new_test_keyspace(manager, "WITH replication = {'class': 'NetworkTopologyStrategy', 'replication_factor': 2} AND tablets = {'initial': 1}") as ks:
|
||||
async with new_test_keyspace(manager, "WITH replication = {'class': 'NetworkTopologyStrategy', 'replication_factor': 3} AND tablets = {'initial': 1}") as ks:
|
||||
await cql.run_async(f"CREATE TABLE {ks}.test (pk int PRIMARY KEY, c int);")
|
||||
|
||||
keys = range(256)
|
||||
@@ -153,30 +155,30 @@ async def test_node_failure_during_tablet_migration(manager: ManagerClient, fail
|
||||
|
||||
replicas = await get_all_tablet_replicas(manager, servers[0], ks, 'test')
|
||||
logger.info(f"Tablet is on [{replicas}]")
|
||||
assert len(replicas) == 1 and len(replicas[0].replicas) == 2
|
||||
assert len(replicas) == 1 and len(replicas[0].replicas) == 3
|
||||
|
||||
last_token = replicas[0].last_token
|
||||
old_replica = None
|
||||
for r in replicas[0].replicas:
|
||||
assert r[0] != host_ids[2], "Tablet got migrated to node2"
|
||||
assert r[0] != host_ids[3], "Tablet got migrated to node3"
|
||||
if r[0] == host_ids[1]:
|
||||
old_replica = r
|
||||
assert old_replica is not None
|
||||
new_replica = (host_ids[2], 0)
|
||||
new_replica = (host_ids[3], 0)
|
||||
logger.info(f"Moving tablet {old_replica} -> {new_replica}")
|
||||
|
||||
class node_failer:
|
||||
def __init__(self, stage, replica, ks):
|
||||
self.stage = stage
|
||||
self.replica = replica
|
||||
self.fail_idx = 1 if self.replica == "source" else 2
|
||||
self.fail_idx = 1 if self.replica == "source" else 3
|
||||
self.ks = ks
|
||||
|
||||
async def setup(self):
|
||||
logger.info(f"Will fail {self.stage}")
|
||||
if self.stage == "streaming":
|
||||
await manager.api.enable_injection(servers[2].ip_addr, "stream_mutation_fragments", one_shot=True)
|
||||
self.log = await manager.server_open_log(servers[2].server_id)
|
||||
await manager.api.enable_injection(servers[3].ip_addr, "stream_mutation_fragments", one_shot=True)
|
||||
self.log = await manager.server_open_log(servers[3].server_id)
|
||||
self.mark = await self.log.mark()
|
||||
elif self.stage in [ "allow_write_both_read_old", "write_both_read_old", "write_both_read_new", "use_new", "end_migration", "do_revert_migration" ]:
|
||||
await manager.api.enable_injection(servers[self.fail_idx].ip_addr, "raft_topology_barrier_and_drain_fail", one_shot=False,
|
||||
@@ -188,7 +190,7 @@ async def test_node_failure_during_tablet_migration(manager: ManagerClient, fail
|
||||
self.log = await manager.server_open_log(servers[self.fail_idx].server_id)
|
||||
self.mark = await self.log.mark()
|
||||
elif self.stage == "cleanup_target":
|
||||
assert self.fail_idx == 2
|
||||
assert self.fail_idx == 3
|
||||
self.stream_fail = node_failer('streaming', 'source', ks)
|
||||
await self.stream_fail.setup()
|
||||
self.cleanup_fail = node_failer('cleanup', 'destination', ks)
|
||||
@@ -222,11 +224,11 @@ async def test_node_failure_during_tablet_migration(manager: ManagerClient, fail
|
||||
|
||||
async def stop(self, via=0):
|
||||
if self.stage == "cleanup_target":
|
||||
await self.cleanup_fail.stop(via=3) # removenode of source is happening via node0 already
|
||||
await self.cleanup_fail.stop(via=4) # removenode of source is happening via node0 already
|
||||
await self.stream_stop_task
|
||||
return
|
||||
if self.stage == "revert_migration":
|
||||
await self.revert_fail.stop(via=3)
|
||||
await self.revert_fail.stop(via=4)
|
||||
await self.wbro_fail_task
|
||||
return
|
||||
|
||||
@@ -236,6 +238,7 @@ async def test_node_failure_during_tablet_migration(manager: ManagerClient, fail
|
||||
await manager.remove_node(servers[via].server_id, servers[self.fail_idx].server_id)
|
||||
logger.info(f"Done with {self.replica} {host_ids[self.fail_idx]}")
|
||||
|
||||
finish_writes = await start_writes(cql, ks, "test")
|
||||
|
||||
failer = node_failer(fail_stage, fail_replica, ks)
|
||||
await failer.setup()
|
||||
@@ -253,6 +256,8 @@ async def test_node_failure_during_tablet_migration(manager: ManagerClient, fail
|
||||
for r in replicas[0].replicas:
|
||||
assert r[0] != host_ids[failer.fail_idx]
|
||||
|
||||
await finish_writes()
|
||||
|
||||
# For dropping the keyspace after the node failure
|
||||
await reconnect_driver(manager)
|
||||
|
||||
|
||||
39
test/cluster/test_vector_store.py
Normal file
39
test/cluster/test_vector_store.py
Normal file
@@ -0,0 +1,39 @@
|
||||
#
|
||||
# Copyright (C) 2025-present ScyllaDB
|
||||
#
|
||||
# SPDX-License-Identifier: LicenseRef-ScyllaDB-Source-Available-1.0
|
||||
#
|
||||
|
||||
import logging
|
||||
import pytest
|
||||
|
||||
from test.pylib.manager_client import ManagerClient
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# Regression test for SCYLLADB-81.
|
||||
#
|
||||
# Unlike regular secondary indexes, vector indexes do not use materialized views
|
||||
# in their implementation, therefore they do not need the keyspace
|
||||
# to be rf-rack-valid in order to function properly.
|
||||
#
|
||||
# In this test, we check that creating a vector index without
|
||||
# rf_rack_valid_keyspaces being set is possible.
|
||||
@pytest.mark.asyncio
|
||||
async def test_vector_store_can_be_created_without_rf_rack_valid(manager: ManagerClient):
|
||||
# Explicitly disable the rf_rack_valid_keyspaces option.
|
||||
config = {"rf_rack_valid_keyspaces": False}
|
||||
srv = await manager.server_add(config=config)
|
||||
cql, _ = await manager.get_ready_cql([srv])
|
||||
|
||||
# Explicitly create a keyspace with tablets.
|
||||
await cql.run_async("CREATE KEYSPACE ks WITH replication = "
|
||||
"{'class': 'NetworkTopologyStrategy', 'replication_factor': 1} "
|
||||
"AND tablets = {'enabled': true}")
|
||||
|
||||
await cql.run_async("CREATE TABLE ks.t (pk int PRIMARY KEY, v vector<float, 3>)")
|
||||
|
||||
# Creating a vector store index should succeed.
|
||||
await cql.run_async("CREATE CUSTOM INDEX ON ks.t(v) USING 'vector_index'")
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user