Compare commits
1 Commits
next-2025.
...
auto-backp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dcbc6c839d |
14
.github/CODEOWNERS
vendored
14
.github/CODEOWNERS
vendored
@@ -1,5 +1,5 @@
|
||||
# AUTH
|
||||
auth/* @nuivall @ptrsmrn
|
||||
auth/* @nuivall @ptrsmrn @KrzaQ
|
||||
|
||||
# CACHE
|
||||
row_cache* @tgrabiec
|
||||
@@ -25,15 +25,15 @@ compaction/* @raphaelsc
|
||||
transport/*
|
||||
|
||||
# CQL QUERY LANGUAGE
|
||||
cql3/* @tgrabiec @nuivall @ptrsmrn
|
||||
cql3/* @tgrabiec @nuivall @ptrsmrn @KrzaQ
|
||||
|
||||
# COUNTERS
|
||||
counters* @nuivall @ptrsmrn
|
||||
tests/counter_test* @nuivall @ptrsmrn
|
||||
counters* @nuivall @ptrsmrn @KrzaQ
|
||||
tests/counter_test* @nuivall @ptrsmrn @KrzaQ
|
||||
|
||||
# DOCS
|
||||
docs/* @annastuchlik @tzach
|
||||
docs/alternator @annastuchlik @tzach @nyh
|
||||
docs/alternator @annastuchlik @tzach @nyh @nuivall @ptrsmrn @KrzaQ
|
||||
|
||||
# GOSSIP
|
||||
gms/* @tgrabiec @asias @kbr-scylla
|
||||
@@ -74,8 +74,8 @@ streaming/* @tgrabiec @asias
|
||||
service/storage_service.* @tgrabiec @asias
|
||||
|
||||
# ALTERNATOR
|
||||
alternator/* @nyh
|
||||
test/alternator/* @nyh
|
||||
alternator/* @nyh @nuivall @ptrsmrn @KrzaQ
|
||||
test/alternator/* @nyh @nuivall @ptrsmrn @KrzaQ
|
||||
|
||||
# HINTED HANDOFF
|
||||
db/hints/* @piodul @vladzcloudius @eliransin
|
||||
|
||||
97
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
97
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -1,86 +1,15 @@
|
||||
name: "Report a bug"
|
||||
description: "File a bug report."
|
||||
title: "[Bug]: "
|
||||
type: "bug"
|
||||
labels: bug
|
||||
body:
|
||||
- type: checkboxes
|
||||
id: terms
|
||||
attributes:
|
||||
label: Code of Conduct
|
||||
description: "This is Scylla's bug tracker, to be used for reporting bugs only.
|
||||
This is Scylla's bug tracker, to be used for reporting bugs only.
|
||||
If you have a question about Scylla, and not a bug, please ask it in
|
||||
our forum at https://forum.scylladb.com/ or in our slack channel https://slack.scylladb.com/ "
|
||||
options:
|
||||
- label: I have read the disclaimer above and am reporting a suspected malfunction in Scylla.
|
||||
required: true
|
||||
our mailing-list at scylladb-dev@googlegroups.com or in our slack channel.
|
||||
|
||||
- type: input
|
||||
id: product-version
|
||||
attributes:
|
||||
label: product version
|
||||
description: Scylla version (or git commit hash)
|
||||
placeholder: ex. scylla-6.1.1
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: cluster-size
|
||||
attributes:
|
||||
label: Cluster Size
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: os
|
||||
attributes:
|
||||
label: OS
|
||||
placeholder: RHEL/CentOS/Ubuntu/AWS AMI
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: additional-data
|
||||
attributes:
|
||||
label: Additional Environmental Data
|
||||
#description:
|
||||
placeholder: Add additional data
|
||||
value: "Platform (physical/VM/cloud instance type/docker):\n
|
||||
Hardware: sockets= cores= hyperthreading= memory=\n
|
||||
Disks: (SSD/HDD, count)"
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: reproducer-steps
|
||||
attributes:
|
||||
label: Reproduction Steps
|
||||
placeholder: Describe how to reproduce the problem
|
||||
value: "The steps to reproduce the problem are:"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: the-problem
|
||||
attributes:
|
||||
label: What is the problem?
|
||||
placeholder: Describe the problem you found
|
||||
value: "The problem is that"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: what-happened
|
||||
attributes:
|
||||
label: Expected behavior?
|
||||
placeholder: Describe what should have happened
|
||||
value: "I expected that "
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Relevant log output
|
||||
description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
|
||||
render: shell
|
||||
- [] I have read the disclaimer above, and I am reporting a suspected malfunction in Scylla.
|
||||
|
||||
*Installation details*
|
||||
Scylla version (or git commit hash):
|
||||
Cluster size:
|
||||
OS (RHEL/CentOS/Ubuntu/AWS AMI):
|
||||
|
||||
*Hardware details (for performance issues)* Delete if unneeded
|
||||
Platform (physical/VM/cloud instance type/docker):
|
||||
Hardware: sockets= cores= hyperthreading= memory=
|
||||
Disks: (SSD/HDD, count)
|
||||
|
||||
50
.github/scripts/auto-backport.py
vendored
50
.github/scripts/auto-backport.py
vendored
@@ -52,7 +52,7 @@ def create_pull_request(repo, new_branch_name, base_branch_name, pr, backport_pr
|
||||
if is_draft:
|
||||
backport_pr.add_to_labels("conflicts")
|
||||
pr_comment = f"@{pr.user.login} - This PR was marked as draft because it has conflicts\n"
|
||||
pr_comment += "Please resolve them and remove the 'conflicts' label. The PR will be made ready for review automatically."
|
||||
pr_comment += "Please resolve them and mark this PR as ready for review"
|
||||
backport_pr.create_issue_comment(pr_comment)
|
||||
logging.info(f"Assigned PR to original author: {pr.user}")
|
||||
return backport_pr
|
||||
@@ -112,45 +112,29 @@ def backport(repo, pr, version, commits, backport_base_branch, is_collaborator):
|
||||
is_draft = True
|
||||
repo_local.git.add(A=True)
|
||||
repo_local.git.cherry_pick('--continue')
|
||||
# Check if the branch already exists in the remote fork
|
||||
remote_refs = repo_local.git.ls_remote('--heads', fork_repo, new_branch_name)
|
||||
if not remote_refs:
|
||||
# Branch does not exist, create it with a regular push
|
||||
repo_local.git.push(fork_repo, new_branch_name)
|
||||
create_pull_request(repo, new_branch_name, backport_base_branch, pr, backport_pr_title, commits,
|
||||
is_draft, is_collaborator)
|
||||
else:
|
||||
logging.info(f"Remote branch {new_branch_name} already exists in fork. Skipping push.")
|
||||
repo_local.git.push(fork_repo, new_branch_name, force=True)
|
||||
create_pull_request(repo, new_branch_name, backport_base_branch, pr, backport_pr_title, commits,
|
||||
is_draft, is_collaborator)
|
||||
|
||||
except GitCommandError as e:
|
||||
logging.warning(f"GitCommandError: {e}")
|
||||
|
||||
|
||||
def with_github_keyword_prefix(repo, pr):
|
||||
# GitHub issue pattern: #123, scylladb/scylladb#123, or full GitHub URLs
|
||||
github_pattern = rf"(?:fix(?:|es|ed))\s*:?\s*(?:(?:(?:{repo.full_name})?#)|https://github\.com/{repo.full_name}/issues/)(\d+)"
|
||||
|
||||
# JIRA issue pattern: PKG-92 or https://scylladb.atlassian.net/browse/PKG-92
|
||||
jira_pattern = r"(?:fix(?:|es|ed))\s*:?\s*(?:(?:https://scylladb\.atlassian\.net/browse/)?([A-Z]+-\d+))"
|
||||
|
||||
# Check PR body for GitHub issues
|
||||
github_match = re.findall(github_pattern, pr.body, re.IGNORECASE)
|
||||
# Check PR body for JIRA issues
|
||||
jira_match = re.findall(jira_pattern, pr.body, re.IGNORECASE)
|
||||
|
||||
match = github_match or jira_match
|
||||
|
||||
if match:
|
||||
pattern = rf"(?:fix(?:|es|ed))\s*:?\s*(?:(?:(?:{repo.full_name})?#)|https://github\.com/{repo.full_name}/issues/)(\d+)"
|
||||
match = re.findall(pattern, pr.body, re.IGNORECASE)
|
||||
if not match:
|
||||
for commit in pr.get_commits():
|
||||
match = re.findall(pattern, commit.commit.message, re.IGNORECASE)
|
||||
if match:
|
||||
print(f'{pr.number} has a valid close reference in commit message {commit.sha}')
|
||||
break
|
||||
if not match:
|
||||
print(f'No valid close reference for {pr.number}')
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
for commit in pr.get_commits():
|
||||
github_match = re.findall(github_pattern, commit.commit.message, re.IGNORECASE)
|
||||
jira_match = re.findall(jira_pattern, commit.commit.message, re.IGNORECASE)
|
||||
if github_match or jira_match:
|
||||
print(f'{pr.number} has a valid close reference in commit message {commit.sha}')
|
||||
return True
|
||||
|
||||
print(f'No valid close reference for {pr.number}')
|
||||
return False
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
|
||||
16
.github/seastar-bad-include.json
vendored
16
.github/seastar-bad-include.json
vendored
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"problemMatcher": [
|
||||
{
|
||||
"owner": "seastar-bad-include",
|
||||
"severity": "error",
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": "^(.+):(\\d+):(.+)$",
|
||||
"file": 1,
|
||||
"line": 2,
|
||||
"message": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
|
||||
// Regular expression pattern to check for "Fixes" prefix
|
||||
// Adjusted to dynamically insert the repository full name
|
||||
const pattern = `Fixes:? ((?:#|${repo.replace('/', '\\/')}#|https://github\\.com/${repo.replace('/', '\\/')}/issues/)(\\d+)|([A-Z]+-\\d+))`;
|
||||
const pattern = `Fixes:? (?:#|${repo.replace('/', '\\/')}#|https://github\\.com/${repo.replace('/', '\\/')}/issues/)(\\d+)`;
|
||||
const regex = new RegExp(pattern);
|
||||
|
||||
if (!regex.test(body)) {
|
||||
|
||||
24
.github/workflows/iwyu.yaml
vendored
24
.github/workflows/iwyu.yaml
vendored
@@ -11,8 +11,7 @@ env:
|
||||
CLEANER_OUTPUT_PATH: build/clang-include-cleaner.log
|
||||
# the "idl" subdirectory does not contain C++ source code. the .hh files in it are
|
||||
# supposed to be processed by idl-compiler.py, so we don't check them using the cleaner
|
||||
CLEANER_DIRS: test/unit exceptions alternator api auth cdc compaction db dht gms index lang message mutation mutation_writer node_ops raft redis replica service
|
||||
SEASTAR_BAD_INCLUDE_OUTPUT_PATH: build/seastar-bad-include.log
|
||||
CLEANER_DIRS: test/unit exceptions alternator api auth cdc compaction db dht gms index lang message mutation mutation_writer node_ops redis replica
|
||||
|
||||
permissions: {}
|
||||
|
||||
@@ -81,24 +80,7 @@ jobs:
|
||||
done
|
||||
- run: |
|
||||
echo "::remove-matcher owner=clang-include-cleaner::"
|
||||
- run: |
|
||||
echo "::add-matcher::.github/seastar-bad-include.json"
|
||||
- name: check for seastar includes
|
||||
run: |
|
||||
git -c safe.directory="$PWD" \
|
||||
grep -nE '#include +"seastar/' \
|
||||
| tee "$SEASTAR_BAD_INCLUDE_OUTPUT_PATH"
|
||||
- run: |
|
||||
echo "::remove-matcher owner=seastar-bad-include::"
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Logs
|
||||
path: |
|
||||
${{ env.CLEANER_OUTPUT_PATH }}
|
||||
${{ env.SEASTAR_BAD_INCLUDE_OUTPUT_PATH }}
|
||||
- name: fail if seastar headers are included as an internal library
|
||||
run: |
|
||||
if [ -s "$SEASTAR_BAD_INCLUDE_OUTPUT_PATH" ]; then
|
||||
echo "::error::Found #include \"seastar/ in the source code. Use angle brackets instead."
|
||||
exit 1
|
||||
fi
|
||||
name: Logs (clang-include-cleaner)
|
||||
path: "./${{ env.CLEANER_OUTPUT_PATH }}"
|
||||
|
||||
@@ -16,13 +16,6 @@ jobs:
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: ${{ github.repository }}
|
||||
ref: ${{ env.DEFAULT_BRANCH }}
|
||||
token: ${{ secrets.AUTO_BACKPORT_TOKEN }}
|
||||
fetch-depth: 1
|
||||
- name: Mark pull request as ready for review
|
||||
run: gh pr ready "${{ github.event.pull_request.number }}"
|
||||
env:
|
||||
|
||||
@@ -13,8 +13,6 @@ jobs:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Wait for label to be added
|
||||
run: sleep 1m
|
||||
- uses: mheap/github-action-required-labels@v5
|
||||
with:
|
||||
mode: minimum
|
||||
|
||||
5
.gitmodules
vendored
5
.gitmodules
vendored
@@ -1,6 +1,6 @@
|
||||
[submodule "seastar"]
|
||||
path = seastar
|
||||
url = ../scylla-seastar
|
||||
url = ../seastar
|
||||
ignore = dirty
|
||||
[submodule "swagger-ui"]
|
||||
path = swagger-ui
|
||||
@@ -9,6 +9,9 @@
|
||||
[submodule "abseil"]
|
||||
path = abseil
|
||||
url = ../abseil-cpp
|
||||
[submodule "scylla-tools"]
|
||||
path = tools/java
|
||||
url = ../scylla-tools-java
|
||||
[submodule "scylla-python3"]
|
||||
path = tools/python3
|
||||
url = ../scylla-python3
|
||||
|
||||
@@ -163,6 +163,14 @@ file(MAKE_DIRECTORY "${scylla_gen_build_dir}")
|
||||
include(add_version_library)
|
||||
generate_scylla_version()
|
||||
|
||||
add_library(scylla-zstd STATIC
|
||||
zstd.cc)
|
||||
target_link_libraries(scylla-zstd
|
||||
PRIVATE
|
||||
db
|
||||
Seastar::seastar
|
||||
zstd::libzstd)
|
||||
|
||||
add_library(scylla-main STATIC)
|
||||
target_sources(scylla-main
|
||||
PRIVATE
|
||||
@@ -174,7 +182,7 @@ target_sources(scylla-main
|
||||
compress.cc
|
||||
converting_mutation_partition_applier.cc
|
||||
counters.cc
|
||||
sstable_dict_autotrainer.cc
|
||||
direct_failure_detector/failure_detector.cc
|
||||
duration.cc
|
||||
exceptions/exceptions.cc
|
||||
frozen_schema.cc
|
||||
@@ -196,7 +204,6 @@ target_sources(scylla-main
|
||||
reader_concurrency_semaphore_group.cc
|
||||
schema_mutations.cc
|
||||
serializer.cc
|
||||
service/direct_failure_detector/failure_detector.cc
|
||||
sstables_loader.cc
|
||||
table_helper.cc
|
||||
tasks/task_handler.cc
|
||||
@@ -207,6 +214,7 @@ target_sources(scylla-main
|
||||
vint-serialization.cc)
|
||||
target_link_libraries(scylla-main
|
||||
PRIVATE
|
||||
"$<LINK_LIBRARY:WHOLE_ARCHIVE,scylla-zstd>"
|
||||
db
|
||||
absl::headers
|
||||
absl::btree
|
||||
|
||||
25
HACKING.md
25
HACKING.md
@@ -220,9 +220,28 @@ On a development machine, one might run Scylla as
|
||||
$ SCYLLA_HOME=$HOME/scylla build/release/scylla --overprovisioned --developer-mode=yes
|
||||
```
|
||||
|
||||
To interact with scylla it is recommended to build our version of
|
||||
cqlsh. It is available at
|
||||
https://github.com/scylladb/scylla-cqlsh and is available as a submodule.
|
||||
To interact with scylla it is recommended to build our versions of
|
||||
cqlsh and nodetool. They are available at
|
||||
https://github.com/scylladb/scylla-tools-java and can be built with
|
||||
|
||||
```bash
|
||||
$ sudo ./install-dependencies.sh
|
||||
$ ant jar
|
||||
```
|
||||
|
||||
cqlsh should work out of the box, but nodetool depends on a running
|
||||
scylla-jmx (https://github.com/scylladb/scylla-jmx). It can be build
|
||||
with
|
||||
|
||||
```bash
|
||||
$ mvn package
|
||||
```
|
||||
|
||||
and must be started with
|
||||
|
||||
```bash
|
||||
$ ./scripts/scylla-jmx
|
||||
```
|
||||
|
||||
### Branches and tags
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ fi
|
||||
|
||||
# Default scylla product/version tags
|
||||
PRODUCT=scylla
|
||||
VERSION=2025.2.6
|
||||
VERSION=2025.2.0-dev
|
||||
|
||||
if test -f version
|
||||
then
|
||||
|
||||
@@ -24,7 +24,7 @@ static constexpr uint64_t KB = 1024ULL;
|
||||
static constexpr uint64_t RCU_BLOCK_SIZE_LENGTH = 4*KB;
|
||||
static constexpr uint64_t WCU_BLOCK_SIZE_LENGTH = 1*KB;
|
||||
|
||||
bool consumed_capacity_counter::should_add_capacity(const rjson::value& request) {
|
||||
static bool should_add_capacity(const rjson::value& request) {
|
||||
const rjson::value* return_consumed = rjson::find(request, "ReturnConsumedCapacity");
|
||||
if (!return_consumed) {
|
||||
return false;
|
||||
@@ -62,22 +62,15 @@ static uint64_t calculate_half_units(uint64_t unit_block_size, uint64_t total_by
|
||||
rcu_consumed_capacity_counter::rcu_consumed_capacity_counter(const rjson::value& request, bool is_quorum) :
|
||||
consumed_capacity_counter(should_add_capacity(request)),_is_quorum(is_quorum) {
|
||||
}
|
||||
uint64_t rcu_consumed_capacity_counter::get_half_units(uint64_t total_bytes, bool is_quorum) noexcept {
|
||||
return calculate_half_units(RCU_BLOCK_SIZE_LENGTH, total_bytes, is_quorum);
|
||||
}
|
||||
|
||||
uint64_t rcu_consumed_capacity_counter::get_half_units() const noexcept {
|
||||
return get_half_units(_total_bytes, _is_quorum);
|
||||
return calculate_half_units(RCU_BLOCK_SIZE_LENGTH, _total_bytes, _is_quorum);
|
||||
}
|
||||
|
||||
uint64_t wcu_consumed_capacity_counter::get_half_units() const noexcept {
|
||||
return calculate_half_units(WCU_BLOCK_SIZE_LENGTH, _total_bytes, true);
|
||||
}
|
||||
|
||||
uint64_t wcu_consumed_capacity_counter::get_units(uint64_t total_bytes) noexcept {
|
||||
return calculate_half_units(WCU_BLOCK_SIZE_LENGTH, total_bytes, true) * HALF_UNIT_MULTIPLIER;
|
||||
}
|
||||
|
||||
wcu_consumed_capacity_counter::wcu_consumed_capacity_counter(const rjson::value& request) :
|
||||
consumed_capacity_counter(should_add_capacity(request)) {
|
||||
}
|
||||
|
||||
@@ -42,25 +42,21 @@ public:
|
||||
*/
|
||||
virtual uint64_t get_half_units() const noexcept = 0;
|
||||
uint64_t _total_bytes = 0;
|
||||
static bool should_add_capacity(const rjson::value& request);
|
||||
protected:
|
||||
bool _should_add_to_reponse = false;
|
||||
};
|
||||
|
||||
class rcu_consumed_capacity_counter : public consumed_capacity_counter {
|
||||
virtual uint64_t get_half_units() const noexcept;
|
||||
bool _is_quorum = false;
|
||||
public:
|
||||
rcu_consumed_capacity_counter(const rjson::value& request, bool is_quorum);
|
||||
rcu_consumed_capacity_counter(): consumed_capacity_counter(false), _is_quorum(false){}
|
||||
virtual uint64_t get_half_units() const noexcept;
|
||||
static uint64_t get_half_units(uint64_t total_bytes, bool is_quorum) noexcept;
|
||||
};
|
||||
|
||||
class wcu_consumed_capacity_counter : public consumed_capacity_counter {
|
||||
virtual uint64_t get_half_units() const noexcept;
|
||||
public:
|
||||
wcu_consumed_capacity_counter(const rjson::value& request);
|
||||
static uint64_t get_units(uint64_t total_bytes) noexcept;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -241,8 +241,7 @@ public:
|
||||
const query::partition_slice&& slice,
|
||||
shared_ptr<cql3::selection::selection> selection,
|
||||
foreign_ptr<lw_shared_ptr<query::result>> query_result,
|
||||
shared_ptr<const std::optional<attrs_to_get>> attrs_to_get,
|
||||
uint64_t& rcu_half_units);
|
||||
shared_ptr<const std::optional<attrs_to_get>> attrs_to_get);
|
||||
|
||||
static void describe_single_item(const cql3::selection::selection&,
|
||||
const std::vector<managed_bytes_opt>&,
|
||||
|
||||
@@ -165,9 +165,7 @@ static std::optional<std::string> resolve_path_component(const std::string& colu
|
||||
fmt::format("ExpressionAttributeNames missing entry '{}' required by expression", column_name));
|
||||
}
|
||||
used_attribute_names.emplace(column_name);
|
||||
auto result = std::string(rjson::to_string_view(*value));
|
||||
validate_attr_name_length("", result.size(), false, "ExpressionAttributeNames contains invalid value: ");
|
||||
return result;
|
||||
return std::string(rjson::to_string_view(*value));
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
@@ -739,26 +737,6 @@ rjson::value calculate_value(const parsed::set_rhs& rhs,
|
||||
return rjson::null_value();
|
||||
}
|
||||
|
||||
void validate_attr_name_length(std::string_view supplementary_context, size_t attr_name_length, bool is_key, std::string_view error_msg_prefix) {
|
||||
constexpr const size_t DYNAMODB_KEY_ATTR_NAME_SIZE_MAX = 255;
|
||||
constexpr const size_t DYNAMODB_NONKEY_ATTR_NAME_SIZE_MAX = 65535;
|
||||
|
||||
const size_t max_length = is_key ? DYNAMODB_KEY_ATTR_NAME_SIZE_MAX : DYNAMODB_NONKEY_ATTR_NAME_SIZE_MAX;
|
||||
if (attr_name_length > max_length) {
|
||||
std::string error_msg;
|
||||
if (!error_msg_prefix.empty()) {
|
||||
error_msg += error_msg_prefix;
|
||||
}
|
||||
if (!supplementary_context.empty()) {
|
||||
error_msg += "in ";
|
||||
error_msg += supplementary_context;
|
||||
error_msg += " - ";
|
||||
}
|
||||
error_msg += fmt::format("Attribute name is too large, must be less than {} bytes", std::to_string(max_length + 1));
|
||||
throw api_error::validation(error_msg);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace alternator
|
||||
|
||||
auto fmt::formatter<alternator::parsed::path>::format(const alternator::parsed::path& p, fmt::format_context& ctx) const
|
||||
|
||||
@@ -91,18 +91,6 @@ options {
|
||||
throw expressions_syntax_error(format("{} at char {}", err,
|
||||
ex->get_charPositionInLine()));
|
||||
}
|
||||
|
||||
// ANTLR3 tries to recover missing tokens - it tries to finish parsing
|
||||
// and create valid objects, as if the missing token was there.
|
||||
// But it has a bug and leaks these tokens.
|
||||
// We override offending method and handle abandoned pointers.
|
||||
std::vector<std::unique_ptr<TokenType>> _missing_tokens;
|
||||
TokenType* getMissingSymbol(IntStreamType* istream, ExceptionBaseType* e,
|
||||
ANTLR_UINT32 expectedTokenType, BitsetListType* follow) {
|
||||
auto token = BaseType::getMissingSymbol(istream, e, expectedTokenType, follow);
|
||||
_missing_tokens.emplace_back(token);
|
||||
return token;
|
||||
}
|
||||
}
|
||||
@lexer::context {
|
||||
void displayRecognitionError(ANTLR_UINT8** token_names, ExceptionBaseType* ex) {
|
||||
|
||||
@@ -91,7 +91,5 @@ rjson::value calculate_value(const parsed::value& v,
|
||||
rjson::value calculate_value(const parsed::set_rhs& rhs,
|
||||
const rjson::value* previous_item);
|
||||
|
||||
void validate_attr_name_length(std::string_view supplementary_context, size_t attr_name_length, bool is_key, std::string_view error_msg_prefix = {});
|
||||
|
||||
|
||||
} /* namespace alternator */
|
||||
|
||||
@@ -228,8 +228,9 @@ protected:
|
||||
// If the rack does not exist, we return an empty list - not an error.
|
||||
sstring query_rack = req->get_query_param("rack");
|
||||
for (auto& id : local_dc_nodes) {
|
||||
auto ip = _gossiper.get_address_map().get(id);
|
||||
if (!query_rack.empty()) {
|
||||
auto rack = _gossiper.get_application_state_value(id, gms::application_state::RACK);
|
||||
auto rack = _gossiper.get_application_state_value(ip, gms::application_state::RACK);
|
||||
if (rack != query_rack) {
|
||||
continue;
|
||||
}
|
||||
@@ -237,10 +238,10 @@ protected:
|
||||
// Note that it's not enough for the node to be is_alive() - a
|
||||
// node joining the cluster is also "alive" but not responsive to
|
||||
// requests. We alive *and* normal. See #19694, #21538.
|
||||
if (_gossiper.is_alive(id) && _gossiper.is_normal(id)) {
|
||||
if (_gossiper.is_alive(ip) && _gossiper.is_normal(ip)) {
|
||||
// Use the gossiped broadcast_rpc_address if available instead
|
||||
// of the internal IP address "ip". See discussion in #18711.
|
||||
rjson::push_back(results, rjson::from_string(_gossiper.get_rpc_address(id)));
|
||||
rjson::push_back(results, rjson::from_string(_gossiper.get_rpc_address(ip)));
|
||||
}
|
||||
}
|
||||
rep->set_status(reply::status_type::ok);
|
||||
@@ -504,7 +505,7 @@ server::server(executor& exec, service::storage_proxy& proxy, gms::gossiper& gos
|
||||
, _key_cache(1024, 1min, slogger)
|
||||
, _enforce_authorization(false)
|
||||
, _enabled_servers{}
|
||||
, _pending_requests("alternator::server::pending_requests")
|
||||
, _pending_requests{}
|
||||
, _timeout_config(_proxy.data_dictionary().get_config())
|
||||
, _callbacks{
|
||||
{"CreateTable", [] (executor& e, executor::client_state& client_state, tracing::trace_state_ptr trace_state, service_permit permit, rjson::value json_request, std::unique_ptr<request> req) {
|
||||
|
||||
@@ -41,7 +41,7 @@ class server : public peering_sharded_service<server> {
|
||||
key_cache _key_cache;
|
||||
utils::updateable_value<bool> _enforce_authorization;
|
||||
utils::small_vector<std::reference_wrapper<seastar::httpd::http_server>, 2> _enabled_servers;
|
||||
named_gate _pending_requests;
|
||||
gate _pending_requests;
|
||||
// In some places we will need a CQL updateable_timeout_config object even
|
||||
// though it isn't really relevant for Alternator which defines its own
|
||||
// timeouts separately. We can create this object only once.
|
||||
|
||||
@@ -14,20 +14,7 @@
|
||||
namespace alternator {
|
||||
|
||||
const char* ALTERNATOR_METRICS = "alternator";
|
||||
static seastar::metrics::histogram estimated_histogram_to_metrics(const utils::estimated_histogram& histogram) {
|
||||
seastar::metrics::histogram res;
|
||||
res.buckets.resize(histogram.bucket_offsets.size());
|
||||
uint64_t cumulative_count = 0;
|
||||
res.sample_count = histogram._count;
|
||||
res.sample_sum = histogram._sample_sum;
|
||||
for (size_t i = 0; i < res.buckets.size(); i++) {
|
||||
auto& v = res.buckets[i];
|
||||
v.upper_bound = histogram.bucket_offsets[i];
|
||||
cumulative_count += histogram.buckets[i];
|
||||
v.count = cumulative_count;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
stats::stats() : api_operations{} {
|
||||
// Register the
|
||||
seastar::metrics::label op("op");
|
||||
@@ -108,26 +95,22 @@ stats::stats() : api_operations{} {
|
||||
seastar::metrics::description("number of rows read during filtering operations"))(alternator_label).set_skip_when_empty(),
|
||||
seastar::metrics::make_total_operations("filtered_rows_matched_total", cql_stats.filtered_rows_matched_total,
|
||||
seastar::metrics::description("number of rows read and matched during filtering operations")),
|
||||
seastar::metrics::make_counter("rcu_total", [this]{return 0.5 * rcu_half_units_total;},
|
||||
seastar::metrics::description("total number of consumed read units"))(alternator_label).set_skip_when_empty(),
|
||||
seastar::metrics::make_counter("rcu_total", rcu_total,
|
||||
seastar::metrics::description("total number of consumed read units, counted as half units"))(alternator_label).set_skip_when_empty(),
|
||||
seastar::metrics::make_counter("wcu_total", wcu_total[wcu_types::PUT_ITEM],
|
||||
seastar::metrics::description("total number of consumed write units"),{op("PutItem")})(alternator_label).set_skip_when_empty(),
|
||||
seastar::metrics::description("total number of consumed write units, counted as half units"),{op("PutItem")})(alternator_label).set_skip_when_empty(),
|
||||
seastar::metrics::make_counter("wcu_total", wcu_total[wcu_types::DELETE_ITEM],
|
||||
seastar::metrics::description("total number of consumed write units"),{op("DeleteItem")})(alternator_label).set_skip_when_empty(),
|
||||
seastar::metrics::description("total number of consumed write units, counted as half units"),{op("DeleteItem")})(alternator_label).set_skip_when_empty(),
|
||||
seastar::metrics::make_counter("wcu_total", wcu_total[wcu_types::UPDATE_ITEM],
|
||||
seastar::metrics::description("total number of consumed write units"),{op("UpdateItem")})(alternator_label).set_skip_when_empty(),
|
||||
seastar::metrics::description("total number of consumed write units, counted as half units"),{op("UpdateItem")})(alternator_label).set_skip_when_empty(),
|
||||
seastar::metrics::make_counter("wcu_total", wcu_total[wcu_types::INDEX],
|
||||
seastar::metrics::description("total number of consumed write units"),{op("Index")})(alternator_label).set_skip_when_empty(),
|
||||
seastar::metrics::description("total number of consumed write units, counted as half units"),{op("Index")})(alternator_label).set_skip_when_empty(),
|
||||
seastar::metrics::make_total_operations("filtered_rows_dropped_total", [this] { return cql_stats.filtered_rows_read_total - cql_stats.filtered_rows_matched_total; },
|
||||
seastar::metrics::description("number of rows read and dropped during filtering operations"))(alternator_label).set_skip_when_empty(),
|
||||
seastar::metrics::make_counter("batch_item_count", seastar::metrics::description("The total number of items processed across all batches"),{op("BatchWriteItem")},
|
||||
api_operations.batch_write_item_batch_total)(alternator_label).set_skip_when_empty(),
|
||||
seastar::metrics::make_counter("batch_item_count", seastar::metrics::description("The total number of items processed across all batches"),{op("BatchGetItem")},
|
||||
api_operations.batch_get_item_batch_total)(alternator_label).set_skip_when_empty(),
|
||||
seastar::metrics::make_histogram("batch_item_count_histogram", seastar::metrics::description("Histogram of the number of items in a batch request"),{op("BatchGetItem")},
|
||||
[this]{ return estimated_histogram_to_metrics(api_operations.batch_get_item_histogram);})(alternator_label).aggregate({seastar::metrics::shard_label}).set_skip_when_empty(),
|
||||
seastar::metrics::make_histogram("batch_item_count_histogram", seastar::metrics::description("Histogram of the number of items in a batch request"),{op("BatchWriteItem")},
|
||||
[this]{ return estimated_histogram_to_metrics(api_operations.batch_write_item_histogram);})(alternator_label).aggregate({seastar::metrics::shard_label}).set_skip_when_empty(),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
|
||||
#include <seastar/core/metrics_registration.hh>
|
||||
#include "utils/histogram.hh"
|
||||
#include "utils/estimated_histogram.hh"
|
||||
#include "cql3/stats.hh"
|
||||
|
||||
namespace alternator {
|
||||
@@ -76,9 +75,6 @@ public:
|
||||
utils::timed_rate_moving_average_summary_and_histogram batch_write_item_latency;
|
||||
utils::timed_rate_moving_average_summary_and_histogram batch_get_item_latency;
|
||||
utils::timed_rate_moving_average_summary_and_histogram get_records_latency;
|
||||
|
||||
utils::estimated_histogram batch_get_item_histogram{22}; // a histogram that covers the range 1 - 100
|
||||
utils::estimated_histogram batch_write_item_histogram{22}; // a histogram that covers the range 1 - 100
|
||||
} api_operations;
|
||||
// Miscellaneous event counters
|
||||
uint64_t total_operations = 0;
|
||||
@@ -88,7 +84,7 @@ public:
|
||||
uint64_t shard_bounce_for_lwt = 0;
|
||||
uint64_t requests_blocked_memory = 0;
|
||||
uint64_t requests_shed = 0;
|
||||
uint64_t rcu_half_units_total = 0;
|
||||
uint64_t rcu_total = 0;
|
||||
// wcu can results from put, update, delete and index
|
||||
// Index related will be done on top of the operation it comes with
|
||||
enum wcu_types {
|
||||
|
||||
@@ -808,9 +808,6 @@ future<executor::request_return_type> executor::get_records(client_state& client
|
||||
if (limit < 1) {
|
||||
throw api_error::validation("Limit must be 1 or more");
|
||||
}
|
||||
if (limit > 1000) {
|
||||
throw api_error::validation("Limit must be less than or equal to 1000");
|
||||
}
|
||||
|
||||
auto db = _proxy.data_dictionary();
|
||||
schema_ptr schema, base;
|
||||
|
||||
@@ -136,6 +136,14 @@
|
||||
"allowMultiple":false,
|
||||
"type":"string",
|
||||
"paramType":"path"
|
||||
},
|
||||
{
|
||||
"name":"unsafe",
|
||||
"description":"Set to True to perform an unsafe assassination",
|
||||
"required":false,
|
||||
"allowMultiple":false,
|
||||
"type":"boolean",
|
||||
"paramType":"query"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -984,7 +984,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"path":"/storage_service/cleanup_all/",
|
||||
"path":"/storage_service/cleanup_all",
|
||||
"operations":[
|
||||
{
|
||||
"method":"POST",
|
||||
@@ -994,30 +994,6 @@
|
||||
"produces":[
|
||||
"application/json"
|
||||
],
|
||||
"parameters":[
|
||||
{
|
||||
"name":"global",
|
||||
"description":"true if cleanup of entire cluster is requested",
|
||||
"required":false,
|
||||
"allowMultiple":false,
|
||||
"type":"boolean",
|
||||
"paramType":"query"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path":"/storage_service/mark_node_as_clean",
|
||||
"operations":[
|
||||
{
|
||||
"method":"POST",
|
||||
"summary":"Mark the node as clean. After that the node will not be considered as needing cleanup during automatic cleanup which is triggered by some topology operations",
|
||||
"type":"void",
|
||||
"nickname":"reset_cleanup_needed",
|
||||
"produces":[
|
||||
"application/json"
|
||||
],
|
||||
"parameters":[]
|
||||
}
|
||||
]
|
||||
@@ -2168,31 +2144,6 @@
|
||||
"allowMultiple":false,
|
||||
"type":"string",
|
||||
"paramType":"query"
|
||||
},
|
||||
{
|
||||
"name":"skip_cleanup",
|
||||
"description":"Don't cleanup keys from loaded sstables. Invalid if load_and_stream is true",
|
||||
"required":false,
|
||||
"allowMultiple":false,
|
||||
"type":"string",
|
||||
"paramType":"query"
|
||||
},
|
||||
{
|
||||
"name":"skip_reshape",
|
||||
"description":"Don't reshape the loaded sstables. Invalid if load_and_stream is true",
|
||||
"required":false,
|
||||
"allowMultiple":false,
|
||||
"type":"string",
|
||||
"paramType":"query"
|
||||
},
|
||||
{
|
||||
"name":"scope",
|
||||
"description":"Defines the set of nodes to which mutations can be streamed",
|
||||
"required":false,
|
||||
"allowMultiple":false,
|
||||
"type":"string",
|
||||
"paramType":"query",
|
||||
"enum": ["all", "dc", "rack", "node"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -3076,73 +3027,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path":"/storage_service/retrain_dict",
|
||||
"operations":[
|
||||
{
|
||||
"method":"POST",
|
||||
"summary":"Retrain the SSTable compression dictionary for the target table.",
|
||||
"type":"void",
|
||||
"nickname":"retrain_dict",
|
||||
"produces":[
|
||||
"application/json"
|
||||
],
|
||||
"parameters":[
|
||||
{
|
||||
"name":"keyspace",
|
||||
"description":"Name of the keyspace containing the target table.",
|
||||
"required":true,
|
||||
"allowMultiple":false,
|
||||
"type":"string",
|
||||
"paramType":"query"
|
||||
},
|
||||
{
|
||||
"name":"cf",
|
||||
"description":"Name of the target table.",
|
||||
"required":true,
|
||||
"allowMultiple":false,
|
||||
"type":"string",
|
||||
"paramType":"query"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path":"/storage_service/estimate_compression_ratios",
|
||||
"operations":[
|
||||
{
|
||||
"method":"GET",
|
||||
"summary":"Compute an estimated compression ratio for SSTables of the given table, for various compression configurations.",
|
||||
"type":"array",
|
||||
"items":{
|
||||
"type":"compression_config_result"
|
||||
},
|
||||
"nickname":"estimate_compression_ratios",
|
||||
"produces":[
|
||||
"application/json"
|
||||
],
|
||||
"parameters":[
|
||||
{
|
||||
"name":"keyspace",
|
||||
"description":"Name of the keyspace containing the target table.",
|
||||
"required":true,
|
||||
"allowMultiple":false,
|
||||
"type":"string",
|
||||
"paramType":"query"
|
||||
},
|
||||
{
|
||||
"name":"cf",
|
||||
"description":"Name of the target table.",
|
||||
"required":true,
|
||||
"allowMultiple":false,
|
||||
"type":"string",
|
||||
"paramType":"query"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path":"/storage_service/raft_topology/reload",
|
||||
"operations":[
|
||||
@@ -3185,22 +3069,6 @@
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path":"/storage_service/raft_topology/cmd_rpc_status",
|
||||
"operations":[
|
||||
{
|
||||
"method":"GET",
|
||||
"summary":"Get information about currently running topology cmd rpc",
|
||||
"type":"string",
|
||||
"nickname":"raft_topology_get_cmd_status",
|
||||
"produces":[
|
||||
"application/json"
|
||||
],
|
||||
"parameters":[
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"models":{
|
||||
@@ -3337,11 +3205,11 @@
|
||||
"properties":{
|
||||
"start_token":{
|
||||
"type":"string",
|
||||
"description":"The range start token (exclusive)"
|
||||
"description":"The range start token"
|
||||
},
|
||||
"end_token":{
|
||||
"type":"string",
|
||||
"description":"The range end token (inclusive)"
|
||||
"description":"The range start token"
|
||||
},
|
||||
"endpoints":{
|
||||
"type":"array",
|
||||
@@ -3460,32 +3328,6 @@
|
||||
"type":"string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"compression_config_result":{
|
||||
"id":"compression_config_result",
|
||||
"description":"Compression ratio estimation result for one config",
|
||||
"properties":{
|
||||
"level":{
|
||||
"type":"long",
|
||||
"description":"The used value of `compression_level`"
|
||||
},
|
||||
"chunk_length_in_kb":{
|
||||
"type":"long",
|
||||
"description":"The used value of `chunk_length_in_kb`"
|
||||
},
|
||||
"dict":{
|
||||
"type":"string",
|
||||
"description":"The used dictionary: `none`, `past` (== current), or `future`"
|
||||
},
|
||||
"sstable_compression":{
|
||||
"type":"string",
|
||||
"description":"The used compressor name (aka `sstable_compression`)"
|
||||
},
|
||||
"ratio":{
|
||||
"type":"float",
|
||||
"description":"The resulting compression ratio (estimated on a random sample of files)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -902,13 +902,17 @@ void set_column_family(http_context& ctx, routes& r, sharded<db::system_keyspace
|
||||
});
|
||||
|
||||
ss::enable_auto_compaction.set(r, [&ctx](std::unique_ptr<http::request> req) {
|
||||
auto [keyspace, tables] = parse_table_infos(ctx, *req);
|
||||
auto keyspace = validate_keyspace(ctx, req);
|
||||
auto tables = parse_table_infos(keyspace, ctx, req->query_parameters, "cf");
|
||||
|
||||
apilog.info("enable_auto_compaction: keyspace={} tables={}", keyspace, tables);
|
||||
return set_tables_autocompaction(ctx, std::move(tables), true);
|
||||
});
|
||||
|
||||
ss::disable_auto_compaction.set(r, [&ctx](std::unique_ptr<http::request> req) {
|
||||
auto [keyspace, tables] = parse_table_infos(ctx, *req);
|
||||
auto keyspace = validate_keyspace(ctx, req);
|
||||
auto tables = parse_table_infos(keyspace, ctx, req->query_parameters, "cf");
|
||||
|
||||
apilog.info("disable_auto_compaction: keyspace={} tables={}", keyspace, tables);
|
||||
return set_tables_autocompaction(ctx, std::move(tables), false);
|
||||
});
|
||||
@@ -932,13 +936,17 @@ void set_column_family(http_context& ctx, routes& r, sharded<db::system_keyspace
|
||||
});
|
||||
|
||||
ss::enable_tombstone_gc.set(r, [&ctx](std::unique_ptr<http::request> req) {
|
||||
auto [keyspace, tables] = parse_table_infos(ctx, *req);
|
||||
auto keyspace = validate_keyspace(ctx, req);
|
||||
auto tables = parse_table_infos(keyspace, ctx, req->query_parameters, "cf");
|
||||
|
||||
apilog.info("enable_tombstone_gc: keyspace={} tables={}", keyspace, tables);
|
||||
return set_tables_tombstone_gc(ctx, std::move(tables), true);
|
||||
});
|
||||
|
||||
ss::disable_tombstone_gc.set(r, [&ctx](std::unique_ptr<http::request> req) {
|
||||
auto [keyspace, tables] = parse_table_infos(ctx, *req);
|
||||
auto keyspace = validate_keyspace(ctx, req);
|
||||
auto tables = parse_table_infos(keyspace, ctx, req->query_parameters, "cf");
|
||||
|
||||
apilog.info("disable_tombstone_gc: keyspace={} tables={}", keyspace, tables);
|
||||
return set_tables_tombstone_gc(ctx, std::move(tables), false);
|
||||
});
|
||||
@@ -1046,7 +1054,7 @@ void set_column_family(http_context& ctx, routes& r, sharded<db::system_keyspace
|
||||
|
||||
return ctx.db.map_reduce0([key, uuid] (replica::database& db) -> future<std::unordered_set<sstring>> {
|
||||
auto sstables = co_await db.find_column_family(uuid).get_sstables_by_partition_key(key);
|
||||
co_return sstables | std::views::transform([] (auto s) -> sstring { return fmt::to_string(s->get_filename()); }) | std::ranges::to<std::unordered_set>();
|
||||
co_return sstables | std::views::transform([] (auto s) { return s->get_filename(); }) | std::ranges::to<std::unordered_set>();
|
||||
}, std::unordered_set<sstring>(),
|
||||
[](std::unordered_set<sstring> a, std::unordered_set<sstring>&& b) mutable {
|
||||
a.merge(b);
|
||||
|
||||
@@ -111,7 +111,8 @@ void set_compaction_manager(http_context& ctx, routes& r, sharded<compaction_man
|
||||
});
|
||||
|
||||
cm::stop_keyspace_compaction.set(r, [&ctx] (std::unique_ptr<http::request> req) -> future<json::json_return_type> {
|
||||
auto [ks_name, tables] = parse_table_infos(ctx, *req, "tables");
|
||||
auto ks_name = validate_keyspace(ctx, req);
|
||||
auto tables = parse_table_infos(ks_name, ctx, req->query_parameters, "tables");
|
||||
auto type = req->get_query_param("type");
|
||||
co_await ctx.db.invoke_on_all([&] (replica::database& db) {
|
||||
auto& cm = db.get_compaction_manager();
|
||||
|
||||
@@ -22,10 +22,10 @@ void set_failure_detector(http_context& ctx, routes& r, gms::gossiper& g) {
|
||||
return g.container().invoke_on(0, [] (gms::gossiper& g) {
|
||||
std::vector<fd::endpoint_state> res;
|
||||
res.reserve(g.num_endpoints());
|
||||
g.for_each_endpoint_state([&] (const gms::endpoint_state& eps) {
|
||||
g.for_each_endpoint_state([&] (const gms::inet_address& addr, const gms::endpoint_state& eps) {
|
||||
fd::endpoint_state val;
|
||||
val.addrs = fmt::to_string(eps.get_ip());
|
||||
val.is_alive = g.is_alive(eps.get_host_id());
|
||||
val.addrs = fmt::to_string(addr);
|
||||
val.is_alive = g.is_alive(addr);
|
||||
val.generation = eps.get_heart_beat_state().get_generation().value();
|
||||
val.version = eps.get_heart_beat_state().get_heart_beat_version().value();
|
||||
val.update_time = eps.get_update_timestamp().time_since_epoch().count();
|
||||
@@ -65,8 +65,8 @@ void set_failure_detector(http_context& ctx, routes& r, gms::gossiper& g) {
|
||||
fd::get_simple_states.set(r, [&g] (std::unique_ptr<request> req) {
|
||||
return g.container().invoke_on(0, [] (gms::gossiper& g) {
|
||||
std::map<sstring, sstring> nodes_status;
|
||||
g.for_each_endpoint_state([&] (const gms::endpoint_state& es) {
|
||||
nodes_status.emplace(fmt::to_string(es.get_ip()), g.is_alive(es.get_host_id()) ? "UP" : "DOWN");
|
||||
g.for_each_endpoint_state([&] (const gms::inet_address& node, const gms::endpoint_state&) {
|
||||
nodes_status.emplace(fmt::to_string(node), g.is_alive(node) ? "UP" : "DOWN");
|
||||
});
|
||||
return make_ready_future<json::json_return_type>(map_to_key_value<fd::mapper>(nodes_status));
|
||||
});
|
||||
@@ -81,7 +81,7 @@ void set_failure_detector(http_context& ctx, routes& r, gms::gossiper& g) {
|
||||
|
||||
fd::get_endpoint_state.set(r, [&g] (std::unique_ptr<request> req) {
|
||||
return g.container().invoke_on(0, [req = std::move(req)] (gms::gossiper& g) {
|
||||
auto state = g.get_endpoint_state_ptr(g.get_host_id(gms::inet_address(req->get_path_param("addr"))));
|
||||
auto state = g.get_endpoint_state_ptr(gms::inet_address(req->get_path_param("addr")));
|
||||
if (!state) {
|
||||
return make_ready_future<json::json_return_type>(format("unknown endpoint {}", req->get_path_param("addr")));
|
||||
}
|
||||
|
||||
@@ -35,32 +35,37 @@ void set_gossiper(http_context& ctx, routes& r, gms::gossiper& g) {
|
||||
gms::inet_address ep(req->get_path_param("addr"));
|
||||
// synchronize unreachable_members on all shards
|
||||
co_await g.get_unreachable_members_synchronized();
|
||||
co_return g.get_endpoint_downtime(g.get_host_id(ep));
|
||||
co_return g.get_endpoint_downtime(ep);
|
||||
});
|
||||
|
||||
httpd::gossiper_json::get_current_generation_number.set(r, [&g] (std::unique_ptr<http::request> req) {
|
||||
gms::inet_address ep(req->get_path_param("addr"));
|
||||
return g.get_current_generation_number(g.get_host_id(ep)).then([] (gms::generation_type res) {
|
||||
return g.get_current_generation_number(ep).then([] (gms::generation_type res) {
|
||||
return make_ready_future<json::json_return_type>(res.value());
|
||||
});
|
||||
});
|
||||
|
||||
httpd::gossiper_json::get_current_heart_beat_version.set(r, [&g] (std::unique_ptr<http::request> req) {
|
||||
gms::inet_address ep(req->get_path_param("addr"));
|
||||
return g.get_current_heart_beat_version(g.get_host_id(ep)).then([] (gms::version_type res) {
|
||||
return g.get_current_heart_beat_version(ep).then([] (gms::version_type res) {
|
||||
return make_ready_future<json::json_return_type>(res.value());
|
||||
});
|
||||
});
|
||||
|
||||
httpd::gossiper_json::assassinate_endpoint.set(r, [&g](std::unique_ptr<http::request> req) {
|
||||
return g.assassinate_endpoint(req->get_path_param("addr")).then([] {
|
||||
if (req->get_query_param("unsafe") != "True") {
|
||||
return g.assassinate_endpoint(req->get_path_param("addr")).then([] {
|
||||
return make_ready_future<json::json_return_type>(json_void());
|
||||
});
|
||||
}
|
||||
return g.unsafe_assassinate_endpoint(req->get_path_param("addr")).then([] {
|
||||
return make_ready_future<json::json_return_type>(json_void());
|
||||
});
|
||||
});
|
||||
|
||||
httpd::gossiper_json::force_remove_endpoint.set(r, [&g](std::unique_ptr<http::request> req) {
|
||||
gms::inet_address ep(req->get_path_param("addr"));
|
||||
return g.force_remove_endpoint(g.get_host_id(ep), gms::null_permit_id).then([] () {
|
||||
return g.force_remove_endpoint(ep, gms::null_permit_id).then([] {
|
||||
return make_ready_future<json::json_return_type>(json_void());
|
||||
});
|
||||
});
|
||||
|
||||
@@ -148,7 +148,7 @@ void set_messaging_service(http_context& ctx, routes& r, sharded<netw::messaging
|
||||
hf::inject_disconnect.set(r, [&ms] (std::unique_ptr<request> req) -> future<json::json_return_type> {
|
||||
auto ip = msg_addr(req->get_path_param("ip"));
|
||||
co_await ms.invoke_on_all([ip] (netw::messaging_service& ms) {
|
||||
ms.remove_rpc_client(ip, std::nullopt);
|
||||
ms.remove_rpc_client(ip);
|
||||
});
|
||||
co_return json::json_void();
|
||||
});
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
#include "cql3/query_processor.hh"
|
||||
#include "cql3/untyped_result_set.hh"
|
||||
#include "db/consistency_level_type.hh"
|
||||
#include <seastar/json/json_elements.hh>
|
||||
#include "seastar/json/json_elements.hh"
|
||||
#include "transport/controller.hh"
|
||||
#include <unordered_map>
|
||||
|
||||
|
||||
@@ -14,13 +14,9 @@
|
||||
#include "api/scrub_status.hh"
|
||||
#include "db/config.hh"
|
||||
#include "db/schema_tables.hh"
|
||||
#include "gms/feature_service.hh"
|
||||
#include "schema/schema_builder.hh"
|
||||
#include "sstables/sstables_manager.hh"
|
||||
#include "utils/hash.hh"
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <time.h>
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
@@ -33,7 +29,6 @@
|
||||
#include "service/raft/raft_group0_client.hh"
|
||||
#include "service/storage_service.hh"
|
||||
#include "service/load_meter.hh"
|
||||
#include "gms/feature_service.hh"
|
||||
#include "gms/gossiper.hh"
|
||||
#include "db/system_keyspace.hh"
|
||||
#include <seastar/http/exception.hh>
|
||||
@@ -60,7 +55,6 @@
|
||||
#include "db/view/view_builder.hh"
|
||||
#include "utils/rjson.hh"
|
||||
#include "utils/user_provided_param.hh"
|
||||
#include "sstable_dict_autotrainer.hh"
|
||||
|
||||
using namespace seastar::httpd;
|
||||
using namespace std::chrono_literals;
|
||||
@@ -133,6 +127,32 @@ int64_t validate_int(const sstring& param) {
|
||||
return std::atoll(param.c_str());
|
||||
}
|
||||
|
||||
// splits a request parameter assumed to hold a comma-separated list of table names
|
||||
// verify that the tables are found, otherwise a bad_param_exception exception is thrown
|
||||
// containing the description of the respective no_such_column_family error.
|
||||
static std::vector<sstring> parse_tables(const sstring& ks_name, const http_context& ctx, sstring value) {
|
||||
if (value.empty()) {
|
||||
return map_keys(ctx.db.local().find_keyspace(ks_name).metadata().get()->cf_meta_data());
|
||||
}
|
||||
std::vector<sstring> names = split(value, ",");
|
||||
try {
|
||||
for (const auto& table_name : names) {
|
||||
ctx.db.local().find_column_family(ks_name, table_name);
|
||||
}
|
||||
} catch (const replica::no_such_column_family& e) {
|
||||
throw bad_param_exception(e.what());
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
static std::vector<sstring> parse_tables(const sstring& ks_name, const http_context& ctx, const std::unordered_map<sstring, sstring>& query_params, sstring param_name) {
|
||||
auto it = query_params.find(param_name);
|
||||
if (it == query_params.end()) {
|
||||
return {};
|
||||
}
|
||||
return parse_tables(ks_name, ctx, it->second);
|
||||
}
|
||||
|
||||
std::vector<table_info> parse_table_infos(const sstring& ks_name, const http_context& ctx, sstring value) {
|
||||
std::vector<table_info> res;
|
||||
try {
|
||||
@@ -158,12 +178,9 @@ std::vector<table_info> parse_table_infos(const sstring& ks_name, const http_con
|
||||
return res;
|
||||
}
|
||||
|
||||
std::pair<sstring, std::vector<table_info>> parse_table_infos(const http_context& ctx, const http::request& req, sstring cf_param_name) {
|
||||
auto keyspace = validate_keyspace(ctx, req);
|
||||
const auto& query_params = req.query_parameters;
|
||||
auto it = query_params.find(cf_param_name);
|
||||
auto tis = parse_table_infos(keyspace, ctx, it != query_params.end() ? it->second : "");
|
||||
return std::make_pair(std::move(keyspace), std::move(tis));
|
||||
std::vector<table_info> parse_table_infos(const sstring& ks_name, const http_context& ctx, const std::unordered_map<sstring, sstring>& query_params, sstring param_name) {
|
||||
auto it = query_params.find(param_name);
|
||||
return parse_table_infos(ks_name, ctx, it != query_params.end() ? it->second : "");
|
||||
}
|
||||
|
||||
static ss::token_range token_range_endpoints_to_json(const dht::token_range_endpoints& d) {
|
||||
@@ -184,6 +201,16 @@ static ss::token_range token_range_endpoints_to_json(const dht::token_range_endp
|
||||
return r;
|
||||
}
|
||||
|
||||
using ks_cf_func = std::function<future<json::json_return_type>(http_context&, std::unique_ptr<http::request>, sstring, std::vector<table_info>)>;
|
||||
|
||||
static auto wrap_ks_cf(http_context &ctx, ks_cf_func f) {
|
||||
return [&ctx, f = std::move(f)](std::unique_ptr<http::request> req) {
|
||||
auto keyspace = validate_keyspace(ctx, req);
|
||||
auto table_infos = parse_table_infos(keyspace, ctx, req->query_parameters, "cf");
|
||||
return f(ctx, std::move(req), std::move(keyspace), std::move(table_infos));
|
||||
};
|
||||
}
|
||||
|
||||
seastar::future<json::json_return_type> run_toppartitions_query(db::toppartitions_query& q, http_context &ctx, bool legacy_request) {
|
||||
return q.scatter().then([&q, legacy_request] {
|
||||
return sleep(q.duration()).then([&q, legacy_request] {
|
||||
@@ -226,7 +253,7 @@ future<scrub_info> parse_scrub_options(const http_context& ctx, sharded<db::snap
|
||||
});
|
||||
rp.process(*req);
|
||||
info.keyspace = validate_keyspace(ctx, *rp.get("keyspace"));
|
||||
info.column_families = parse_table_infos(info.keyspace, ctx, *rp.get("cf")) | std::views::transform([] (auto ti) { return ti.name; }) | std::ranges::to<std::vector>();
|
||||
info.column_families = parse_tables(info.keyspace, ctx, *rp.get("cf"));
|
||||
auto scrub_mode_opt = rp.get("scrub_mode");
|
||||
auto scrub_mode = sstables::compaction_type_options::scrub::mode::abort;
|
||||
|
||||
@@ -251,9 +278,11 @@ future<scrub_info> parse_scrub_options(const http_context& ctx, sharded<db::snap
|
||||
}
|
||||
}
|
||||
|
||||
if (!req_param<bool>(*req, "disable_snapshot", false) && !info.column_families.empty()) {
|
||||
if (!req_param<bool>(*req, "disable_snapshot", false)) {
|
||||
auto tag = format("pre-scrub-{:d}", db_clock::now().time_since_epoch().count());
|
||||
co_await snap_ctl.local().take_column_family_snapshot(info.keyspace, info.column_families, tag, db::snapshot_ctl::skip_flush::no);
|
||||
co_await coroutine::parallel_for_each(info.column_families, [&snap_ctl, keyspace = info.keyspace, tag](sstring cf) {
|
||||
return snap_ctl.local().take_column_family_snapshot(keyspace, cf, tag, db::snapshot_ctl::skip_flush::no);
|
||||
});
|
||||
}
|
||||
|
||||
info.opts = {
|
||||
@@ -454,26 +483,17 @@ void set_sstables_loader(http_context& ctx, routes& r, sharded<sstables_loader>&
|
||||
auto cf = req->get_query_param("cf");
|
||||
auto stream = req->get_query_param("load_and_stream");
|
||||
auto primary_replica = req->get_query_param("primary_replica_only");
|
||||
auto skip_cleanup_p = req->get_query_param("skip_cleanup");
|
||||
boost::algorithm::to_lower(stream);
|
||||
boost::algorithm::to_lower(primary_replica);
|
||||
bool load_and_stream = stream == "true" || stream == "1";
|
||||
bool primary_replica_only = primary_replica == "true" || primary_replica == "1";
|
||||
bool skip_cleanup = skip_cleanup_p == "true" || skip_cleanup_p == "1";
|
||||
auto scope = parse_stream_scope(req->get_query_param("scope"));
|
||||
auto skip_reshape_p = req->get_query_param("skip_reshape");
|
||||
auto skip_reshape = skip_reshape_p == "true" || skip_reshape_p == "1";
|
||||
|
||||
if (scope != sstables_loader::stream_scope::all && !load_and_stream) {
|
||||
throw httpd::bad_param_exception("scope takes no effect without load-and-stream");
|
||||
}
|
||||
// No need to add the keyspace, since all we want is to avoid always sending this to the same
|
||||
// CPU. Even then I am being overzealous here. This is not something that happens all the time.
|
||||
auto coordinator = std::hash<sstring>()(cf) % smp::count;
|
||||
return sst_loader.invoke_on(coordinator,
|
||||
[ks = std::move(ks), cf = std::move(cf),
|
||||
load_and_stream, primary_replica_only, skip_cleanup, skip_reshape, scope] (sstables_loader& loader) {
|
||||
return loader.load_new_sstables(ks, cf, load_and_stream, primary_replica_only, skip_cleanup, skip_reshape, scope);
|
||||
load_and_stream, primary_replica_only] (sstables_loader& loader) {
|
||||
return loader.load_new_sstables(ks, cf, load_and_stream, primary_replica_only, sstables_loader::stream_scope::all);
|
||||
}).then_wrapped([] (auto&& f) {
|
||||
if (f.failed()) {
|
||||
auto msg = fmt::format("Failed to load new sstables: {}", f.get_exception());
|
||||
@@ -705,7 +725,7 @@ rest_get_load(http_context& ctx, std::unique_ptr<http::request> req) {
|
||||
static
|
||||
future<json::json_return_type>
|
||||
rest_get_current_generation_number(sharded<service::storage_service>& ss, std::unique_ptr<http::request> req) {
|
||||
auto ep = ss.local().get_token_metadata().get_topology().my_host_id();
|
||||
auto ep = ss.local().get_token_metadata().get_topology().my_address();
|
||||
return ss.local().gossiper().get_current_generation_number(ep).then([](gms::generation_type res) {
|
||||
return make_ready_future<json::json_return_type>(res.value());
|
||||
});
|
||||
@@ -748,7 +768,13 @@ rest_force_compaction(http_context& ctx, std::unique_ptr<http::request> req) {
|
||||
fmopt = flush_mode::skip;
|
||||
}
|
||||
auto task = co_await compaction_module.make_and_start_task<global_major_compaction_task_impl>({}, db, fmopt, consider_only_existing_data);
|
||||
co_await task->done();
|
||||
try {
|
||||
co_await task->done();
|
||||
} catch (...) {
|
||||
apilog.error("force_compaction failed: {}", std::current_exception());
|
||||
throw;
|
||||
}
|
||||
|
||||
co_return json_void();
|
||||
}
|
||||
|
||||
@@ -775,7 +801,13 @@ rest_force_keyspace_compaction(http_context& ctx, std::unique_ptr<http::request>
|
||||
fmopt = flush_mode::skip;
|
||||
}
|
||||
auto task = co_await compaction_module.make_and_start_task<major_keyspace_compaction_task_impl>({}, std::move(keyspace), tasks::task_id::create_null_id(), db, table_infos, fmopt, consider_only_existing_data);
|
||||
co_await task->done();
|
||||
try {
|
||||
co_await task->done();
|
||||
} catch (...) {
|
||||
apilog.error("force_keyspace_compaction: keyspace={} tables={} failed: {}", task->get_status().keyspace, table_infos, std::current_exception());
|
||||
throw;
|
||||
}
|
||||
|
||||
co_return json_void();
|
||||
}
|
||||
|
||||
@@ -783,7 +815,8 @@ static
|
||||
future<json::json_return_type>
|
||||
rest_force_keyspace_cleanup(http_context& ctx, sharded<service::storage_service>& ss, std::unique_ptr<http::request> req) {
|
||||
auto& db = ctx.db;
|
||||
auto [keyspace, table_infos] = parse_table_infos(ctx, *req);
|
||||
auto keyspace = validate_keyspace(ctx, req);
|
||||
auto table_infos = parse_table_infos(keyspace, ctx, req->query_parameters, "cf");
|
||||
const auto& rs = db.local().find_keyspace(keyspace).get_replication_strategy();
|
||||
if (rs.get_type() == locator::replication_strategy_type::local || !rs.is_vnode_based()) {
|
||||
auto reason = rs.get_type() == locator::replication_strategy_type::local ? "require" : "support";
|
||||
@@ -800,21 +833,21 @@ rest_force_keyspace_cleanup(http_context& ctx, sharded<service::storage_service>
|
||||
auto& compaction_module = db.local().get_compaction_manager().get_task_manager_module();
|
||||
auto task = co_await compaction_module.make_and_start_task<cleanup_keyspace_compaction_task_impl>(
|
||||
{}, std::move(keyspace), db, table_infos, flush_mode::all_tables, tasks::is_user_task::yes);
|
||||
co_await task->done();
|
||||
try {
|
||||
co_await task->done();
|
||||
} catch (...) {
|
||||
apilog.error("force_keyspace_cleanup: keyspace={} tables={} failed: {}", task->get_status().keyspace, table_infos, std::current_exception());
|
||||
throw;
|
||||
}
|
||||
|
||||
co_return json::json_return_type(0);
|
||||
}
|
||||
|
||||
static
|
||||
future<json::json_return_type>
|
||||
rest_cleanup_all(http_context& ctx, sharded<service::storage_service>& ss, std::unique_ptr<http::request> req) {
|
||||
bool global = true;
|
||||
if (auto global_param = req->get_query_param("global"); !global_param.empty()) {
|
||||
global = validate_bool(global_param);
|
||||
}
|
||||
|
||||
apilog.info("cleanup_all global={}", global);
|
||||
|
||||
auto done = !global ? false : co_await ss.invoke_on(0, [] (service::storage_service& ss) -> future<bool> {
|
||||
apilog.info("cleanup_all");
|
||||
auto done = co_await ss.invoke_on(0, [] (service::storage_service& ss) -> future<bool> {
|
||||
if (!ss.is_topology_coordinator_enabled()) {
|
||||
co_return false;
|
||||
}
|
||||
@@ -824,59 +857,53 @@ rest_cleanup_all(http_context& ctx, sharded<service::storage_service>& ss, std::
|
||||
if (done) {
|
||||
co_return json::json_return_type(0);
|
||||
}
|
||||
// fall back to the local cleanup if topology coordinator is not enabled or local cleanup is requested
|
||||
// fall back to the local global cleanup if topology coordinator is not enabled
|
||||
auto& db = ctx.db;
|
||||
auto& compaction_module = db.local().get_compaction_manager().get_task_manager_module();
|
||||
auto task = co_await compaction_module.make_and_start_task<global_cleanup_compaction_task_impl>({}, db);
|
||||
co_await task->done();
|
||||
|
||||
// Mark this node as clean
|
||||
co_await ss.invoke_on(0, [] (service::storage_service& ss) -> future<> {
|
||||
if (ss.is_topology_coordinator_enabled()) {
|
||||
co_await ss.reset_cleanup_needed();
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
co_await task->done();
|
||||
} catch (...) {
|
||||
apilog.error("cleanup_all failed: {}", std::current_exception());
|
||||
throw;
|
||||
}
|
||||
co_return json::json_return_type(0);
|
||||
}
|
||||
|
||||
static
|
||||
future<json::json_return_type>
|
||||
rest_reset_cleanup_needed(http_context& ctx, sharded<service::storage_service>& ss, std::unique_ptr<http::request> req) {
|
||||
apilog.info("reset_cleanup_needed");
|
||||
co_await ss.invoke_on(0, [] (service::storage_service& ss) {
|
||||
if (!ss.is_topology_coordinator_enabled()) {
|
||||
throw std::runtime_error("mark_node_as_clean is only supported when topology over raft is enabled");
|
||||
}
|
||||
return ss.reset_cleanup_needed();
|
||||
});
|
||||
co_return json_void();
|
||||
}
|
||||
|
||||
static
|
||||
future<json::json_return_type>
|
||||
rest_perform_keyspace_offstrategy_compaction(http_context& ctx, std::unique_ptr<http::request> req) {
|
||||
auto [keyspace, table_infos] = parse_table_infos(ctx, *req);
|
||||
rest_perform_keyspace_offstrategy_compaction(http_context& ctx, std::unique_ptr<http::request> req, sstring keyspace, std::vector<table_info> table_infos) {
|
||||
apilog.info("perform_keyspace_offstrategy_compaction: keyspace={} tables={}", keyspace, table_infos);
|
||||
bool res = false;
|
||||
auto& compaction_module = ctx.db.local().get_compaction_manager().get_task_manager_module();
|
||||
auto task = co_await compaction_module.make_and_start_task<offstrategy_keyspace_compaction_task_impl>({}, std::move(keyspace), ctx.db, table_infos, &res);
|
||||
co_await task->done();
|
||||
try {
|
||||
co_await task->done();
|
||||
} catch (...) {
|
||||
apilog.error("perform_keyspace_offstrategy_compaction: keyspace={} tables={} failed: {}", task->get_status().keyspace, table_infos, std::current_exception());
|
||||
throw;
|
||||
}
|
||||
|
||||
co_return json::json_return_type(res);
|
||||
}
|
||||
|
||||
static
|
||||
future<json::json_return_type>
|
||||
rest_upgrade_sstables(http_context& ctx, std::unique_ptr<http::request> req) {
|
||||
rest_upgrade_sstables(http_context& ctx, std::unique_ptr<http::request> req, sstring keyspace, std::vector<table_info> table_infos) {
|
||||
auto& db = ctx.db;
|
||||
auto [keyspace, table_infos] = parse_table_infos(ctx, *req);
|
||||
bool exclude_current_version = req_param<bool>(*req, "exclude_current_version", false);
|
||||
|
||||
apilog.info("upgrade_sstables: keyspace={} tables={} exclude_current_version={}", keyspace, table_infos, exclude_current_version);
|
||||
|
||||
auto& compaction_module = db.local().get_compaction_manager().get_task_manager_module();
|
||||
auto task = co_await compaction_module.make_and_start_task<upgrade_sstables_compaction_task_impl>({}, std::move(keyspace), db, table_infos, exclude_current_version);
|
||||
co_await task->done();
|
||||
try {
|
||||
co_await task->done();
|
||||
} catch (...) {
|
||||
apilog.error("upgrade_sstables: keyspace={} tables={} failed: {}", keyspace, table_infos, std::current_exception());
|
||||
throw;
|
||||
}
|
||||
|
||||
co_return json::json_return_type(0);
|
||||
}
|
||||
|
||||
@@ -893,10 +920,15 @@ rest_force_flush(http_context& ctx, std::unique_ptr<http::request> req) {
|
||||
static
|
||||
future<json::json_return_type>
|
||||
rest_force_keyspace_flush(http_context& ctx, std::unique_ptr<http::request> req) {
|
||||
auto [keyspace, table_infos] = parse_table_infos(ctx, *req);
|
||||
apilog.info("perform_keyspace_flush: keyspace={} tables={}", keyspace, table_infos);
|
||||
auto keyspace = validate_keyspace(ctx, req);
|
||||
auto column_families = parse_tables(keyspace, ctx, req->query_parameters, "cf");
|
||||
apilog.info("perform_keyspace_flush: keyspace={} tables={}", keyspace, column_families);
|
||||
auto& db = ctx.db;
|
||||
co_await replica::database::flush_tables_on_all_shards(db, std::move(table_infos));
|
||||
if (column_families.empty()) {
|
||||
co_await replica::database::flush_keyspace_on_all_shards(db, keyspace);
|
||||
} else {
|
||||
co_await replica::database::flush_tables_on_all_shards(db, keyspace, std::move(column_families));
|
||||
}
|
||||
co_return json_void();
|
||||
}
|
||||
|
||||
@@ -1416,95 +1448,6 @@ rest_get_effective_ownership(http_context& ctx, sharded<service::storage_service
|
||||
});
|
||||
}
|
||||
|
||||
static
|
||||
future<json::json_return_type>
|
||||
rest_estimate_compression_ratios(http_context& ctx, sharded<service::storage_service>& ss, std::unique_ptr<http::request> req) {
|
||||
if (!ss.local().get_feature_service().sstable_compression_dicts) {
|
||||
apilog.warn("estimate_compression_ratios: called before the cluster feature was enabled");
|
||||
throw std::runtime_error("estimate_compression_ratios requires all nodes to support the SSTABLE_COMPRESSION_DICTS cluster feature");
|
||||
}
|
||||
auto ticket = get_units(ss.local().get_do_sample_sstables_concurrency_limiter(), 1);
|
||||
auto ks = api::req_param<sstring>(*req, "keyspace", {}).value;
|
||||
auto cf = api::req_param<sstring>(*req, "cf", {}).value;
|
||||
apilog.debug("estimate_compression_ratios: called with ks={} cf={}", ks, cf);
|
||||
|
||||
auto s = ctx.db.local().find_column_family(ks, cf).schema();
|
||||
|
||||
auto training_sample = co_await ss.local().do_sample_sstables(s->id(), 4096, 4096);
|
||||
auto validation_sample = co_await ss.local().do_sample_sstables(s->id(), 16*1024, 1024);
|
||||
apilog.debug("estimate_compression_ratios: got training sample with {} blocks and validation sample with {}", training_sample.size(), validation_sample.size());
|
||||
|
||||
auto dict = co_await ss.local().train_dict(std::move(training_sample));
|
||||
apilog.debug("estimate_compression_ratios: got dict of size {}", dict.size());
|
||||
|
||||
std::vector<ss::compression_config_result> res;
|
||||
auto make_result = [](std::string_view name, int chunk_length_kb, std::string_view dict, int level, float ratio) -> ss::compression_config_result {
|
||||
ss::compression_config_result x;
|
||||
x.sstable_compression = sstring(name);
|
||||
x.chunk_length_in_kb = chunk_length_kb;
|
||||
x.dict = sstring(dict);
|
||||
x.level = level;
|
||||
x.ratio = ratio;
|
||||
return x;
|
||||
};
|
||||
|
||||
using algorithm = compression_parameters::algorithm;
|
||||
for (const auto& algo : {algorithm::lz4_with_dicts, algorithm::zstd_with_dicts}) {
|
||||
for (const auto& chunk_size_kb : {1, 4, 16}) {
|
||||
std::vector<int> levels;
|
||||
if (algo == compressor::algorithm::zstd_with_dicts) {
|
||||
for (int i = 1; i <= 5; ++i) {
|
||||
levels.push_back(i);
|
||||
}
|
||||
} else {
|
||||
levels.push_back(1);
|
||||
}
|
||||
for (auto level : levels) {
|
||||
auto algo_name = compression_parameters::algorithm_to_name(algo);
|
||||
auto m = std::map<sstring, sstring>{
|
||||
{compression_parameters::CHUNK_LENGTH_KB, std::to_string(chunk_size_kb)},
|
||||
{compression_parameters::SSTABLE_COMPRESSION, sstring(algo_name)},
|
||||
};
|
||||
if (algo == compressor::algorithm::zstd_with_dicts) {
|
||||
m.insert(decltype(m)::value_type{sstring("compression_level"), sstring(std::to_string(level))});
|
||||
}
|
||||
auto params = compression_parameters(std::move(m));
|
||||
auto ratio_with_no_dict = co_await try_one_compression_config({}, s, params, validation_sample);
|
||||
auto ratio_with_past_dict = co_await try_one_compression_config(ctx.db.local().get_user_sstables_manager().get_compressor_factory(), s, params, validation_sample);
|
||||
auto ratio_with_future_dict = co_await try_one_compression_config(dict, s, params, validation_sample);
|
||||
res.push_back(make_result(algo_name, chunk_size_kb, "none", level, ratio_with_no_dict));
|
||||
res.push_back(make_result(algo_name, chunk_size_kb, "past", level, ratio_with_past_dict));
|
||||
res.push_back(make_result(algo_name, chunk_size_kb, "future", level, ratio_with_future_dict));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
co_return res;
|
||||
}
|
||||
|
||||
static
|
||||
future<json::json_return_type>
|
||||
rest_retrain_dict(http_context& ctx, sharded<service::storage_service>& ss, service::raft_group0_client& group0_client, std::unique_ptr<http::request> req) {
|
||||
if (!ss.local().get_feature_service().sstable_compression_dicts) {
|
||||
apilog.warn("retrain_dict: called before the cluster feature was enabled");
|
||||
throw std::runtime_error("retrain_dict requires all nodes to support the SSTABLE_COMPRESSION_DICTS cluster feature");
|
||||
}
|
||||
auto ticket = get_units(ss.local().get_do_sample_sstables_concurrency_limiter(), 1);
|
||||
auto ks = api::req_param<sstring>(*req, "keyspace", {}).value;
|
||||
auto cf = api::req_param<sstring>(*req, "cf", {}).value;
|
||||
apilog.debug("retrain_dict: called with ks={} cf={}", ks, cf);
|
||||
const auto t_id = ctx.db.local().find_column_family(ks, cf).schema()->id();
|
||||
constexpr uint64_t chunk_size = 4096;
|
||||
constexpr uint64_t n_chunks = 4096;
|
||||
auto sample = co_await ss.local().do_sample_sstables(t_id, chunk_size, n_chunks);
|
||||
apilog.debug("retrain_dict: got sample with {} blocks", sample.size());
|
||||
auto dict = co_await ss.local().train_dict(std::move(sample));
|
||||
apilog.debug("retrain_dict: got dict of size {}", dict.size());
|
||||
co_await ss.local().publish_new_sstable_dict(t_id, dict, group0_client);
|
||||
apilog.debug("retrain_dict: published new dict");
|
||||
co_return json_void();
|
||||
}
|
||||
|
||||
static
|
||||
future<json::json_return_type>
|
||||
rest_sstable_info(http_context& ctx, std::unique_ptr<http::request> req) {
|
||||
@@ -1566,23 +1509,21 @@ rest_sstable_info(http_context& ctx, std::unique_ptr<http::request> req) {
|
||||
info.version = sstable->get_version();
|
||||
|
||||
if (sstable->has_component(sstables::component_type::CompressionInfo)) {
|
||||
const auto& cp = sstable->get_compression().get_compressor();
|
||||
auto& c = sstable->get_compression();
|
||||
auto cp = sstables::get_sstable_compressor(c);
|
||||
|
||||
ss::named_maps nm;
|
||||
nm.group = "compression_parameters";
|
||||
for (auto& p : cp.options()) {
|
||||
if (compressor::is_hidden_option_name(p.first)) {
|
||||
continue;
|
||||
}
|
||||
for (auto& p : cp->options()) {
|
||||
ss::mapper e;
|
||||
e.key = p.first;
|
||||
e.value = p.second;
|
||||
nm.attributes.push(std::move(e));
|
||||
}
|
||||
if (!cp.options().contains(compression_parameters::SSTABLE_COMPRESSION)) {
|
||||
if (!cp->options().contains(compression_parameters::SSTABLE_COMPRESSION)) {
|
||||
ss::mapper e;
|
||||
e.key = compression_parameters::SSTABLE_COMPRESSION;
|
||||
e.value = sstring(cp.name());
|
||||
e.value = cp->name();
|
||||
nm.attributes.push(std::move(e));
|
||||
}
|
||||
info.extended_properties.push(std::move(nm));
|
||||
@@ -1669,18 +1610,6 @@ rest_raft_topology_upgrade_status(sharded<service::storage_service>& ss, std::un
|
||||
co_return sstring(format("{}", ustate));
|
||||
}
|
||||
|
||||
static
|
||||
future<json::json_return_type>
|
||||
rest_raft_topology_get_cmd_status(sharded<service::storage_service>& ss, std::unique_ptr<http::request> req) {
|
||||
const auto status = co_await ss.invoke_on(0, [] (auto& ss) {
|
||||
return ss.get_topology_cmd_status();
|
||||
});
|
||||
if (status.active_dst.empty()) {
|
||||
co_return sstring("none");
|
||||
}
|
||||
co_return sstring(fmt::format("{}[{}]: {}", status.current, status.index, fmt::join(status.active_dst, ",")));
|
||||
}
|
||||
|
||||
static
|
||||
future<json::json_return_type>
|
||||
rest_move_tablet(http_context& ctx, sharded<service::storage_service>& ss, std::unique_ptr<http::request> req) {
|
||||
@@ -1711,7 +1640,7 @@ rest_add_tablet_replica(http_context& ctx, sharded<service::storage_service>& ss
|
||||
auto token = dht::token::from_int64(validate_int(req->get_query_param("token")));
|
||||
auto ks = req->get_query_param("ks");
|
||||
auto table = req->get_query_param("table");
|
||||
auto table_id = validate_table(ctx.db.local(), ks, table);
|
||||
auto table_id = ctx.db.local().find_column_family(ks, table).schema()->id();
|
||||
auto force_str = req->get_query_param("force");
|
||||
auto force = service::loosen_constraints(force_str == "" ? false : validate_bool(force_str));
|
||||
|
||||
@@ -1730,7 +1659,7 @@ rest_del_tablet_replica(http_context& ctx, sharded<service::storage_service>& ss
|
||||
auto token = dht::token::from_int64(validate_int(req->get_query_param("token")));
|
||||
auto ks = req->get_query_param("ks");
|
||||
auto table = req->get_query_param("table");
|
||||
auto table_id = validate_table(ctx.db.local(), ks, table);
|
||||
auto table_id = ctx.db.local().find_column_family(ks, table).schema()->id();
|
||||
auto force_str = req->get_query_param("force");
|
||||
auto force = service::loosen_constraints(force_str == "" ? false : validate_bool(force_str));
|
||||
|
||||
@@ -1838,6 +1767,12 @@ rest_bind(FuncType func, BindArgs&... args) {
|
||||
return std::bind_front(func, std::ref(args)...);
|
||||
}
|
||||
|
||||
static
|
||||
seastar::httpd::future_json_function
|
||||
rest_bind(ks_cf_func func, http_context& ctx) {
|
||||
return wrap_ks_cf(ctx, func);
|
||||
}
|
||||
|
||||
void set_storage_service(http_context& ctx, routes& r, sharded<service::storage_service>& ss, service::raft_group0_client& group0_client) {
|
||||
ss::get_token_endpoint.set(r, rest_bind(rest_get_token_endpoint, ctx, ss));
|
||||
ss::toppartitions_generic.set(r, rest_bind(rest_toppartitions_generic, ctx));
|
||||
@@ -1855,7 +1790,6 @@ void set_storage_service(http_context& ctx, routes& r, sharded<service::storage_
|
||||
ss::force_keyspace_compaction.set(r, rest_bind(rest_force_keyspace_compaction, ctx));
|
||||
ss::force_keyspace_cleanup.set(r, rest_bind(rest_force_keyspace_cleanup, ctx, ss));
|
||||
ss::cleanup_all.set(r, rest_bind(rest_cleanup_all, ctx, ss));
|
||||
ss::reset_cleanup_needed.set(r, rest_bind(rest_reset_cleanup_needed, ctx, ss));
|
||||
ss::perform_keyspace_offstrategy_compaction.set(r, rest_bind(rest_perform_keyspace_offstrategy_compaction, ctx));
|
||||
ss::upgrade_sstables.set(r, rest_bind(rest_upgrade_sstables, ctx));
|
||||
ss::force_flush.set(r, rest_bind(rest_force_flush, ctx));
|
||||
@@ -1907,13 +1841,10 @@ void set_storage_service(http_context& ctx, routes& r, sharded<service::storage_
|
||||
ss::get_total_hints.set(r, rest_bind(rest_get_total_hints));
|
||||
ss::get_ownership.set(r, rest_bind(rest_get_ownership, ctx, ss));
|
||||
ss::get_effective_ownership.set(r, rest_bind(rest_get_effective_ownership, ctx, ss));
|
||||
ss::retrain_dict.set(r, rest_bind(rest_retrain_dict, ctx, ss, group0_client));
|
||||
ss::estimate_compression_ratios.set(r, rest_bind(rest_estimate_compression_ratios, ctx, ss));
|
||||
ss::sstable_info.set(r, rest_bind(rest_sstable_info, ctx));
|
||||
ss::reload_raft_topology_state.set(r, rest_bind(rest_reload_raft_topology_state, ss, group0_client));
|
||||
ss::upgrade_to_raft_topology.set(r, rest_bind(rest_upgrade_to_raft_topology, ss));
|
||||
ss::raft_topology_upgrade_status.set(r, rest_bind(rest_raft_topology_upgrade_status, ss));
|
||||
ss::raft_topology_get_cmd_status.set(r, rest_bind(rest_raft_topology_get_cmd_status, ss));
|
||||
ss::move_tablet.set(r, rest_bind(rest_move_tablet, ctx, ss));
|
||||
ss::add_tablet_replica.set(r, rest_bind(rest_add_tablet_replica, ctx, ss));
|
||||
ss::del_tablet_replica.set(r, rest_bind(rest_del_tablet_replica, ctx, ss));
|
||||
@@ -1940,7 +1871,6 @@ void unset_storage_service(http_context& ctx, routes& r) {
|
||||
ss::force_keyspace_compaction.unset(r);
|
||||
ss::force_keyspace_cleanup.unset(r);
|
||||
ss::cleanup_all.unset(r);
|
||||
ss::reset_cleanup_needed.unset(r);
|
||||
ss::perform_keyspace_offstrategy_compaction.unset(r);
|
||||
ss::upgrade_sstables.unset(r);
|
||||
ss::force_flush.unset(r);
|
||||
@@ -1996,7 +1926,6 @@ void unset_storage_service(http_context& ctx, routes& r) {
|
||||
ss::reload_raft_topology_state.unset(r);
|
||||
ss::upgrade_to_raft_topology.unset(r);
|
||||
ss::raft_topology_upgrade_status.unset(r);
|
||||
ss::raft_topology_get_cmd_status.unset(r);
|
||||
ss::move_tablet.unset(r);
|
||||
ss::add_tablet_replica.unset(r);
|
||||
ss::del_tablet_replica.unset(r);
|
||||
|
||||
@@ -52,11 +52,10 @@ table_id validate_table(const replica::database& db, sstring ks_name, sstring ta
|
||||
// containing the description of the respective no_such_column_family error.
|
||||
// Returns a vector of all table infos given by the parameter, or
|
||||
// if the parameter is not found or is empty, returns a list of all table infos in the keyspace.
|
||||
std::vector<table_info> parse_table_infos(const sstring& ks_name, const http_context& ctx, const std::unordered_map<sstring, sstring>& query_params, sstring param_name);
|
||||
|
||||
std::vector<table_info> parse_table_infos(const sstring& ks_name, const http_context& ctx, sstring value);
|
||||
|
||||
std::pair<sstring, std::vector<table_info>> parse_table_infos(const http_context& ctx, const http::request& req, sstring cf_param_name = "cf");
|
||||
|
||||
struct scrub_info {
|
||||
sstables::compaction_type_options::scrub opts;
|
||||
sstring keyspace;
|
||||
|
||||
@@ -31,7 +31,8 @@ using ks_cf_func = std::function<future<json::json_return_type>(http_context&, s
|
||||
|
||||
static auto wrap_ks_cf(http_context &ctx, ks_cf_func f) {
|
||||
return [&ctx, f = std::move(f)](std::unique_ptr<http::request> req) {
|
||||
auto [keyspace, table_infos] = parse_table_infos(ctx, *req);
|
||||
auto keyspace = validate_keyspace(ctx, req);
|
||||
auto table_infos = parse_table_infos(keyspace, ctx, req->query_parameters, "cf");
|
||||
return f(ctx, std::move(req), std::move(keyspace), std::move(table_infos));
|
||||
};
|
||||
}
|
||||
@@ -62,7 +63,8 @@ void set_tasks_compaction_module(http_context& ctx, routes& r, sharded<service::
|
||||
|
||||
t::force_keyspace_cleanup_async.set(r, [&ctx, &ss](std::unique_ptr<http::request> req) -> future<json::json_return_type> {
|
||||
auto& db = ctx.db;
|
||||
auto [keyspace, table_infos] = parse_table_infos(ctx, *req);
|
||||
auto keyspace = validate_keyspace(ctx, req);
|
||||
auto table_infos = parse_table_infos(keyspace, ctx, req->query_parameters, "cf");
|
||||
apilog.info("force_keyspace_cleanup_async: keyspace={} tables={}", keyspace, table_infos);
|
||||
if (!co_await ss.local().is_cleanup_allowed(keyspace)) {
|
||||
auto msg = "Can not perform cleanup operation when topology changes";
|
||||
|
||||
@@ -74,9 +74,6 @@ void set_token_metadata(http_context& ctx, routes& r, sharded<locator::shared_to
|
||||
});
|
||||
|
||||
ss::get_host_id_map.set(r, [&tm, &g](const_req req) {
|
||||
if (!g.local().is_enabled()) {
|
||||
throw std::runtime_error("The gossiper is not ready yet");
|
||||
}
|
||||
std::vector<ss::mapper> res;
|
||||
auto map = tm.local().get()->get_host_ids() |
|
||||
std::views::transform([&g] (locator::host_id id) { return std::make_pair(g.local().get_address_map().get(id), id); }) |
|
||||
|
||||
@@ -33,6 +33,20 @@ namespace audit {
|
||||
|
||||
namespace {
|
||||
|
||||
future<> syslog_send_helper(net::datagram_channel& sender,
|
||||
const socket_address& address,
|
||||
const sstring& msg) {
|
||||
return sender.send(address, net::packet{msg.data(), msg.size()}).handle_exception([address](auto&& exception_ptr) {
|
||||
auto error_msg = seastar::format(
|
||||
"Syslog audit backend failed (sending a message to {} resulted in {}).",
|
||||
address,
|
||||
exception_ptr
|
||||
);
|
||||
logger.error("{}", error_msg);
|
||||
throw audit_exception(std::move(error_msg));
|
||||
});
|
||||
}
|
||||
|
||||
static auto syslog_address_helper(const db::config& cfg)
|
||||
{
|
||||
return cfg.audit_unix_socket_path.is_set()
|
||||
@@ -40,40 +54,11 @@ static auto syslog_address_helper(const db::config& cfg)
|
||||
: unix_domain_addr(_PATH_LOG);
|
||||
}
|
||||
|
||||
static std::string json_escape(std::string_view str) {
|
||||
std::string result;
|
||||
result.reserve(str.size() * 1.2);
|
||||
for (auto c : str) {
|
||||
if (c == '"' || c == '\\') {
|
||||
result.push_back('\\');
|
||||
}
|
||||
result.push_back(c);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
future<> audit_syslog_storage_helper::syslog_send_helper(const sstring& msg) {
|
||||
try {
|
||||
auto lock = co_await get_units(_semaphore, 1, std::chrono::hours(1));
|
||||
co_await _sender.send(_syslog_address, net::packet{msg.data(), msg.size()});
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
auto error_msg = seastar::format(
|
||||
"Syslog audit backend failed (sending a message to {} resulted in {}).",
|
||||
_syslog_address,
|
||||
e
|
||||
);
|
||||
logger.error("{}", error_msg);
|
||||
throw audit_exception(std::move(error_msg));
|
||||
}
|
||||
}
|
||||
|
||||
audit_syslog_storage_helper::audit_syslog_storage_helper(cql3::query_processor& qp, service::migration_manager&) :
|
||||
_syslog_address(syslog_address_helper(qp.db().get_config())),
|
||||
_sender(make_unbound_datagram_channel(AF_UNIX)),
|
||||
_semaphore(1) {
|
||||
_sender(make_unbound_datagram_channel(AF_UNIX)) {
|
||||
}
|
||||
|
||||
audit_syslog_storage_helper::~audit_syslog_storage_helper() {
|
||||
@@ -88,10 +73,10 @@ audit_syslog_storage_helper::~audit_syslog_storage_helper() {
|
||||
*/
|
||||
future<> audit_syslog_storage_helper::start(const db::config& cfg) {
|
||||
if (this_shard_id() != 0) {
|
||||
co_return;
|
||||
return make_ready_future();
|
||||
}
|
||||
|
||||
co_await syslog_send_helper("Initializing syslog audit backend.");
|
||||
return syslog_send_helper(_sender, _syslog_address, "Initializing syslog audit backend.");
|
||||
}
|
||||
|
||||
future<> audit_syslog_storage_helper::stop() {
|
||||
@@ -108,7 +93,7 @@ future<> audit_syslog_storage_helper::write(const audit_info* audit_info,
|
||||
auto now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
|
||||
tm time;
|
||||
localtime_r(&now, &time);
|
||||
sstring msg = seastar::format(R"(<{}>{:%h %e %T} scylla-audit: node="{}" category="{}" cl="{}" error="{}" keyspace="{}" query="{}" client_ip="{}" table="{}" username="{}")",
|
||||
sstring msg = seastar::format("<{}>{:%h %e %T} scylla-audit: \"{}\", \"{}\", \"{}\", \"{}\", \"{}\", \"{}\", \"{}\", \"{}\", \"{}\"",
|
||||
LOG_NOTICE | LOG_USER,
|
||||
time,
|
||||
node_ip,
|
||||
@@ -116,12 +101,12 @@ future<> audit_syslog_storage_helper::write(const audit_info* audit_info,
|
||||
cl,
|
||||
(error ? "true" : "false"),
|
||||
audit_info->keyspace(),
|
||||
json_escape(audit_info->query()),
|
||||
audit_info->query(),
|
||||
client_ip,
|
||||
audit_info->table(),
|
||||
username);
|
||||
|
||||
co_await syslog_send_helper(msg);
|
||||
return syslog_send_helper(_sender, _syslog_address, msg);
|
||||
}
|
||||
|
||||
future<> audit_syslog_storage_helper::write_login(const sstring& username,
|
||||
@@ -132,15 +117,15 @@ future<> audit_syslog_storage_helper::write_login(const sstring& username,
|
||||
auto now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
|
||||
tm time;
|
||||
localtime_r(&now, &time);
|
||||
sstring msg = seastar::format(R"(<{}>{:%h %e %T} scylla-audit: node="{}", category="AUTH", cl="", error="{}", keyspace="", query="", client_ip="{}", table="", username="{}")",
|
||||
sstring msg = seastar::format("<{}>{:%h %e %T} scylla-audit: \"{}\", \"AUTH\", \"\", \"\", \"\", \"\", \"{}\", \"{}\", \"{}\"",
|
||||
LOG_NOTICE | LOG_USER,
|
||||
time,
|
||||
node_ip,
|
||||
(error ? "true" : "false"),
|
||||
client_ip,
|
||||
username);
|
||||
username,
|
||||
(error ? "true" : "false"));
|
||||
|
||||
co_await syslog_send_helper(msg.c_str());
|
||||
co_await syslog_send_helper(_sender, _syslog_address, msg.c_str());
|
||||
}
|
||||
|
||||
using registry = class_registrator<storage_helper, audit_syslog_storage_helper, cql3::query_processor&, service::migration_manager&>;
|
||||
|
||||
@@ -24,9 +24,6 @@ namespace audit {
|
||||
class audit_syslog_storage_helper : public storage_helper {
|
||||
socket_address _syslog_address;
|
||||
net::datagram_channel _sender;
|
||||
seastar::semaphore _semaphore;
|
||||
|
||||
future<> syslog_send_helper(const sstring& msg);
|
||||
public:
|
||||
explicit audit_syslog_storage_helper(cql3::query_processor&, service::migration_manager&);
|
||||
virtual ~audit_syslog_storage_helper();
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#include "auth/allow_all_authenticator.hh"
|
||||
|
||||
#include "service/migration_manager.hh"
|
||||
#include "utils/alien_worker.hh"
|
||||
#include "utils/class_registrator.hh"
|
||||
|
||||
namespace auth {
|
||||
@@ -22,7 +21,6 @@ static const class_registrator<
|
||||
allow_all_authenticator,
|
||||
cql3::query_processor&,
|
||||
::service::raft_group0_client&,
|
||||
::service::migration_manager&,
|
||||
utils::alien_worker&> registration("org.apache.cassandra.auth.AllowAllAuthenticator");
|
||||
::service::migration_manager&> registration("org.apache.cassandra.auth.AllowAllAuthenticator");
|
||||
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
#include "auth/authenticated_user.hh"
|
||||
#include "auth/authenticator.hh"
|
||||
#include "auth/common.hh"
|
||||
#include "utils/alien_worker.hh"
|
||||
|
||||
namespace cql3 {
|
||||
class query_processor;
|
||||
@@ -29,7 +28,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&) {
|
||||
}
|
||||
|
||||
virtual future<> start() override {
|
||||
|
||||
@@ -33,14 +33,13 @@ static const class_registrator<auth::authenticator
|
||||
, auth::certificate_authenticator
|
||||
, cql3::query_processor&
|
||||
, ::service::raft_group0_client&
|
||||
, ::service::migration_manager&
|
||||
, utils::alien_worker&> cert_auth_reg(CERT_AUTH_NAME);
|
||||
, ::service::migration_manager&> cert_auth_reg(CERT_AUTH_NAME);
|
||||
|
||||
enum class auth::certificate_authenticator::query_source {
|
||||
subject, altname
|
||||
};
|
||||
|
||||
auth::certificate_authenticator::certificate_authenticator(cql3::query_processor& qp, ::service::raft_group0_client&, ::service::migration_manager&, utils::alien_worker&)
|
||||
auth::certificate_authenticator::certificate_authenticator(cql3::query_processor& qp, ::service::raft_group0_client&, ::service::migration_manager&)
|
||||
: _queries([&] {
|
||||
auto& conf = qp.db().get_config();
|
||||
auto queries = conf.auth_certificate_role_queries();
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "auth/authenticator.hh"
|
||||
#include "utils/alien_worker.hh"
|
||||
#include <boost/regex_fwd.hpp> // IWYU pragma: keep
|
||||
|
||||
namespace cql3 {
|
||||
@@ -32,7 +31,7 @@ class certificate_authenticator : public authenticator {
|
||||
enum class query_source;
|
||||
std::vector<std::pair<query_source, boost::regex>> _queries;
|
||||
public:
|
||||
certificate_authenticator(cql3::query_processor&, ::service::raft_group0_client&, ::service::migration_manager&, utils::alien_worker&);
|
||||
certificate_authenticator(cql3::query_processor&, ::service::raft_group0_client&, ::service::migration_manager&);
|
||||
~certificate_authenticator();
|
||||
|
||||
future<> start() override;
|
||||
|
||||
@@ -119,11 +119,6 @@ future<> create_legacy_metadata_table_if_missing(
|
||||
return qs;
|
||||
}
|
||||
|
||||
::service::raft_timeout get_raft_timeout() noexcept {
|
||||
auto dur = internal_distributed_query_state().get_client_state().get_timeout_config().other_timeout;
|
||||
return ::service::raft_timeout{.value = lowres_clock::now() + dur};
|
||||
}
|
||||
|
||||
static future<> announce_mutations_with_guard(
|
||||
::service::raft_group0_client& group0_client,
|
||||
std::vector<canonical_mutation> muts,
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
|
||||
#include "types/types.hh"
|
||||
#include "service/raft/raft_group0_client.hh"
|
||||
#include "timeout_config.hh"
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
@@ -78,8 +77,6 @@ future<> create_legacy_metadata_table_if_missing(
|
||||
///
|
||||
::service::query_state& internal_distributed_query_state() noexcept;
|
||||
|
||||
::service::raft_timeout get_raft_timeout() noexcept;
|
||||
|
||||
// Execute update query via group0 mechanism, mutations will be applied on all nodes.
|
||||
// Use this function when need to perform read before write on a single guard or if
|
||||
// you have more than one mutation and potentially exceed single command size limit.
|
||||
|
||||
@@ -233,9 +233,9 @@ future<role_set> ldap_role_manager::query_granted(std::string_view grantee_name,
|
||||
}
|
||||
|
||||
future<role_to_directly_granted_map>
|
||||
ldap_role_manager::query_all_directly_granted(::service::query_state& qs) {
|
||||
ldap_role_manager::query_all_directly_granted() {
|
||||
role_to_directly_granted_map result;
|
||||
auto roles = co_await query_all(qs);
|
||||
auto roles = co_await query_all();
|
||||
for (auto& role: roles) {
|
||||
auto granted_set = co_await query_granted(role, recursive_role_query::no);
|
||||
for (auto& granted: granted_set) {
|
||||
@@ -247,8 +247,8 @@ ldap_role_manager::query_all_directly_granted(::service::query_state& qs) {
|
||||
co_return result;
|
||||
}
|
||||
|
||||
future<role_set> ldap_role_manager::query_all(::service::query_state& qs) {
|
||||
return _std_mgr.query_all(qs);
|
||||
future<role_set> ldap_role_manager::query_all() {
|
||||
return _std_mgr.query_all();
|
||||
}
|
||||
|
||||
future<> ldap_role_manager::create_role(std::string_view role_name) {
|
||||
@@ -311,12 +311,12 @@ future<bool> ldap_role_manager::can_login(std::string_view role_name) {
|
||||
}
|
||||
|
||||
future<std::optional<sstring>> ldap_role_manager::get_attribute(
|
||||
std::string_view role_name, std::string_view attribute_name, ::service::query_state& qs) {
|
||||
return _std_mgr.get_attribute(role_name, attribute_name, qs);
|
||||
std::string_view role_name, std::string_view attribute_name) {
|
||||
return _std_mgr.get_attribute(role_name, attribute_name);
|
||||
}
|
||||
|
||||
future<role_manager::attribute_vals> ldap_role_manager::query_attribute_for_all(std::string_view attribute_name, ::service::query_state& qs) {
|
||||
return _std_mgr.query_attribute_for_all(attribute_name, qs);
|
||||
future<role_manager::attribute_vals> ldap_role_manager::query_attribute_for_all(std::string_view attribute_name) {
|
||||
return _std_mgr.query_attribute_for_all(attribute_name);
|
||||
}
|
||||
|
||||
future<> ldap_role_manager::set_attribute(
|
||||
@@ -338,7 +338,8 @@ future<std::vector<cql3::description>> ldap_role_manager::describe_role_grants()
|
||||
}
|
||||
|
||||
future<> ldap_role_manager::ensure_superuser_is_created() {
|
||||
return _std_mgr.ensure_superuser_is_created();
|
||||
// ldap is responsible for users
|
||||
co_return;
|
||||
}
|
||||
|
||||
} // namespace auth
|
||||
|
||||
@@ -75,9 +75,9 @@ class ldap_role_manager : public role_manager {
|
||||
|
||||
future<role_set> query_granted(std::string_view, recursive_role_query) override;
|
||||
|
||||
future<role_to_directly_granted_map> query_all_directly_granted(::service::query_state&) override;
|
||||
future<role_to_directly_granted_map> query_all_directly_granted() override;
|
||||
|
||||
future<role_set> query_all(::service::query_state&) override;
|
||||
future<role_set> query_all() override;
|
||||
|
||||
future<bool> exists(std::string_view) override;
|
||||
|
||||
@@ -85,9 +85,9 @@ class ldap_role_manager : public role_manager {
|
||||
|
||||
future<bool> can_login(std::string_view) override;
|
||||
|
||||
future<std::optional<sstring>> get_attribute(std::string_view, std::string_view, ::service::query_state&) override;
|
||||
future<std::optional<sstring>> get_attribute(std::string_view, std::string_view) override;
|
||||
|
||||
future<role_manager::attribute_vals> query_attribute_for_all(std::string_view, ::service::query_state&) override;
|
||||
future<role_manager::attribute_vals> query_attribute_for_all(std::string_view) override;
|
||||
|
||||
future<> set_attribute(std::string_view, std::string_view, std::string_view, ::service::group0_batch& mc) override;
|
||||
|
||||
|
||||
@@ -78,11 +78,11 @@ future<role_set> maintenance_socket_role_manager::query_granted(std::string_view
|
||||
return operation_not_supported_exception<role_set>("QUERY GRANTED");
|
||||
}
|
||||
|
||||
future<role_to_directly_granted_map> maintenance_socket_role_manager::query_all_directly_granted(::service::query_state&) {
|
||||
future<role_to_directly_granted_map> maintenance_socket_role_manager::query_all_directly_granted() {
|
||||
return operation_not_supported_exception<role_to_directly_granted_map>("QUERY ALL DIRECTLY GRANTED");
|
||||
}
|
||||
|
||||
future<role_set> maintenance_socket_role_manager::query_all(::service::query_state&) {
|
||||
future<role_set> maintenance_socket_role_manager::query_all() {
|
||||
return operation_not_supported_exception<role_set>("QUERY ALL");
|
||||
}
|
||||
|
||||
@@ -98,11 +98,11 @@ future<bool> maintenance_socket_role_manager::can_login(std::string_view role_na
|
||||
return make_ready_future<bool>(true);
|
||||
}
|
||||
|
||||
future<std::optional<sstring>> maintenance_socket_role_manager::get_attribute(std::string_view role_name, std::string_view attribute_name, ::service::query_state&) {
|
||||
future<std::optional<sstring>> maintenance_socket_role_manager::get_attribute(std::string_view role_name, std::string_view attribute_name) {
|
||||
return operation_not_supported_exception<std::optional<sstring>>("GET ATTRIBUTE");
|
||||
}
|
||||
|
||||
future<role_manager::attribute_vals> maintenance_socket_role_manager::query_attribute_for_all(std::string_view attribute_name, ::service::query_state&) {
|
||||
future<role_manager::attribute_vals> maintenance_socket_role_manager::query_attribute_for_all(std::string_view attribute_name) {
|
||||
return operation_not_supported_exception<role_manager::attribute_vals>("QUERY ATTRIBUTE");
|
||||
}
|
||||
|
||||
|
||||
@@ -53,9 +53,9 @@ public:
|
||||
|
||||
virtual future<role_set> query_granted(std::string_view grantee_name, recursive_role_query) override;
|
||||
|
||||
virtual future<role_to_directly_granted_map> query_all_directly_granted(::service::query_state&) override;
|
||||
virtual future<role_to_directly_granted_map> query_all_directly_granted() override;
|
||||
|
||||
virtual future<role_set> query_all(::service::query_state&) override;
|
||||
virtual future<role_set> query_all() override;
|
||||
|
||||
virtual future<bool> exists(std::string_view role_name) override;
|
||||
|
||||
@@ -63,9 +63,9 @@ public:
|
||||
|
||||
virtual future<bool> can_login(std::string_view role_name) override;
|
||||
|
||||
virtual future<std::optional<sstring>> get_attribute(std::string_view role_name, std::string_view attribute_name, ::service::query_state&) override;
|
||||
virtual future<std::optional<sstring>> get_attribute(std::string_view role_name, std::string_view attribute_name) override;
|
||||
|
||||
virtual future<role_manager::attribute_vals> query_attribute_for_all(std::string_view attribute_name, ::service::query_state&) override;
|
||||
virtual future<role_manager::attribute_vals> query_attribute_for_all(std::string_view attribute_name) override;
|
||||
|
||||
virtual future<> set_attribute(std::string_view role_name, std::string_view attribute_name, std::string_view attribute_value, ::service::group0_batch& mc) override;
|
||||
|
||||
|
||||
@@ -48,14 +48,14 @@ static const class_registrator<
|
||||
password_authenticator,
|
||||
cql3::query_processor&,
|
||||
::service::raft_group0_client&,
|
||||
::service::migration_manager&,
|
||||
utils::alien_worker&> password_auth_reg("org.apache.cassandra.auth.PasswordAuthenticator");
|
||||
::service::migration_manager&> password_auth_reg("org.apache.cassandra.auth.PasswordAuthenticator");
|
||||
|
||||
static thread_local auto rng_for_salt = std::default_random_engine(std::random_device{}());
|
||||
|
||||
static std::string_view get_config_value(std::string_view value, std::string_view def) {
|
||||
return value.empty() ? def : value;
|
||||
}
|
||||
|
||||
std::string password_authenticator::default_superuser(const db::config& cfg) {
|
||||
return std::string(get_config_value(cfg.auth_superuser_name(), DEFAULT_USER_NAME));
|
||||
}
|
||||
@@ -63,13 +63,12 @@ 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)
|
||||
: _qp(qp)
|
||||
, _group0_client(g0)
|
||||
, _migration_manager(mm)
|
||||
, _stopped(make_ready_future<>())
|
||||
, _superuser(default_superuser(qp.db().get_config()))
|
||||
, _hashing_worker(hashing_worker)
|
||||
{}
|
||||
|
||||
static bool has_salted_hash(const cql3::untyped_result_set_row& row) {
|
||||
@@ -118,95 +117,33 @@ future<> password_authenticator::migrate_legacy_metadata() const {
|
||||
});
|
||||
}
|
||||
|
||||
future<> password_authenticator::legacy_create_default_if_missing() {
|
||||
SCYLLA_ASSERT(legacy_mode(_qp));
|
||||
future<> password_authenticator::create_default_if_missing() {
|
||||
const auto exists = co_await default_role_row_satisfies(_qp, &has_salted_hash, _superuser);
|
||||
if (exists) {
|
||||
co_return;
|
||||
}
|
||||
std::string salted_pwd(get_config_value(_qp.db().get_config().auth_superuser_salted_password(), ""));
|
||||
if (salted_pwd.empty()) {
|
||||
salted_pwd = passwords::hash(DEFAULT_USER_PASSWORD, rng_for_salt, _scheme);
|
||||
salted_pwd = passwords::hash(DEFAULT_USER_PASSWORD, rng_for_salt);
|
||||
}
|
||||
const auto query = update_row_query();
|
||||
co_await _qp.execute_internal(
|
||||
if (legacy_mode(_qp)) {
|
||||
co_await _qp.execute_internal(
|
||||
query,
|
||||
db::consistency_level::QUORUM,
|
||||
internal_distributed_query_state(),
|
||||
{salted_pwd, _superuser},
|
||||
cql3::query_processor::cache_internal::no);
|
||||
plogger.info("Created default superuser authentication record.");
|
||||
}
|
||||
|
||||
future<> password_authenticator::maybe_create_default_password() {
|
||||
auto needs_password = [this] () -> future<bool> {
|
||||
const sstring query = seastar::format("SELECT * FROM {}.{} WHERE is_superuser = true ALLOW FILTERING", get_auth_ks_name(_qp), meta::roles_table::name);
|
||||
auto results = co_await _qp.execute_internal(query,
|
||||
db::consistency_level::LOCAL_ONE,
|
||||
internal_distributed_query_state(), cql3::query_processor::cache_internal::yes);
|
||||
// Don't add default password if
|
||||
// - there is no default superuser
|
||||
// - there is a superuser with a password.
|
||||
bool has_default = false;
|
||||
bool has_superuser_with_password = false;
|
||||
for (auto& result : *results) {
|
||||
if (result.get_as<sstring>(meta::roles_table::role_col_name) == _superuser) {
|
||||
has_default = true;
|
||||
}
|
||||
if (has_salted_hash(result)) {
|
||||
has_superuser_with_password = true;
|
||||
}
|
||||
}
|
||||
co_return has_default && !has_superuser_with_password;
|
||||
};
|
||||
if (!co_await needs_password()) {
|
||||
co_return;
|
||||
}
|
||||
// We don't want to start operation earlier to avoid quorum requirement in
|
||||
// a common case.
|
||||
::service::group0_batch batch(
|
||||
co_await _group0_client.start_operation(_as, get_raft_timeout()));
|
||||
// Check again as the state may have changed before we took the guard (batch).
|
||||
if (!co_await needs_password()) {
|
||||
co_return;
|
||||
}
|
||||
// Set default superuser's password.
|
||||
std::string salted_pwd(get_config_value(_qp.db().get_config().auth_superuser_salted_password(), ""));
|
||||
if (salted_pwd.empty()) {
|
||||
salted_pwd = passwords::hash(DEFAULT_USER_PASSWORD, rng_for_salt, _scheme);
|
||||
}
|
||||
const auto update_query = update_row_query();
|
||||
co_await collect_mutations(_qp, batch, update_query, {salted_pwd, _superuser});
|
||||
co_await std::move(batch).commit(_group0_client, _as, get_raft_timeout());
|
||||
plogger.info("Created default superuser authentication record.");
|
||||
}
|
||||
|
||||
future<> password_authenticator::maybe_create_default_password_with_retries() {
|
||||
size_t retries = _migration_manager.get_concurrent_ddl_retries();
|
||||
while (true) {
|
||||
try {
|
||||
co_return co_await maybe_create_default_password();
|
||||
} catch (const ::service::group0_concurrent_modification& ex) {
|
||||
plogger.warn("Failed to execute maybe_create_default_password due to guard conflict.{}.", retries ? " Retrying" : " Number of retries exceeded, giving up");
|
||||
if (retries--) {
|
||||
continue;
|
||||
}
|
||||
// Log error but don't crash the whole node startup sequence.
|
||||
plogger.error("Failed to create default superuser password due to guard conflict.");
|
||||
co_return;
|
||||
} catch (const ::service::raft_operation_timeout_error& ex) {
|
||||
plogger.error("Failed to create default superuser password due to exception: {}", ex.what());
|
||||
co_return;
|
||||
}
|
||||
plogger.info("Created default superuser authentication record.");
|
||||
} else {
|
||||
co_await announce_mutations(_qp, _group0_client, query,
|
||||
{salted_pwd, _superuser}, _as, ::service::raft_timeout{});
|
||||
plogger.info("Created default superuser authentication record.");
|
||||
}
|
||||
}
|
||||
|
||||
future<> password_authenticator::start() {
|
||||
return once_among_shards([this] {
|
||||
// Verify that at least one hashing scheme is supported.
|
||||
passwords::detail::verify_scheme(_scheme);
|
||||
plogger.info("Using password hashing scheme: {}", passwords::detail::prefix_for_scheme(_scheme));
|
||||
|
||||
_stopped = do_after_system_ready(_as, [this] {
|
||||
return async([this] {
|
||||
if (legacy_mode(_qp)) {
|
||||
@@ -227,14 +164,11 @@ future<> password_authenticator::start() {
|
||||
migrate_legacy_metadata().get();
|
||||
return;
|
||||
}
|
||||
legacy_create_default_if_missing().get();
|
||||
}
|
||||
utils::get_local_injector().inject("password_authenticator_start_pause", utils::wait_for_message(5min)).get();
|
||||
create_default_if_missing().get();
|
||||
if (!legacy_mode(_qp)) {
|
||||
maybe_create_default_password_with_retries().get();
|
||||
if (!_superuser_created_promise.available()) {
|
||||
_superuser_created_promise.set_value();
|
||||
}
|
||||
_superuser_created_promise.set_value();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -294,13 +228,7 @@ future<authenticated_user> password_authenticator::authenticate(
|
||||
|
||||
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");
|
||||
}
|
||||
const bool password_match = co_await _hashing_worker.submit<bool>([password = std::move(password), salted_hash = std::move(salted_hash)]{
|
||||
return passwords::check(password, *salted_hash);
|
||||
});
|
||||
if (!password_match) {
|
||||
if (!salted_hash || !passwords::check(password, *salted_hash)) {
|
||||
throw exceptions::authentication_exception("Username and/or password are incorrect");
|
||||
}
|
||||
co_return username;
|
||||
@@ -324,7 +252,7 @@ future<> password_authenticator::create(std::string_view role_name, const authen
|
||||
auto maybe_hash = options.credentials.transform([&] (const auto& creds) -> sstring {
|
||||
return std::visit(make_visitor(
|
||||
[&] (const password_option& opt) {
|
||||
return passwords::hash(opt.password, rng_for_salt, _scheme);
|
||||
return passwords::hash(opt.password, rng_for_salt);
|
||||
},
|
||||
[] (const hashed_password_option& opt) {
|
||||
return opt.hashed_password;
|
||||
@@ -367,11 +295,11 @@ future<> password_authenticator::alter(std::string_view role_name, const authent
|
||||
query,
|
||||
consistency_for_user(role_name),
|
||||
internal_distributed_query_state(),
|
||||
{passwords::hash(password, rng_for_salt, _scheme), sstring(role_name)},
|
||||
{passwords::hash(password, rng_for_salt), sstring(role_name)},
|
||||
cql3::query_processor::cache_internal::no).discard_result();
|
||||
} else {
|
||||
co_await collect_mutations(_qp, mc, query,
|
||||
{passwords::hash(password, rng_for_salt, _scheme), sstring(role_name)});
|
||||
{passwords::hash(password, rng_for_salt), sstring(role_name)});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,9 +15,7 @@
|
||||
|
||||
#include "db/consistency_level_type.hh"
|
||||
#include "auth/authenticator.hh"
|
||||
#include "auth/passwords.hh"
|
||||
#include "service/raft/raft_group0_client.hh"
|
||||
#include "utils/alien_worker.hh"
|
||||
|
||||
namespace db {
|
||||
class config;
|
||||
@@ -43,17 +41,14 @@ class password_authenticator : public authenticator {
|
||||
::service::migration_manager& _migration_manager;
|
||||
future<> _stopped;
|
||||
abort_source _as;
|
||||
std::string _superuser; // default superuser name from the config (may or may not be present in roles table)
|
||||
std::string _superuser;
|
||||
shared_promise<> _superuser_created_promise;
|
||||
// We used to also support bcrypt, SHA-256, and MD5 (ref. scylladb#24524).
|
||||
constexpr static auth::passwords::scheme _scheme = passwords::scheme::sha_512;
|
||||
utils::alien_worker& _hashing_worker;
|
||||
|
||||
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&);
|
||||
|
||||
~password_authenticator();
|
||||
|
||||
@@ -94,10 +89,7 @@ private:
|
||||
|
||||
future<> migrate_legacy_metadata() const;
|
||||
|
||||
future<> legacy_create_default_if_missing();
|
||||
|
||||
future<> maybe_create_default_password();
|
||||
future<> maybe_create_default_password_with_retries();
|
||||
future<> create_default_if_missing();
|
||||
|
||||
sstring update_row_query() const;
|
||||
};
|
||||
|
||||
@@ -21,14 +21,18 @@ static thread_local crypt_data tlcrypt = {};
|
||||
|
||||
namespace detail {
|
||||
|
||||
void verify_scheme(scheme scheme) {
|
||||
scheme identify_best_supported_scheme() {
|
||||
const auto all_schemes = { scheme::bcrypt_y, scheme::bcrypt_a, scheme::sha_512, scheme::sha_256, scheme::md5 };
|
||||
// "Random", for testing schemes.
|
||||
const sstring random_part_of_salt = "aaaabbbbccccdddd";
|
||||
|
||||
const sstring salt = sstring(prefix_for_scheme(scheme)) + random_part_of_salt;
|
||||
const char* e = crypt_r("fisk", salt.c_str(), &tlcrypt);
|
||||
for (scheme c : all_schemes) {
|
||||
const sstring salt = sstring(prefix_for_scheme(c)) + random_part_of_salt;
|
||||
const char* e = crypt_r("fisk", salt.c_str(), &tlcrypt);
|
||||
|
||||
if (e && (e[0] != '*')) {
|
||||
return;
|
||||
if (e && (e[0] != '*')) {
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
||||
throw no_supported_schemes();
|
||||
|
||||
@@ -21,11 +21,10 @@ class no_supported_schemes : public std::runtime_error {
|
||||
public:
|
||||
no_supported_schemes();
|
||||
};
|
||||
|
||||
///
|
||||
/// Apache Cassandra uses a library to provide the bcrypt scheme. In ScyllaDB, we use SHA-512
|
||||
/// instead of bcrypt for performance and for historical reasons (see scylladb#24524).
|
||||
/// Currently, SHA-512 is always chosen as the hashing scheme for new passwords, but the other
|
||||
/// algorithms remain supported for CREATE ROLE WITH HASHED PASSWORD and backward compatibility.
|
||||
/// Apache Cassandra uses a library to provide the bcrypt scheme. Many Linux implementations do not support bcrypt, so
|
||||
/// we support alternatives. The cost is loss of direct compatibility with Apache Cassandra system tables.
|
||||
///
|
||||
enum class scheme {
|
||||
bcrypt_y,
|
||||
@@ -52,11 +51,11 @@ sstring generate_random_salt_bytes(RandomNumberEngine& g) {
|
||||
}
|
||||
|
||||
///
|
||||
/// Test given hashing scheme on the current system.
|
||||
/// Test each allowed hashing scheme and report the best supported one on the current system.
|
||||
///
|
||||
/// \throws \ref no_supported_schemes when scheme is unsupported.
|
||||
/// \throws \ref no_supported_schemes when none of the known schemes is supported.
|
||||
///
|
||||
void verify_scheme(scheme scheme);
|
||||
scheme identify_best_supported_scheme();
|
||||
|
||||
std::string_view prefix_for_scheme(scheme) noexcept;
|
||||
|
||||
@@ -68,7 +67,8 @@ std::string_view prefix_for_scheme(scheme) noexcept;
|
||||
/// \throws \ref no_supported_schemes when no known hashing schemes are supported on the system.
|
||||
///
|
||||
template <typename RandomNumberEngine>
|
||||
sstring generate_salt(RandomNumberEngine& g, scheme scheme) {
|
||||
sstring generate_salt(RandomNumberEngine& g) {
|
||||
static const scheme scheme = identify_best_supported_scheme();
|
||||
static const sstring prefix = sstring(prefix_for_scheme(scheme));
|
||||
return prefix + generate_random_salt_bytes(g);
|
||||
}
|
||||
@@ -93,8 +93,8 @@ sstring hash_with_salt(const sstring& pass, const sstring& salt);
|
||||
/// \throws \ref std::system_error when the implementation-specific implementation fails to hash the cleartext.
|
||||
///
|
||||
template <typename RandomNumberEngine>
|
||||
sstring hash(const sstring& pass, RandomNumberEngine& g, scheme scheme) {
|
||||
return detail::hash_with_salt(pass, detail::generate_salt(g, scheme));
|
||||
sstring hash(const sstring& pass, RandomNumberEngine& g) {
|
||||
return detail::hash_with_salt(pass, detail::generate_salt(g));
|
||||
}
|
||||
|
||||
///
|
||||
|
||||
@@ -17,17 +17,12 @@
|
||||
#include <seastar/core/format.hh>
|
||||
#include <seastar/core/sstring.hh>
|
||||
|
||||
#include "auth/common.hh"
|
||||
#include "auth/resource.hh"
|
||||
#include "cql3/description.hh"
|
||||
#include "seastarx.hh"
|
||||
#include "exceptions/exceptions.hh"
|
||||
#include "service/raft/raft_group0_client.hh"
|
||||
|
||||
namespace service {
|
||||
class query_state;
|
||||
};
|
||||
|
||||
namespace auth {
|
||||
|
||||
struct role_config final {
|
||||
@@ -172,9 +167,9 @@ public:
|
||||
/// (role2, role3)
|
||||
/// }
|
||||
///
|
||||
virtual future<role_to_directly_granted_map> query_all_directly_granted(::service::query_state& = internal_distributed_query_state()) = 0;
|
||||
virtual future<role_to_directly_granted_map> query_all_directly_granted() = 0;
|
||||
|
||||
virtual future<role_set> query_all(::service::query_state& = internal_distributed_query_state()) = 0;
|
||||
virtual future<role_set> query_all() = 0;
|
||||
|
||||
virtual future<bool> exists(std::string_view role_name) = 0;
|
||||
|
||||
@@ -191,12 +186,12 @@ public:
|
||||
///
|
||||
/// \returns the value of the named attribute, if one is set.
|
||||
///
|
||||
virtual future<std::optional<sstring>> get_attribute(std::string_view role_name, std::string_view attribute_name, ::service::query_state& = internal_distributed_query_state()) = 0;
|
||||
virtual future<std::optional<sstring>> get_attribute(std::string_view role_name, std::string_view attribute_name) = 0;
|
||||
|
||||
///
|
||||
/// \returns a mapping of each role's value for the named attribute, if one is set for the role.
|
||||
///
|
||||
virtual future<attribute_vals> query_attribute_for_all(std::string_view attribute_name, ::service::query_state& = internal_distributed_query_state()) = 0;
|
||||
virtual future<attribute_vals> query_attribute_for_all(std::string_view attribute_name) = 0;
|
||||
|
||||
/// Sets `attribute_name` with `attribute_value` for `role_name`.
|
||||
/// \returns an exceptional future with nonexistant_role if the role does not exist.
|
||||
|
||||
@@ -34,10 +34,9 @@ static const class_registrator<
|
||||
saslauthd_authenticator,
|
||||
cql3::query_processor&,
|
||||
::service::raft_group0_client&,
|
||||
::service::migration_manager&,
|
||||
utils::alien_worker&> saslauthd_auth_reg("com.scylladb.auth.SaslauthdAuthenticator");
|
||||
::service::migration_manager&> 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&)
|
||||
: _socket_path(qp.db().get_config().saslauthd_socket_path())
|
||||
{}
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "auth/authenticator.hh"
|
||||
#include "utils/alien_worker.hh"
|
||||
|
||||
namespace cql3 {
|
||||
class query_processor;
|
||||
@@ -29,7 +28,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&);
|
||||
|
||||
future<> start() override;
|
||||
|
||||
|
||||
@@ -187,15 +187,14 @@ service::service(
|
||||
::service::migration_notifier& mn,
|
||||
::service::migration_manager& mm,
|
||||
const service_config& sc,
|
||||
maintenance_socket_enabled used_by_maintenance_socket,
|
||||
utils::alien_worker& hashing_worker)
|
||||
maintenance_socket_enabled used_by_maintenance_socket)
|
||||
: service(
|
||||
std::move(c),
|
||||
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<authenticator>(sc.authenticator_java_name, qp, g0, mm),
|
||||
create_object<role_manager>(sc.role_manager_java_name, qp, g0, mm),
|
||||
used_by_maintenance_socket) {
|
||||
}
|
||||
@@ -241,13 +240,6 @@ future<> service::start(::service::migration_manager& mm, db::system_keyspace& s
|
||||
});
|
||||
}
|
||||
co_await _role_manager->start();
|
||||
if (this_shard_id() == 0) {
|
||||
// Role manager and password authenticator have this odd startup
|
||||
// mechanism where they asynchronously create the superuser role
|
||||
// in the background. Correct password creation depends on role
|
||||
// creation therefore we need to wait here.
|
||||
co_await _role_manager->ensure_superuser_is_created();
|
||||
}
|
||||
co_await when_all_succeed(_authorizer->start(), _authenticator->start()).discard_result();
|
||||
_permissions_cache = std::make_unique<permissions_cache>(_loading_cache_config, *this, log);
|
||||
co_await once_among_shards([this] {
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
#include "cql3/description.hh"
|
||||
#include "seastarx.hh"
|
||||
#include "service/raft/raft_group0_client.hh"
|
||||
#include "utils/alien_worker.hh"
|
||||
#include "utils/observable.hh"
|
||||
#include "utils/serialized_action.hh"
|
||||
#include "service/maintenance_mode.hh"
|
||||
@@ -127,8 +126,7 @@ public:
|
||||
::service::migration_notifier&,
|
||||
::service::migration_manager&,
|
||||
const service_config&,
|
||||
maintenance_socket_enabled,
|
||||
utils::alien_worker&);
|
||||
maintenance_socket_enabled);
|
||||
|
||||
future<> start(::service::migration_manager&, db::system_keyspace&);
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#include "auth/standard_role_manager.hh"
|
||||
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
@@ -29,7 +28,6 @@
|
||||
#include "cql3/util.hh"
|
||||
#include "db/consistency_level_type.hh"
|
||||
#include "exceptions/exceptions.hh"
|
||||
#include "utils/error_injection.hh"
|
||||
#include "utils/log.hh"
|
||||
#include <seastar/core/loop.hh>
|
||||
#include <seastar/coroutine/maybe_yield.hh>
|
||||
@@ -180,8 +178,7 @@ future<> standard_role_manager::create_legacy_metadata_tables_if_missing() const
|
||||
_migration_manager)).discard_result();
|
||||
}
|
||||
|
||||
future<> standard_role_manager::legacy_create_default_role_if_missing() {
|
||||
SCYLLA_ASSERT(legacy_mode(_qp));
|
||||
future<> standard_role_manager::create_default_role_if_missing() {
|
||||
try {
|
||||
const auto exists = co_await default_role_row_satisfies(_qp, &has_can_login, _superuser);
|
||||
if (exists) {
|
||||
@@ -191,12 +188,16 @@ future<> standard_role_manager::legacy_create_default_role_if_missing() {
|
||||
get_auth_ks_name(_qp),
|
||||
meta::roles_table::name,
|
||||
meta::roles_table::role_col_name);
|
||||
co_await _qp.execute_internal(
|
||||
query,
|
||||
db::consistency_level::QUORUM,
|
||||
internal_distributed_query_state(),
|
||||
{_superuser},
|
||||
cql3::query_processor::cache_internal::no).discard_result();
|
||||
if (legacy_mode(_qp)) {
|
||||
co_await _qp.execute_internal(
|
||||
query,
|
||||
db::consistency_level::QUORUM,
|
||||
internal_distributed_query_state(),
|
||||
{_superuser},
|
||||
cql3::query_processor::cache_internal::no).discard_result();
|
||||
} else {
|
||||
co_await announce_mutations(_qp, _group0_client, query, {_superuser}, _as, ::service::raft_timeout{});
|
||||
}
|
||||
log.info("Created default superuser role '{}'.", _superuser);
|
||||
} catch(const exceptions::unavailable_exception& e) {
|
||||
log.warn("Skipped default role setup: some nodes were not ready; will retry");
|
||||
@@ -204,60 +205,6 @@ future<> standard_role_manager::legacy_create_default_role_if_missing() {
|
||||
}
|
||||
}
|
||||
|
||||
future<> standard_role_manager::maybe_create_default_role() {
|
||||
auto has_superuser = [this] () -> future<bool> {
|
||||
const sstring query = seastar::format("SELECT * FROM {}.{} WHERE is_superuser = true ALLOW FILTERING", get_auth_ks_name(_qp), meta::roles_table::name);
|
||||
auto results = co_await _qp.execute_internal(query, db::consistency_level::LOCAL_ONE,
|
||||
internal_distributed_query_state(), cql3::query_processor::cache_internal::yes);
|
||||
for (const auto& result : *results) {
|
||||
if (has_can_login(result)) {
|
||||
co_return true;
|
||||
}
|
||||
}
|
||||
co_return false;
|
||||
};
|
||||
if (co_await has_superuser()) {
|
||||
co_return;
|
||||
}
|
||||
// We don't want to start operation earlier to avoid quorum requirement in
|
||||
// a common case.
|
||||
::service::group0_batch batch(
|
||||
co_await _group0_client.start_operation(_as, get_raft_timeout()));
|
||||
// Check again as the state may have changed before we took the guard (batch).
|
||||
if (co_await has_superuser()) {
|
||||
co_return;
|
||||
}
|
||||
// There is no superuser which has can_login field - create default role.
|
||||
// Note that we don't check if can_login is set to true.
|
||||
const sstring insert_query = seastar::format("INSERT INTO {}.{} ({}, is_superuser, can_login) VALUES (?, true, true)",
|
||||
get_auth_ks_name(_qp),
|
||||
meta::roles_table::name,
|
||||
meta::roles_table::role_col_name);
|
||||
co_await collect_mutations(_qp, batch, insert_query, {_superuser});
|
||||
co_await std::move(batch).commit(_group0_client, _as, get_raft_timeout());
|
||||
log.info("Created default superuser role '{}'.", _superuser);
|
||||
}
|
||||
|
||||
future<> standard_role_manager::maybe_create_default_role_with_retries() {
|
||||
size_t retries = _migration_manager.get_concurrent_ddl_retries();
|
||||
while (true) {
|
||||
try {
|
||||
co_return co_await maybe_create_default_role();
|
||||
} catch (const ::service::group0_concurrent_modification& ex) {
|
||||
log.warn("Failed to execute maybe_create_default_role due to guard conflict.{}.", retries ? " Retrying" : " Number of retries exceeded, giving up");
|
||||
if (retries--) {
|
||||
continue;
|
||||
}
|
||||
// Log error but don't crash the whole node startup sequence.
|
||||
log.error("Failed to create default superuser role due to guard conflict.");
|
||||
co_return;
|
||||
} catch (const ::service::raft_operation_timeout_error& ex) {
|
||||
log.error("Failed to create default superuser role due to exception: {}", ex.what());
|
||||
co_return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static const sstring legacy_table_name{"users"};
|
||||
|
||||
bool standard_role_manager::legacy_metadata_exists() {
|
||||
@@ -319,13 +266,10 @@ future<> standard_role_manager::start() {
|
||||
co_await migrate_legacy_metadata();
|
||||
co_return;
|
||||
}
|
||||
co_await legacy_create_default_role_if_missing();
|
||||
}
|
||||
co_await create_default_role_if_missing();
|
||||
if (!legacy) {
|
||||
co_await maybe_create_default_role_with_retries();
|
||||
if (!_superuser_created_promise.available()) {
|
||||
_superuser_created_promise.set_value();
|
||||
}
|
||||
_superuser_created_promise.set_value();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -652,30 +596,21 @@ 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) {
|
||||
future<role_to_directly_granted_map> standard_role_manager::query_all_directly_granted() {
|
||||
const sstring query = seastar::format("SELECT * FROM {}.{}",
|
||||
get_auth_ks_name(_qp),
|
||||
meta::role_members_table::name);
|
||||
|
||||
const auto results = co_await _qp.execute_internal(
|
||||
query,
|
||||
db::consistency_level::ONE,
|
||||
qs,
|
||||
cql3::query_processor::cache_internal::yes);
|
||||
|
||||
role_to_directly_granted_map roles_map;
|
||||
std::transform(
|
||||
results->begin(),
|
||||
results->end(),
|
||||
std::inserter(roles_map, roles_map.begin()),
|
||||
[] (const cql3::untyped_result_set_row& row) {
|
||||
return std::make_pair(row.get_as<sstring>("member"), row.get_as<sstring>("role")); }
|
||||
);
|
||||
co_await _qp.query_internal(query, [&roles_map] (const cql3::untyped_result_set_row& row) -> future<stop_iteration> {
|
||||
roles_map.insert({row.get_as<sstring>("member"), row.get_as<sstring>("role")});
|
||||
co_return stop_iteration::no;
|
||||
});
|
||||
|
||||
co_return roles_map;
|
||||
}
|
||||
|
||||
future<role_set> standard_role_manager::query_all(::service::query_state& qs) {
|
||||
future<role_set> standard_role_manager::query_all() {
|
||||
const sstring query = seastar::format("SELECT {} FROM {}.{}",
|
||||
meta::roles_table::role_col_name,
|
||||
get_auth_ks_name(_qp),
|
||||
@@ -684,16 +619,10 @@ future<role_set> standard_role_manager::query_all(::service::query_state& qs) {
|
||||
// To avoid many copies of a view.
|
||||
static const auto role_col_name_string = sstring(meta::roles_table::role_col_name);
|
||||
|
||||
if (utils::get_local_injector().enter("standard_role_manager_fail_legacy_query")) {
|
||||
if (legacy_mode(_qp)) {
|
||||
throw std::runtime_error("standard_role_manager::query_all: failed due to error injection");
|
||||
}
|
||||
}
|
||||
|
||||
const auto results = co_await _qp.execute_internal(
|
||||
query,
|
||||
db::consistency_level::QUORUM,
|
||||
qs,
|
||||
internal_distributed_query_state(),
|
||||
cql3::query_processor::cache_internal::yes);
|
||||
|
||||
role_set roles;
|
||||
@@ -725,11 +654,11 @@ future<bool> standard_role_manager::can_login(std::string_view role_name) {
|
||||
});
|
||||
}
|
||||
|
||||
future<std::optional<sstring>> standard_role_manager::get_attribute(std::string_view role_name, std::string_view attribute_name, ::service::query_state& qs) {
|
||||
future<std::optional<sstring>> standard_role_manager::get_attribute(std::string_view role_name, std::string_view attribute_name) {
|
||||
const sstring query = seastar::format("SELECT name, value FROM {}.{} WHERE role = ? AND name = ?",
|
||||
get_auth_ks_name(_qp),
|
||||
meta::role_attributes_table::name);
|
||||
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);
|
||||
const auto result_set = co_await _qp.execute_internal(query, {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();
|
||||
co_return std::optional<sstring>(row.get_as<sstring>("value"));
|
||||
@@ -737,11 +666,11 @@ future<std::optional<sstring>> standard_role_manager::get_attribute(std::string_
|
||||
co_return std::optional<sstring>{};
|
||||
}
|
||||
|
||||
future<role_manager::attribute_vals> standard_role_manager::query_attribute_for_all (std::string_view attribute_name, ::service::query_state& qs) {
|
||||
return query_all(qs).then([this, attribute_name, &qs] (role_set roles) {
|
||||
return do_with(attribute_vals{}, [this, attribute_name, roles = std::move(roles), &qs] (attribute_vals &role_to_att_val) {
|
||||
return parallel_for_each(roles.begin(), roles.end(), [this, &role_to_att_val, attribute_name, &qs] (sstring role) {
|
||||
return get_attribute(role, attribute_name, qs).then([&role_to_att_val, role] (std::optional<sstring> att_val) {
|
||||
future<role_manager::attribute_vals> standard_role_manager::query_attribute_for_all (std::string_view attribute_name) {
|
||||
return query_all().then([this, attribute_name] (role_set roles) {
|
||||
return do_with(attribute_vals{}, [this, attribute_name, roles = std::move(roles)] (attribute_vals &role_to_att_val) {
|
||||
return parallel_for_each(roles.begin(), roles.end(), [this, &role_to_att_val, attribute_name] (sstring role) {
|
||||
return get_attribute(role, attribute_name).then([&role_to_att_val, role] (std::optional<sstring> att_val) {
|
||||
if (att_val) {
|
||||
role_to_att_val.emplace(std::move(role), std::move(*att_val));
|
||||
}
|
||||
@@ -786,7 +715,7 @@ future<> standard_role_manager::remove_attribute(std::string_view role_name, std
|
||||
future<std::vector<cql3::description>> standard_role_manager::describe_role_grants() {
|
||||
std::vector<cql3::description> result{};
|
||||
|
||||
const auto grants = co_await query_all_directly_granted(internal_distributed_query_state());
|
||||
const auto grants = co_await query_all_directly_granted();
|
||||
result.reserve(grants.size());
|
||||
|
||||
for (const auto& [grantee_role, granted_role] : grants) {
|
||||
|
||||
@@ -66,9 +66,9 @@ public:
|
||||
|
||||
virtual future<role_set> query_granted(std::string_view grantee_name, recursive_role_query) override;
|
||||
|
||||
virtual future<role_to_directly_granted_map> query_all_directly_granted(::service::query_state&) override;
|
||||
virtual future<role_to_directly_granted_map> query_all_directly_granted() override;
|
||||
|
||||
virtual future<role_set> query_all(::service::query_state&) override;
|
||||
virtual future<role_set> query_all() override;
|
||||
|
||||
virtual future<bool> exists(std::string_view role_name) override;
|
||||
|
||||
@@ -76,9 +76,9 @@ public:
|
||||
|
||||
virtual future<bool> can_login(std::string_view role_name) override;
|
||||
|
||||
virtual future<std::optional<sstring>> get_attribute(std::string_view role_name, std::string_view attribute_name, ::service::query_state&) override;
|
||||
virtual future<std::optional<sstring>> get_attribute(std::string_view role_name, std::string_view attribute_name) override;
|
||||
|
||||
virtual future<role_manager::attribute_vals> query_attribute_for_all(std::string_view attribute_name, ::service::query_state&) override;
|
||||
virtual future<role_manager::attribute_vals> query_attribute_for_all(std::string_view attribute_name) override;
|
||||
|
||||
virtual future<> set_attribute(std::string_view role_name, std::string_view attribute_name, std::string_view attribute_value, ::service::group0_batch& mc) override;
|
||||
|
||||
@@ -95,10 +95,7 @@ private:
|
||||
|
||||
future<> migrate_legacy_metadata();
|
||||
|
||||
future<> legacy_create_default_role_if_missing();
|
||||
|
||||
future<> maybe_create_default_role();
|
||||
future<> maybe_create_default_role_with_retries();
|
||||
future<> create_default_role_if_missing();
|
||||
|
||||
future<> create_or_replace(std::string_view role_name, const role_config&, ::service::group0_batch&);
|
||||
|
||||
|
||||
@@ -37,8 +37,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)
|
||||
: transitional_authenticator(std::make_unique<password_authenticator>(qp, g0, mm)) {
|
||||
}
|
||||
transitional_authenticator(std::unique_ptr<authenticator> a)
|
||||
: _authenticator(std::move(a)) {
|
||||
@@ -239,8 +239,7 @@ static const class_registrator<
|
||||
auth::transitional_authenticator,
|
||||
cql3::query_processor&,
|
||||
::service::raft_group0_client&,
|
||||
::service::migration_manager&,
|
||||
utils::alien_worker&> transitional_authenticator_reg(auth::PACKAGE_NAME + "TransitionalAuthenticator");
|
||||
::service::migration_manager&> transitional_authenticator_reg(auth::PACKAGE_NAME + "TransitionalAuthenticator");
|
||||
|
||||
static const class_registrator<
|
||||
auth::authorizer,
|
||||
|
||||
5
bytes.hh
5
bytes.hh
@@ -35,9 +35,8 @@ inline bytes_view to_bytes_view(std::string_view view) {
|
||||
}
|
||||
|
||||
struct fmt_hex {
|
||||
std::span<const std::byte> v;
|
||||
fmt_hex(const bytes_view& v) noexcept : v(std::as_bytes(std::span(v))) {}
|
||||
fmt_hex(std::span<const std::byte> v) noexcept : v(v) {}
|
||||
const bytes_view& v;
|
||||
fmt_hex(const bytes_view& v) noexcept : v(v) {}
|
||||
};
|
||||
|
||||
bytes from_hex(std::string_view s);
|
||||
|
||||
@@ -23,10 +23,6 @@ class cdc_extension : public schema_extension {
|
||||
public:
|
||||
static constexpr auto NAME = "cdc";
|
||||
|
||||
// cdc_extension was written before schema_extension was deprecated, so support it
|
||||
// without warnings
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
cdc_extension() = default;
|
||||
cdc_extension(const options& opts) : _cdc_options(opts) {}
|
||||
explicit cdc_extension(std::map<sstring, sstring> tags) : _cdc_options(std::move(tags)) {}
|
||||
@@ -34,7 +30,6 @@ public:
|
||||
explicit cdc_extension(const sstring& s) {
|
||||
throw std::logic_error("Cannot create cdc info from string");
|
||||
}
|
||||
#pragma clang diagnostic pop
|
||||
bytes serialize() const override {
|
||||
return ser::serialize_to_buffer<bytes>(_cdc_options.to_map());
|
||||
}
|
||||
|
||||
@@ -39,12 +39,12 @@
|
||||
|
||||
extern logging::logger cdc_log;
|
||||
|
||||
static int get_shard_count(const locator::host_id& endpoint, const gms::gossiper& g) {
|
||||
static int get_shard_count(const gms::inet_address& endpoint, const gms::gossiper& g) {
|
||||
auto ep_state = g.get_application_state_ptr(endpoint, gms::application_state::SHARD_COUNT);
|
||||
return ep_state ? std::stoi(ep_state->value()) : -1;
|
||||
}
|
||||
|
||||
static unsigned get_sharding_ignore_msb(const locator::host_id& endpoint, const gms::gossiper& g) {
|
||||
static unsigned get_sharding_ignore_msb(const gms::inet_address& endpoint, const gms::gossiper& g) {
|
||||
auto ep_state = g.get_application_state_ptr(endpoint, gms::application_state::IGNORE_MSB_BITS);
|
||||
return ep_state ? std::stoi(ep_state->value()) : 0;
|
||||
}
|
||||
@@ -198,7 +198,7 @@ static std::vector<stream_id> create_stream_ids(
|
||||
}
|
||||
|
||||
bool should_propose_first_generation(const locator::host_id& my_host_id, const gms::gossiper& g) {
|
||||
return g.for_each_endpoint_state_until([&] (const gms::endpoint_state& eps) {
|
||||
return g.for_each_endpoint_state_until([&] (const gms::inet_address&, const gms::endpoint_state& eps) {
|
||||
return stop_iteration(my_host_id < eps.get_host_id());
|
||||
}) == stop_iteration::no;
|
||||
}
|
||||
@@ -365,9 +365,6 @@ cdc::topology_description make_new_generation_description(
|
||||
const noncopyable_function<std::pair<size_t, uint8_t>(dht::token)>& get_sharding_info,
|
||||
const locator::token_metadata_ptr tmptr) {
|
||||
const auto tokens = get_tokens(bootstrap_tokens, tmptr);
|
||||
if (tokens.empty()) {
|
||||
on_internal_error(cdc_log, "Attempted to create a CDC generation from an empty list of tokens");
|
||||
}
|
||||
|
||||
utils::chunked_vector<token_range_description> vnode_descriptions;
|
||||
vnode_descriptions.reserve(tokens.size());
|
||||
@@ -405,8 +402,9 @@ future<cdc::generation_id> generation_service::legacy_make_new_generation(const
|
||||
throw std::runtime_error(
|
||||
format("Can't find endpoint for token {}", end));
|
||||
}
|
||||
auto sc = get_shard_count(*endpoint, _gossiper);
|
||||
return {sc > 0 ? sc : 1, get_sharding_ignore_msb(*endpoint, _gossiper)};
|
||||
const auto ep = _gossiper.get_address_map().get(*endpoint);
|
||||
auto sc = get_shard_count(ep, _gossiper);
|
||||
return {sc > 0 ? sc : 1, get_sharding_ignore_msb(ep, _gossiper)};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -465,7 +463,7 @@ future<cdc::generation_id> generation_service::legacy_make_new_generation(const
|
||||
* but if the cluster already supports CDC, then every newly joining node will propose a new CDC generation,
|
||||
* which means it will gossip the generation's timestamp.
|
||||
*/
|
||||
static std::optional<cdc::generation_id> get_generation_id_for(const locator::host_id& endpoint, const gms::endpoint_state& eps) {
|
||||
static std::optional<cdc::generation_id> get_generation_id_for(const gms::inet_address& endpoint, const gms::endpoint_state& eps) {
|
||||
const auto* gen_id_ptr = eps.get_application_state_ptr(gms::application_state::CDC_GENERATION_ID);
|
||||
if (!gen_id_ptr) {
|
||||
return std::nullopt;
|
||||
@@ -843,18 +841,18 @@ future<> generation_service::leave_ring() {
|
||||
co_await _gossiper.unregister_(shared_from_this());
|
||||
}
|
||||
|
||||
future<> generation_service::on_join(gms::inet_address ep, locator::host_id id, gms::endpoint_state_ptr ep_state, gms::permit_id pid) {
|
||||
return on_change(ep, id, ep_state->get_application_state_map(), pid);
|
||||
future<> generation_service::on_join(gms::inet_address ep, gms::endpoint_state_ptr ep_state, gms::permit_id pid) {
|
||||
return on_change(ep, ep_state->get_application_state_map(), pid);
|
||||
}
|
||||
|
||||
future<> generation_service::on_change(gms::inet_address ep, locator::host_id id, const gms::application_state_map& states, gms::permit_id pid) {
|
||||
future<> generation_service::on_change(gms::inet_address ep, const gms::application_state_map& states, gms::permit_id pid) {
|
||||
assert_shard_zero(__PRETTY_FUNCTION__);
|
||||
|
||||
if (_raft_topology_change_enabled()) {
|
||||
return make_ready_future<>();
|
||||
}
|
||||
|
||||
return on_application_state_change(ep, id, states, gms::application_state::CDC_GENERATION_ID, pid, [this] (gms::inet_address ep, locator::host_id id, const gms::versioned_value& v, gms::permit_id) {
|
||||
return on_application_state_change(ep, states, gms::application_state::CDC_GENERATION_ID, pid, [this] (gms::inet_address ep, const gms::versioned_value& v, gms::permit_id) {
|
||||
auto gen_id = gms::versioned_value::cdc_generation_id_from_string(v.value());
|
||||
cdc_log.debug("Endpoint: {}, CDC generation ID change: {}", ep, gen_id);
|
||||
|
||||
@@ -869,8 +867,7 @@ future<> generation_service::check_and_repair_cdc_streams() {
|
||||
}
|
||||
|
||||
std::optional<cdc::generation_id> latest = _gen_id;
|
||||
_gossiper.for_each_endpoint_state([&] (const gms::endpoint_state& state) {
|
||||
auto addr = state.get_host_id();
|
||||
_gossiper.for_each_endpoint_state([&] (const gms::inet_address& addr, const gms::endpoint_state& state) {
|
||||
if (_gossiper.is_left(addr)) {
|
||||
cdc_log.info("check_and_repair_cdc_streams ignored node {} because it is in LEFT state", addr);
|
||||
return;
|
||||
@@ -1069,8 +1066,8 @@ future<> generation_service::legacy_scan_cdc_generations() {
|
||||
assert_shard_zero(__PRETTY_FUNCTION__);
|
||||
|
||||
std::optional<cdc::generation_id> latest;
|
||||
_gossiper.for_each_endpoint_state([&] (const gms::endpoint_state& eps) {
|
||||
auto gen_id = get_generation_id_for(eps.get_host_id(), eps);
|
||||
_gossiper.for_each_endpoint_state([&] (const gms::inet_address& node, const gms::endpoint_state& eps) {
|
||||
auto gen_id = get_generation_id_for(node, eps);
|
||||
if (!latest || (gen_id && get_ts(*gen_id) > get_ts(*latest))) {
|
||||
latest = gen_id;
|
||||
}
|
||||
|
||||
@@ -110,8 +110,13 @@ public:
|
||||
return _cdc_metadata;
|
||||
}
|
||||
|
||||
virtual future<> on_join(gms::inet_address, locator::host_id id, gms::endpoint_state_ptr, gms::permit_id) override;
|
||||
virtual future<> on_change(gms::inet_address, locator::host_id id, const gms::application_state_map&, gms::permit_id) override;
|
||||
virtual future<> on_alive(gms::inet_address, gms::endpoint_state_ptr, gms::permit_id) override { return make_ready_future(); }
|
||||
virtual future<> on_dead(gms::inet_address, gms::endpoint_state_ptr, gms::permit_id) override { return make_ready_future(); }
|
||||
virtual future<> on_remove(gms::inet_address, gms::permit_id) override { return make_ready_future(); }
|
||||
virtual future<> on_restart(gms::inet_address, gms::endpoint_state_ptr, gms::permit_id) override { return make_ready_future(); }
|
||||
|
||||
virtual future<> on_join(gms::inet_address, gms::endpoint_state_ptr, gms::permit_id) override;
|
||||
virtual future<> on_change(gms::inet_address, const gms::application_state_map&, gms::permit_id) override;
|
||||
|
||||
future<> check_and_repair_cdc_streams();
|
||||
|
||||
|
||||
28
cdc/log.cc
28
cdc/log.cc
@@ -960,12 +960,8 @@ public:
|
||||
// Given a reference to such a column from the base schema, this function sets the corresponding column
|
||||
// in the log to the given value for the given row.
|
||||
void set_value(const clustering_key& log_ck, const column_definition& base_cdef, const managed_bytes_view& value) {
|
||||
auto log_cdef_ptr = _log_schema.get_column_definition(log_data_column_name_bytes(base_cdef.name()));
|
||||
if (!log_cdef_ptr) {
|
||||
throw exceptions::invalid_request_exception(format("CDC log schema for {}.{} does not have base column {}",
|
||||
_log_schema.ks_name(), _log_schema.cf_name(), base_cdef.name_as_text()));
|
||||
}
|
||||
_log_mut.set_cell(log_ck, *log_cdef_ptr, atomic_cell::make_live(*base_cdef.type, _ts, value, _ttl));
|
||||
auto& log_cdef = *_log_schema.get_column_definition(log_data_column_name_bytes(base_cdef.name()));
|
||||
_log_mut.set_cell(log_ck, log_cdef, atomic_cell::make_live(*base_cdef.type, _ts, value, _ttl));
|
||||
}
|
||||
|
||||
// Each regular and static column in the base schema has a corresponding column in the log schema
|
||||
@@ -973,13 +969,7 @@ public:
|
||||
// Given a reference to such a column from the base schema, this function sets the corresponding column
|
||||
// in the log to `true` for the given row. If not called, the column will be `null`.
|
||||
void set_deleted(const clustering_key& log_ck, const column_definition& base_cdef) {
|
||||
auto log_cdef_ptr = _log_schema.get_column_definition(log_data_column_deleted_name_bytes(base_cdef.name()));
|
||||
if (!log_cdef_ptr) {
|
||||
throw exceptions::invalid_request_exception(format("CDC log schema for {}.{} does not have base column {}",
|
||||
_log_schema.ks_name(), _log_schema.cf_name(), base_cdef.name_as_text()));
|
||||
}
|
||||
auto& log_cdef = *log_cdef_ptr;
|
||||
_log_mut.set_cell(log_ck, *log_cdef_ptr, atomic_cell::make_live(*log_cdef.type, _ts, log_cdef.type->decompose(true), _ttl));
|
||||
_log_mut.set_cell(log_ck, log_data_column_deleted_name_bytes(base_cdef.name()), data_value(true), _ts, _ttl);
|
||||
}
|
||||
|
||||
// Each regular and static non-atomic column in the base schema has a corresponding column in the log schema
|
||||
@@ -988,12 +978,7 @@ public:
|
||||
// Given a reference to such a column from the base schema, this function sets the corresponding column
|
||||
// in the log to the given set of keys for the given row.
|
||||
void set_deleted_elements(const clustering_key& log_ck, const column_definition& base_cdef, const managed_bytes& deleted_elements) {
|
||||
auto log_cdef_ptr = _log_schema.get_column_definition(log_data_column_deleted_elements_name_bytes(base_cdef.name()));
|
||||
if (!log_cdef_ptr) {
|
||||
throw exceptions::invalid_request_exception(format("CDC log schema for {}.{} does not have base column {}",
|
||||
_log_schema.ks_name(), _log_schema.cf_name(), base_cdef.name_as_text()));
|
||||
}
|
||||
auto& log_cdef = *log_cdef_ptr;
|
||||
auto& log_cdef = *_log_schema.get_column_definition(log_data_column_deleted_elements_name_bytes(base_cdef.name()));
|
||||
_log_mut.set_cell(log_ck, log_cdef, atomic_cell::make_live(*log_cdef.type, _ts, deleted_elements, _ttl));
|
||||
}
|
||||
|
||||
@@ -1880,10 +1865,5 @@ bool cdc::cdc_service::needs_cdc_augmentation(const std::vector<mutation>& mutat
|
||||
|
||||
future<std::tuple<std::vector<mutation>, lw_shared_ptr<cdc::operation_result_tracker>>>
|
||||
cdc::cdc_service::augment_mutation_call(lowres_clock::time_point timeout, std::vector<mutation>&& mutations, tracing::trace_state_ptr tr_state, db::consistency_level write_cl) {
|
||||
if (utils::get_local_injector().enter("sleep_before_cdc_augmentation")) {
|
||||
return seastar::sleep(std::chrono::milliseconds(100)).then([this, timeout, mutations = std::move(mutations), tr_state = std::move(tr_state), write_cl] () mutable {
|
||||
return _impl->augment_mutation_call(timeout, std::move(mutations), std::move(tr_state), write_cl);
|
||||
});
|
||||
}
|
||||
return _impl->augment_mutation_call(timeout, std::move(mutations), std::move(tr_state), write_cl);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ set(CMAKE_CXX_FLAGS_COVERAGE
|
||||
CACHE
|
||||
INTERNAL
|
||||
"")
|
||||
update_build_flags(Coverage
|
||||
update_cxx_flags(CMAKE_CXX_FLAGS_COVERAGE
|
||||
WITH_DEBUG_INFO
|
||||
OPTIMIZATION_LEVEL "g")
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
set(OptimizationLevel "g")
|
||||
|
||||
update_build_flags(Debug
|
||||
update_cxx_flags(CMAKE_CXX_FLAGS_DEBUG
|
||||
WITH_DEBUG_INFO
|
||||
OPTIMIZATION_LEVEL ${OptimizationLevel})
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ set(CMAKE_CXX_FLAGS_DEV
|
||||
CACHE
|
||||
INTERNAL
|
||||
"")
|
||||
update_build_flags(Dev
|
||||
update_cxx_flags(CMAKE_CXX_FLAGS_DEV
|
||||
OPTIMIZATION_LEVEL "2")
|
||||
|
||||
set(scylla_build_mode_Dev "dev")
|
||||
|
||||
@@ -8,7 +8,7 @@ set(CMAKE_CXX_FLAGS_RELWITHDEBINFO
|
||||
CACHE
|
||||
INTERNAL
|
||||
"")
|
||||
update_build_flags(RelWithDebInfo
|
||||
update_cxx_flags(CMAKE_CXX_FLAGS_RELWITHDEBINFO
|
||||
WITH_DEBUG_INFO
|
||||
OPTIMIZATION_LEVEL "3")
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ set(CMAKE_CXX_FLAGS_SANITIZE
|
||||
CACHE
|
||||
INTERNAL
|
||||
"")
|
||||
update_build_flags(Sanitize
|
||||
update_cxx_flags(CMAKE_CXX_FLAGS_SANITIZE
|
||||
WITH_DEBUG_INFO
|
||||
OPTIMIZATION_LEVEL "s")
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ function(get_padded_dynamic_linker_option output length)
|
||||
ERROR_VARIABLE driver_command_line
|
||||
ERROR_STRIP_TRAILING_WHITESPACE)
|
||||
# extract the argument for the "-dynamic-linker" option
|
||||
if(driver_command_line MATCHES ".*\"?${dynamic_linker_option}\"?[ =]\"?([^ \"]*)\"?[ \n].*")
|
||||
if(driver_command_line MATCHES ".*\"?${dynamic_linker_option}\"? \"?([^ \"]*)\"? .*")
|
||||
set(dynamic_linker ${CMAKE_MATCH_1})
|
||||
else()
|
||||
message(FATAL_ERROR "Unable to find ${dynamic_linker_option} in driver-generated command: "
|
||||
@@ -135,7 +135,7 @@ function(maybe_limit_stack_usage_in_KB stack_usage_threshold_in_KB config)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
macro(update_build_flags config)
|
||||
macro(update_cxx_flags flags)
|
||||
cmake_parse_arguments (
|
||||
parsed_args
|
||||
"WITH_DEBUG_INFO"
|
||||
@@ -145,15 +145,11 @@ macro(update_build_flags config)
|
||||
if(NOT DEFINED parsed_args_OPTIMIZATION_LEVEL)
|
||||
message(FATAL_ERROR "OPTIMIZATION_LEVEL is missing")
|
||||
endif()
|
||||
string(TOUPPER ${config} CONFIG)
|
||||
set(cxx_flags "CMAKE_CXX_FLAGS_${CONFIG}")
|
||||
string(APPEND ${cxx_flags}
|
||||
string(APPEND ${flags}
|
||||
" -O${parsed_args_OPTIMIZATION_LEVEL}")
|
||||
if(parsed_args_WITH_DEBUG_INFO)
|
||||
string(APPEND ${cxx_flags} " -g -gz")
|
||||
string(APPEND ${flags} " -g -gz")
|
||||
endif()
|
||||
unset(CONFIG)
|
||||
unset(cxx_flags)
|
||||
endmacro()
|
||||
|
||||
set(pgo_opts "")
|
||||
|
||||
@@ -769,7 +769,7 @@ private:
|
||||
}
|
||||
|
||||
virtual sstables::sstable_set make_sstable_set_for_input() const {
|
||||
return _table_s.get_compaction_strategy().make_sstable_set(_table_s);
|
||||
return _table_s.get_compaction_strategy().make_sstable_set(_schema);
|
||||
}
|
||||
|
||||
const tombstone_gc_state& get_tombstone_gc_state() const {
|
||||
@@ -1301,7 +1301,7 @@ public:
|
||||
}
|
||||
|
||||
virtual sstables::sstable_set make_sstable_set_for_input() const override {
|
||||
return sstables::make_partitioned_sstable_set(_schema, _table_s.token_range());
|
||||
return sstables::make_partitioned_sstable_set(_schema, false);
|
||||
}
|
||||
|
||||
// Unconditionally enable incremental compaction if the strategy specifies a max output size, e.g. LCS.
|
||||
@@ -1910,11 +1910,7 @@ static future<compaction_result> scrub_sstables_validate_mode(sstables::compacti
|
||||
using scrub = sstables::compaction_type_options::scrub;
|
||||
if (validation_errors != 0 && descriptor.options.as<scrub>().quarantine_sstables == scrub::quarantine_invalid_sstables::yes) {
|
||||
for (auto& sst : descriptor.sstables) {
|
||||
try {
|
||||
co_await sst->change_state(sstables::sstable_state::quarantine);
|
||||
} catch (...) {
|
||||
clogger.error("Moving {} to quarantine failed due to {}, continuing.", sst->get_filename(), std::current_exception());
|
||||
}
|
||||
co_await sst->change_state(sstables::sstable_state::quarantine);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -395,7 +395,7 @@ future<sstables::sstable_set> compaction_task_executor::sstable_set_for_tombston
|
||||
auto compound_set = t.sstable_set_for_tombstone_gc();
|
||||
// Compound set will be linearized into a single set, since compaction might add or remove sstables
|
||||
// to it for incremental compaction to work.
|
||||
auto new_set = sstables::make_partitioned_sstable_set(t.schema(), t.token_range());
|
||||
auto new_set = sstables::make_partitioned_sstable_set(t.schema(), false);
|
||||
co_await compound_set->for_each_sstable_gently([&] (const sstables::shared_sstable& sst) {
|
||||
auto inserted = new_set.insert(sst);
|
||||
if (!inserted) {
|
||||
@@ -458,7 +458,9 @@ future<sstables::compaction_result> compaction_task_executor::compact_sstables(s
|
||||
future<> compaction_task_executor::update_history(table_state& t, const sstables::compaction_result& res, const sstables::compaction_data& cdata) {
|
||||
auto ended_at = std::chrono::duration_cast<std::chrono::milliseconds>(res.stats.ended_at.time_since_epoch());
|
||||
|
||||
if (auto sys_ks = _cm._sys_ks.get_permit()) {
|
||||
if (_cm._sys_ks) {
|
||||
auto sys_ks = _cm._sys_ks; // hold pointer on sys_ks
|
||||
|
||||
co_await utils::get_local_injector().inject("update_history_wait", utils::wait_for_message(120s));
|
||||
std::unordered_map<int32_t, int64_t> rows_merged;
|
||||
for (size_t id=0; id<res.stats.reader_statistics.rows_merged_histogram.size(); ++id) {
|
||||
@@ -473,9 +475,11 @@ future<> compaction_task_executor::update_history(table_state& t, const sstables
|
||||
}
|
||||
|
||||
future<> compaction_manager::get_compaction_history(compaction_history_consumer&& f) {
|
||||
if (auto sys_ks = _sys_ks.get_permit()) {
|
||||
co_await sys_ks->get_compaction_history(std::move(f));
|
||||
if (!_sys_ks) {
|
||||
return make_ready_future<>();
|
||||
}
|
||||
|
||||
return _sys_ks->get_compaction_history(std::move(f)).finally([s = _sys_ks] {});
|
||||
}
|
||||
|
||||
template<std::derived_from<compaction::compaction_task_executor> Executor>
|
||||
@@ -920,7 +924,6 @@ public:
|
||||
|
||||
compaction_manager::compaction_manager(config cfg, abort_source& as, tasks::task_manager& tm)
|
||||
: _task_manager_module(make_shared<task_manager_module>(tm))
|
||||
, _sys_ks("compaction_manager::system_keyspace")
|
||||
, _cfg(std::move(cfg))
|
||||
, _compaction_submission_timer(compaction_sg(), compaction_submission_callback())
|
||||
, _compaction_controller(make_compaction_controller(compaction_sg(), static_shares(), [this] () -> float {
|
||||
@@ -957,7 +960,6 @@ compaction_manager::compaction_manager(config cfg, abort_source& as, tasks::task
|
||||
|
||||
compaction_manager::compaction_manager(tasks::task_manager& tm)
|
||||
: _task_manager_module(make_shared<task_manager_module>(tm))
|
||||
, _sys_ks("compaction_manager::system_keyspace")
|
||||
, _cfg(config{ .available_memory = 1 })
|
||||
, _compaction_submission_timer(compaction_sg(), compaction_submission_callback())
|
||||
, _compaction_controller(make_compaction_controller(compaction_sg(), 1, [] () -> float { return 1.0; }))
|
||||
@@ -1126,16 +1128,16 @@ future<> compaction_manager::drain() {
|
||||
// Disable the state so that it can be enabled later if requested.
|
||||
_state = state::disabled;
|
||||
}
|
||||
_compaction_submission_timer.cancel();
|
||||
// Stop ongoing compactions, if the request has not been sent already and wait for them to stop.
|
||||
co_await stop_ongoing_compactions("drain");
|
||||
// Trigger a signal to properly exit from postponed_compactions_reevaluation() fiber
|
||||
reevaluate_postponed_compactions();
|
||||
cmlog.info("Drained");
|
||||
}
|
||||
|
||||
future<> compaction_manager::stop() {
|
||||
do_stop();
|
||||
if (auto cm = std::exchange(_task_manager_module, nullptr)) {
|
||||
co_await cm->stop();
|
||||
}
|
||||
if (_stop_future) {
|
||||
co_await std::exchange(*_stop_future, make_ready_future());
|
||||
}
|
||||
@@ -1146,18 +1148,16 @@ future<> compaction_manager::really_do_stop() noexcept {
|
||||
// Reset the metrics registry
|
||||
_metrics.clear();
|
||||
co_await stop_ongoing_compactions("shutdown");
|
||||
co_await _task_manager_module->stop();
|
||||
if (!_tasks.empty()) {
|
||||
on_fatal_internal_error(cmlog, format("{} tasks still exist after being stopped", _tasks.size()));
|
||||
}
|
||||
co_await coroutine::parallel_for_each(_compaction_state | std::views::values, [] (compaction_state& cs) -> future<> {
|
||||
if (!cs.gate.is_closed()) {
|
||||
co_await cs.gate.close();
|
||||
}
|
||||
});
|
||||
if (!_tasks.empty()) {
|
||||
on_fatal_internal_error(cmlog, format("{} tasks still exist after being stopped", _tasks.size()));
|
||||
}
|
||||
reevaluate_postponed_compactions();
|
||||
co_await std::move(_waiting_reevalution);
|
||||
co_await _sys_ks.close();
|
||||
_weight_tracker.clear();
|
||||
_compaction_submission_timer.cancel();
|
||||
co_await _compaction_controller.shutdown();
|
||||
@@ -1818,21 +1818,8 @@ future<compaction_manager::compaction_stats_opt> compaction_manager::perform_sst
|
||||
if (!gh) {
|
||||
co_return compaction_stats_opt{};
|
||||
}
|
||||
|
||||
// Collect and register all sstables as compacting while compaction is disabled, to avoid a race condition where
|
||||
// regular compaction runs in between and picks the same files.
|
||||
std::vector<sstables::shared_sstable> all_sstables;
|
||||
compacting_sstable_registration compacting(*this, get_compaction_state(&t));
|
||||
co_await run_with_compaction_disabled(t, [&all_sstables, &compacting, &t] () -> future<> {
|
||||
// All sstables must be included.
|
||||
all_sstables = get_all_sstables(t);
|
||||
compacting.register_compacting(all_sstables);
|
||||
return make_ready_future();
|
||||
});
|
||||
if (all_sstables.empty()) {
|
||||
co_return compaction_stats_opt{};
|
||||
}
|
||||
|
||||
// All sstables must be included, even the ones being compacted, such that everything in table is validated.
|
||||
auto all_sstables = get_all_sstables(t);
|
||||
co_return co_await perform_compaction<validate_sstables_compaction_task_executor>(throw_if_stopping::no, info, &t, info.id, std::move(all_sstables), quarantine_sstables);
|
||||
}
|
||||
|
||||
@@ -2191,8 +2178,7 @@ future<compaction_manager::compaction_stats_opt> compaction_manager::perform_sst
|
||||
}
|
||||
|
||||
compaction::compaction_state::compaction_state(table_state& t)
|
||||
: gate(format("compaction_state for table {}.{}", t.schema()->ks_name(), t.schema()->cf_name()))
|
||||
, backlog_tracker(t.get_compaction_strategy().make_backlog_tracker())
|
||||
: backlog_tracker(t.get_compaction_strategy().make_backlog_tracker())
|
||||
{
|
||||
}
|
||||
|
||||
@@ -2308,11 +2294,11 @@ strategy_control& compaction_manager::get_strategy_control() const noexcept {
|
||||
}
|
||||
|
||||
void compaction_manager::plug_system_keyspace(db::system_keyspace& sys_ks) noexcept {
|
||||
_sys_ks.plug(sys_ks.shared_from_this());
|
||||
_sys_ks = sys_ks.shared_from_this();
|
||||
}
|
||||
|
||||
future<> compaction_manager::unplug_system_keyspace() noexcept {
|
||||
co_await _sys_ks.unplug();
|
||||
void compaction_manager::unplug_system_keyspace() noexcept {
|
||||
_sys_ks = nullptr;
|
||||
}
|
||||
|
||||
double compaction_backlog_tracker::backlog() const {
|
||||
|
||||
@@ -32,7 +32,6 @@
|
||||
#include "seastarx.hh"
|
||||
#include "sstables/exceptions.hh"
|
||||
#include "tombstone_gc.hh"
|
||||
#include "utils/pluggable.hh"
|
||||
|
||||
namespace db {
|
||||
class system_keyspace;
|
||||
@@ -139,7 +138,7 @@ private:
|
||||
// being picked more than once.
|
||||
seastar::named_semaphore _off_strategy_sem = {1, named_semaphore_exception_factory{"off-strategy compaction"}};
|
||||
|
||||
utils::pluggable<db::system_keyspace> _sys_ks;
|
||||
seastar::shared_ptr<db::system_keyspace> _sys_ks;
|
||||
|
||||
std::function<void()> compaction_submission_callback();
|
||||
// all registered tables are reevaluated at a constant interval.
|
||||
@@ -301,11 +300,6 @@ public:
|
||||
// unless it is moved back to enabled state.
|
||||
future<> drain();
|
||||
|
||||
// Check if compaction manager is running, i.e. it was enabled or drained
|
||||
bool is_running() const noexcept {
|
||||
return _state == state::enabled || _state == state::disabled;
|
||||
}
|
||||
|
||||
using compaction_history_consumer = noncopyable_function<future<>(const db::compaction_history_entry&)>;
|
||||
future<> get_compaction_history(compaction_history_consumer&& f);
|
||||
|
||||
@@ -397,7 +391,7 @@ public:
|
||||
future<> run_with_compaction_disabled(compaction::table_state& t, std::function<future<> ()> func);
|
||||
|
||||
void plug_system_keyspace(db::system_keyspace& sys_ks) noexcept;
|
||||
future<> unplug_system_keyspace() noexcept;
|
||||
void unplug_system_keyspace() noexcept;
|
||||
|
||||
// Adds a table to the compaction manager.
|
||||
// Creates a compaction_state structure that can be used for submitting
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace compaction {
|
||||
struct compaction_state {
|
||||
// Used both by compaction tasks that refer to the compaction_state
|
||||
// and by any function running under run_with_compaction_disabled().
|
||||
seastar::named_gate gate;
|
||||
seastar::gate gate;
|
||||
|
||||
// Prevents table from running major and minor compaction at the same time.
|
||||
seastar::rwlock lock;
|
||||
|
||||
@@ -789,8 +789,8 @@ future<reshape_config> make_reshape_config(const sstables::storage& storage, res
|
||||
};
|
||||
}
|
||||
|
||||
std::unique_ptr<sstable_set_impl> incremental_compaction_strategy::make_sstable_set(const table_state& ts) const {
|
||||
return std::make_unique<partitioned_sstable_set>(ts.schema(), ts.token_range());
|
||||
std::unique_ptr<sstable_set_impl> incremental_compaction_strategy::make_sstable_set(schema_ptr schema) const {
|
||||
return std::make_unique<partitioned_sstable_set>(std::move(schema), false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ public:
|
||||
return name(type());
|
||||
}
|
||||
|
||||
sstable_set make_sstable_set(const table_state& ts) const;
|
||||
sstable_set make_sstable_set(schema_ptr schema) const;
|
||||
|
||||
compaction_backlog_tracker make_backlog_tracker() const;
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ public:
|
||||
return true;
|
||||
}
|
||||
virtual int64_t estimated_pending_compactions(table_state& table_s) const = 0;
|
||||
virtual std::unique_ptr<sstable_set_impl> make_sstable_set(const table_state& ts) const;
|
||||
virtual std::unique_ptr<sstable_set_impl> make_sstable_set(schema_ptr schema) const;
|
||||
|
||||
bool use_clustering_key_filter() const {
|
||||
return _use_clustering_key_filter;
|
||||
|
||||
@@ -98,7 +98,7 @@ public:
|
||||
|
||||
virtual compaction_descriptor get_reshaping_job(std::vector<shared_sstable> input, schema_ptr schema, reshape_config cfg) const override;
|
||||
|
||||
virtual std::unique_ptr<sstable_set_impl> make_sstable_set(const table_state& ts) const override;
|
||||
virtual std::unique_ptr<sstable_set_impl> make_sstable_set(schema_ptr schema) const override;
|
||||
|
||||
friend class ::incremental_backlog_tracker;
|
||||
};
|
||||
|
||||
@@ -70,7 +70,7 @@ public:
|
||||
virtual compaction_strategy_type type() const override {
|
||||
return compaction_strategy_type::leveled;
|
||||
}
|
||||
virtual std::unique_ptr<sstable_set_impl> make_sstable_set(const table_state& ts) const override;
|
||||
virtual std::unique_ptr<sstable_set_impl> make_sstable_set(schema_ptr schema) const override;
|
||||
|
||||
virtual std::unique_ptr<compaction_backlog_tracker::impl> make_backlog_tracker() const override;
|
||||
|
||||
|
||||
@@ -33,7 +33,6 @@ namespace compaction {
|
||||
class table_state {
|
||||
public:
|
||||
virtual ~table_state() {}
|
||||
virtual dht::token_range token_range() const noexcept = 0;
|
||||
virtual const schema_ptr& schema() const noexcept = 0;
|
||||
// min threshold as defined by table.
|
||||
virtual unsigned min_compaction_threshold() const noexcept = 0;
|
||||
|
||||
@@ -150,7 +150,7 @@ public:
|
||||
return compaction_strategy_type::time_window;
|
||||
}
|
||||
|
||||
virtual std::unique_ptr<sstable_set_impl> make_sstable_set(const table_state& ts) const override;
|
||||
virtual std::unique_ptr<sstable_set_impl> make_sstable_set(schema_ptr schema) const override;
|
||||
|
||||
virtual std::unique_ptr<compaction_backlog_tracker::impl> make_backlog_tracker() const override;
|
||||
|
||||
|
||||
@@ -255,9 +255,6 @@ public:
|
||||
// Returns true iff given prefix has no missing components
|
||||
bool is_full(managed_bytes_view v) const {
|
||||
SCYLLA_ASSERT(AllowPrefixes == allow_prefixes::yes);
|
||||
if (_types.size() == 0) {
|
||||
return v.empty();
|
||||
}
|
||||
return std::distance(begin(v), end(v)) == (ssize_t)_types.size();
|
||||
}
|
||||
bool is_empty(managed_bytes_view v) const {
|
||||
|
||||
1127
compress.cc
1127
compress.cc
File diff suppressed because it is too large
Load Diff
95
compress.hh
95
compress.hh
@@ -10,25 +10,17 @@
|
||||
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
|
||||
#include <seastar/core/future.hh>
|
||||
#include <seastar/core/shared_ptr.hh>
|
||||
#include <seastar/core/sstring.hh>
|
||||
#include <seastar/util/bool_class.hh>
|
||||
#include "seastarx.hh"
|
||||
|
||||
class compression_parameters;
|
||||
|
||||
class compressor {
|
||||
sstring _name;
|
||||
public:
|
||||
enum class algorithm {
|
||||
lz4,
|
||||
lz4_with_dicts,
|
||||
zstd,
|
||||
zstd_with_dicts,
|
||||
snappy,
|
||||
deflate,
|
||||
none,
|
||||
};
|
||||
compressor(sstring);
|
||||
|
||||
virtual ~compressor() {}
|
||||
|
||||
@@ -50,38 +42,44 @@ public:
|
||||
virtual size_t compress_max_size(size_t input_len) const = 0;
|
||||
|
||||
/**
|
||||
* Returns metadata which must be written together with the compressed
|
||||
* data and used to construct a corresponding decompressor.
|
||||
* Returns accepted option names for this compressor
|
||||
*/
|
||||
virtual std::set<sstring> option_names() const;
|
||||
/**
|
||||
* Returns original options used in instantiating this compressor
|
||||
*/
|
||||
virtual std::map<sstring, sstring> options() const;
|
||||
|
||||
static bool is_hidden_option_name(std::string_view sv);
|
||||
/**
|
||||
* Compressor class name.
|
||||
*/
|
||||
const sstring& name() const {
|
||||
return _name;
|
||||
}
|
||||
|
||||
std::string name() const;
|
||||
// to cheaply bridge sstable compression options / maps
|
||||
using opt_string = std::optional<sstring>;
|
||||
using opt_getter = std::function<opt_string(const sstring&)>;
|
||||
using ptr_type = shared_ptr<compressor>;
|
||||
|
||||
virtual algorithm get_algorithm() const = 0;
|
||||
static ptr_type create(const sstring& name, const opt_getter&);
|
||||
static ptr_type create(const std::map<sstring, sstring>&);
|
||||
|
||||
virtual std::optional<unsigned> get_dict_owner_for_test() const;
|
||||
static thread_local const ptr_type lz4;
|
||||
static thread_local const ptr_type snappy;
|
||||
static thread_local const ptr_type deflate;
|
||||
|
||||
using ptr_type = std::unique_ptr<compressor>;
|
||||
static sstring make_name(std::string_view short_name);
|
||||
};
|
||||
|
||||
template<typename BaseType, typename... Args>
|
||||
class class_registry;
|
||||
|
||||
using compressor_ptr = compressor::ptr_type;
|
||||
using compressor_registry = class_registry<compressor, const typename compressor::opt_getter&>;
|
||||
|
||||
compressor_ptr make_lz4_sstable_compressor_for_tests();
|
||||
|
||||
// Per-table compression options, parsed and validated.
|
||||
//
|
||||
// Compression options are configured through the JSON-like `compression` entry in the schema.
|
||||
// The CQL layer parses the text of that entry to a `map<string, string>`.
|
||||
// A `compression_parameters` object is constructed from this map.
|
||||
// and the passed keys and values are parsed and validated in the constructor.
|
||||
// This object can be then used to create a `compressor` objects for sstable readers and writers.
|
||||
class compression_parameters {
|
||||
public:
|
||||
using algorithm = compressor::algorithm;
|
||||
static constexpr std::string_view name_prefix = "org.apache.cassandra.io.compress.";
|
||||
|
||||
static constexpr int32_t DEFAULT_CHUNK_LENGTH = 4 * 1024;
|
||||
static constexpr double DEFAULT_CRC_CHECK_CHANCE = 1.0;
|
||||
|
||||
@@ -90,47 +88,26 @@ public:
|
||||
static const sstring CHUNK_LENGTH_KB_ERR;
|
||||
static const sstring CRC_CHECK_CHANCE;
|
||||
private:
|
||||
algorithm _algorithm;
|
||||
compressor_ptr _compressor;
|
||||
std::optional<int> _chunk_length;
|
||||
std::optional<double> _crc_check_chance;
|
||||
std::optional<int> _zstd_compression_level;
|
||||
public:
|
||||
compression_parameters();
|
||||
compression_parameters(algorithm);
|
||||
compression_parameters(compressor_ptr);
|
||||
compression_parameters(const std::map<sstring, sstring>& options);
|
||||
~compression_parameters();
|
||||
|
||||
compressor_ptr get_compressor() const { return _compressor; }
|
||||
int32_t chunk_length() const { return _chunk_length.value_or(int(DEFAULT_CHUNK_LENGTH)); }
|
||||
double crc_check_chance() const { return _crc_check_chance.value_or(double(DEFAULT_CRC_CHECK_CHANCE)); }
|
||||
algorithm get_algorithm() const { return _algorithm; }
|
||||
std::optional<int> zstd_compression_level() const { return _zstd_compression_level; }
|
||||
|
||||
using dicts_feature_enabled = bool_class<struct dicts_feature_enabled_tag>;
|
||||
using dicts_usage_allowed = bool_class<struct dicts_usage_allowed_tag>;
|
||||
void validate(dicts_feature_enabled, dicts_usage_allowed) const;
|
||||
|
||||
void validate();
|
||||
std::map<sstring, sstring> get_options() const;
|
||||
bool operator==(const compression_parameters& other) const;
|
||||
|
||||
bool compression_enabled() const {
|
||||
return _algorithm != algorithm::none;
|
||||
}
|
||||
static compression_parameters no_compression() {
|
||||
return compression_parameters(algorithm::none);
|
||||
return compression_parameters(nullptr);
|
||||
}
|
||||
bool operator==(const compression_parameters&) const = default;
|
||||
static std::string_view algorithm_to_name(algorithm);
|
||||
static std::string algorithm_to_qualified_name(algorithm);
|
||||
private:
|
||||
static void validate_options(const std::map<sstring, sstring>&);
|
||||
static algorithm name_to_algorithm(std::string_view name);
|
||||
};
|
||||
|
||||
// Stream operator for boost::program_options support
|
||||
std::istream& operator>>(std::istream& is, compression_parameters& cp);
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<compression_parameters> : fmt::formatter<std::string_view> {
|
||||
auto format(const compression_parameters& cp, fmt::format_context& ctx) const -> decltype(ctx.out()) {
|
||||
return fmt::format_to(ctx.out(), "{}", cp.get_options());
|
||||
}
|
||||
void validate_options(const std::map<sstring, sstring>&);
|
||||
};
|
||||
|
||||
@@ -825,9 +825,7 @@ maintenance_socket: ignore
|
||||
# Guardrail to enable the deprecated feature of CREATE TABLE WITH COMPACT STORAGE.
|
||||
# enable_create_table_with_compact_storage: false
|
||||
|
||||
# Control tablets for new keyspaces.
|
||||
# Can be set to: disabled|enabled
|
||||
#
|
||||
# Enable tablets for new keyspaces.
|
||||
# When enabled, newly created keyspaces will have tablets enabled by default.
|
||||
# That can be explicitly disabled in the CREATE KEYSPACE query
|
||||
# by using the `tablets = {'enabled': false}` replication option.
|
||||
@@ -836,37 +834,6 @@ maintenance_socket: ignore
|
||||
# unless tablets are explicitly enabled in the CREATE KEYSPACE query
|
||||
# by using the `tablets = {'enabled': true}` replication option.
|
||||
#
|
||||
# When set to `enforced`, newly created keyspaces will always have tablets enabled by default.
|
||||
# This prevents explicitly disabling tablets in the CREATE KEYSPACE query
|
||||
# using the `tablets = {'enabled': false}` replication option.
|
||||
# It also mandates a replication strategy supporting tablets, like
|
||||
# NetworkTopologyStrategy
|
||||
#
|
||||
# Note that creating keyspaces with tablets enabled or disabled is irreversible.
|
||||
# The `tablets` option cannot be changed using `ALTER KEYSPACE`.
|
||||
tablets_mode_for_new_keyspaces: enabled
|
||||
|
||||
# Enforce RF-rack-valid keyspaces.
|
||||
rf_rack_valid_keyspaces: false
|
||||
|
||||
#
|
||||
# Alternator options
|
||||
#
|
||||
# Maximum number of items in single BatchWriteItem command. Default is 100.
|
||||
# Note: DynamoDB has a hard-coded limit of 25.
|
||||
# alternator_max_items_in_batch_write: 100
|
||||
|
||||
#
|
||||
# io-streaming rate limiting
|
||||
# When setting this value to be non-zero scylla throttles disk throughput for
|
||||
# stream (network) activities such as backup, repair, tablet migration and more.
|
||||
# This limit is useful for user queries so the network interface does
|
||||
# not get saturated by streaming activities.
|
||||
# The recommended value is 75% of network bandwidth
|
||||
# E.g for i4i.8xlarge (https://github.com/scylladb/scylla-machine-image/tree/next/common/aws_net_params.json):
|
||||
# network: 18.75 GiB/s --> 18750 Mib/s --> 1875 MB/s (from network bits to network bytes: divide by 10, not 8)
|
||||
# Converted to disk bytes: 1875 * 1000 / 1024 = 1831 MB/s (disk wise)
|
||||
# 75% of disk bytes is: 0.75 * 1831 = 1373 megabytes/s
|
||||
# stream_io_throughput_mb_per_sec: 1373
|
||||
#
|
||||
|
||||
enable_tablets: true
|
||||
|
||||
89
configure.py
89
configure.py
@@ -610,10 +610,6 @@ perf_tests = set([
|
||||
'test/perf/perf_sort_by_proximity',
|
||||
])
|
||||
|
||||
perf_standalone_tests = set([
|
||||
'test/perf/perf_generic_server',
|
||||
])
|
||||
|
||||
raft_tests = set([
|
||||
'test/raft/replication_test',
|
||||
'test/raft/randomized_nemesis_test',
|
||||
@@ -648,7 +644,7 @@ lto_binaries = set([
|
||||
'scylla'
|
||||
])
|
||||
|
||||
tests = scylla_tests | perf_tests | perf_standalone_tests | raft_tests
|
||||
tests = scylla_tests | perf_tests | raft_tests
|
||||
|
||||
other = set([
|
||||
'iotune',
|
||||
@@ -684,8 +680,8 @@ arg_parser.add_argument('--compiler', action='store', dest='cxx', default='clang
|
||||
help='C++ compiler path')
|
||||
arg_parser.add_argument('--c-compiler', action='store', dest='cc', default='clang',
|
||||
help='C compiler path')
|
||||
add_tristate(arg_parser, name='dpdk', dest='dpdk', default=False,
|
||||
help='Use dpdk (from seastar dpdk sources)')
|
||||
add_tristate(arg_parser, name='dpdk', dest='dpdk',
|
||||
help='Use dpdk (from seastar dpdk sources) (default=True for release builds)')
|
||||
arg_parser.add_argument('--dpdk-target', action='store', dest='dpdk_target', default='',
|
||||
help='Path to DPDK SDK target location (e.g. <DPDK SDK dir>/x86_64-native-linuxapp-gcc)')
|
||||
arg_parser.add_argument('--debuginfo', action='store', dest='debuginfo', type=int, default=1,
|
||||
@@ -816,7 +812,6 @@ scylla_core = (['message/messaging_service.cc',
|
||||
'utils/rjson.cc',
|
||||
'utils/human_readable.cc',
|
||||
'utils/histogram_metrics_helper.cc',
|
||||
'utils/io-wrappers.cc',
|
||||
'utils/on_internal_error.cc',
|
||||
'utils/pretty_printers.cc',
|
||||
'utils/stream_compressor.cc',
|
||||
@@ -830,7 +825,7 @@ scylla_core = (['message/messaging_service.cc',
|
||||
'keys.cc',
|
||||
'counters.cc',
|
||||
'compress.cc',
|
||||
'sstable_dict_autotrainer.cc',
|
||||
'zstd.cc',
|
||||
'sstables/sstables.cc',
|
||||
'sstables/sstables_manager.cc',
|
||||
'sstables/sstable_set.cc',
|
||||
@@ -981,7 +976,6 @@ scylla_core = (['message/messaging_service.cc',
|
||||
'cql3/result_set.cc',
|
||||
'cql3/prepare_context.cc',
|
||||
'db/batchlog_manager.cc',
|
||||
'db/corrupt_data_handler.cc',
|
||||
'db/commitlog/commitlog.cc',
|
||||
'db/commitlog/commitlog_entry.cc',
|
||||
'db/commitlog/commitlog_replayer.cc',
|
||||
@@ -1035,18 +1029,14 @@ scylla_core = (['message/messaging_service.cc',
|
||||
'utils/multiprecision_int.cc',
|
||||
'utils/gz/crc_combine.cc',
|
||||
'utils/gz/crc_combine_table.cc',
|
||||
'utils/http.cc',
|
||||
'utils/s3/aws_error.cc',
|
||||
'utils/s3/client.cc',
|
||||
'utils/s3/retryable_http_client.cc',
|
||||
'utils/s3/retry_strategy.cc',
|
||||
'utils/s3/s3_retry_strategy.cc',
|
||||
'utils/s3/credentials_providers/aws_credentials_provider.cc',
|
||||
'utils/s3/credentials_providers/environment_aws_credentials_provider.cc',
|
||||
'utils/s3/credentials_providers/instance_profile_credentials_provider.cc',
|
||||
'utils/s3/credentials_providers/sts_assume_role_credentials_provider.cc',
|
||||
'utils/s3/credentials_providers/aws_credentials_provider_chain.cc',
|
||||
'utils/s3/utils/manip_s3.cc',
|
||||
'utils/advanced_rpc_compressor.cc',
|
||||
'gms/version_generator.cc',
|
||||
'gms/versioned_value.cc',
|
||||
@@ -1116,7 +1106,7 @@ scylla_core = (['message/messaging_service.cc',
|
||||
'utils/lister.cc',
|
||||
'repair/repair.cc',
|
||||
'repair/row_level.cc',
|
||||
'streaming/table_check.cc',
|
||||
'repair/table_check.cc',
|
||||
'exceptions/exceptions.cc',
|
||||
'auth/allow_all_authenticator.cc',
|
||||
'auth/allow_all_authorizer.cc',
|
||||
@@ -1170,7 +1160,6 @@ scylla_core = (['message/messaging_service.cc',
|
||||
'ent/encryption/kms_key_provider.cc',
|
||||
'ent/encryption/gcp_host.cc',
|
||||
'ent/encryption/gcp_key_provider.cc',
|
||||
'ent/encryption/utils.cc',
|
||||
'ent/ldap/ldap_connection.cc',
|
||||
'multishard_mutation_query.cc',
|
||||
'reader_concurrency_semaphore.cc',
|
||||
@@ -1193,7 +1182,6 @@ scylla_core = (['message/messaging_service.cc',
|
||||
'service/raft/group0_state_id_handler.cc',
|
||||
'service/raft/group0_state_machine.cc',
|
||||
'service/raft/group0_state_machine_merger.cc',
|
||||
'service/raft/group0_voter_handler.cc',
|
||||
'service/raft/raft_sys_table_storage.cc',
|
||||
'serializer.cc',
|
||||
'release.cc',
|
||||
@@ -1201,7 +1189,7 @@ scylla_core = (['message/messaging_service.cc',
|
||||
'service/raft/raft_group_registry.cc',
|
||||
'service/raft/discovery.cc',
|
||||
'service/raft/raft_group0.cc',
|
||||
'service/direct_failure_detector/failure_detector.cc',
|
||||
'direct_failure_detector/failure_detector.cc',
|
||||
'service/raft/raft_group0_client.cc',
|
||||
'service/broadcast_tables/experimental/lang.cc',
|
||||
'tasks/task_handler.cc',
|
||||
@@ -1340,7 +1328,6 @@ idls = ['idl/gossip_digest.idl.hh',
|
||||
'idl/replica_exception.idl.hh',
|
||||
'idl/per_partition_rate_limit_info.idl.hh',
|
||||
'idl/position_in_partition.idl.hh',
|
||||
'idl/full_position.idl.hh',
|
||||
'idl/experimental/broadcast_tables_lang.idl.hh',
|
||||
'idl/storage_service.idl.hh',
|
||||
'idl/join_node.idl.hh',
|
||||
@@ -1392,6 +1379,7 @@ scylla_perfs = ['test/perf/perf_alternator.cc',
|
||||
'test/perf/perf_tablets.cc',
|
||||
'test/perf/tablet_load_balancing.cc',
|
||||
'test/perf/perf.cc',
|
||||
'test/lib/alternator_test_env.cc',
|
||||
'test/lib/cql_test_env.cc',
|
||||
'test/lib/log.cc',
|
||||
'test/lib/test_services.cc',
|
||||
@@ -1482,16 +1470,13 @@ for t in sorted(scylla_tests):
|
||||
else:
|
||||
deps[t] += scylla_core + alternator + idls + scylla_tests_generic_dependencies
|
||||
|
||||
for t in sorted(perf_tests | perf_standalone_tests):
|
||||
deps[t] = [t + '.cc'] + scylla_tests_dependencies
|
||||
deps[t] += ['test/perf/perf.cc', 'seastar/tests/perf/linux_perf_event.cc']
|
||||
|
||||
perf_tests_seastar_deps = [
|
||||
'seastar/tests/perf/perf_tests.cc'
|
||||
]
|
||||
|
||||
for t in sorted(perf_tests):
|
||||
deps[t] += perf_tests_seastar_deps
|
||||
deps[t] = [t + '.cc'] + scylla_tests_dependencies + perf_tests_seastar_deps
|
||||
deps[t] += ['test/perf/perf.cc', 'seastar/tests/perf/linux_perf_event.cc']
|
||||
|
||||
deps['test/boost/combined_tests'] += [
|
||||
'test/boost/aggregate_fcts_test.cc',
|
||||
@@ -1516,7 +1501,6 @@ deps['test/boost/combined_tests'] += [
|
||||
'test/boost/filtering_test.cc',
|
||||
'test/boost/group0_cmd_merge_test.cc',
|
||||
'test/boost/group0_test.cc',
|
||||
'test/boost/group0_voter_calculator_test.cc',
|
||||
'test/boost/index_with_paging_test.cc',
|
||||
'test/boost/json_cql_query_test.cc',
|
||||
'test/boost/large_paging_state_test.cc',
|
||||
@@ -1528,12 +1512,10 @@ deps['test/boost/combined_tests'] += [
|
||||
'test/boost/mutation_writer_test.cc',
|
||||
'test/boost/network_topology_strategy_test.cc',
|
||||
'test/boost/per_partition_rate_limit_test.cc',
|
||||
'test/boost/pluggable_test.cc',
|
||||
'test/boost/querier_cache_test.cc',
|
||||
'test/boost/query_processor_test.cc',
|
||||
'test/boost/reader_concurrency_semaphore_test.cc',
|
||||
'test/boost/repair_test.cc',
|
||||
'test/boost/replicator_test.cc',
|
||||
'test/boost/restrictions_test.cc',
|
||||
'test/boost/role_manager_test.cc',
|
||||
'test/boost/row_cache_test.cc',
|
||||
@@ -1542,8 +1524,6 @@ deps['test/boost/combined_tests'] += [
|
||||
'test/boost/secondary_index_test.cc',
|
||||
'test/boost/sessions_test.cc',
|
||||
'test/boost/sstable_compaction_test.cc',
|
||||
'test/boost/sstable_compressor_factory_test.cc',
|
||||
'test/boost/sstable_compression_config_test.cc',
|
||||
'test/boost/sstable_directory_test.cc',
|
||||
'test/boost/sstable_set_test.cc',
|
||||
'test/boost/statement_restrictions_test.cc',
|
||||
@@ -1606,8 +1586,8 @@ deps['test/boost/rust_test'] += ['rust/inc/src/lib.rs']
|
||||
|
||||
deps['test/raft/replication_test'] = ['test/raft/replication_test.cc', 'test/raft/replication.cc', 'test/raft/helpers.cc', 'test/lib/eventually.cc'] + scylla_raft_dependencies
|
||||
deps['test/raft/raft_server_test'] = ['test/raft/raft_server_test.cc', 'test/raft/replication.cc', 'test/raft/helpers.cc', 'test/lib/eventually.cc'] + scylla_raft_dependencies
|
||||
deps['test/raft/randomized_nemesis_test'] = ['test/raft/randomized_nemesis_test.cc', 'service/direct_failure_detector/failure_detector.cc', 'test/raft/helpers.cc'] + scylla_raft_dependencies
|
||||
deps['test/raft/failure_detector_test'] = ['test/raft/failure_detector_test.cc', 'service/direct_failure_detector/failure_detector.cc', 'test/raft/helpers.cc'] + scylla_raft_dependencies
|
||||
deps['test/raft/randomized_nemesis_test'] = ['test/raft/randomized_nemesis_test.cc', 'direct_failure_detector/failure_detector.cc', 'test/raft/helpers.cc'] + scylla_raft_dependencies
|
||||
deps['test/raft/failure_detector_test'] = ['test/raft/failure_detector_test.cc', 'direct_failure_detector/failure_detector.cc', 'test/raft/helpers.cc'] + scylla_raft_dependencies
|
||||
deps['test/raft/many_test'] = ['test/raft/many_test.cc', 'test/raft/replication.cc', 'test/raft/helpers.cc', 'test/lib/eventually.cc'] + scylla_raft_dependencies
|
||||
deps['test/raft/fsm_test'] = ['test/raft/fsm_test.cc', 'test/raft/helpers.cc', 'test/lib/log.cc'] + scylla_raft_dependencies
|
||||
deps['test/raft/etcd_test'] = ['test/raft/etcd_test.cc', 'test/raft/helpers.cc', 'test/lib/log.cc'] + scylla_raft_dependencies
|
||||
@@ -1747,11 +1727,19 @@ def generate_version(date_stamp):
|
||||
# the program headers.
|
||||
def dynamic_linker_option():
|
||||
gcc_linker_output = subprocess.check_output(['gcc', '-###', '/dev/null', '-o', 't'], stderr=subprocess.STDOUT).decode('utf-8')
|
||||
original_dynamic_linker = re.search('"?-dynamic-linker"?[ =]"?([^ "]*)"?[ \n]', gcc_linker_output).groups()[0]
|
||||
original_dynamic_linker = re.search('-dynamic-linker ([^ ]*)', gcc_linker_output).groups()[0]
|
||||
|
||||
# gdb has a SO_NAME_MAX_PATH_SIZE of 512, so limit the path size to
|
||||
# that. The 512 includes the null at the end, hence the 511 below.
|
||||
dynamic_linker = '/' * (511 - len(original_dynamic_linker)) + original_dynamic_linker
|
||||
employ_ld_trickery = True
|
||||
# distro-specific setup
|
||||
if os.environ.get('NIX_CC'):
|
||||
employ_ld_trickery = False
|
||||
|
||||
if employ_ld_trickery:
|
||||
# gdb has a SO_NAME_MAX_PATH_SIZE of 512, so limit the path size to
|
||||
# that. The 512 includes the null at the end, hence the 511 below.
|
||||
dynamic_linker = '/' * (511 - len(original_dynamic_linker)) + original_dynamic_linker
|
||||
else:
|
||||
dynamic_linker = original_dynamic_linker
|
||||
return f'--dynamic-linker={dynamic_linker}'
|
||||
|
||||
forced_ldflags = '-Wl,'
|
||||
@@ -1887,8 +1875,9 @@ def prepare_advanced_optimizations(*, modes, build_modes, args):
|
||||
submode['profile_target'] = profile_target
|
||||
submode['lib_cflags'] += f" -f{it}profile-generate={os.path.realpath(outdir)}/{submode_name} {conservative_opts}"
|
||||
submode['cxx_ld_flags'] += f" -f{it}profile-generate={os.path.realpath(outdir)}/{submode_name} {conservative_opts}"
|
||||
# Profile collection depends on java tools because we use cassandra-stress as the load.
|
||||
submode['profile_recipe'] = textwrap.dedent(f"""\
|
||||
build $builddir/{submode_name}/profiles/prof.profdata: train $builddir/{submode_name}/scylla
|
||||
build $builddir/{submode_name}/profiles/prof.profdata: train $builddir/{submode_name}/scylla | dist-tools-tar
|
||||
build $builddir/{submode_name}/profiles/merged.profdata: merge_profdata $builddir/{submode_name}/profiles/prof.profdata {profile_target or str()}
|
||||
""")
|
||||
submode['is_profile'] = True
|
||||
@@ -1972,6 +1961,8 @@ def configure_seastar(build_dir, mode, mode_config):
|
||||
seastar_cmake_args += ['-DSeastar_STACK_GUARDS={}'.format(stack_guards)]
|
||||
|
||||
dpdk = args.dpdk
|
||||
if dpdk is None:
|
||||
dpdk = platform.machine() == 'x86_64' and mode == 'release'
|
||||
if dpdk:
|
||||
seastar_cmake_args += ['-DSeastar_DPDK=ON', '-DSeastar_DPDK_MACHINE=westmere']
|
||||
if args.split_dwarf:
|
||||
@@ -2636,10 +2627,11 @@ def write_build_file(f,
|
||||
f.write(f' mode = {mode}\n')
|
||||
f.write(f'build dist-server-{mode}: phony $builddir/dist/{mode}/redhat $builddir/dist/{mode}/debian\n')
|
||||
f.write(f'build dist-server-debuginfo-{mode}: phony $builddir/{mode}/dist/tar/{scylla_product}-debuginfo-{scylla_version}-{scylla_release}.{arch}.tar.gz\n')
|
||||
f.write(f'build dist-tools-{mode}: phony $builddir/{mode}/dist/tar/{scylla_product}-tools-{scylla_version}-{scylla_release}.noarch.tar.gz dist-tools-rpm dist-tools-deb\n')
|
||||
f.write(f'build dist-cqlsh-{mode}: phony $builddir/{mode}/dist/tar/{scylla_product}-cqlsh-{scylla_version}-{scylla_release}.{arch}.tar.gz dist-cqlsh-rpm dist-cqlsh-deb\n')
|
||||
f.write(f'build dist-python3-{mode}: phony dist-python3-tar dist-python3-rpm dist-python3-deb\n')
|
||||
f.write(f'build dist-unified-{mode}: phony $builddir/{mode}/dist/tar/{scylla_product}-unified-{scylla_version}-{scylla_release}.{arch}.tar.gz\n')
|
||||
f.write(f'build $builddir/{mode}/dist/tar/{scylla_product}-unified-{scylla_version}-{scylla_release}.{arch}.tar.gz: unified $builddir/{mode}/dist/tar/{scylla_product}-{scylla_version}-{scylla_release}.{arch}.tar.gz $builddir/{mode}/dist/tar/{scylla_product}-python3-{scylla_version}-{scylla_release}.{arch}.tar.gz $builddir/{mode}/dist/tar/{scylla_product}-cqlsh-{scylla_version}-{scylla_release}.{arch}.tar.gz | always\n')
|
||||
f.write(f'build $builddir/{mode}/dist/tar/{scylla_product}-unified-{scylla_version}-{scylla_release}.{arch}.tar.gz: unified $builddir/{mode}/dist/tar/{scylla_product}-{scylla_version}-{scylla_release}.{arch}.tar.gz $builddir/{mode}/dist/tar/{scylla_product}-python3-{scylla_version}-{scylla_release}.{arch}.tar.gz $builddir/{mode}/dist/tar/{scylla_product}-tools-{scylla_version}-{scylla_release}.noarch.tar.gz $builddir/{mode}/dist/tar/{scylla_product}-cqlsh-{scylla_version}-{scylla_release}.{arch}.tar.gz | always\n')
|
||||
f.write(f' mode = {mode}\n')
|
||||
f.write(f'build $builddir/{mode}/dist/tar/{scylla_product}-unified-package-{scylla_version}-{scylla_release}.tar.gz: copy $builddir/{mode}/dist/tar/{scylla_product}-unified-{scylla_version}-{scylla_release}.{arch}.tar.gz\n')
|
||||
f.write(f'build $builddir/{mode}/dist/tar/{scylla_product}-unified-{arch}-package-{scylla_version}-{scylla_release}.tar.gz: copy $builddir/{mode}/dist/tar/{scylla_product}-unified-{scylla_version}-{scylla_release}.{arch}.tar.gz\n')
|
||||
@@ -2680,6 +2672,17 @@ def write_build_file(f,
|
||||
rule build-submodule-deb
|
||||
command = cd $dir && ./reloc/build_deb.sh --reloc-pkg $artifact
|
||||
|
||||
build tools/java/build/{scylla_product}-tools-{scylla_version}-{scylla_release}.noarch.tar.gz: build-submodule-reloc | $builddir/SCYLLA-PRODUCT-FILE $builddir/SCYLLA-VERSION-FILE $builddir/SCYLLA-RELEASE-FILE
|
||||
reloc_dir = tools/java
|
||||
build dist-tools-rpm: build-submodule-rpm tools/java/build/{scylla_product}-tools-{scylla_version}-{scylla_release}.noarch.tar.gz
|
||||
dir = tools/java
|
||||
artifact = build/{scylla_product}-tools-{scylla_version}-{scylla_release}.noarch.tar.gz
|
||||
build dist-tools-deb: build-submodule-deb tools/java/build/{scylla_product}-tools-{scylla_version}-{scylla_release}.noarch.tar.gz
|
||||
dir = tools/java
|
||||
artifact = build/{scylla_product}-tools-{scylla_version}-{scylla_release}.noarch.tar.gz
|
||||
build dist-tools-tar: phony {' '.join(['$builddir/{mode}/dist/tar/{scylla_product}-tools-{scylla_version}-{scylla_release}.noarch.tar.gz'.format(mode=mode, scylla_product=scylla_product, scylla_version=scylla_version, scylla_release=scylla_release) for mode in default_modes])}
|
||||
build dist-tools: phony dist-tools-tar dist-tools-rpm dist-tools-deb
|
||||
|
||||
build tools/cqlsh/build/{scylla_product}-cqlsh-{scylla_version}-{scylla_release}.{arch}.tar.gz: build-submodule-reloc | $builddir/SCYLLA-PRODUCT-FILE $builddir/SCYLLA-VERSION-FILE $builddir/SCYLLA-RELEASE-FILE
|
||||
reloc_dir = tools/cqlsh
|
||||
build dist-cqlsh-rpm: build-submodule-rpm tools/cqlsh/build/{scylla_product}-cqlsh-{scylla_version}-{scylla_release}.{arch}.tar.gz
|
||||
@@ -2702,11 +2705,11 @@ def write_build_file(f,
|
||||
artifact = build/{scylla_product}-python3-{scylla_version}-{scylla_release}.{arch}.tar.gz
|
||||
build dist-python3-tar: phony {' '.join(['$builddir/{mode}/dist/tar/{scylla_product}-python3-{scylla_version}-{scylla_release}.{arch}.tar.gz'.format(mode=mode, scylla_product=scylla_product, arch=arch, scylla_version=scylla_version, scylla_release=scylla_release) for mode in default_modes])}
|
||||
build dist-python3: phony dist-python3-tar dist-python3-rpm dist-python3-deb
|
||||
build dist-deb: phony dist-server-deb dist-python3-deb dist-cqlsh-deb
|
||||
build dist-rpm: phony dist-server-rpm dist-python3-rpm dist-cqlsh-rpm
|
||||
build dist-tar: phony dist-unified-tar dist-server-tar dist-python3-tar dist-cqlsh-tar
|
||||
build dist-deb: phony dist-server-deb dist-python3-deb dist-tools-deb dist-cqlsh-deb
|
||||
build dist-rpm: phony dist-server-rpm dist-python3-rpm dist-tools-rpm dist-cqlsh-rpm
|
||||
build dist-tar: phony dist-unified-tar dist-server-tar dist-python3-tar dist-tools-tar dist-cqlsh-tar
|
||||
|
||||
build dist: phony dist-unified dist-server dist-python3 dist-cqlsh
|
||||
build dist: phony dist-unified dist-server dist-python3 dist-tools dist-cqlsh
|
||||
'''))
|
||||
|
||||
f.write(textwrap.dedent(f'''\
|
||||
@@ -2719,10 +2722,12 @@ def write_build_file(f,
|
||||
build $builddir/{mode}/dist/tar/{scylla_product}-python3-{scylla_version}-{scylla_release}.{arch}.tar.gz: copy tools/python3/build/{scylla_product}-python3-{scylla_version}-{scylla_release}.{arch}.tar.gz
|
||||
build $builddir/{mode}/dist/tar/{scylla_product}-python3-package.tar.gz: copy tools/python3/build/{scylla_product}-python3-{scylla_version}-{scylla_release}.{arch}.tar.gz
|
||||
build $builddir/{mode}/dist/tar/{scylla_product}-python3-{arch}-package.tar.gz: copy tools/python3/build/{scylla_product}-python3-{scylla_version}-{scylla_release}.{arch}.tar.gz
|
||||
build $builddir/{mode}/dist/tar/{scylla_product}-tools-{scylla_version}-{scylla_release}.noarch.tar.gz: copy tools/java/build/{scylla_product}-tools-{scylla_version}-{scylla_release}.noarch.tar.gz
|
||||
build $builddir/{mode}/dist/tar/{scylla_product}-tools-package.tar.gz: copy tools/java/build/{scylla_product}-tools-{scylla_version}-{scylla_release}.noarch.tar.gz
|
||||
build $builddir/{mode}/dist/tar/{scylla_product}-cqlsh-{scylla_version}-{scylla_release}.{arch}.tar.gz: copy tools/cqlsh/build/{scylla_product}-cqlsh-{scylla_version}-{scylla_release}.{arch}.tar.gz
|
||||
build $builddir/{mode}/dist/tar/{scylla_product}-cqlsh-package.tar.gz: copy tools/cqlsh/build/{scylla_product}-cqlsh-{scylla_version}-{scylla_release}.{arch}.tar.gz
|
||||
|
||||
build {mode}-dist: phony dist-server-{mode} dist-server-debuginfo-{mode} dist-python3-{mode} dist-unified-{mode} dist-cqlsh-{mode}
|
||||
build {mode}-dist: phony dist-server-{mode} dist-server-debuginfo-{mode} dist-python3-{mode} dist-tools-{mode} dist-unified-{mode} dist-cqlsh-{mode}
|
||||
build dist-{mode}: phony {mode}-dist
|
||||
build dist-check-{mode}: dist-check
|
||||
mode = {mode}
|
||||
|
||||
16
cql3/Cql.g
16
cql3/Cql.g
@@ -709,23 +709,17 @@ batchStatement returns [std::unique_ptr<cql3::statements::raw::batch_statement>
|
||||
: K_BEGIN
|
||||
( K_UNLOGGED { type = btype::UNLOGGED; } | K_COUNTER { type = btype::COUNTER; } )?
|
||||
K_BATCH ( usingClause[attrs] )?
|
||||
( s=batchStatementObjective ';'?
|
||||
{
|
||||
auto&& stmt = *$s.statement;
|
||||
stmt->add_raw(sstring{$s.text});
|
||||
statements.push_back(std::move(stmt));
|
||||
} )*
|
||||
( s=batchStatementObjective ';'? { statements.push_back(std::move(s)); } )*
|
||||
K_APPLY K_BATCH
|
||||
{
|
||||
$expr = std::make_unique<cql3::statements::raw::batch_statement>(type, std::move(attrs), std::move(statements));
|
||||
}
|
||||
;
|
||||
|
||||
batchStatementObjective returns [::lw_shared_ptr<std::unique_ptr<cql3::statements::raw::modification_statement>> statement]
|
||||
@init { using original_ret_type = std::unique_ptr<cql3::statements::raw::modification_statement>; }
|
||||
: i=insertStatement { $statement = make_lw_shared<original_ret_type>(std::move(i)); }
|
||||
| u=updateStatement { $statement = make_lw_shared<original_ret_type>(std::move(u)); }
|
||||
| d=deleteStatement { $statement = make_lw_shared<original_ret_type>(std::move(d)); }
|
||||
batchStatementObjective returns [std::unique_ptr<cql3::statements::raw::modification_statement> statement]
|
||||
: i=insertStatement { $statement = std::move(i); }
|
||||
| u=updateStatement { $statement = std::move(u); }
|
||||
| d=deleteStatement { $statement = std::move(d); }
|
||||
;
|
||||
|
||||
dropAggregateStatement returns [std::unique_ptr<cql3::statements::drop_aggregate_statement> expr]
|
||||
|
||||
@@ -82,7 +82,7 @@ public:
|
||||
|
||||
const sstring& text() const;
|
||||
|
||||
sstring to_string() const;
|
||||
virtual sstring to_string() const;
|
||||
sstring to_cql_string() const;
|
||||
|
||||
friend std::hash<column_identifier_raw>;
|
||||
|
||||
@@ -48,16 +48,14 @@ const std::chrono::minutes prepared_statements_cache::entry_expiry = std::chrono
|
||||
struct query_processor::remote {
|
||||
remote(service::migration_manager& mm, service::mapreduce_service& fwd,
|
||||
service::storage_service& ss, service::raft_group0_client& group0_client)
|
||||
: mm(mm), mapreducer(fwd), ss(ss), group0_client(group0_client)
|
||||
, gate("query_processor::remote")
|
||||
{}
|
||||
: mm(mm), mapreducer(fwd), ss(ss), group0_client(group0_client) {}
|
||||
|
||||
service::migration_manager& mm;
|
||||
service::mapreduce_service& mapreducer;
|
||||
service::storage_service& ss;
|
||||
service::raft_group0_client& group0_client;
|
||||
|
||||
seastar::named_gate gate;
|
||||
seastar::gate gate;
|
||||
};
|
||||
|
||||
bool query_processor::topology_global_queue_empty() {
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
#include <seastar/core/on_internal_error.hh>
|
||||
#include <stdexcept>
|
||||
#include "alter_keyspace_statement.hh"
|
||||
#include "locator/tablets.hh"
|
||||
#include "prepared_statement.hh"
|
||||
#include "service/migration_manager.hh"
|
||||
#include "service/storage_proxy.hh"
|
||||
@@ -26,7 +25,6 @@
|
||||
#include "create_keyspace_statement.hh"
|
||||
#include "gms/feature_service.hh"
|
||||
#include "replica/database.hh"
|
||||
#include "db/config.hh"
|
||||
|
||||
using namespace std::string_literals;
|
||||
|
||||
@@ -196,9 +194,9 @@ cql3::statements::alter_keyspace_statement::prepare_schema_mutations(query_proce
|
||||
event::schema_change::target_type target_type = event::schema_change::target_type::KEYSPACE;
|
||||
auto ks = qp.db().find_keyspace(_name);
|
||||
auto ks_md = ks.metadata();
|
||||
const auto tmptr = qp.proxy().get_token_metadata_ptr();
|
||||
const auto& tm = *qp.proxy().get_token_metadata_ptr();
|
||||
const auto& feat = qp.proxy().features();
|
||||
auto ks_md_update = _attrs->as_ks_metadata_update(ks_md, *tmptr, feat);
|
||||
auto ks_md_update = _attrs->as_ks_metadata_update(ks_md, tm, feat);
|
||||
std::vector<mutation> muts;
|
||||
std::vector<sstring> warnings;
|
||||
auto old_ks_options = get_old_options_flattened(ks);
|
||||
@@ -267,47 +265,6 @@ cql3::statements::alter_keyspace_statement::prepare_schema_mutations(query_proce
|
||||
muts.insert(muts.begin(), schema_mutations.begin(), schema_mutations.end());
|
||||
}
|
||||
|
||||
auto rs = locator::abstract_replication_strategy::create_replication_strategy(
|
||||
ks_md_update->strategy_name(),
|
||||
locator::replication_strategy_params(ks_md_update->strategy_options(), ks_md_update->initial_tablets()));
|
||||
|
||||
// If `rf_rack_valid_keyspaces` is enabled, it's forbidden to perform a schema change that
|
||||
// would lead to an RF-rack-valid keyspace. Verify that this change does not.
|
||||
// For more context, see: scylladb/scylladb#23071.
|
||||
try {
|
||||
// There are two things to note here:
|
||||
// 1. We hold a group0_guard, so it's correct to check this here.
|
||||
// The topology or schema cannot change while we're performing this query.
|
||||
// 2. The replication strategy we use here does NOT represent the actual state
|
||||
// we will arrive at after applying the schema change. For instance, if the user
|
||||
// did not specify the RF for some of the DCs, it's equal to 0 in the replication
|
||||
// strategy we pass to this function, while in reality that means that the RF
|
||||
// will NOT change. That is not a problem:
|
||||
// - RF=0 is valid for all DCs, so it won't trigger an exception on its own,
|
||||
// - the keyspace must've been RF-rack-valid before this change. We check that
|
||||
// condition for all keyspaces at startup.
|
||||
// The second hyphen is not really true because currently topological changes can
|
||||
// disturb it (see scylladb/scylladb#23345), but we ignore that.
|
||||
locator::assert_rf_rack_valid_keyspace(_name, tmptr, *rs);
|
||||
} catch (const std::exception& e) {
|
||||
if (qp.db().get_config().rf_rack_valid_keyspaces()) {
|
||||
// There's no guarantee what the type of the exception will be, so we need to
|
||||
// wrap it manually here in a type that can be passed to the user.
|
||||
throw exceptions::invalid_request_exception(e.what());
|
||||
} else {
|
||||
// Even when the configuration option `rf_rack_valid_keyspaces` is set to false,
|
||||
// we'd like to inform the user that the keyspace they're altering will not
|
||||
// satisfy the restriction after the change--but just as a warning.
|
||||
// For more context, see issue: scylladb/scylladb#23330.
|
||||
warnings.push_back(seastar::format(
|
||||
"Keyspace '{}' is not RF-rack-valid: the replication factor doesn't match "
|
||||
"the rack count in at least one datacenter. A rack failure may reduce availability. "
|
||||
"For more context, see: "
|
||||
"https://docs.scylladb.com/manual/stable/reference/glossary.html#term-RF-rack-valid-keyspace.",
|
||||
_name));
|
||||
}
|
||||
}
|
||||
|
||||
auto ret = ::make_shared<event::schema_change>(
|
||||
event::schema_change::change_type::UPDATED,
|
||||
target_type,
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
* SPDX-License-Identifier: (LicenseRef-ScyllaDB-Source-Available-1.0 and Apache-2.0)
|
||||
*/
|
||||
|
||||
#include "cdc/log.hh"
|
||||
#include "utils/assert.hh"
|
||||
#include <seastar/core/coroutine.hh>
|
||||
#include "cql3/query_options.hh"
|
||||
@@ -28,7 +27,6 @@
|
||||
#include "db/view/view.hh"
|
||||
#include "cql3/query_processor.hh"
|
||||
#include "cdc/cdc_extension.hh"
|
||||
#include "cdc/cdc_partitioner.hh"
|
||||
|
||||
namespace cql3 {
|
||||
|
||||
@@ -286,59 +284,12 @@ void alter_table_statement::drop_column(const query_options& options, const sche
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<schema_ptr, std::vector<view_ptr>> alter_table_statement::prepare_schema_update(data_dictionary::database db, const query_options& options) const {
|
||||
std::pair<schema_builder, std::vector<view_ptr>> alter_table_statement::prepare_schema_update(data_dictionary::database db, const query_options& options) const {
|
||||
auto s = validation::validate_column_family(db, keyspace(), column_family());
|
||||
if (s->is_view()) {
|
||||
throw exceptions::invalid_request_exception("Cannot use ALTER TABLE on Materialized View");
|
||||
}
|
||||
|
||||
const bool is_cdc_log_table = cdc::is_log_for_some_table(db.real_database(), s->ks_name(), s->cf_name());
|
||||
// Only a CDC log table will have this partitioner name. User tables should
|
||||
// not be able to set this. Note that we perform a similar check when trying to
|
||||
// re-enable CDC for a table, when the log table has been replaced by a user table.
|
||||
// For better visualization of the above, consider this
|
||||
//
|
||||
// cqlsh> CREATE TABLE ks.t (p int PRIMARY KEY, v int) WITH cdc = {'enabled': true};
|
||||
// cqlsh> INSERT INTO ks.t (p, v) VALUES (1, 2);
|
||||
// cqlsh> ALTER TABLE ks.t WITH cdc = {'enabled': false};
|
||||
// cqlsh> DESC TABLE ks.t_scylla_cdc_log WITH INTERNALS; # Save this output!
|
||||
// cqlsh> DROP TABLE ks.t_scylla_cdc_log;
|
||||
// cqlsh> [Recreate the log table using the received statement]
|
||||
// cqlsh> ALTER TABLE ks.t WITH cdc = {'enabled': true};
|
||||
//
|
||||
// InvalidRequest: Error from server: code=2200 [Invalid query] message="Cannot create CDC log
|
||||
// table for table ks.t because a table of name ks.t_scylla_cdc_log already exists"
|
||||
//
|
||||
// See commit adda43edc75b901b2329bca8f3eb74596698d05f for more information on THAT case.
|
||||
// We reuse the same technique here.
|
||||
const bool was_cdc_log_table = s->get_partitioner().name() == cdc::cdc_partitioner::classname;
|
||||
|
||||
if (_column_changes.size() != 0 && is_cdc_log_table) {
|
||||
throw exceptions::invalid_request_exception(
|
||||
"You cannot modify the set of columns of a CDC log table directly. "
|
||||
"Modify the base table instead.");
|
||||
}
|
||||
if (_column_changes.size() != 0 && was_cdc_log_table) {
|
||||
throw exceptions::invalid_request_exception(
|
||||
"You cannot modify the set of columns of a CDC log table directly. "
|
||||
"Although the base table has deactivated CDC, this table will continue being "
|
||||
"a CDC log table until it is dropped. If you want to modify the columns in it, "
|
||||
"you can only do that by reenabling CDC on the base table, which will reattach "
|
||||
"this log table. Then you will be able to modify the columns in the base table, "
|
||||
"and that will have effect on the log table too. Modifying the columns of a CDC "
|
||||
"log table directly is never allowed.");
|
||||
}
|
||||
|
||||
if (_renames.size() != 0 && is_cdc_log_table) {
|
||||
throw exceptions::invalid_request_exception("Cannot rename a column of a CDC log table.");
|
||||
}
|
||||
if (_renames.size() != 0 && was_cdc_log_table) {
|
||||
throw exceptions::invalid_request_exception(
|
||||
"You cannot rename a column of a CDC log table. Although the base table "
|
||||
"has deactivated CDC, this table will continue being a CDC log table until it "
|
||||
"is dropped.");
|
||||
}
|
||||
|
||||
auto cfm = schema_builder(s);
|
||||
|
||||
if (_properties->get_id()) {
|
||||
@@ -426,45 +377,41 @@ std::pair<schema_ptr, std::vector<view_ptr>> alter_table_statement::prepare_sche
|
||||
|
||||
validate_column_rename(db, *s, *from, *to);
|
||||
cfm.rename_column(from->name(), to->name());
|
||||
}
|
||||
// New view schemas contain the new column names, so we need to base them on the
|
||||
// new base schema.
|
||||
schema_ptr new_base_schema = cfm.build();
|
||||
// If the view includes a renamed column, it must be renamed in
|
||||
// the view table and the definition.
|
||||
for (auto&& view : cf.views()) {
|
||||
schema_builder builder(view);
|
||||
std::vector<std::pair<::shared_ptr<column_identifier>, ::shared_ptr<column_identifier>>> view_renames;
|
||||
for (auto&& entry : _renames) {
|
||||
auto from = entry.first->prepare_column_identifier(*s);
|
||||
|
||||
// If the view includes a renamed column, it must be renamed in
|
||||
// the view table and the definition.
|
||||
for (auto&& view : cf.views()) {
|
||||
if (view->get_column_definition(from->name())) {
|
||||
schema_builder builder(view);
|
||||
|
||||
auto view_from = entry.first->prepare_column_identifier(*view);
|
||||
auto view_to = entry.second->prepare_column_identifier(*view);
|
||||
validate_column_rename(db, *view, *view_from, *view_to);
|
||||
builder.rename_column(view_from->name(), view_to->name());
|
||||
view_renames.emplace_back(view_from, view_to);
|
||||
|
||||
auto new_where = util::rename_column_in_where_clause(
|
||||
view->view_info()->where_clause(),
|
||||
column_identifier::raw(view_from->text(), true),
|
||||
column_identifier::raw(view_to->text(), true),
|
||||
cql3::dialect{});
|
||||
builder.with_view_info(view->view_info()->base_id(), view->view_info()->base_name(),
|
||||
view->view_info()->include_all_columns(), std::move(new_where));
|
||||
|
||||
view_updates.push_back(view_ptr(builder.build()));
|
||||
}
|
||||
}
|
||||
if (!view_renames.empty()) {
|
||||
auto new_where = util::rename_columns_in_where_clause(
|
||||
view->view_info()->where_clause(),
|
||||
view_renames,
|
||||
cql3::dialect{});
|
||||
builder.with_view_info(new_base_schema, view->view_info()->include_all_columns(), std::move(new_where));
|
||||
view_updates.push_back(view_ptr(builder.build()));
|
||||
}
|
||||
}
|
||||
return make_pair(std::move(new_base_schema), std::move(view_updates));
|
||||
break;
|
||||
}
|
||||
|
||||
return make_pair(cfm.build(), std::move(view_updates));
|
||||
return make_pair(std::move(cfm), std::move(view_updates));
|
||||
}
|
||||
|
||||
future<std::tuple<::shared_ptr<cql_transport::event::schema_change>, std::vector<mutation>, cql3::cql_warnings_vec>>
|
||||
alter_table_statement::prepare_schema_mutations(query_processor& qp, const query_options& options, api::timestamp_type ts) const {
|
||||
data_dictionary::database db = qp.db();
|
||||
auto [s, view_updates] = prepare_schema_update(db, options);
|
||||
auto m = co_await service::prepare_column_family_update_announcement(qp.proxy(), std::move(s), std::move(view_updates), ts);
|
||||
auto [cfm, view_updates] = prepare_schema_update(db, options);
|
||||
auto m = co_await service::prepare_column_family_update_announcement(qp.proxy(), cfm.build(), std::move(view_updates), ts);
|
||||
|
||||
using namespace cql_transport;
|
||||
auto ret = ::make_shared<event::schema_change>(
|
||||
|
||||
@@ -69,7 +69,7 @@ private:
|
||||
void add_column(const query_options& options, const schema& schema, data_dictionary::table cf, schema_builder& cfm, std::vector<view_ptr>& view_updates, const column_identifier& column_name, const cql3_type validator, const column_definition* def, bool is_static) const;
|
||||
void alter_column(const query_options& options, const schema& schema, data_dictionary::table cf, schema_builder& cfm, std::vector<view_ptr>& view_updates, const column_identifier& column_name, const cql3_type validator, const column_definition* def, bool is_static) const;
|
||||
void drop_column(const query_options& options, const schema& schema, data_dictionary::table cf, schema_builder& cfm, std::vector<view_ptr>& view_updates, const column_identifier& column_name, const cql3_type validator, const column_definition* def, bool is_static) const;
|
||||
std::pair<schema_ptr, std::vector<view_ptr>> prepare_schema_update(data_dictionary::database db, const query_options& options) const;
|
||||
std::pair<schema_builder, std::vector<view_ptr>> prepare_schema_update(data_dictionary::database db, const query_options& options) const;
|
||||
};
|
||||
|
||||
class alter_table_statement::raw_statement : public raw::cf_statement {
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
#include "db/per_partition_rate_limit_options.hh"
|
||||
#include "db/tablet_options.hh"
|
||||
#include "utils/bloom_calculations.hh"
|
||||
#include "db/config.hh"
|
||||
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
|
||||
@@ -136,9 +135,7 @@ void cf_prop_defs::validate(const data_dictionary::database db, sstring ks_name,
|
||||
throw exceptions::configuration_exception(sstring("Missing sub-option '") + compression_parameters::SSTABLE_COMPRESSION + "' for the '" + KW_COMPRESSION + "' option.");
|
||||
}
|
||||
compression_parameters cp(*compression_options);
|
||||
cp.validate(
|
||||
compression_parameters::dicts_feature_enabled(bool(db.features().sstable_compression_dicts)),
|
||||
compression_parameters::dicts_usage_allowed(db.get_config().sstable_compression_dictionaries_allow_in_ddl()));
|
||||
cp.validate();
|
||||
}
|
||||
|
||||
auto per_partition_rate_limit_options = get_per_partition_rate_limit_options(schema_extensions);
|
||||
|
||||
@@ -53,8 +53,7 @@ const sstring& cf_statement::keyspace() const
|
||||
|
||||
const sstring& cf_statement::column_family() const
|
||||
{
|
||||
thread_local static sstring empty = "";
|
||||
return bool(_cf_name) ? _cf_name->get_column_family() : empty;
|
||||
return _cf_name->get_column_family();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -11,8 +11,6 @@
|
||||
#include <seastar/core/coroutine.hh>
|
||||
#include "cql3/statements/create_keyspace_statement.hh"
|
||||
#include "cql3/statements/ks_prop_defs.hh"
|
||||
#include "exceptions/exceptions.hh"
|
||||
#include "locator/tablets.hh"
|
||||
#include "prepared_statement.hh"
|
||||
#include "data_dictionary/data_dictionary.hh"
|
||||
#include "data_dictionary/keyspace_metadata.hh"
|
||||
@@ -92,14 +90,14 @@ void create_keyspace_statement::validate(query_processor& qp, const service::cli
|
||||
|
||||
future<std::tuple<::shared_ptr<cql_transport::event::schema_change>, std::vector<mutation>, cql3::cql_warnings_vec>> create_keyspace_statement::prepare_schema_mutations(query_processor& qp, const query_options&, api::timestamp_type ts) const {
|
||||
using namespace cql_transport;
|
||||
const auto tmptr = qp.proxy().get_token_metadata_ptr();
|
||||
const auto& tm = *qp.proxy().get_token_metadata_ptr();
|
||||
const auto& feat = qp.proxy().features();
|
||||
const auto& cfg = qp.db().get_config();
|
||||
std::vector<mutation> m;
|
||||
std::vector<sstring> warnings;
|
||||
|
||||
try {
|
||||
auto ksm = _attrs->as_ks_metadata(_name, *tmptr, feat, cfg);
|
||||
auto ksm = _attrs->as_ks_metadata(_name, tm, feat, cfg);
|
||||
m = service::prepare_new_keyspace_announcement(qp.db().real_database(), ksm, ts);
|
||||
// If the new keyspace uses tablets, as long as there are features
|
||||
// which aren't supported by tablets we want to warn the user that
|
||||
@@ -113,39 +111,14 @@ future<std::tuple<::shared_ptr<cql_transport::event::schema_change>, std::vector
|
||||
if (rs->uses_tablets()) {
|
||||
warnings.push_back(
|
||||
"Tables in this keyspace will be replicated using Tablets "
|
||||
"and will not support Materialized Views, Secondary Indexes, CDC, LWT and counters features. "
|
||||
"To use Materialized Views, Secondary Indexes, CDC, LWT or counters, drop this keyspace and re-create it "
|
||||
"without tablets by adding AND TABLETS = {'enabled': false} to the CREATE KEYSPACE statement.");
|
||||
"and will not support CDC, LWT and counters features. "
|
||||
"To use CDC, LWT or counters, drop this keyspace and re-create it "
|
||||
"without tablets by adding AND TABLETS = {'enabled': false} "
|
||||
"to the CREATE KEYSPACE statement.");
|
||||
if (ksm->initial_tablets().value()) {
|
||||
warnings.push_back("Keyspace `initial` tablets option is deprecated. Use per-table tablet options instead.");
|
||||
}
|
||||
}
|
||||
|
||||
// If `rf_rack_valid_keyspaces` is enabled, it's forbidden to create an RF-rack-invalid keyspace.
|
||||
// Verify that it's RF-rack-valid.
|
||||
// For more context, see: scylladb/scylladb#23071.
|
||||
try {
|
||||
// We hold a group0_guard, so it's correct to check this here.
|
||||
// The topology or schema cannot change while we're performing this query.
|
||||
locator::assert_rf_rack_valid_keyspace(_name, tmptr, *rs);
|
||||
} catch (const std::exception& e) {
|
||||
if (cfg.rf_rack_valid_keyspaces()) {
|
||||
// There's no guarantee what the type of the exception will be, so we need to
|
||||
// wrap it manually here in a type that can be passed to the user.
|
||||
throw exceptions::invalid_request_exception(e.what());
|
||||
} else {
|
||||
// Even when the configuration option `rf_rack_valid_keyspaces` is set to false,
|
||||
// we'd like to inform the user that the keyspace they're creating does not
|
||||
// satisfy the restriction--but just as a warning.
|
||||
// For more context, see issue: scylladb/scylladb#23330.
|
||||
warnings.push_back(seastar::format(
|
||||
"Keyspace '{}' is not RF-rack-valid: the replication factor doesn't match "
|
||||
"the rack count in at least one datacenter. A rack failure may reduce availability. "
|
||||
"For more context, see: "
|
||||
"https://docs.scylladb.com/manual/stable/reference/glossary.html#term-RF-rack-valid-keyspace.",
|
||||
_name));
|
||||
}
|
||||
}
|
||||
} catch (const exceptions::already_exists_exception& e) {
|
||||
if (!_if_not_exists) {
|
||||
co_return coroutine::exception(std::current_exception());
|
||||
@@ -247,6 +220,9 @@ std::vector<sstring> check_against_restricted_replication_strategies(
|
||||
// We ignore errors (non-number, negative number, etc.) here,
|
||||
// these are checked and reported elsewhere.
|
||||
for (auto opt : attrs.get_replication_options()) {
|
||||
if (opt.first == sstring("initial_tablets")) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
auto rf = std::stol(opt.second);
|
||||
if (rf > 0) {
|
||||
|
||||
@@ -31,8 +31,6 @@
|
||||
#include "db/config.hh"
|
||||
#include "compaction/time_window_compaction_strategy.hh"
|
||||
|
||||
bool is_internal_keyspace(std::string_view name);
|
||||
|
||||
namespace cql3 {
|
||||
|
||||
namespace statements {
|
||||
@@ -124,10 +122,6 @@ void create_table_statement::apply_properties_to(schema_builder& builder, const
|
||||
addColumnMetadataFromAliases(cfmd, Collections.singletonList(valueAlias), defaultValidator, ColumnDefinition.Kind.COMPACT_VALUE);
|
||||
#endif
|
||||
|
||||
if (!_properties->get_compression_options() && !is_internal_keyspace(keyspace())) {
|
||||
builder.set_compressor_params(db.get_config().sstable_compression_user_table_options());
|
||||
}
|
||||
|
||||
_properties->apply_to_builder(builder, _properties->make_schema_extensions(db.extensions()), db, keyspace());
|
||||
}
|
||||
|
||||
|
||||
@@ -378,7 +378,7 @@ std::pair<view_ptr, cql3::cql_warnings_vec> create_view_statement::prepare_view(
|
||||
}
|
||||
|
||||
auto where_clause_text = util::relations_to_where_clause(_where_clause);
|
||||
builder.with_view_info(schema, included.empty(), std::move(where_clause_text));
|
||||
builder.with_view_info(schema->id(), schema->cf_name(), included.empty(), std::move(where_clause_text));
|
||||
|
||||
return std::make_pair(view_ptr(builder.build()), std::move(warnings));
|
||||
}
|
||||
|
||||
@@ -150,7 +150,7 @@ data_dictionary::storage_options ks_prop_defs::get_storage_options() const {
|
||||
return opts;
|
||||
}
|
||||
|
||||
std::optional<unsigned> ks_prop_defs::get_initial_tablets(std::optional<unsigned> default_value, bool enforce_tablets) const {
|
||||
std::optional<unsigned> ks_prop_defs::get_initial_tablets(std::optional<unsigned> default_value) const {
|
||||
auto tablets_options = get_map(KW_TABLETS);
|
||||
if (!tablets_options) {
|
||||
return default_value;
|
||||
@@ -165,9 +165,6 @@ std::optional<unsigned> ks_prop_defs::get_initial_tablets(std::optional<unsigned
|
||||
if (enabled == "true") {
|
||||
// nothing
|
||||
} else if (enabled == "false") {
|
||||
if (enforce_tablets) {
|
||||
throw exceptions::configuration_exception("Cannot disable tablets for keyspace since tablets are enforced using the `tablets_mode_for_new_keyspaces: enforced` config option.");
|
||||
}
|
||||
return std::nullopt;
|
||||
} else {
|
||||
throw exceptions::configuration_exception(sstring("Tablets enabled value must be true or false; found: ") + enabled);
|
||||
@@ -202,10 +199,8 @@ bool ks_prop_defs::get_durable_writes() const {
|
||||
lw_shared_ptr<data_dictionary::keyspace_metadata> ks_prop_defs::as_ks_metadata(sstring ks_name, const locator::token_metadata& tm, const gms::feature_service& feat, const db::config& cfg) {
|
||||
auto sc = get_replication_strategy_class().value();
|
||||
// if tablets options have not been specified, but tablets are globally enabled, set the value to 0 for N.T.S. only
|
||||
auto enable_tablets = feat.tablets && cfg.enable_tablets_by_default();
|
||||
std::optional<unsigned> default_initial_tablets = enable_tablets && locator::abstract_replication_strategy::to_qualified_class_name(sc) == "org.apache.cassandra.locator.NetworkTopologyStrategy"
|
||||
? std::optional<unsigned>(0) : std::nullopt;
|
||||
auto initial_tablets = get_initial_tablets(default_initial_tablets, cfg.enforce_tablets());
|
||||
auto enable_tablets = feat.tablets && cfg.enable_tablets();
|
||||
auto initial_tablets = get_initial_tablets(enable_tablets && locator::abstract_replication_strategy::to_qualified_class_name(sc) == "org.apache.cassandra.locator.NetworkTopologyStrategy" ? std::optional<unsigned>(0) : std::nullopt);
|
||||
auto options = prepare_options(sc, tm, get_replication_options());
|
||||
return data_dictionary::keyspace_metadata::new_keyspace(ks_name, sc,
|
||||
std::move(options), initial_tablets, get_boolean(KW_DURABLE_WRITES, true), get_storage_options());
|
||||
|
||||
@@ -60,7 +60,7 @@ public:
|
||||
void validate();
|
||||
std::map<sstring, sstring> get_replication_options() const;
|
||||
std::optional<sstring> get_replication_strategy_class() const;
|
||||
std::optional<unsigned> get_initial_tablets(std::optional<unsigned> default_value, bool enforce_tablets = false) const;
|
||||
std::optional<unsigned> get_initial_tablets(std::optional<unsigned> default_value) const;
|
||||
data_dictionary::storage_options get_storage_options() const;
|
||||
bool get_durable_writes() const;
|
||||
lw_shared_ptr<data_dictionary::keyspace_metadata> as_ks_metadata(sstring ks_name, const locator::token_metadata&, const gms::feature_service&, const db::config&);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user