Compare commits
1 Commits
copilot/fi
...
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)
|
||||
|
||||
31
.github/scripts/auto-backport.py
vendored
31
.github/scripts/auto-backport.py
vendored
@@ -47,29 +47,13 @@ def create_pull_request(repo, new_branch_name, base_branch_name, pr, backport_pr
|
||||
draft=is_draft
|
||||
)
|
||||
logging.info(f"Pull request created: {backport_pr.html_url}")
|
||||
labels_to_add = []
|
||||
priority_labels = {"P0", "P1"}
|
||||
parent_pr_labels = [label.name for label in pr.labels]
|
||||
for label in priority_labels:
|
||||
if label in parent_pr_labels:
|
||||
labels_to_add.append(label)
|
||||
labels_to_add.append("force_on_cloud")
|
||||
logging.info(f"Adding {label} and force_on_cloud labels from parent PR to backport PR")
|
||||
break # Only apply the highest priority label
|
||||
|
||||
if is_collaborator:
|
||||
backport_pr.add_to_assignees(pr.user)
|
||||
if is_draft:
|
||||
labels_to_add.append("conflicts")
|
||||
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 mark this PR as ready for review"
|
||||
backport_pr.create_issue_comment(pr_comment)
|
||||
|
||||
# Apply all labels at once if we have any
|
||||
if labels_to_add:
|
||||
backport_pr.add_to_labels(*labels_to_add)
|
||||
logging.info(f"Added labels to backport PR: {labels_to_add}")
|
||||
|
||||
logging.info(f"Assigned PR to original author: {pr.user}")
|
||||
return backport_pr
|
||||
except GithubException as e:
|
||||
@@ -128,15 +112,10 @@ 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}")
|
||||
|
||||
|
||||
20
.github/scripts/sync_labels.py
vendored
20
.github/scripts/sync_labels.py
vendored
@@ -30,13 +30,8 @@ def copy_labels_from_linked_issues(repo, pr_number):
|
||||
try:
|
||||
issue = repo.get_issue(int(issue_number))
|
||||
for label in issue.labels:
|
||||
# Copy ALL labels from issues to PR when PR is opened
|
||||
pr.add_to_labels(label.name)
|
||||
print(f"Copied label '{label.name}' from issue #{issue_number} to PR #{pr_number}")
|
||||
if label.name in ['P0', 'P1']:
|
||||
pr.add_to_labels('force_on_cloud')
|
||||
print(f"Added force_on_cloud label to PR #{pr_number} due to {label.name} label")
|
||||
print(f"All labels from issue #{issue_number} copied to PR #{pr_number}")
|
||||
print(f"Labels from issue #{issue_number} copied to PR #{pr_number}")
|
||||
except Exception as e:
|
||||
print(f"Error processing issue #{issue_number}: {e}")
|
||||
|
||||
@@ -79,22 +74,9 @@ def sync_labels(repo, number, label, action, is_issue=False):
|
||||
target = repo.get_issue(int(pr_or_issue_number))
|
||||
if action == 'labeled':
|
||||
target.add_to_labels(label)
|
||||
if label in ['P0', 'P1'] and is_issue:
|
||||
# Only add force_on_cloud to PRs when P0/P1 is added to an issue
|
||||
target.add_to_labels('force_on_cloud')
|
||||
print(f"Added 'force_on_cloud' label to PR #{pr_or_issue_number} due to {label} label")
|
||||
print(f"Label '{label}' successfully added.")
|
||||
elif action == 'unlabeled':
|
||||
target.remove_from_labels(label)
|
||||
if label in ['P0', 'P1'] and is_issue:
|
||||
# Check if any other P0/P1 labels remain before removing force_on_cloud
|
||||
remaining_priority_labels = [l.name for l in target.labels if l.name in ['P0', 'P1']]
|
||||
if not remaining_priority_labels:
|
||||
try:
|
||||
target.remove_from_labels('force_on_cloud')
|
||||
print(f"Removed 'force_on_cloud' label from PR #{pr_or_issue_number} as no P0/P1 labels remain")
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not remove force_on_cloud label: {e}")
|
||||
print(f"Label '{label}' successfully removed.")
|
||||
elif action == 'opened':
|
||||
copy_labels_from_linked_issues(repo, number)
|
||||
|
||||
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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -54,13 +54,10 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.AUTO_BACKPORT_TOKEN }}
|
||||
run: python .github/scripts/auto-backport.py --repo ${{ github.repository }} --base-branch ${{ github.ref }} --commits ${{ github.event.before }}..${{ github.sha }}
|
||||
- name: Check if a valid backport label exists and no backport_error
|
||||
env:
|
||||
LABELS_JSON: ${{ toJson(github.event.pull_request.labels) }}
|
||||
id: check_label
|
||||
run: |
|
||||
labels_json="$LABELS_JSON"
|
||||
echo "Checking labels:"
|
||||
echo "$labels_json" | jq -r '.[].name'
|
||||
labels_json='${{ toJson(github.event.pull_request.labels) }}'
|
||||
echo "Checking labels: $(echo "$labels_json" | jq -r '.[].name')"
|
||||
|
||||
# Check if a valid backport label exists
|
||||
if echo "$labels_json" | jq -e 'any(.[] | .name; test("backport/[0-9]+\\.[0-9]+$"))' > /dev/null; then
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
name: Call Jira Status In Progress
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened]
|
||||
|
||||
jobs:
|
||||
call-jira-status-in-progress:
|
||||
uses: scylladb/github-automation/.github/workflows/main_update_jira_status_to_in_progress.yml@main
|
||||
secrets:
|
||||
caller_jira_auth: ${{ secrets.USER_AND_KEY_FOR_JIRA_AUTOMATION }}
|
||||
|
||||
12
.github/workflows/call_jira_status_in_review.yml
vendored
12
.github/workflows/call_jira_status_in_review.yml
vendored
@@ -1,12 +0,0 @@
|
||||
name: Call Jira Status In Review
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [ready_for_review, review_requested]
|
||||
|
||||
jobs:
|
||||
call-jira-status-in-review:
|
||||
uses: scylladb/github-automation/.github/workflows/main_update_jira_status_to_in_review.yml@main
|
||||
secrets:
|
||||
caller_jira_auth: ${{ secrets.USER_AND_KEY_FOR_JIRA_AUTOMATION }}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
name: Call Jira Status Ready For Merge
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [labeled]
|
||||
|
||||
jobs:
|
||||
call-jira-status-update:
|
||||
uses: scylladb/github-automation/.github/workflows/main_update_jira_status_to_ready_for_merge.yml@main
|
||||
secrets:
|
||||
caller_jira_auth: ${{ secrets.USER_AND_KEY_FOR_JIRA_AUTOMATION }}
|
||||
|
||||
143
.github/workflows/conflict_reminder.yaml
vendored
143
.github/workflows/conflict_reminder.yaml
vendored
@@ -1,16 +1,9 @@
|
||||
name: Notify PR Authors of Conflicts
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
- 'branch-*'
|
||||
schedule:
|
||||
- cron: '0 10 * * 1' # Runs every Monday at 10:00am
|
||||
- cron: '0 10 * * 1,4' # Runs every Monday and Thursday at 10:00am
|
||||
workflow_dispatch: # Manual trigger for testing
|
||||
|
||||
jobs:
|
||||
notify_conflict_prs:
|
||||
@@ -21,134 +14,32 @@ jobs:
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
console.log("Starting conflict reminder script...");
|
||||
// Print trigger event
|
||||
if (process.env.GITHUB_EVENT_NAME) {
|
||||
console.log(`Workflow triggered by: ${process.env.GITHUB_EVENT_NAME}`);
|
||||
} else {
|
||||
console.log("Could not determine workflow trigger event.");
|
||||
}
|
||||
const isPushEvent = process.env.GITHUB_EVENT_NAME === 'push';
|
||||
console.log(`isPushEvent: ${isPushEvent}`);
|
||||
const twoMonthsAgo = new Date();
|
||||
twoMonthsAgo.setMonth(twoMonthsAgo.getMonth() - 2);
|
||||
const prs = await github.paginate(github.rest.pulls.list, {
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
state: 'open',
|
||||
per_page: 100
|
||||
});
|
||||
console.log(`Fetched ${prs.length} open PRs`);
|
||||
const recentPrs = prs.filter(pr => new Date(pr.created_at) >= twoMonthsAgo);
|
||||
const validBaseBranches = ['master'];
|
||||
const branchPrefix = 'branch-';
|
||||
const oneWeekAgo = new Date();
|
||||
const conflictLabel = 'conflicts';
|
||||
oneWeekAgo.setDate(oneWeekAgo.getDate() - 7);
|
||||
console.log(`One week ago: ${oneWeekAgo.toISOString()}`);
|
||||
|
||||
for (const pr of recentPrs) {
|
||||
console.log(`Checking PR #${pr.number} on base branch '${pr.base.ref}'`);
|
||||
const isBranchX = pr.base.ref.startsWith(branchPrefix);
|
||||
const isMaster = validBaseBranches.includes(pr.base.ref);
|
||||
if (!(isBranchX || isMaster)) {
|
||||
console.log(`PR #${pr.number} skipped: base branch is not 'master' or does not start with '${branchPrefix}'`);
|
||||
continue;
|
||||
}
|
||||
const threeDaysAgo = new Date();
|
||||
const conflictLabel = 'conflicts';
|
||||
threeDaysAgo.setDate(threeDaysAgo.getDate() - 3);
|
||||
for (const pr of prs) {
|
||||
if (!pr.base.ref.startsWith(branchPrefix)) continue;
|
||||
const hasConflictLabel = pr.labels.some(label => label.name === conflictLabel);
|
||||
if (!hasConflictLabel) continue;
|
||||
const updatedDate = new Date(pr.updated_at);
|
||||
console.log(`PR #${pr.number} last updated at: ${updatedDate.toISOString()}`);
|
||||
if (!isPushEvent && updatedDate >= oneWeekAgo) {
|
||||
console.log(`PR #${pr.number} skipped: updated within last week`);
|
||||
continue;
|
||||
}
|
||||
if (pr.assignee === null) {
|
||||
console.log(`PR #${pr.number} skipped: no assignee`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Fetch PR details to check mergeability
|
||||
let { data: prDetails } = await github.rest.pulls.get({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: pr.number,
|
||||
});
|
||||
console.log(`PR #${pr.number} mergeable: ${prDetails.mergeable}`);
|
||||
|
||||
// Wait and re-fetch if mergeable is null
|
||||
if (prDetails.mergeable === null) {
|
||||
console.log(`PR #${pr.number} mergeable is null, waiting 2 seconds and retrying...`);
|
||||
await new Promise(resolve => setTimeout(resolve, 2000)); // wait 2 seconds
|
||||
prDetails = (await github.rest.pulls.get({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: pr.number,
|
||||
})).data;
|
||||
console.log(`PR #${pr.number} mergeable after retry: ${prDetails.mergeable}`);
|
||||
}
|
||||
|
||||
if (prDetails.mergeable === false) {
|
||||
const hasConflictLabel = pr.labels.some(label => label.name === conflictLabel);
|
||||
console.log(`PR #${pr.number} has conflict label: ${hasConflictLabel}`);
|
||||
|
||||
// Fetch comments to check for existing notifications
|
||||
const comments = await github.paginate(github.rest.issues.listComments, {
|
||||
if (updatedDate >= threeDaysAgo) continue;
|
||||
if (pr.assignee === null) continue;
|
||||
const assignee = pr.assignee.login;
|
||||
if (assignee) {
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: pr.number,
|
||||
per_page: 100,
|
||||
body: `@${assignee}, this PR has been open with conflicts. Please resolve the conflicts so we can merge it.`,
|
||||
});
|
||||
|
||||
// Find last notification comment from the bot
|
||||
const notificationPrefix = `@${pr.assignee.login}, this PR has merge conflicts with the base branch.`;
|
||||
const lastNotification = comments
|
||||
.filter(c =>
|
||||
c.user.type === "Bot" &&
|
||||
c.body.startsWith(notificationPrefix)
|
||||
)
|
||||
.sort((a, b) => new Date(b.created_at) - new Date(a.created_at))[0];
|
||||
|
||||
// Check if we should skip notification based on recent notification
|
||||
let shouldSkipNotification = false;
|
||||
if (lastNotification) {
|
||||
const lastNotified = new Date(lastNotification.created_at);
|
||||
if (lastNotified >= oneWeekAgo) {
|
||||
console.log(`PR #${pr.number} skipped: last notification was less than 1 week ago`);
|
||||
shouldSkipNotification = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Additional check for push events on draft PRs with conflict labels
|
||||
if (
|
||||
isPushEvent &&
|
||||
pr.draft === true &&
|
||||
hasConflictLabel &&
|
||||
shouldSkipNotification
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!hasConflictLabel) {
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: pr.number,
|
||||
labels: [conflictLabel],
|
||||
});
|
||||
console.log(`Added 'conflicts' label to PR #${pr.number}`);
|
||||
}
|
||||
|
||||
const assignee = pr.assignee.login;
|
||||
if (assignee && !shouldSkipNotification) {
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: pr.number,
|
||||
body: `@${assignee}, this PR has merge conflicts with the base branch. Please resolve the conflicts so we can merge it.`,
|
||||
});
|
||||
console.log(`Notified @${assignee} for PR #${pr.number}`);
|
||||
}
|
||||
} else {
|
||||
console.log(`PR #${pr.number} is mergeable, no action needed.`);
|
||||
}
|
||||
console.log(`Notified @${assignee} for PR #${pr.number}`);
|
||||
}
|
||||
}
|
||||
console.log(`Total PRs checked: ${prs.length}`);
|
||||
|
||||
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
|
||||
|
||||
4
.github/workflows/sync-labels.yaml
vendored
4
.github/workflows/sync-labels.yaml
vendored
@@ -37,13 +37,13 @@ jobs:
|
||||
run: python .github/scripts/sync_labels.py --repo ${{ github.repository }} --number ${{ github.event.number }} --action ${{ github.event.action }}
|
||||
|
||||
- name: Pull request labeled or unlabeled event
|
||||
if: github.event_name == 'pull_request_target' && (startsWith(github.event.label.name, 'backport/') || github.event.label.name == 'P0' || github.event.label.name == 'P1')
|
||||
if: github.event_name == 'pull_request_target' && startsWith(github.event.label.name, 'backport/')
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: python .github/scripts/sync_labels.py --repo ${{ github.repository }} --number ${{ github.event.number }} --action ${{ github.event.action }} --label ${{ github.event.label.name }}
|
||||
|
||||
- name: Issue labeled or unlabeled event
|
||||
if: github.event_name == 'issues' && (startsWith(github.event.label.name, 'backport/') || github.event.label.name == 'P0' || github.event.label.name == 'P1')
|
||||
if: github.event_name == 'issues' && startsWith(github.event.label.name, 'backport/')
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: python .github/scripts/sync_labels.py --repo ${{ github.repository }} --number ${{ github.event.issue.number }} --action ${{ github.event.action }} --is_issue --label ${{ github.event.label.name }}
|
||||
|
||||
21
.github/workflows/trigger-scylla-ci.yaml
vendored
21
.github/workflows/trigger-scylla-ci.yaml
vendored
@@ -1,21 +0,0 @@
|
||||
name: Trigger Scylla CI Route
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
|
||||
jobs:
|
||||
trigger-jenkins:
|
||||
if: github.event.comment.user.login != 'scylladbbot' && contains(github.event.comment.body, '@scylladbbot') && contains(github.event.comment.body, 'trigger-ci')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Trigger Scylla-CI-Route Jenkins Job
|
||||
env:
|
||||
JENKINS_USER: ${{ secrets.JENKINS_USERNAME }}
|
||||
JENKINS_API_TOKEN: ${{ secrets.JENKINS_TOKEN }}
|
||||
JENKINS_URL: "https://jenkins.scylladb.com"
|
||||
run: |
|
||||
PR_NUMBER=${{ github.event.issue.number }}
|
||||
PR_REPO_NAME=${{ github.event.repository.full_name }}
|
||||
curl -X POST "$JENKINS_URL/job/releng/job/Scylla-CI-Route/buildWithParameters?PR_NUMBER=$PR_NUMBER&PR_REPO_NAME=$PR_REPO_NAME" \
|
||||
--user "$JENKINS_USER:$JENKINS_API_TOKEN" --fail -i -v
|
||||
2
.github/workflows/urgent_issue_reminder.yml
vendored
2
.github/workflows/urgent_issue_reminder.yml
vendored
@@ -2,7 +2,7 @@ name: Urgent Issue Reminder
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '10 8 * * *' # Runs daily at 8 AM
|
||||
- cron: '10 8 * * 1' # Runs every Monday at 8 AM
|
||||
|
||||
jobs:
|
||||
reminder:
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -35,6 +35,3 @@ compile_commands.json
|
||||
.envrc
|
||||
clang_build
|
||||
.idea/
|
||||
nuke
|
||||
rust/target
|
||||
|
||||
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -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
|
||||
|
||||
@@ -49,7 +49,7 @@ include(limit_jobs)
|
||||
set(CMAKE_CXX_STANDARD "23" CACHE INTERNAL "")
|
||||
set(CMAKE_CXX_EXTENSIONS ON CACHE INTERNAL "")
|
||||
set(CMAKE_CXX_SCAN_FOR_MODULES OFF CACHE INTERNAL "")
|
||||
set(CMAKE_VISIBILITY_INLINES_HIDDEN ON)
|
||||
set(CMAKE_CXX_VISIBILITY_PRESET hidden)
|
||||
|
||||
if(is_multi_config)
|
||||
find_package(Seastar)
|
||||
@@ -90,13 +90,13 @@ if(is_multi_config)
|
||||
add_dependencies(Seastar::seastar_testing Seastar)
|
||||
else()
|
||||
set(Seastar_TESTING ON CACHE BOOL "" FORCE)
|
||||
set(Seastar_API_LEVEL 9 CACHE STRING "" FORCE)
|
||||
set(Seastar_API_LEVEL 7 CACHE STRING "" FORCE)
|
||||
set(Seastar_DEPRECATED_OSTREAM_FORMATTERS OFF CACHE BOOL "" FORCE)
|
||||
set(Seastar_APPS ON CACHE BOOL "" FORCE)
|
||||
set(Seastar_EXCLUDE_APPS_FROM_ALL ON CACHE BOOL "" FORCE)
|
||||
set(Seastar_EXCLUDE_TESTS_FROM_ALL ON CACHE BOOL "" FORCE)
|
||||
set(Seastar_IO_URING ON CACHE BOOL "" FORCE)
|
||||
set(Seastar_SCHEDULING_GROUPS_COUNT 21 CACHE STRING "" FORCE)
|
||||
set(Seastar_SCHEDULING_GROUPS_COUNT 19 CACHE STRING "" FORCE)
|
||||
set(Seastar_UNUSED_RESULT_ERROR ON CACHE BOOL "" FORCE)
|
||||
add_subdirectory(seastar)
|
||||
target_compile_definitions (seastar
|
||||
@@ -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
|
||||
@@ -170,23 +178,32 @@ target_sources(scylla-main
|
||||
bytes.cc
|
||||
client_data.cc
|
||||
clocks-impl.cc
|
||||
sstable_dict_autotrainer.cc
|
||||
collection_mutation.cc
|
||||
compress.cc
|
||||
converting_mutation_partition_applier.cc
|
||||
counters.cc
|
||||
direct_failure_detector/failure_detector.cc
|
||||
duration.cc
|
||||
exceptions/exceptions.cc
|
||||
frozen_schema.cc
|
||||
generic_server.cc
|
||||
debug.cc
|
||||
init.cc
|
||||
keys/keys.cc
|
||||
keys.cc
|
||||
multishard_mutation_query.cc
|
||||
mutation_query.cc
|
||||
node_ops/task_manager_module.cc
|
||||
partition_slice_builder.cc
|
||||
query/query.cc
|
||||
querier.cc
|
||||
query.cc
|
||||
query_ranges_to_vnodes.cc
|
||||
query/query-result-set.cc
|
||||
query-result-set.cc
|
||||
tombstone_gc_options.cc
|
||||
tombstone_gc.cc
|
||||
reader_concurrency_semaphore.cc
|
||||
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
|
||||
@@ -197,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
|
||||
@@ -262,6 +280,7 @@ add_subdirectory(mutation)
|
||||
add_subdirectory(mutation_writer)
|
||||
add_subdirectory(node_ops)
|
||||
add_subdirectory(readers)
|
||||
add_subdirectory(redis)
|
||||
add_subdirectory(replica)
|
||||
add_subdirectory(raft)
|
||||
add_subdirectory(repair)
|
||||
@@ -276,7 +295,6 @@ add_subdirectory(tracing)
|
||||
add_subdirectory(transport)
|
||||
add_subdirectory(types)
|
||||
add_subdirectory(utils)
|
||||
add_subdirectory(vector_search)
|
||||
add_version_library(scylla_version
|
||||
release.cc)
|
||||
|
||||
@@ -306,6 +324,7 @@ set(scylla_libs
|
||||
mutation_writer
|
||||
raft
|
||||
readers
|
||||
redis
|
||||
repair
|
||||
replica
|
||||
schema
|
||||
@@ -318,8 +337,7 @@ set(scylla_libs
|
||||
tracing
|
||||
transport
|
||||
types
|
||||
utils
|
||||
vector_search)
|
||||
utils)
|
||||
target_link_libraries(scylla PRIVATE
|
||||
${scylla_libs})
|
||||
|
||||
@@ -353,6 +371,3 @@ endif()
|
||||
if(Scylla_BUILD_INSTRUMENTED)
|
||||
add_subdirectory(pgo)
|
||||
endif()
|
||||
|
||||
add_executable(patchelf
|
||||
tools/patchelf.cc)
|
||||
|
||||
@@ -12,7 +12,7 @@ Please use the [issue tracker](https://github.com/scylladb/scylla/issues/) to re
|
||||
|
||||
## Contributing code to Scylla
|
||||
|
||||
Before you can contribute code to Scylla for the first time, you should sign the [Contributor License Agreement](https://www.scylladb.com/open-source/contributor-agreement/) and send the signed form to cla@scylladb.com. You can then submit your changes as patches to the [scylladb-dev mailing list](https://groups.google.com/forum/#!forum/scylladb-dev) or as a pull request to the [Scylla project on github](https://github.com/scylladb/scylla).
|
||||
Before you can contribute code to Scylla for the first time, you should sign the [Contributor License Agreement](https://www.scylladb.com/open-source/contributor-agreement/) and send the signed form cla@scylladb.com. You can then submit your changes as patches to the [scylladb-dev mailing list](https://groups.google.com/forum/#!forum/scylladb-dev) or as a pull request to the [Scylla project on github](https://github.com/scylladb/scylla).
|
||||
If you need help formatting or sending patches, [check out these instructions](https://github.com/scylladb/scylla/wiki/Formatting-and-sending-patches).
|
||||
|
||||
The Scylla C++ source code uses the [Seastar coding style](https://github.com/scylladb/seastar/blob/master/coding-style.md) so please adhere to that in your patches. Note that Scylla code is written with `using namespace seastar`, so should not explicitly add the `seastar::` prefix to Seastar symbols. You will usually not need to add `using namespace seastar` to new source files, because most Scylla header files have `#include "seastarx.hh"`, which does this.
|
||||
|
||||
33
HACKING.md
33
HACKING.md
@@ -43,7 +43,7 @@ $ ./tools/toolchain/dbuild ninja build/release/scylla
|
||||
$ ./tools/toolchain/dbuild ./build/release/scylla --developer-mode 1
|
||||
```
|
||||
|
||||
Note: do not mix environments - either perform all your work with dbuild, or natively on the host.
|
||||
Note: do not mix environemtns - either perform all your work with dbuild, or natively on the host.
|
||||
Note2: you can get to an interactive shell within dbuild by running it without any parameters:
|
||||
```bash
|
||||
$ ./tools/toolchain/dbuild
|
||||
@@ -91,7 +91,7 @@ You can also specify a single mode. For example
|
||||
$ ninja-build release
|
||||
```
|
||||
|
||||
Will build everything in release mode. The valid modes are
|
||||
Will build everytihng in release mode. The valid modes are
|
||||
|
||||
* Debug: Enables [AddressSanitizer](https://github.com/google/sanitizers/wiki/AddressSanitizer)
|
||||
and other sanity checks. It has no optimizations, which allows for debugging with tools like
|
||||
@@ -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
|
||||
|
||||
@@ -361,7 +380,7 @@ avoid that the gold linker can be told to create an index with
|
||||
|
||||
More info at https://gcc.gnu.org/wiki/DebugFission.
|
||||
|
||||
Both options can be enabled by passing `--split-dwarf` to configure.py.
|
||||
Both options can be enable by passing `--split-dwarf` to configure.py.
|
||||
|
||||
Note that distcc is *not* compatible with it, but icecream
|
||||
(https://github.com/icecc/icecream) is.
|
||||
@@ -370,7 +389,7 @@ Note that distcc is *not* compatible with it, but icecream
|
||||
|
||||
Sometimes Scylla development is closely tied with a feature being developed in Seastar. It can be useful to compile Scylla with a particular check-out of Seastar.
|
||||
|
||||
One way to do this is to create a local remote for the Seastar submodule in the Scylla repository:
|
||||
One way to do this it to create a local remote for the Seastar submodule in the Scylla repository:
|
||||
|
||||
```bash
|
||||
$ cd $HOME/src/scylla
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
This project includes code developed by the Apache Software Foundation (http://www.apache.org/),
|
||||
especially Apache Cassandra.
|
||||
|
||||
It includes files from https://github.com/antonblanchard/crc32-vpmsum (author Anton Blanchard <anton@au.ibm.com>, IBM).
|
||||
These files are located in utils/arch/powerpc/crc32-vpmsum. Their license may be found in licenses/LICENSE-crc32-vpmsum.TXT.
|
||||
|
||||
It includes modified code from https://gitbox.apache.org/repos/asf?p=cassandra-dtest.git (owned by The Apache Software Foundation)
|
||||
|
||||
It includes modified tests from https://github.com/etcd-io/etcd.git (owned by The etcd Authors)
|
||||
|
||||
@@ -18,7 +18,7 @@ Scylla is fairly fussy about its build environment, requiring very recent
|
||||
versions of the C++23 compiler and of many libraries to build. The document
|
||||
[HACKING.md](HACKING.md) includes detailed information on building and
|
||||
developing Scylla, but to get Scylla building quickly on (almost) any build
|
||||
machine, Scylla offers a [frozen toolchain](tools/toolchain/README.md).
|
||||
machine, Scylla offers a [frozen toolchain](tools/toolchain/README.md),
|
||||
This is a pre-configured Docker image which includes recent versions of all
|
||||
the required compilers, libraries and build tools. Using the frozen toolchain
|
||||
allows you to avoid changing anything in your build machine to meet Scylla's
|
||||
|
||||
@@ -78,7 +78,7 @@ fi
|
||||
|
||||
# Default scylla product/version tags
|
||||
PRODUCT=scylla
|
||||
VERSION=2026.1.0-dev
|
||||
VERSION=2025.2.0-dev
|
||||
|
||||
if test -f version
|
||||
then
|
||||
|
||||
@@ -17,7 +17,6 @@ target_sources(alternator
|
||||
streams.cc
|
||||
consumed_capacity.cc
|
||||
ttl.cc
|
||||
parsed_expression_cache.cc
|
||||
${cql_grammar_srcs})
|
||||
target_include_directories(alternator
|
||||
PUBLIC
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "utils/log.hh"
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include "bytes.hh"
|
||||
#include "alternator/auth.hh"
|
||||
#include <fmt/format.h>
|
||||
#include "auth/password_authenticator.hh"
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -136,7 +136,6 @@ future<> controller::start_server() {
|
||||
[this, addr, alternator_port, alternator_https_port, creds = std::move(creds)] (server& server) mutable {
|
||||
return server.init(addr, alternator_port, alternator_https_port, creds,
|
||||
_config.alternator_enforce_authorization,
|
||||
_config.alternator_max_users_query_size_in_trace_output,
|
||||
&_memory_limiter.local().get_semaphore(),
|
||||
_config.max_concurrent_requests_per_shard);
|
||||
}).handle_exception([this, addr, alternator_port, alternator_https_port] (std::exception_ptr ep) {
|
||||
@@ -168,8 +167,4 @@ future<> controller::request_stop_server() {
|
||||
});
|
||||
}
|
||||
|
||||
future<utils::chunked_vector<client_data>> controller::get_client_data() {
|
||||
return _server.local().get_client_data();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
#include <seastar/core/sharded.hh>
|
||||
#include <seastar/core/smp.hh>
|
||||
|
||||
#include "transport/protocol_server.hh"
|
||||
#include "protocol_server.hh"
|
||||
|
||||
namespace service {
|
||||
class storage_proxy;
|
||||
@@ -90,10 +90,6 @@ public:
|
||||
virtual future<> start_server() override;
|
||||
virtual future<> stop_server() override;
|
||||
virtual future<> request_stop_server() override;
|
||||
// This virtual function is called (on each shard separately) when the
|
||||
// virtual table "system.clients" is read. It is expected to generate a
|
||||
// list of clients connected to this server (on this shard).
|
||||
virtual future<utils::chunked_vector<client_data>> get_client_data() override;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -94,9 +94,6 @@ public:
|
||||
static api_error internal(std::string msg) {
|
||||
return api_error("InternalServerError", std::move(msg), http::reply::status_type::internal_server_error);
|
||||
}
|
||||
static api_error payload_too_large(std::string msg) {
|
||||
return api_error("PayloadTooLarge", std::move(msg), status_type::payload_too_large);
|
||||
}
|
||||
|
||||
// Provide the "std::exception" interface, to make it easier to print this
|
||||
// exception in log messages. Note that this function is *not* used to
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -10,8 +10,8 @@
|
||||
|
||||
#include <seastar/core/future.hh>
|
||||
#include "seastarx.hh"
|
||||
#include <seastar/json/json_elements.hh>
|
||||
#include <seastar/core/sharded.hh>
|
||||
#include <seastar/util/noncopyable_function.hh>
|
||||
|
||||
#include "service/migration_manager.hh"
|
||||
#include "service/client_state.hh"
|
||||
@@ -58,6 +58,33 @@ namespace alternator {
|
||||
|
||||
class rmw_operation;
|
||||
|
||||
struct make_jsonable : public json::jsonable {
|
||||
rjson::value _value;
|
||||
public:
|
||||
explicit make_jsonable(rjson::value&& value);
|
||||
std::string to_json() const override;
|
||||
};
|
||||
|
||||
/**
|
||||
* Make return type for serializing the object "streamed",
|
||||
* i.e. direct to HTTP output stream. Note: only useful for
|
||||
* (very) large objects as there are overhead issues with this
|
||||
* as well, but for massive lists of return objects this can
|
||||
* help avoid large allocations/many re-allocs
|
||||
*/
|
||||
json::json_return_type make_streamed(rjson::value&&);
|
||||
|
||||
struct json_string : public json::jsonable {
|
||||
std::string _value;
|
||||
public:
|
||||
explicit json_string(std::string&& value);
|
||||
std::string to_json() const override;
|
||||
};
|
||||
|
||||
namespace parsed {
|
||||
class path;
|
||||
};
|
||||
|
||||
schema_ptr get_table(service::storage_proxy& proxy, const rjson::value& request);
|
||||
bool is_alternator_keyspace(const sstring& ks_name);
|
||||
// Wraps the db::get_tags_of_table and throws if the table is missing the tags extension.
|
||||
@@ -128,9 +155,6 @@ using attrs_to_get_node = attribute_path_map_node<std::monostate>;
|
||||
// optional means we should get all attributes, not specific ones.
|
||||
using attrs_to_get = attribute_path_map<std::monostate>;
|
||||
|
||||
namespace parsed {
|
||||
class expression_cache;
|
||||
}
|
||||
|
||||
class executor : public peering_sharded_service<executor> {
|
||||
gms::gossiper& _gossiper;
|
||||
@@ -143,27 +167,10 @@ class executor : public peering_sharded_service<executor> {
|
||||
// forwarding Alternator request between shards - if necessary for LWT.
|
||||
smp_service_group _ssg;
|
||||
|
||||
std::unique_ptr<parsed::expression_cache> _parsed_expression_cache;
|
||||
|
||||
public:
|
||||
using client_state = service::client_state;
|
||||
// request_return_type is the return type of the executor methods, which
|
||||
// can be one of:
|
||||
// 1. A string, which is the response body for the request.
|
||||
// 2. A body_writer, an asynchronous function (returning future<>) that
|
||||
// takes an output_stream and writes the response body into it.
|
||||
// 3. An api_error, which is an error response that should be returned to
|
||||
// the client.
|
||||
// The body_writer is used for streaming responses, where the response body
|
||||
// is written in chunks to the output_stream. This allows for efficient
|
||||
// handling of large responses without needing to allocate a large buffer
|
||||
// in memory.
|
||||
using body_writer = noncopyable_function<future<>(output_stream<char>&&)>;
|
||||
using request_return_type = std::variant<std::string, body_writer, api_error>;
|
||||
using request_return_type = std::variant<json::json_return_type, api_error>;
|
||||
stats _stats;
|
||||
// The metric_groups object holds this stat object's metrics registered
|
||||
// as long as the stats object is alive.
|
||||
seastar::metrics::metric_groups _metrics;
|
||||
static constexpr auto ATTRS_COLUMN_NAME = ":attrs";
|
||||
static constexpr auto KEYSPACE_NAME_PREFIX = "alternator_";
|
||||
static constexpr std::string_view INTERNAL_TABLE_PREFIX = ".scylla.alternator.";
|
||||
@@ -175,7 +182,6 @@ public:
|
||||
cdc::metadata& cdc_metadata,
|
||||
smp_service_group ssg,
|
||||
utils::updateable_value<uint32_t> default_timeout_in_ms);
|
||||
~executor();
|
||||
|
||||
future<request_return_type> create_table(client_state& client_state, tracing::trace_state_ptr trace_state, service_permit permit, rjson::value request);
|
||||
future<request_return_type> describe_table(client_state& client_state, tracing::trace_state_ptr trace_state, service_permit permit, rjson::value request);
|
||||
@@ -203,23 +209,26 @@ public:
|
||||
future<request_return_type> describe_continuous_backups(client_state& client_state, service_permit permit, rjson::value request);
|
||||
|
||||
future<> start();
|
||||
future<> stop();
|
||||
future<> stop() {
|
||||
// disconnect from the value source, but keep the value unchanged.
|
||||
s_default_timeout_in_ms = utils::updateable_value<uint32_t>{s_default_timeout_in_ms()};
|
||||
return make_ready_future<>();
|
||||
}
|
||||
|
||||
static sstring table_name(const schema&);
|
||||
static db::timeout_clock::time_point default_timeout();
|
||||
private:
|
||||
static thread_local utils::updateable_value<uint32_t> s_default_timeout_in_ms;
|
||||
public:
|
||||
static schema_ptr find_table(service::storage_proxy&, std::string_view table_name);
|
||||
static schema_ptr find_table(service::storage_proxy&, const rjson::value& request);
|
||||
|
||||
private:
|
||||
friend class rmw_operation;
|
||||
|
||||
static void describe_key_schema(rjson::value& parent, const schema&, std::unordered_map<std::string,std::string> * = nullptr, const std::map<sstring, sstring> *tags = nullptr);
|
||||
static void describe_key_schema(rjson::value& parent, const schema&, std::unordered_map<std::string,std::string> * = nullptr);
|
||||
|
||||
public:
|
||||
static void describe_key_schema(rjson::value& parent, const schema& schema, std::unordered_map<std::string,std::string>&, const std::map<sstring, sstring> *tags = nullptr);
|
||||
static void describe_key_schema(rjson::value& parent, const schema& schema, std::unordered_map<std::string,std::string>&);
|
||||
|
||||
static std::optional<rjson::value> describe_single_item(schema_ptr,
|
||||
const query::partition_slice&,
|
||||
@@ -228,15 +237,11 @@ public:
|
||||
const std::optional<attrs_to_get>&,
|
||||
uint64_t* = nullptr);
|
||||
|
||||
// Converts a multi-row selection result to JSON compatible with DynamoDB.
|
||||
// For each row, this method calls item_callback, which takes the size of
|
||||
// the item as the parameter.
|
||||
static future<std::vector<rjson::value>> describe_multi_item(schema_ptr schema,
|
||||
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,
|
||||
noncopyable_function<void(uint64_t)> item_callback = {});
|
||||
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>&,
|
||||
@@ -245,7 +250,7 @@ public:
|
||||
uint64_t* item_length_in_bytes = nullptr,
|
||||
bool = false);
|
||||
|
||||
static bool add_stream_options(const rjson::value& stream_spec, schema_builder&, service::storage_proxy& sp);
|
||||
static void add_stream_options(const rjson::value& stream_spec, schema_builder&, service::storage_proxy& sp);
|
||||
static void supplement_table_info(rjson::value& descr, const schema& schema, service::storage_proxy& sp);
|
||||
static void supplement_table_stream_info(rjson::value& descr, const schema& schema, const service::storage_proxy& sp);
|
||||
};
|
||||
@@ -266,13 +271,4 @@ bool is_big(const rjson::value& val, int big_size = 100'000);
|
||||
// appropriate user-readable api_error::access_denied is thrown.
|
||||
future<> verify_permission(bool enforce_authorization, const service::client_state&, const schema_ptr&, auth::permission);
|
||||
|
||||
/**
|
||||
* Make return type for serializing the object "streamed",
|
||||
* i.e. direct to HTTP output stream. Note: only useful for
|
||||
* (very) large objects as there are overhead issues with this
|
||||
* as well, but for massive lists of return objects this can
|
||||
* help avoid large allocations/many re-allocs
|
||||
*/
|
||||
executor::body_writer make_streamed(rjson::value&&);
|
||||
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
@@ -196,13 +184,7 @@ path_component: NAME | NAMEREF;
|
||||
path returns [parsed::path p]:
|
||||
root=path_component { $p.set_root($root.text); }
|
||||
( '.' name=path_component { $p.add_dot($name.text); }
|
||||
| '[' INTEGER ']' {
|
||||
try {
|
||||
$p.add_index(std::stoi($INTEGER.text));
|
||||
} catch(std::out_of_range&) {
|
||||
throw expressions_syntax_error("list index out of integer range");
|
||||
}
|
||||
}
|
||||
| '[' INTEGER ']' { $p.add_index(std::stoi($INTEGER.text)); }
|
||||
)*;
|
||||
|
||||
/* See comment above why the "depth" counter was needed here */
|
||||
@@ -248,7 +230,7 @@ update_expression_clause returns [parsed::update_expression e]:
|
||||
// Note the "EOF" token at the end of the update expression. We want to the
|
||||
// parser to match the entire string given to it - not just its beginning!
|
||||
update_expression returns [parsed::update_expression e]:
|
||||
(update_expression_clause { e.append($update_expression_clause.e); })+ EOF;
|
||||
(update_expression_clause { e.append($update_expression_clause.e); })* EOF;
|
||||
|
||||
projection_expression returns [std::vector<parsed::path> v]:
|
||||
p=path { $v.push_back(std::move($p.p)); }
|
||||
@@ -275,13 +257,6 @@ primitive_condition returns [parsed::primitive_condition c]:
|
||||
(',' v=value[0] { $c.add_value(std::move($v.v)); })*
|
||||
')'
|
||||
)?
|
||||
{
|
||||
// Post-parse check to reject non-function single values
|
||||
if ($c._op == parsed::primitive_condition::type::VALUE &&
|
||||
!$c._values.front().is_func()) {
|
||||
throw expressions_syntax_error("Single value must be a function");
|
||||
}
|
||||
}
|
||||
;
|
||||
|
||||
// The following rules for parsing boolean expressions are verbose and
|
||||
|
||||
@@ -18,8 +18,6 @@
|
||||
|
||||
#include "expressions_types.hh"
|
||||
#include "utils/rjson.hh"
|
||||
#include "utils/updateable_value.hh"
|
||||
#include "stats.hh"
|
||||
|
||||
namespace alternator {
|
||||
|
||||
@@ -28,26 +26,6 @@ public:
|
||||
using runtime_error::runtime_error;
|
||||
};
|
||||
|
||||
namespace parsed {
|
||||
class expression_cache_impl;
|
||||
class expression_cache {
|
||||
std::unique_ptr<expression_cache_impl> _impl;
|
||||
public:
|
||||
struct config {
|
||||
utils::updateable_value<uint32_t> max_cache_entries;
|
||||
};
|
||||
expression_cache(config cfg, stats& stats);
|
||||
~expression_cache();
|
||||
// stop background tasks, if any
|
||||
future<> stop();
|
||||
|
||||
update_expression parse_update_expression(std::string_view query);
|
||||
std::vector<path> parse_projection_expression(std::string_view query);
|
||||
condition_expression parse_condition_expression(std::string_view query, const char* caller);
|
||||
};
|
||||
} // namespace parsed
|
||||
|
||||
// Preferably use parsed::expression_cache instance instead of this free functions.
|
||||
parsed::update_expression parse_update_expression(std::string_view query);
|
||||
std::vector<parsed::path> parse_projection_expression(std::string_view query);
|
||||
parsed::condition_expression parse_condition_expression(std::string_view query, const char* caller);
|
||||
@@ -113,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 */
|
||||
|
||||
@@ -209,7 +209,9 @@ public:
|
||||
// function is supported).
|
||||
// 2. Ternary operator - v1 BETWEEN v2 and v3 (means v1 >= v2 AND v1 <= v3).
|
||||
// 3. N-ary operator - v1 IN ( v2, v3, ... )
|
||||
// 4. A single function call (attribute_exists etc.).
|
||||
// 4. A single function call (attribute_exists etc.). The parser actually
|
||||
// accepts a more general "value" here but later stages reject a value
|
||||
// which is not a function call (because DynamoDB does it too).
|
||||
class primitive_condition {
|
||||
public:
|
||||
enum class type {
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
#include "utils/rjson.hh"
|
||||
#include "serialization.hh"
|
||||
#include "schema/column_computation.hh"
|
||||
#include "column_computation.hh"
|
||||
#include "db/view/regular_column_transformation.hh"
|
||||
|
||||
namespace alternator {
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
/*
|
||||
* Copyright 2025-present ScyllaDB
|
||||
*/
|
||||
|
||||
/*
|
||||
* SPDX-License-Identifier: LicenseRef-ScyllaDB-Source-Available-1.0
|
||||
*/
|
||||
|
||||
#include "expressions.hh"
|
||||
#include "utils/log.hh"
|
||||
|
||||
#include "utils/lru_string_map.hh"
|
||||
#include <variant>
|
||||
|
||||
static logging::logger logger_("parsed-expression-cache");
|
||||
|
||||
namespace alternator::parsed {
|
||||
|
||||
struct expression_cache_impl {
|
||||
stats& _stats;
|
||||
|
||||
using cached_expressions_types = std::variant<
|
||||
update_expression,
|
||||
condition_expression,
|
||||
std::vector<path>
|
||||
>;
|
||||
sized_lru_string_map<cached_expressions_types> _cached_entries;
|
||||
utils::observable<uint32_t>::observer _max_cache_entries_observer;
|
||||
|
||||
expression_cache_impl(expression_cache::config cfg, stats& stats);
|
||||
|
||||
// to define the specialized return type of `get_or_create()`
|
||||
template <typename Func, typename... Args>
|
||||
using ParseResult = std::invoke_result_t<Func, std::string_view, Args...>;
|
||||
|
||||
// Caching layer for parsed expressions
|
||||
// The expression type is determined by the type of the parsing function passed as a parameter,
|
||||
// and the return type is exactly the same as the return type of this parsing function.
|
||||
// StatsType is used only to update appropriate statistics - currently it is aligned with the expression type,
|
||||
// but it could be extended in the future if needed, e.g. split per operation.
|
||||
template <stats::expression_types StatsType, typename Func, typename... Args>
|
||||
ParseResult<Func, Args...> get_or_create(std::string_view query, Func&& parse_func, Args&&... other_args) {
|
||||
if (_cached_entries.disabled()) {
|
||||
return parse_func(query, std::forward<Args>(other_args)...);
|
||||
}
|
||||
if (!_cached_entries.sanity_check()) {
|
||||
_stats.expression_cache.requests[StatsType].misses++;
|
||||
return parse_func(query, std::forward<Args>(other_args)...);
|
||||
}
|
||||
auto value = _cached_entries.find(query);
|
||||
if (value) {
|
||||
logger_.trace("Cache hit for query: {}", query);
|
||||
_stats.expression_cache.requests[StatsType].hits++;
|
||||
try {
|
||||
return std::get<ParseResult<Func, Args...>>(value->get());
|
||||
} catch (const std::bad_variant_access&) {
|
||||
// User can reach this code, by sending the same query string as a different expression type.
|
||||
// In practice valid queries are different enough to not collide.
|
||||
// Entries in cache are only valid queries.
|
||||
// This request will fail at parsing below.
|
||||
// If, by any chance this is a valid query, it will be updated below with the new value.
|
||||
logger_.trace("Cache hit for '{}', but type mismatch.", query);
|
||||
_stats.expression_cache.requests[StatsType].hits--;
|
||||
}
|
||||
} else {
|
||||
logger_.trace("Cache miss for query: {}", query);
|
||||
}
|
||||
ParseResult<Func, Args...> expr = parse_func(query, std::forward<Args>(other_args)...);
|
||||
// Invalid query will throw here ^
|
||||
|
||||
_stats.expression_cache.requests[StatsType].misses++;
|
||||
if (value) [[unlikely]] {
|
||||
value->get() = cached_expressions_types{expr};
|
||||
} else {
|
||||
_cached_entries.insert(query, cached_expressions_types{expr});
|
||||
}
|
||||
return expr;
|
||||
}
|
||||
};
|
||||
|
||||
expression_cache_impl::expression_cache_impl(expression_cache::config cfg, stats& stats) :
|
||||
_stats(stats), _cached_entries(logger_, _stats.expression_cache.evictions),
|
||||
_max_cache_entries_observer(cfg.max_cache_entries.observe([this] (uint32_t max_value) {
|
||||
_cached_entries.set_max_size(max_value);
|
||||
})) {
|
||||
_cached_entries.set_max_size(cfg.max_cache_entries());
|
||||
}
|
||||
|
||||
expression_cache::expression_cache(expression_cache::config cfg, stats& stats) :
|
||||
_impl(std::make_unique<expression_cache_impl>(std::move(cfg), stats)) {
|
||||
}
|
||||
expression_cache::~expression_cache() = default;
|
||||
future<> expression_cache::stop() {
|
||||
return _impl->_cached_entries.stop();
|
||||
}
|
||||
|
||||
update_expression expression_cache::parse_update_expression(std::string_view query) {
|
||||
return _impl->get_or_create<stats::expression_types::UPDATE_EXPRESSION>(query, alternator::parse_update_expression);
|
||||
}
|
||||
|
||||
std::vector<path> expression_cache::parse_projection_expression(std::string_view query) {
|
||||
return _impl->get_or_create<stats::expression_types::PROJECTION_EXPRESSION>(query, alternator::parse_projection_expression);
|
||||
}
|
||||
|
||||
condition_expression expression_cache::parse_condition_expression(std::string_view query, const char* caller) {
|
||||
return _impl->get_or_create<stats::expression_types::CONDITION_EXPRESSION>(query, alternator::parse_condition_expression, caller);
|
||||
}
|
||||
|
||||
} // namespace alternator::parsed
|
||||
@@ -8,16 +8,13 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "cdc/cdc_options.hh"
|
||||
#include "cdc/log.hh"
|
||||
#include "seastarx.hh"
|
||||
#include "service/paxos/cas_request.hh"
|
||||
#include "service/cas_shard.hh"
|
||||
#include "utils/rjson.hh"
|
||||
#include "consumed_capacity.hh"
|
||||
#include "executor.hh"
|
||||
#include "tracing/trace_state.hh"
|
||||
#include "keys/keys.hh"
|
||||
#include "keys.hh"
|
||||
|
||||
namespace alternator {
|
||||
|
||||
@@ -58,7 +55,7 @@ public:
|
||||
static write_isolation get_write_isolation_for_schema(schema_ptr schema);
|
||||
|
||||
static write_isolation default_write_isolation;
|
||||
|
||||
public:
|
||||
static void set_default_write_isolation(std::string_view mode);
|
||||
|
||||
protected:
|
||||
@@ -109,27 +106,21 @@ public:
|
||||
// violating this). We mark apply() "const" to let the compiler validate
|
||||
// this for us. The output-only field _return_attributes is marked
|
||||
// "mutable" above so that apply() can still write to it.
|
||||
virtual std::optional<mutation> apply(std::unique_ptr<rjson::value> previous_item, api::timestamp_type ts, cdc::per_request_options& cdc_opts) const = 0;
|
||||
virtual std::optional<mutation> apply(std::unique_ptr<rjson::value> previous_item, api::timestamp_type ts) const = 0;
|
||||
// Convert the above apply() into the signature needed by cas_request:
|
||||
virtual std::optional<mutation> apply(foreign_ptr<lw_shared_ptr<query::result>> qr, const query::partition_slice& slice, api::timestamp_type ts, cdc::per_request_options& cdc_opts) override;
|
||||
virtual std::optional<mutation> apply(foreign_ptr<lw_shared_ptr<query::result>> qr, const query::partition_slice& slice, api::timestamp_type ts) override;
|
||||
virtual ~rmw_operation() = default;
|
||||
const wcu_consumed_capacity_counter& consumed_capacity() const noexcept { return _consumed_capacity; }
|
||||
schema_ptr schema() const { return _schema; }
|
||||
const rjson::value& request() const { return _request; }
|
||||
rjson::value&& move_request() && { return std::move(_request); }
|
||||
future<executor::request_return_type> execute(service::storage_proxy& proxy,
|
||||
std::optional<service::cas_shard> cas_shard,
|
||||
service::client_state& client_state,
|
||||
tracing::trace_state_ptr trace_state,
|
||||
service_permit permit,
|
||||
bool needs_read_before_write,
|
||||
stats& global_stats,
|
||||
stats& per_table_stats,
|
||||
stats& stats,
|
||||
uint64_t& wcu_total);
|
||||
std::optional<service::cas_shard> shard_for_execute(bool needs_read_before_write);
|
||||
|
||||
private:
|
||||
inline bool should_fill_preimage() const { return _schema->cdc_options().enabled(); }
|
||||
std::optional<shard_id> shard_for_execute(bool needs_read_before_write);
|
||||
};
|
||||
|
||||
} // namespace alternator
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
#include "utils/log.hh"
|
||||
#include "serialization.hh"
|
||||
#include "error.hh"
|
||||
#include "types/concrete_types.hh"
|
||||
#include "types/json_utils.hh"
|
||||
#include "concrete_types.hh"
|
||||
#include "cql3/type_json.hh"
|
||||
#include "mutation/position_in_partition.hh"
|
||||
|
||||
static logging::logger slogger("alternator-serialization");
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
#include <optional>
|
||||
#include "types/types.hh"
|
||||
#include "schema/schema_fwd.hh"
|
||||
#include "keys/keys.hh"
|
||||
#include "keys.hh"
|
||||
#include "utils/rjson.hh"
|
||||
#include "utils/big_decimal.hh"
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <seastar/http/function_handlers.hh>
|
||||
#include <seastar/http/short_streams.hh>
|
||||
#include <seastar/core/coroutine.hh>
|
||||
#include <seastar/json/json_elements.hh>
|
||||
#include <seastar/util/defer.hh>
|
||||
#include <seastar/util/short_streams.hh>
|
||||
#include "seastarx.hh"
|
||||
@@ -30,7 +31,6 @@
|
||||
#include "gms/gossiper.hh"
|
||||
#include "utils/overloaded_functor.hh"
|
||||
#include "utils/aws_sigv4.hh"
|
||||
#include "client_data.hh"
|
||||
|
||||
static logging::logger slogger("alternator-server");
|
||||
|
||||
@@ -100,13 +100,6 @@ static void handle_CORS(const request& req, reply& rep, bool preflight) {
|
||||
// the user directly. Other exceptions are unexpected, and reported as
|
||||
// Internal Server Error.
|
||||
class api_handler : public handler_base {
|
||||
// Although the the DynamoDB API responses are JSON, additional
|
||||
// conventions apply to these responses. For this reason, DynamoDB uses
|
||||
// the content type "application/x-amz-json-1.0" instead of the standard
|
||||
// "application/json". Some other AWS services use later versions instead
|
||||
// of "1.0", but DynamoDB currently uses "1.0". Note that this content
|
||||
// type applies to all replies, both success and error.
|
||||
static constexpr const char* REPLY_CONTENT_TYPE = "application/x-amz-json-1.0";
|
||||
public:
|
||||
api_handler(const std::function<future<executor::request_return_type>(std::unique_ptr<request> req)>& _handle) : _f_handle(
|
||||
[this, _handle](std::unique_ptr<request> req, std::unique_ptr<reply> rep) {
|
||||
@@ -131,19 +124,22 @@ public:
|
||||
}
|
||||
auto res = resf.get();
|
||||
std::visit(overloaded_functor {
|
||||
[&] (std::string&& str) {
|
||||
// Note that despite the move, there is a copy here -
|
||||
// as str is std::string and rep->_content is sstring.
|
||||
rep->_content = std::move(str);
|
||||
rep->set_content_type(REPLY_CONTENT_TYPE);
|
||||
},
|
||||
[&] (executor::body_writer&& body_writer) {
|
||||
rep->write_body(REPLY_CONTENT_TYPE, std::move(body_writer));
|
||||
},
|
||||
[&] (const api_error& err) {
|
||||
generate_error_reply(*rep, err);
|
||||
}
|
||||
}, std::move(res));
|
||||
[&] (const json::json_return_type& json_return_value) {
|
||||
slogger.trace("api_handler success case");
|
||||
if (json_return_value._body_writer) {
|
||||
// Unfortunately, write_body() forces us to choose
|
||||
// from a fixed and irrelevant list of "mime-types"
|
||||
// at this point. But we'll override it with the
|
||||
// one (application/x-amz-json-1.0) below.
|
||||
rep->write_body("json", std::move(json_return_value._body_writer));
|
||||
} else {
|
||||
rep->_content += json_return_value._res;
|
||||
}
|
||||
},
|
||||
[&] (const api_error& err) {
|
||||
generate_error_reply(*rep, err);
|
||||
}
|
||||
}, res);
|
||||
|
||||
return make_ready_future<std::unique_ptr<reply>>(std::move(rep));
|
||||
});
|
||||
@@ -155,6 +151,7 @@ public:
|
||||
handle_CORS(*req, *rep, false);
|
||||
return _f_handle(std::move(req), std::move(rep)).then(
|
||||
[](std::unique_ptr<reply> rep) {
|
||||
rep->set_mime_type("application/x-amz-json-1.0");
|
||||
rep->done();
|
||||
return make_ready_future<std::unique_ptr<reply>>(std::move(rep));
|
||||
});
|
||||
@@ -170,7 +167,6 @@ protected:
|
||||
rjson::add(results, "message", err._msg);
|
||||
rep._content = rjson::print(std::move(results));
|
||||
rep._status = err._http_code;
|
||||
rep.set_content_type(REPLY_CONTENT_TYPE);
|
||||
slogger.trace("api_handler error case: {}", rep._content);
|
||||
}
|
||||
|
||||
@@ -232,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;
|
||||
}
|
||||
@@ -241,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);
|
||||
@@ -378,82 +375,35 @@ static tracing::trace_state_ptr create_tracing_session(tracing::tracing& tracing
|
||||
return tracing_instance.create_session(tracing::trace_type::QUERY, props);
|
||||
}
|
||||
|
||||
// A helper class to represent a potentially truncated view of a chunked_content.
|
||||
// If the content is short enough and single chunked, it just holds a view into the content.
|
||||
// Otherwise it will be copied into an internal buffer, possibly truncated (depending on maximum allowed size passed in),
|
||||
// and the view will point into that buffer.
|
||||
// `as_view()` method will return the view.
|
||||
// `take_as_sstring()` will either move out the internal buffer (if any), or create a new sstring from the view.
|
||||
// You should consider `as_view()` valid as long both the original chunked_content and the truncated_content object are alive.
|
||||
class truncated_content {
|
||||
std::string_view _view;
|
||||
sstring _content_maybe;
|
||||
|
||||
void copy_from_content(const chunked_content& content) {
|
||||
size_t offset = 0;
|
||||
for(auto &tmp : content) {
|
||||
size_t to_copy = std::min(tmp.size(), _content_maybe.size() - offset);
|
||||
std::copy(tmp.get(), tmp.get() + to_copy, _content_maybe.data() + offset);
|
||||
offset += to_copy;
|
||||
if (offset >= _content_maybe.size()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// truncated_content_view() prints a potentially long chunked_content for
|
||||
// debugging purposes. In the common case when the content is not excessively
|
||||
// long, it just returns a view into the given content, without any copying.
|
||||
// But when the content is very long, it is truncated after some arbitrary
|
||||
// max_len (or one chunk, whichever comes first), with "<truncated>" added at
|
||||
// the end. To do this modification to the string, we need to create a new
|
||||
// std::string, so the caller must pass us a reference to one, "buf", where
|
||||
// we can store the content. The returned view is only alive for as long this
|
||||
// buf is kept alive.
|
||||
static std::string_view truncated_content_view(const chunked_content& content, std::string& buf) {
|
||||
constexpr size_t max_len = 1024;
|
||||
if (content.empty()) {
|
||||
return std::string_view();
|
||||
} else if (content.size() == 1 && content.begin()->size() <= max_len) {
|
||||
return std::string_view(content.begin()->get(), content.begin()->size());
|
||||
} else {
|
||||
buf = std::string(content.begin()->get(), std::min(content.begin()->size(), max_len)) + "<truncated>";
|
||||
return std::string_view(buf);
|
||||
}
|
||||
public:
|
||||
truncated_content(const chunked_content& content, size_t max_len = std::numeric_limits<size_t>::max()) {
|
||||
if (content.empty()) return;
|
||||
if (content.size() == 1 && content.begin()->size() <= max_len) {
|
||||
_view = std::string_view(content.begin()->get(), content.begin()->size());
|
||||
return;
|
||||
}
|
||||
|
||||
constexpr std::string_view truncated_text = "<truncated>";
|
||||
size_t content_size = 0;
|
||||
for(auto &tmp : content) {
|
||||
content_size += tmp.size();
|
||||
}
|
||||
if (content_size <= max_len) {
|
||||
_content_maybe = sstring{ sstring::initialized_later{}, content_size };
|
||||
copy_from_content(content);
|
||||
}
|
||||
else {
|
||||
_content_maybe = sstring{ sstring::initialized_later{}, max_len + truncated_text.size() };
|
||||
copy_from_content(content);
|
||||
std::copy(truncated_text.begin(), truncated_text.end(), _content_maybe.data() + _content_maybe.size() - truncated_text.size());
|
||||
}
|
||||
_view = std::string_view(_content_maybe);
|
||||
}
|
||||
|
||||
std::string_view as_view() const { return _view; }
|
||||
sstring take_as_sstring() && {
|
||||
if (_content_maybe.empty() && !_view.empty()) {
|
||||
return sstring{_view};
|
||||
}
|
||||
return std::move(_content_maybe);
|
||||
}
|
||||
};
|
||||
|
||||
// `truncated_content_view` will produce an object representing a view to a passed content
|
||||
// possibly truncated at some length. The value returned is used in two ways:
|
||||
// - to print it in logs (use `as_view()` method for this)
|
||||
// - to pass it to tracing object, where it will be stored and used later
|
||||
// (use `take_as_sstring()` method as this produces a copy in form of a sstring)
|
||||
// `truncated_content` delays constructing `sstring` object until it's actually needed.
|
||||
// `truncated_content` is valid as long as passed `content` is alive.
|
||||
// if the content is truncated, `<truncated>` will be appended at the maximum size limit
|
||||
// and total size will be `max_users_query_size_in_trace_output() + strlen("<truncated>")`.
|
||||
static truncated_content truncated_content_view(const chunked_content& content, size_t max_size) {
|
||||
return truncated_content{content, max_size};
|
||||
}
|
||||
|
||||
static tracing::trace_state_ptr maybe_trace_query(service::client_state& client_state, std::string_view username, std::string_view op, const chunked_content& query, size_t max_users_query_size_in_trace_output) {
|
||||
static tracing::trace_state_ptr maybe_trace_query(service::client_state& client_state, std::string_view username, std::string_view op, const chunked_content& query) {
|
||||
tracing::trace_state_ptr trace_state;
|
||||
tracing::tracing& tracing_instance = tracing::tracing::get_local_tracing_instance();
|
||||
if (tracing_instance.trace_next_query() || tracing_instance.slow_query_tracing_enabled()) {
|
||||
trace_state = create_tracing_session(tracing_instance);
|
||||
std::string buf;
|
||||
tracing::add_session_param(trace_state, "alternator_op", op);
|
||||
tracing::add_query(trace_state, truncated_content_view(query, max_users_query_size_in_trace_output).take_as_sstring());
|
||||
tracing::add_query(trace_state, truncated_content_view(query, buf));
|
||||
tracing::begin(trace_state, seastar::format("Alternator {}", op), client_state.get_client_address());
|
||||
if (!username.empty()) {
|
||||
tracing::set_username(trace_state, auth::authenticated_user(username));
|
||||
@@ -462,92 +412,30 @@ static tracing::trace_state_ptr maybe_trace_query(service::client_state& client_
|
||||
return trace_state;
|
||||
}
|
||||
|
||||
// This read_entire_stream() is similar to Seastar's read_entire_stream()
|
||||
// which reads the given content_stream until its end into non-contiguous
|
||||
// memory. The difference is that this implementation takes an extra length
|
||||
// limit, and throws an error if we read more than this limit.
|
||||
// This length-limited variant would not have been needed if Seastar's HTTP
|
||||
// server's set_content_length_limit() worked in every case, but unfortunately
|
||||
// it does not - it only works if the request has a Content-Length header (see
|
||||
// issue #8196). In contrast this function can limit the request's length no
|
||||
// matter how it's encoded. We need this limit to protect Alternator from
|
||||
// oversized requests that can deplete memory.
|
||||
static future<chunked_content>
|
||||
read_entire_stream(input_stream<char>& inp, size_t length_limit) {
|
||||
chunked_content ret;
|
||||
// We try to read length_limit + 1 bytes, so that we can throw an
|
||||
// exception if we managed to read more than length_limit.
|
||||
ssize_t remain = length_limit + 1;
|
||||
do {
|
||||
temporary_buffer<char> buf = co_await inp.read_up_to(remain);
|
||||
if (buf.empty()) {
|
||||
break;
|
||||
}
|
||||
remain -= buf.size();
|
||||
ret.push_back(std::move(buf));
|
||||
} while (remain > 0);
|
||||
// If we read the full length_limit + 1 bytes, we went over the limit:
|
||||
if (remain <= 0) {
|
||||
// By throwing here an error, we may send a reply (the error message)
|
||||
// without having read the full request body. Seastar's httpd will
|
||||
// realize that we have not read the entire content stream, and
|
||||
// correctly mark the connection unreusable, i.e., close it.
|
||||
// This means we are currently exposed to issue #12166 caused by
|
||||
// Seastar issue 1325), where the client may get an RST instead of
|
||||
// a FIN, and may rarely get a "Connection reset by peer" before
|
||||
// reading the error we send.
|
||||
throw api_error::payload_too_large(fmt::format("Request content length limit of {} bytes exceeded", length_limit));
|
||||
}
|
||||
co_return ret;
|
||||
}
|
||||
|
||||
future<executor::request_return_type> server::handle_api_request(std::unique_ptr<request> req) {
|
||||
_executor._stats.total_operations++;
|
||||
sstring target = req->get_header("X-Amz-Target");
|
||||
// target is DynamoDB API version followed by a dot '.' and operation type (e.g. CreateTable)
|
||||
auto dot = target.find('.');
|
||||
std::string_view op = (dot == sstring::npos) ? std::string_view() : std::string_view(target).substr(dot+1);
|
||||
if (req->content_length > request_content_length_limit) {
|
||||
// If we have a Content-Length header and know the request will be too
|
||||
// long, we don't need to wait for read_entire_stream() below to
|
||||
// discover it. And we definitely mustn't try to get_units() below for
|
||||
// for such a size.
|
||||
co_return api_error::payload_too_large(fmt::format("Request content length limit of {} bytes exceeded", request_content_length_limit));
|
||||
}
|
||||
// JSON parsing can allocate up to roughly 2x the size of the raw
|
||||
// document, + a couple of bytes for maintenance.
|
||||
// If the Content-Length of the request is not available, we assume
|
||||
// the largest possible request (request_content_length_limit, i.e., 16 MB)
|
||||
// and after reading the request we return_units() the excess.
|
||||
size_t mem_estimate = (req->content_length ? req->content_length : request_content_length_limit) * 2 + 8000;
|
||||
// TODO: consider the case where req->content_length is missing. Maybe
|
||||
// we need to take the content_length_limit and return some of the units
|
||||
// when we finish read_content_and_verify_signature?
|
||||
size_t mem_estimate = req->content_length * 2 + 8000;
|
||||
auto units_fut = get_units(*_memory_limiter, mem_estimate);
|
||||
if (_memory_limiter->waiters()) {
|
||||
++_executor._stats.requests_blocked_memory;
|
||||
}
|
||||
auto units = co_await std::move(units_fut);
|
||||
SCYLLA_ASSERT(req->content_stream);
|
||||
chunked_content content = co_await read_entire_stream(*req->content_stream, request_content_length_limit);
|
||||
// If the request had no Content-Length, we reserved too many units
|
||||
// so need to return some
|
||||
if (req->content_length == 0) {
|
||||
size_t content_length = 0;
|
||||
for (const auto& chunk : content) {
|
||||
content_length += chunk.size();
|
||||
}
|
||||
size_t new_mem_estimate = content_length * 2 + 8000;
|
||||
units.return_units(mem_estimate - new_mem_estimate);
|
||||
}
|
||||
chunked_content content = co_await util::read_entire_stream(*req->content_stream);
|
||||
auto username = co_await verify_signature(*req, content);
|
||||
// As long as the system_clients_entry object is alive, this request will
|
||||
// be visible in the "system.clients" virtual table. When requested, this
|
||||
// entry will be formatted by server::ongoing_request::make_client_data().
|
||||
auto system_clients_entry = _ongoing_requests.emplace(
|
||||
req->get_client_address(), req->get_header("User-Agent"),
|
||||
username, current_scheduling_group(),
|
||||
req->get_protocol_name() == "https");
|
||||
|
||||
if (slogger.is_enabled(log_level::trace)) {
|
||||
slogger.trace("Request: {} {} {}", op, truncated_content_view(content, _max_users_query_size_in_trace_output).as_view(), req->_headers);
|
||||
std::string buf;
|
||||
slogger.trace("Request: {} {} {}", op, truncated_content_view(content, buf), req->_headers);
|
||||
}
|
||||
auto callback_it = _callbacks.find(op);
|
||||
if (callback_it == _callbacks.end()) {
|
||||
@@ -567,7 +455,7 @@ future<executor::request_return_type> server::handle_api_request(std::unique_ptr
|
||||
}
|
||||
co_await client_state.maybe_update_per_service_level_params();
|
||||
|
||||
tracing::trace_state_ptr trace_state = maybe_trace_query(client_state, username, op, content, _max_users_query_size_in_trace_output.get());
|
||||
tracing::trace_state_ptr trace_state = maybe_trace_query(client_state, username, op, content);
|
||||
tracing::trace(trace_state, "{}", op);
|
||||
|
||||
auto user = client_state.user();
|
||||
@@ -575,9 +463,6 @@ future<executor::request_return_type> server::handle_api_request(std::unique_ptr
|
||||
client_state = std::move(client_state), trace_state = std::move(trace_state),
|
||||
units = std::move(units), req = std::move(req)] () mutable -> future<executor::request_return_type> {
|
||||
rjson::value json_request = co_await _json_parser.parse(std::move(content));
|
||||
if (!json_request.IsObject()) {
|
||||
co_return api_error::validation("Request content must be an object");
|
||||
}
|
||||
co_return co_await callback(_executor, client_state, trace_state,
|
||||
make_service_permit(std::move(units)), std::move(json_request), std::move(req));
|
||||
};
|
||||
@@ -619,9 +504,8 @@ server::server(executor& exec, service::storage_proxy& proxy, gms::gossiper& gos
|
||||
, _sl_controller(sl_controller)
|
||||
, _key_cache(1024, 1min, slogger)
|
||||
, _enforce_authorization(false)
|
||||
, _max_users_query_size_in_trace_output(1024)
|
||||
, _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) {
|
||||
@@ -700,12 +584,10 @@ server::server(executor& exec, service::storage_proxy& proxy, gms::gossiper& gos
|
||||
}
|
||||
|
||||
future<> server::init(net::inet_address addr, std::optional<uint16_t> port, std::optional<uint16_t> https_port, std::optional<tls::credentials_builder> creds,
|
||||
utils::updateable_value<bool> enforce_authorization, utils::updateable_value<uint64_t> max_users_query_size_in_trace_output,
|
||||
semaphore* memory_limiter, utils::updateable_value<uint32_t> max_concurrent_requests) {
|
||||
utils::updateable_value<bool> enforce_authorization, semaphore* memory_limiter, utils::updateable_value<uint32_t> max_concurrent_requests) {
|
||||
_memory_limiter = memory_limiter;
|
||||
_enforce_authorization = std::move(enforce_authorization);
|
||||
_max_concurrent_requests = std::move(max_concurrent_requests);
|
||||
_max_users_query_size_in_trace_output = std::move(max_users_query_size_in_trace_output);
|
||||
if (!port && !https_port) {
|
||||
return make_exception_future<>(std::runtime_error("Either regular port or TLS port"
|
||||
" must be specified in order to init an alternator HTTP server instance"));
|
||||
@@ -715,12 +597,14 @@ future<> server::init(net::inet_address addr, std::optional<uint16_t> port, std:
|
||||
|
||||
if (port) {
|
||||
set_routes(_http_server._routes);
|
||||
_http_server.set_content_length_limit(server::content_length_limit);
|
||||
_http_server.set_content_streaming(true);
|
||||
_http_server.listen(socket_address{addr, *port}).get();
|
||||
_enabled_servers.push_back(std::ref(_http_server));
|
||||
}
|
||||
if (https_port) {
|
||||
set_routes(_https_server._routes);
|
||||
_https_server.set_content_length_limit(server::content_length_limit);
|
||||
_https_server.set_content_streaming(true);
|
||||
|
||||
if (this_shard_id() == 0) {
|
||||
@@ -795,37 +679,6 @@ future<> server::json_parser::stop() {
|
||||
return std::move(_run_parse_json_thread);
|
||||
}
|
||||
|
||||
// Convert an entry in the server's list of ongoing Alternator requests
|
||||
// (_ongoing_requests) into a client_data object. This client_data object
|
||||
// will then be used to produce a row for the "system.clients" virtual table.
|
||||
client_data server::ongoing_request::make_client_data() const {
|
||||
client_data cd;
|
||||
cd.ct = client_type::alternator;
|
||||
cd.ip = _client_address.addr();
|
||||
cd.port = _client_address.port();
|
||||
cd.shard_id = this_shard_id();
|
||||
cd.connection_stage = client_connection_stage::established;
|
||||
cd.username = _username;
|
||||
cd.scheduling_group_name = _scheduling_group.name();
|
||||
cd.ssl_enabled = _is_https;
|
||||
// For now, we save the full User-Agent header as the "driver name"
|
||||
// and keep "driver_version" unset.
|
||||
cd.driver_name = _user_agent;
|
||||
// Leave "protocol_version" unset, it has no meaning in Alternator.
|
||||
// Leave "hostname", "ssl_protocol" and "ssl_cipher_suite" unset.
|
||||
// As reported in issue #9216, we never set these fields in CQL
|
||||
// either (see cql_server::connection::make_client_data()).
|
||||
return cd;
|
||||
}
|
||||
|
||||
future<utils::chunked_vector<client_data>> server::get_client_data() {
|
||||
utils::chunked_vector<client_data> ret;
|
||||
co_await _ongoing_requests.for_each_gently([&ret] (const ongoing_request& r) {
|
||||
ret.emplace_back(r.make_client_data());
|
||||
});
|
||||
co_return ret;
|
||||
}
|
||||
|
||||
const char* api_error::what() const noexcept {
|
||||
if (_what_string.empty()) {
|
||||
_what_string = fmt::format("{} {}: {}", std::to_underlying(_http_code), _type, _msg);
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "alternator/executor.hh"
|
||||
#include "utils/scoped_item_list.hh"
|
||||
#include <seastar/core/future.hh>
|
||||
#include <seastar/core/condition-variable.hh>
|
||||
#include <seastar/http/httpd.hh>
|
||||
@@ -21,18 +20,12 @@
|
||||
#include "utils/updateable_value.hh"
|
||||
#include <seastar/core/units.hh>
|
||||
|
||||
struct client_data;
|
||||
|
||||
namespace alternator {
|
||||
|
||||
using chunked_content = rjson::chunked_content;
|
||||
|
||||
class server : public peering_sharded_service<server> {
|
||||
// The maximum size of a request body that Alternator will accept,
|
||||
// in bytes. This is a safety measure to prevent Alternator from
|
||||
// running out of memory when a client sends a very large request.
|
||||
// DynamoDB also has the same limit set to 16 MB.
|
||||
static constexpr size_t request_content_length_limit = 16*MB;
|
||||
static constexpr size_t content_length_limit = 16*MB;
|
||||
using alternator_callback = std::function<future<executor::request_return_type>(executor&, executor::client_state&,
|
||||
tracing::trace_state_ptr, service_permit, rjson::value, std::unique_ptr<http::request>)>;
|
||||
using alternator_callbacks_map = std::unordered_map<std::string_view, alternator_callback>;
|
||||
@@ -47,9 +40,8 @@ class server : public peering_sharded_service<server> {
|
||||
|
||||
key_cache _key_cache;
|
||||
utils::updateable_value<bool> _enforce_authorization;
|
||||
utils::updateable_value<uint64_t> _max_users_query_size_in_trace_output;
|
||||
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.
|
||||
@@ -82,31 +74,12 @@ class server : public peering_sharded_service<server> {
|
||||
};
|
||||
json_parser _json_parser;
|
||||
|
||||
// The server maintains a list of ongoing requests, that are being handled
|
||||
// by handle_api_request(). It uses this list in get_client_data(), which
|
||||
// is called when reading the "system.clients" virtual table.
|
||||
struct ongoing_request {
|
||||
socket_address _client_address;
|
||||
sstring _user_agent;
|
||||
sstring _username;
|
||||
scheduling_group _scheduling_group;
|
||||
bool _is_https;
|
||||
client_data make_client_data() const;
|
||||
};
|
||||
utils::scoped_item_list<ongoing_request> _ongoing_requests;
|
||||
|
||||
public:
|
||||
server(executor& executor, service::storage_proxy& proxy, gms::gossiper& gossiper, auth::service& service, qos::service_level_controller& sl_controller);
|
||||
|
||||
future<> init(net::inet_address addr, std::optional<uint16_t> port, std::optional<uint16_t> https_port, std::optional<tls::credentials_builder> creds,
|
||||
utils::updateable_value<bool> enforce_authorization, utils::updateable_value<uint64_t> max_users_query_size_in_trace_output,
|
||||
semaphore* memory_limiter, utils::updateable_value<uint32_t> max_concurrent_requests);
|
||||
utils::updateable_value<bool> enforce_authorization, semaphore* memory_limiter, utils::updateable_value<uint32_t> max_concurrent_requests);
|
||||
future<> stop();
|
||||
// get_client_data() is called (on each shard separately) when the virtual
|
||||
// table "system.clients" is read. It is expected to generate a list of
|
||||
// clients connected to this server (on this shard). This function is
|
||||
// called by alternator::controller::get_client_data().
|
||||
future<utils::chunked_vector<client_data>> get_client_data();
|
||||
private:
|
||||
void set_routes(seastar::httpd::routes& r);
|
||||
// If verification succeeds, returns the authenticated user's username
|
||||
|
||||
@@ -14,58 +14,28 @@
|
||||
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;
|
||||
}
|
||||
|
||||
static seastar::metrics::label column_family_label("cf");
|
||||
static seastar::metrics::label keyspace_label("ks");
|
||||
|
||||
|
||||
static void register_metrics_with_optional_table(seastar::metrics::metric_groups& metrics, const stats& stats, const sstring& ks, const sstring& table) {
|
||||
|
||||
stats::stats() : api_operations{} {
|
||||
// Register the
|
||||
seastar::metrics::label op("op");
|
||||
bool has_table = table.length();
|
||||
std::vector<seastar::metrics::label> aggregate_labels;
|
||||
std::vector<seastar::metrics::label_instance> labels = {alternator_label};
|
||||
sstring group_name = (has_table)? "alternator_table" : "alternator";
|
||||
if (has_table) {
|
||||
labels.push_back(column_family_label(table));
|
||||
labels.push_back(keyspace_label(ks));
|
||||
aggregate_labels.push_back(seastar::metrics::shard_label);
|
||||
}
|
||||
metrics.add_group(group_name, {
|
||||
#define OPERATION(name, CamelCaseName) \
|
||||
seastar::metrics::make_total_operations("operation", stats.api_operations.name, \
|
||||
seastar::metrics::description("number of operations via Alternator API"), labels)(basic_level)(op(CamelCaseName)).aggregate(aggregate_labels).set_skip_when_empty(),
|
||||
#define OPERATION_LATENCY(name, CamelCaseName) \
|
||||
metrics.add_group(group_name, { \
|
||||
seastar::metrics::make_histogram("op_latency", \
|
||||
seastar::metrics::description("Latency histogram of an operation via Alternator API"), labels, [&stats]{return to_metrics_histogram(stats.api_operations.name.histogram());})(op(CamelCaseName))(basic_level).aggregate({seastar::metrics::shard_label}).set_skip_when_empty()}); \
|
||||
if (!has_table) {\
|
||||
metrics.add_group("alternator", { \
|
||||
seastar::metrics::make_summary("op_latency_summary", \
|
||||
seastar::metrics::description("Latency summary of an operation via Alternator API"), [&stats]{return to_metrics_summary(stats.api_operations.name.summary());})(op(CamelCaseName))(basic_level)(alternator_label).set_skip_when_empty()}); \
|
||||
}
|
||||
|
||||
_metrics.add_group("alternator", {
|
||||
#define OPERATION(name, CamelCaseName) \
|
||||
seastar::metrics::make_total_operations("operation", api_operations.name, \
|
||||
seastar::metrics::description("number of operations via Alternator API"), {op(CamelCaseName), alternator_label, basic_level}).set_skip_when_empty(),
|
||||
#define OPERATION_LATENCY(name, CamelCaseName) \
|
||||
seastar::metrics::make_histogram("op_latency", \
|
||||
seastar::metrics::description("Latency histogram of an operation via Alternator API"), {op(CamelCaseName), alternator_label, basic_level}, [this]{return to_metrics_histogram(api_operations.name.histogram());}).aggregate({seastar::metrics::shard_label}).set_skip_when_empty(), \
|
||||
seastar::metrics::make_summary("op_latency_summary", \
|
||||
seastar::metrics::description("Latency summary of an operation via Alternator API"), [this]{return to_metrics_summary(api_operations.name.summary());})(op(CamelCaseName))(basic_level)(alternator_label).set_skip_when_empty(),
|
||||
OPERATION(batch_get_item, "BatchGetItem")
|
||||
OPERATION(batch_write_item, "BatchWriteItem")
|
||||
OPERATION(create_backup, "CreateBackup")
|
||||
OPERATION(create_global_table, "CreateGlobalTable")
|
||||
OPERATION(create_table, "CreateTable")
|
||||
OPERATION(delete_backup, "DeleteBackup")
|
||||
OPERATION(delete_item, "DeleteItem")
|
||||
OPERATION(delete_table, "DeleteTable")
|
||||
OPERATION(describe_backup, "DescribeBackup")
|
||||
OPERATION(describe_continuous_backups, "DescribeContinuousBackups")
|
||||
OPERATION(describe_endpoints, "DescribeEndpoints")
|
||||
@@ -94,107 +64,55 @@ static void register_metrics_with_optional_table(seastar::metrics::metric_groups
|
||||
OPERATION(update_item, "UpdateItem")
|
||||
OPERATION(update_table, "UpdateTable")
|
||||
OPERATION(update_time_to_live, "UpdateTimeToLive")
|
||||
OPERATION_LATENCY(put_item_latency, "PutItem")
|
||||
OPERATION_LATENCY(get_item_latency, "GetItem")
|
||||
OPERATION_LATENCY(delete_item_latency, "DeleteItem")
|
||||
OPERATION_LATENCY(update_item_latency, "UpdateItem")
|
||||
OPERATION_LATENCY(batch_write_item_latency, "BatchWriteItem")
|
||||
OPERATION_LATENCY(batch_get_item_latency, "BatchGetItem")
|
||||
OPERATION(list_streams, "ListStreams")
|
||||
OPERATION(describe_stream, "DescribeStream")
|
||||
OPERATION(get_shard_iterator, "GetShardIterator")
|
||||
OPERATION(get_records, "GetRecords")
|
||||
OPERATION_LATENCY(get_records_latency, "GetRecords")
|
||||
});
|
||||
OPERATION_LATENCY(put_item_latency, "PutItem")
|
||||
OPERATION_LATENCY(get_item_latency, "GetItem")
|
||||
OPERATION_LATENCY(delete_item_latency, "DeleteItem")
|
||||
OPERATION_LATENCY(update_item_latency, "UpdateItem")
|
||||
OPERATION_LATENCY(batch_write_item_latency, "BatchWriteItem")
|
||||
OPERATION_LATENCY(batch_get_item_latency, "BatchGetItem")
|
||||
OPERATION_LATENCY(get_records_latency, "GetRecords")
|
||||
if (!has_table) {
|
||||
// Create and delete operations are not applicable to a per-table metrics
|
||||
// only register it for the global metrics
|
||||
metrics.add_group("alternator", {
|
||||
OPERATION(create_table, "CreateTable")
|
||||
OPERATION(delete_table, "DeleteTable")
|
||||
|
||||
});
|
||||
}
|
||||
metrics.add_group(group_name, {
|
||||
seastar::metrics::make_total_operations("unsupported_operations", stats.unsupported_operations,
|
||||
seastar::metrics::description("number of unsupported operations via Alternator API"), labels).set_skip_when_empty(),
|
||||
seastar::metrics::make_total_operations("total_operations", stats.total_operations,
|
||||
seastar::metrics::description("number of total operations via Alternator API"), labels)(basic_level).aggregate(aggregate_labels).set_skip_when_empty(),
|
||||
seastar::metrics::make_total_operations("reads_before_write", stats.reads_before_write,
|
||||
seastar::metrics::description("number of performed read-before-write operations"), labels).aggregate(aggregate_labels).set_skip_when_empty(),
|
||||
seastar::metrics::make_total_operations("write_using_lwt", stats.write_using_lwt,
|
||||
seastar::metrics::description("number of writes that used LWT"), labels).aggregate(aggregate_labels).set_skip_when_empty(),
|
||||
seastar::metrics::make_total_operations("shard_bounce_for_lwt", stats.shard_bounce_for_lwt,
|
||||
seastar::metrics::description("number writes that had to be bounced from this shard because of LWT requirements"), labels).aggregate(aggregate_labels).set_skip_when_empty(),
|
||||
seastar::metrics::make_total_operations("requests_blocked_memory", stats.requests_blocked_memory,
|
||||
seastar::metrics::description("Counts a number of requests blocked due to memory pressure."), labels).aggregate(aggregate_labels).set_skip_when_empty(),
|
||||
seastar::metrics::make_total_operations("requests_shed", stats.requests_shed,
|
||||
seastar::metrics::description("Counts a number of requests shed due to overload."), labels).aggregate(aggregate_labels).set_skip_when_empty(),
|
||||
seastar::metrics::make_total_operations("filtered_rows_read_total", stats.cql_stats.filtered_rows_read_total,
|
||||
seastar::metrics::description("number of rows read during filtering operations"), labels).aggregate(aggregate_labels).set_skip_when_empty(),
|
||||
seastar::metrics::make_total_operations("filtered_rows_matched_total", stats.cql_stats.filtered_rows_matched_total,
|
||||
seastar::metrics::description("number of rows read and matched during filtering operations"), labels).aggregate(aggregate_labels).set_skip_when_empty(),
|
||||
seastar::metrics::make_counter("rcu_total", [&stats]{return 0.5 * stats.rcu_half_units_total;},
|
||||
seastar::metrics::description("total number of consumed read units"), labels).aggregate(aggregate_labels).set_skip_when_empty(),
|
||||
seastar::metrics::make_counter("wcu_total", stats.wcu_total[stats::wcu_types::PUT_ITEM],
|
||||
seastar::metrics::description("total number of consumed write units"), labels)(op("PutItem")).aggregate(aggregate_labels).set_skip_when_empty(),
|
||||
seastar::metrics::make_counter("wcu_total", stats.wcu_total[stats::wcu_types::DELETE_ITEM],
|
||||
seastar::metrics::description("total number of consumed write units"), labels)(op("DeleteItem")).aggregate(aggregate_labels).set_skip_when_empty(),
|
||||
seastar::metrics::make_counter("wcu_total", stats.wcu_total[stats::wcu_types::UPDATE_ITEM],
|
||||
seastar::metrics::description("total number of consumed write units"), labels)(op("UpdateItem")).aggregate(aggregate_labels).set_skip_when_empty(),
|
||||
seastar::metrics::make_counter("wcu_total", stats.wcu_total[stats::wcu_types::INDEX],
|
||||
seastar::metrics::description("total number of consumed write units"), labels)(op("Index")).aggregate(aggregate_labels).set_skip_when_empty(),
|
||||
seastar::metrics::make_total_operations("filtered_rows_dropped_total", [&stats] { return stats.cql_stats.filtered_rows_read_total - stats.cql_stats.filtered_rows_matched_total; },
|
||||
seastar::metrics::description("number of rows read and dropped during filtering operations"), labels).aggregate(aggregate_labels).set_skip_when_empty(),
|
||||
seastar::metrics::make_counter("batch_item_count", seastar::metrics::description("The total number of items processed across all batches"), labels,
|
||||
stats.api_operations.batch_write_item_batch_total)(op("BatchWriteItem")).aggregate(aggregate_labels).set_skip_when_empty(),
|
||||
seastar::metrics::make_counter("batch_item_count", seastar::metrics::description("The total number of items processed across all batches"), labels,
|
||||
stats.api_operations.batch_get_item_batch_total)(op("BatchGetItem")).aggregate(aggregate_labels).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"), labels,
|
||||
[&stats]{ return estimated_histogram_to_metrics(stats.api_operations.batch_get_item_histogram);})(op("BatchGetItem")).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"), labels,
|
||||
[&stats]{ return estimated_histogram_to_metrics(stats.api_operations.batch_write_item_histogram);})(op("BatchWriteItem")).aggregate({seastar::metrics::shard_label}).set_skip_when_empty(),
|
||||
seastar::metrics::make_histogram("operation_size_kb", seastar::metrics::description("Histogram of item sizes involved in a request"), labels,
|
||||
[&stats]{ return estimated_histogram_to_metrics(stats.operation_sizes.get_item_op_size_kb);})(op("GetItem")).aggregate({seastar::metrics::shard_label}).set_skip_when_empty(),
|
||||
seastar::metrics::make_histogram("operation_size_kb", seastar::metrics::description("Histogram of item sizes involved in a request"), labels,
|
||||
[&stats]{ return estimated_histogram_to_metrics(stats.operation_sizes.put_item_op_size_kb);})(op("PutItem")).aggregate({seastar::metrics::shard_label}).set_skip_when_empty(),
|
||||
seastar::metrics::make_histogram("operation_size_kb", seastar::metrics::description("Histogram of item sizes involved in a request"), labels,
|
||||
[&stats]{ return estimated_histogram_to_metrics(stats.operation_sizes.delete_item_op_size_kb);})(op("DeleteItem")).aggregate({seastar::metrics::shard_label}).set_skip_when_empty(),
|
||||
seastar::metrics::make_histogram("operation_size_kb", seastar::metrics::description("Histogram of item sizes involved in a request"), labels,
|
||||
[&stats]{ return estimated_histogram_to_metrics(stats.operation_sizes.update_item_op_size_kb);})(op("UpdateItem")).aggregate({seastar::metrics::shard_label}).set_skip_when_empty(),
|
||||
seastar::metrics::make_histogram("operation_size_kb", seastar::metrics::description("Histogram of item sizes involved in a request"), labels,
|
||||
[&stats]{ return estimated_histogram_to_metrics(stats.operation_sizes.batch_get_item_op_size_kb);})(op("BatchGetItem")).aggregate({seastar::metrics::shard_label}).set_skip_when_empty(),
|
||||
seastar::metrics::make_histogram("operation_size_kb", seastar::metrics::description("Histogram of item sizes involved in a request"), labels,
|
||||
[&stats]{ return estimated_histogram_to_metrics(stats.operation_sizes.batch_write_item_op_size_kb);})(op("BatchWriteItem")).aggregate({seastar::metrics::shard_label}).set_skip_when_empty(),
|
||||
});
|
||||
|
||||
seastar::metrics::label expression_label("expression");
|
||||
metrics.add_group(group_name, {
|
||||
seastar::metrics::make_total_operations("expression_cache_evictions", stats.expression_cache.evictions,
|
||||
seastar::metrics::description("Counts number of entries evicted from expressions cache"), labels).aggregate(aggregate_labels).set_skip_when_empty(),
|
||||
|
||||
seastar::metrics::make_total_operations("expression_cache_hits", stats.expression_cache.requests[stats::expression_types::UPDATE_EXPRESSION].hits,
|
||||
seastar::metrics::description("Counts number of hits of cached expressions"), labels)(expression_label("UpdateExpression")).aggregate(aggregate_labels).set_skip_when_empty(),
|
||||
seastar::metrics::make_total_operations("expression_cache_misses", stats.expression_cache.requests[stats::expression_types::UPDATE_EXPRESSION].misses,
|
||||
seastar::metrics::description("Counts number of misses of cached expressions"), labels)(expression_label("UpdateExpression")).aggregate(aggregate_labels).set_skip_when_empty(),
|
||||
|
||||
seastar::metrics::make_total_operations("expression_cache_hits", stats.expression_cache.requests[stats::expression_types::CONDITION_EXPRESSION].hits,
|
||||
seastar::metrics::description("Counts number of hits of cached expressions"), labels)(expression_label("ConditionExpression")).aggregate(aggregate_labels).set_skip_when_empty(),
|
||||
seastar::metrics::make_total_operations("expression_cache_misses", stats.expression_cache.requests[stats::expression_types::CONDITION_EXPRESSION].misses,
|
||||
seastar::metrics::description("Counts number of misses of cached expressions"), labels)(expression_label("ConditionExpression")).aggregate(aggregate_labels).set_skip_when_empty(),
|
||||
|
||||
seastar::metrics::make_total_operations("expression_cache_hits", stats.expression_cache.requests[stats::expression_types::PROJECTION_EXPRESSION].hits,
|
||||
seastar::metrics::description("Counts number of hits of cached expressions"), labels)(expression_label("ProjectionExpression")).aggregate(aggregate_labels).set_skip_when_empty(),
|
||||
seastar::metrics::make_total_operations("expression_cache_misses", stats.expression_cache.requests[stats::expression_types::PROJECTION_EXPRESSION].misses,
|
||||
seastar::metrics::description("Counts number of misses of cached expressions"), labels)(expression_label("ProjectionExpression")).aggregate(aggregate_labels).set_skip_when_empty()
|
||||
_metrics.add_group("alternator", {
|
||||
seastar::metrics::make_total_operations("unsupported_operations", unsupported_operations,
|
||||
seastar::metrics::description("number of unsupported operations via Alternator API"))(alternator_label).set_skip_when_empty(),
|
||||
seastar::metrics::make_total_operations("total_operations", total_operations,
|
||||
seastar::metrics::description("number of total operations via Alternator API"))(basic_level)(alternator_label).set_skip_when_empty(),
|
||||
seastar::metrics::make_total_operations("reads_before_write", reads_before_write,
|
||||
seastar::metrics::description("number of performed read-before-write operations"))(alternator_label).set_skip_when_empty(),
|
||||
seastar::metrics::make_total_operations("write_using_lwt", write_using_lwt,
|
||||
seastar::metrics::description("number of writes that used LWT"))(alternator_label).set_skip_when_empty(),
|
||||
seastar::metrics::make_total_operations("shard_bounce_for_lwt", shard_bounce_for_lwt,
|
||||
seastar::metrics::description("number writes that had to be bounced from this shard because of LWT requirements"))(alternator_label).set_skip_when_empty(),
|
||||
seastar::metrics::make_total_operations("requests_blocked_memory", requests_blocked_memory,
|
||||
seastar::metrics::description("Counts a number of requests blocked due to memory pressure."))(alternator_label).set_skip_when_empty(),
|
||||
seastar::metrics::make_total_operations("requests_shed", requests_shed,
|
||||
seastar::metrics::description("Counts a number of requests shed due to overload."))(alternator_label).set_skip_when_empty(),
|
||||
seastar::metrics::make_total_operations("filtered_rows_read_total", cql_stats.filtered_rows_read_total,
|
||||
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", 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, 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, 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, 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, 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(),
|
||||
});
|
||||
}
|
||||
|
||||
void register_metrics(seastar::metrics::metric_groups& metrics, const stats& stats) {
|
||||
register_metrics_with_optional_table(metrics, stats, "", "");
|
||||
}
|
||||
table_stats::table_stats(const sstring& ks, const sstring& table) {
|
||||
_stats = make_lw_shared<stats>();
|
||||
register_metrics_with_optional_table(_metrics, *_stats, ks, table);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
@@ -22,6 +21,7 @@ namespace alternator {
|
||||
// visible by the metrics REST API, with the "alternator" prefix.
|
||||
class stats {
|
||||
public:
|
||||
stats();
|
||||
// Count of DynamoDB API operations by types
|
||||
struct {
|
||||
uint64_t batch_get_item = 0;
|
||||
@@ -75,36 +75,7 @@ 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;
|
||||
// Operation size metrics
|
||||
struct {
|
||||
// Item size statistics collected per table and aggregated per node.
|
||||
// Each histogram covers the range 0 - 446. Resolves #25143.
|
||||
// A size is the retrieved item's size.
|
||||
utils::estimated_histogram get_item_op_size_kb{30};
|
||||
// A size is the maximum of the new item's size and the old item's size.
|
||||
utils::estimated_histogram put_item_op_size_kb{30};
|
||||
// A size is the deleted item's size. If the deleted item's size is
|
||||
// unknown (i.e. read-before-write wasn't necessary and it wasn't
|
||||
// forced by a configuration option), it won't be recorded on the
|
||||
// histogram.
|
||||
utils::estimated_histogram delete_item_op_size_kb{30};
|
||||
// A size is the maximum of existing item's size and the estimated size
|
||||
// of the update. This will be changed to the maximum of the existing item's
|
||||
// size and the new item's size in a subsequent PR.
|
||||
utils::estimated_histogram update_item_op_size_kb{30};
|
||||
|
||||
// A size is the sum of the sizes of all items per table. This means
|
||||
// that a single BatchGetItem / BatchWriteItem updates the histogram
|
||||
// for each table that it has items in.
|
||||
// The sizes are the retrieved items' sizes grouped per table.
|
||||
utils::estimated_histogram batch_get_item_op_size_kb{30};
|
||||
// The sizes are the the written items' sizes grouped per table.
|
||||
utils::estimated_histogram batch_write_item_op_size_kb{30};
|
||||
} operation_sizes;
|
||||
// Miscellaneous event counters
|
||||
uint64_t total_operations = 0;
|
||||
uint64_t unsupported_operations = 0;
|
||||
@@ -113,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 {
|
||||
@@ -127,33 +98,10 @@ public:
|
||||
uint64_t wcu_total[NUM_TYPES] = {0};
|
||||
// CQL-derived stats
|
||||
cql3::cql_stats cql_stats;
|
||||
|
||||
// Enumeration of expression types only for stats
|
||||
// if needed it can be extended e.g. per operation
|
||||
enum expression_types {
|
||||
UPDATE_EXPRESSION,
|
||||
CONDITION_EXPRESSION,
|
||||
PROJECTION_EXPRESSION,
|
||||
NUM_EXPRESSION_TYPES
|
||||
};
|
||||
struct {
|
||||
struct {
|
||||
uint64_t hits = 0;
|
||||
uint64_t misses = 0;
|
||||
} requests[NUM_EXPRESSION_TYPES];
|
||||
uint64_t evictions = 0;
|
||||
} expression_cache;
|
||||
};
|
||||
|
||||
struct table_stats {
|
||||
table_stats(const sstring& ks, const sstring& table);
|
||||
private:
|
||||
// The metric_groups object holds this stat object's metrics registered
|
||||
// as long as the stats object is alive.
|
||||
seastar::metrics::metric_groups _metrics;
|
||||
lw_shared_ptr<stats> _stats;
|
||||
};
|
||||
void register_metrics(seastar::metrics::metric_groups& metrics, const stats& stats);
|
||||
|
||||
inline uint64_t bytes_to_kb_ceil(uint64_t bytes) {
|
||||
return (bytes + 1023) / 1024;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
#include <seastar/json/formatter.hh>
|
||||
|
||||
#include "auth/permission.hh"
|
||||
#include "db/config.hh"
|
||||
|
||||
#include "cdc/log.hh"
|
||||
@@ -31,7 +32,6 @@
|
||||
|
||||
#include "executor.hh"
|
||||
#include "data_dictionary/data_dictionary.hh"
|
||||
#include "utils/rjson.hh"
|
||||
|
||||
/**
|
||||
* Base template type to implement rapidjson::internal::TypeHelper<...>:s
|
||||
@@ -126,7 +126,7 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace alternator
|
||||
}
|
||||
|
||||
template<typename ValueType>
|
||||
struct rapidjson::internal::TypeHelper<ValueType, alternator::stream_arn>
|
||||
@@ -217,7 +217,7 @@ future<alternator::executor::request_return_type> alternator::executor::list_str
|
||||
rjson::add(ret, "LastEvaluatedStreamArn", *last);
|
||||
}
|
||||
|
||||
return make_ready_future<executor::request_return_type>(rjson::print(std::move(ret)));
|
||||
return make_ready_future<executor::request_return_type>(make_jsonable(std::move(ret)));
|
||||
}
|
||||
|
||||
struct shard_id {
|
||||
@@ -296,7 +296,7 @@ sequence_number::sequence_number(std::string_view v)
|
||||
}())
|
||||
{}
|
||||
|
||||
} // namespace alternator
|
||||
}
|
||||
|
||||
template<typename ValueType>
|
||||
struct rapidjson::internal::TypeHelper<ValueType, alternator::shard_id>
|
||||
@@ -356,7 +356,7 @@ static stream_view_type cdc_options_to_steam_view_type(const cdc::options& opts)
|
||||
return type;
|
||||
}
|
||||
|
||||
} // namespace alternator
|
||||
}
|
||||
|
||||
template<typename ValueType>
|
||||
struct rapidjson::internal::TypeHelper<ValueType, alternator::stream_view_type>
|
||||
@@ -475,10 +475,10 @@ future<executor::request_return_type> executor::describe_stream(client_state& cl
|
||||
} else {
|
||||
status = "ENABLED";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto ttl = std::chrono::seconds(opts.ttl());
|
||||
|
||||
|
||||
rjson::add(stream_desc, "StreamStatus", rjson::from_string(status));
|
||||
|
||||
stream_view_type type = cdc_options_to_steam_view_type(opts);
|
||||
@@ -491,7 +491,7 @@ future<executor::request_return_type> executor::describe_stream(client_state& cl
|
||||
|
||||
if (!opts.enabled()) {
|
||||
rjson::add(ret, "StreamDescription", std::move(stream_desc));
|
||||
return make_ready_future<executor::request_return_type>(rjson::print(std::move(ret)));
|
||||
return make_ready_future<executor::request_return_type>(make_jsonable(std::move(ret)));
|
||||
}
|
||||
|
||||
// TODO: label
|
||||
@@ -617,7 +617,7 @@ future<executor::request_return_type> executor::describe_stream(client_state& cl
|
||||
rjson::add(stream_desc, "Shards", std::move(shards));
|
||||
rjson::add(ret, "StreamDescription", std::move(stream_desc));
|
||||
|
||||
return make_ready_future<executor::request_return_type>(rjson::print(std::move(ret)));
|
||||
return make_ready_future<executor::request_return_type>(make_jsonable(std::move(ret)));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -714,7 +714,7 @@ future<executor::request_return_type> executor::get_shard_iterator(client_state&
|
||||
|
||||
auto type = rjson::get<shard_iterator_type>(request, "ShardIteratorType");
|
||||
auto seq_num = rjson::get_opt<sequence_number>(request, "SequenceNumber");
|
||||
|
||||
|
||||
if (type < shard_iterator_type::TRIM_HORIZON && !seq_num) {
|
||||
throw api_error::validation("Missing required parameter \"SequenceNumber\"");
|
||||
}
|
||||
@@ -724,7 +724,7 @@ future<executor::request_return_type> executor::get_shard_iterator(client_state&
|
||||
|
||||
auto stream_arn = rjson::get<alternator::stream_arn>(request, "StreamArn");
|
||||
auto db = _proxy.data_dictionary();
|
||||
|
||||
|
||||
schema_ptr schema = nullptr;
|
||||
std::optional<shard_id> sid;
|
||||
|
||||
@@ -770,7 +770,7 @@ future<executor::request_return_type> executor::get_shard_iterator(client_state&
|
||||
auto ret = rjson::empty_object();
|
||||
rjson::add(ret, "ShardIterator", iter);
|
||||
|
||||
return make_ready_future<executor::request_return_type>(rjson::print(std::move(ret)));
|
||||
return make_ready_future<executor::request_return_type>(make_jsonable(std::move(ret)));
|
||||
}
|
||||
|
||||
struct event_id {
|
||||
@@ -789,7 +789,7 @@ struct event_id {
|
||||
return os;
|
||||
}
|
||||
};
|
||||
} // namespace alternator
|
||||
}
|
||||
|
||||
template<typename ValueType>
|
||||
struct rapidjson::internal::TypeHelper<ValueType, alternator::event_id>
|
||||
@@ -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;
|
||||
@@ -871,12 +868,10 @@ future<executor::request_return_type> executor::get_records(client_state& client
|
||||
|
||||
std::transform(pks.begin(), pks.end(), std::back_inserter(columns), [](auto& c) { return &c; });
|
||||
std::transform(cks.begin(), cks.end(), std::back_inserter(columns), [](auto& c) { return &c; });
|
||||
auto regular_column_start_idx = columns.size();
|
||||
auto regular_column_filter = std::views::filter([](const column_definition& cdef) { return cdef.name() == op_column_name || cdef.name() == eor_column_name || !cdc::is_cdc_metacolumn_name(cdef.name_as_text()); });
|
||||
std::ranges::transform(schema->regular_columns() | regular_column_filter, std::back_inserter(columns), [](auto& c) { return &c; });
|
||||
|
||||
auto regular_columns = std::ranges::subrange(columns.begin() + regular_column_start_idx, columns.end())
|
||||
| std::views::transform(&column_definition::id)
|
||||
auto regular_columns = schema->regular_columns()
|
||||
| std::views::filter([](const column_definition& cdef) { return cdef.name() == op_column_name || cdef.name() == eor_column_name || !cdc::is_cdc_metacolumn_name(cdef.name_as_text()); })
|
||||
| std::views::transform([&] (const column_definition& cdef) { columns.emplace_back(&cdef); return cdef.id; })
|
||||
| std::ranges::to<query::column_id_vector>()
|
||||
;
|
||||
|
||||
@@ -927,7 +922,6 @@ future<executor::request_return_type> executor::get_records(client_state& client
|
||||
std::optional<utils::UUID> timestamp;
|
||||
auto dynamodb = rjson::empty_object();
|
||||
auto record = rjson::empty_object();
|
||||
const auto dc_name = _proxy.get_token_metadata_ptr()->get_topology().get_datacenter();
|
||||
|
||||
using op_utype = std::underlying_type_t<cdc::operation>;
|
||||
|
||||
@@ -937,10 +931,9 @@ future<executor::request_return_type> executor::get_records(client_state& client
|
||||
dynamodb = rjson::empty_object();
|
||||
}
|
||||
if (!record.ObjectEmpty()) {
|
||||
rjson::add(record, "awsRegion", rjson::from_string(dc_name));
|
||||
// TODO: awsRegion?
|
||||
rjson::add(record, "eventID", event_id(iter.shard.id, *timestamp));
|
||||
rjson::add(record, "eventSource", "scylladb:alternator");
|
||||
rjson::add(record, "eventVersion", "1.1");
|
||||
rjson::push_back(records, std::move(record));
|
||||
record = rjson::empty_object();
|
||||
--limit;
|
||||
@@ -959,7 +952,7 @@ future<executor::request_return_type> executor::get_records(client_state& client
|
||||
rjson::add(dynamodb, "ApproximateCreationDateTime", utils::UUID_gen::unix_timestamp_in_sec(ts).count());
|
||||
rjson::add(dynamodb, "SequenceNumber", sequence_number(ts));
|
||||
rjson::add(dynamodb, "StreamViewType", type);
|
||||
// TODO: SizeBytes
|
||||
//TODO: SizeInBytes
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -999,16 +992,6 @@ future<executor::request_return_type> executor::get_records(client_state& client
|
||||
case cdc::operation::insert:
|
||||
rjson::add(record, "eventName", "INSERT");
|
||||
break;
|
||||
case cdc::operation::service_row_delete:
|
||||
case cdc::operation::service_partition_delete:
|
||||
{
|
||||
auto user_identity = rjson::empty_object();
|
||||
rjson::add(user_identity, "Type", "Service");
|
||||
rjson::add(user_identity, "PrincipalId", "dynamodb.amazonaws.com");
|
||||
rjson::add(record, "userIdentity", std::move(user_identity));
|
||||
rjson::add(record, "eventName", "REMOVE");
|
||||
break;
|
||||
}
|
||||
default:
|
||||
rjson::add(record, "eventName", "REMOVE");
|
||||
break;
|
||||
@@ -1035,7 +1018,7 @@ future<executor::request_return_type> executor::get_records(client_state& client
|
||||
// will notice end end of shard and not return NextShardIterator.
|
||||
rjson::add(ret, "NextShardIterator", next_iter);
|
||||
_stats.api_operations.get_records_latency.mark(std::chrono::steady_clock::now() - start_time);
|
||||
return make_ready_future<executor::request_return_type>(rjson::print(std::move(ret)));
|
||||
return make_ready_future<executor::request_return_type>(make_jsonable(std::move(ret)));
|
||||
}
|
||||
|
||||
// ugh. figure out if we are and end-of-shard
|
||||
@@ -1061,12 +1044,12 @@ future<executor::request_return_type> executor::get_records(client_state& client
|
||||
if (is_big(ret)) {
|
||||
return make_ready_future<executor::request_return_type>(make_streamed(std::move(ret)));
|
||||
}
|
||||
return make_ready_future<executor::request_return_type>(rjson::print(std::move(ret)));
|
||||
return make_ready_future<executor::request_return_type>(make_jsonable(std::move(ret)));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
bool executor::add_stream_options(const rjson::value& stream_specification, schema_builder& builder, service::storage_proxy& sp) {
|
||||
void executor::add_stream_options(const rjson::value& stream_specification, schema_builder& builder, service::storage_proxy& sp) {
|
||||
auto stream_enabled = rjson::find(stream_specification, "StreamEnabled");
|
||||
if (!stream_enabled || !stream_enabled->IsBool()) {
|
||||
throw api_error::validation("StreamSpecification needs boolean StreamEnabled");
|
||||
@@ -1100,12 +1083,10 @@ bool executor::add_stream_options(const rjson::value& stream_specification, sche
|
||||
break;
|
||||
}
|
||||
builder.with_cdc_options(opts);
|
||||
return true;
|
||||
} else {
|
||||
cdc::options opts;
|
||||
opts.enabled(false);
|
||||
builder.with_cdc_options(opts);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1134,4 +1115,4 @@ void executor::supplement_table_stream_info(rjson::value& descr, const schema& s
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace alternator
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
#include <seastar/core/lowres_clock.hh>
|
||||
#include <seastar/coroutine/maybe_yield.hh>
|
||||
|
||||
#include "cdc/log.hh"
|
||||
#include "exceptions/exceptions.hh"
|
||||
#include "gms/gossiper.hh"
|
||||
#include "gms/inet_address.hh"
|
||||
@@ -28,7 +27,7 @@
|
||||
#include "replica/database.hh"
|
||||
#include "service/client_state.hh"
|
||||
#include "service_permit.hh"
|
||||
#include "mutation/timestamp.hh"
|
||||
#include "timestamp.hh"
|
||||
#include "service/storage_proxy.hh"
|
||||
#include "service/pager/paging_state.hh"
|
||||
#include "service/pager/query_pagers.hh"
|
||||
@@ -57,14 +56,14 @@ static logging::logger tlogger("alternator_ttl");
|
||||
|
||||
namespace alternator {
|
||||
|
||||
// We write the expiration-time attribute enabled on a table in a
|
||||
// We write the expiration-time attribute enabled on a table using a
|
||||
// tag TTL_TAG_KEY.
|
||||
// Currently, the *value* of this tag is simply the name of the attribute,
|
||||
// and the expiration scanner interprets it as an Alternator attribute name -
|
||||
// It can refer to a real column or if that doesn't exist, to a member of
|
||||
// the ":attrs" map column. Although this is designed for Alternator, it may
|
||||
// be good enough for CQL as well (there, the ":attrs" column won't exist).
|
||||
extern const sstring TTL_TAG_KEY;
|
||||
static const sstring TTL_TAG_KEY("system:ttl_attribute");
|
||||
|
||||
future<executor::request_return_type> executor::update_time_to_live(client_state& client_state, service_permit permit, rjson::value request) {
|
||||
_stats.api_operations.update_time_to_live++;
|
||||
@@ -82,6 +81,11 @@ future<executor::request_return_type> executor::update_time_to_live(client_state
|
||||
co_return api_error::validation("UpdateTimeToLive requires boolean Enabled");
|
||||
}
|
||||
bool enabled = v->GetBool();
|
||||
// Alternator TTL doesn't yet work when the table uses tablets (#16567)
|
||||
if (enabled && _proxy.local_db().find_keyspace(schema->ks_name()).get_replication_strategy().uses_tablets()) {
|
||||
co_return api_error::validation("TTL not yet supported on a table using tablets (issue #16567). "
|
||||
"Create a table with the tag 'experimental:initial_tablets' set to 'none' to use vnodes.");
|
||||
}
|
||||
v = rjson::find(*spec, "AttributeName");
|
||||
if (!v || !v->IsString()) {
|
||||
co_return api_error::validation("UpdateTimeToLive requires string AttributeName");
|
||||
@@ -119,7 +123,7 @@ future<executor::request_return_type> executor::update_time_to_live(client_state
|
||||
// basically identical to the request's
|
||||
rjson::value response = rjson::empty_object();
|
||||
rjson::add(response, "TimeToLiveSpecification", std::move(*spec));
|
||||
co_return rjson::print(std::move(response));
|
||||
co_return make_jsonable(std::move(response));
|
||||
}
|
||||
|
||||
future<executor::request_return_type> executor::describe_time_to_live(client_state& client_state, service_permit permit, rjson::value request) {
|
||||
@@ -136,7 +140,7 @@ future<executor::request_return_type> executor::describe_time_to_live(client_sta
|
||||
}
|
||||
rjson::value response = rjson::empty_object();
|
||||
rjson::add(response, "TimeToLiveDescription", std::move(desc));
|
||||
co_return rjson::print(std::move(response));
|
||||
co_return make_jsonable(std::move(response));
|
||||
}
|
||||
|
||||
// expiration_service is a sharded service responsible for cleaning up expired
|
||||
@@ -287,18 +291,13 @@ static future<> expire_item(service::storage_proxy& proxy,
|
||||
auto ck = clustering_key::from_exploded(exploded_ck);
|
||||
m.partition().clustered_row(*schema, ck).apply(tombstone(ts, gc_clock::now()));
|
||||
}
|
||||
utils::chunked_vector<mutation> mutations;
|
||||
std::vector<mutation> mutations;
|
||||
mutations.push_back(std::move(m));
|
||||
return proxy.mutate(std::move(mutations),
|
||||
db::consistency_level::LOCAL_QUORUM,
|
||||
executor::default_timeout(), // FIXME - which timeout?
|
||||
qs.get_trace_state(), qs.get_permit(),
|
||||
db::allow_per_partition_rate_limit::no,
|
||||
false,
|
||||
cdc::per_request_options{
|
||||
.is_system_originated = true,
|
||||
}
|
||||
);
|
||||
db::allow_per_partition_rate_limit::no);
|
||||
}
|
||||
|
||||
static size_t random_offset(size_t min, size_t max) {
|
||||
@@ -316,10 +315,8 @@ static size_t random_offset(size_t min, size_t max) {
|
||||
// this range's primary node is down. For this we need to return not just
|
||||
// a list of this node's secondary ranges - but also the primary owner of
|
||||
// each of those ranges.
|
||||
//
|
||||
// The function is to be used with vnodes only
|
||||
static future<std::vector<std::pair<dht::token_range, locator::host_id>>> get_secondary_ranges(
|
||||
const locator::effective_replication_map* erm,
|
||||
const locator::effective_replication_map_ptr& erm,
|
||||
locator::host_id ep) {
|
||||
const auto& tm = *erm->get_token_metadata_ptr();
|
||||
const auto& sorted_tokens = tm.sorted_tokens();
|
||||
@@ -330,7 +327,6 @@ static future<std::vector<std::pair<dht::token_range, locator::host_id>>> get_se
|
||||
auto prev_tok = sorted_tokens.back();
|
||||
for (const auto& tok : sorted_tokens) {
|
||||
co_await coroutine::maybe_yield();
|
||||
// FIXME: pass is_vnode=true to get_natural_replicas since the token is in tm.sorted_tokens()
|
||||
host_id_vector_replica_set eps = erm->get_natural_replicas(tok);
|
||||
if (eps.size() <= 1 || eps[1] != ep) {
|
||||
prev_tok = tok;
|
||||
@@ -400,7 +396,7 @@ class ranges_holder_primary {
|
||||
dht::token_range_vector _token_ranges;
|
||||
public:
|
||||
explicit ranges_holder_primary(dht::token_range_vector token_ranges) : _token_ranges(std::move(token_ranges)) {}
|
||||
static future<ranges_holder_primary> make(const locator::vnode_effective_replication_map* erm, locator::host_id ep) {
|
||||
static future<ranges_holder_primary> make(const locator::vnode_effective_replication_map_ptr& erm, locator::host_id ep) {
|
||||
co_return ranges_holder_primary(co_await erm->get_primary_ranges(ep));
|
||||
}
|
||||
std::size_t size() const { return _token_ranges.size(); }
|
||||
@@ -420,7 +416,7 @@ public:
|
||||
explicit ranges_holder_secondary(std::vector<std::pair<dht::token_range, locator::host_id>> token_ranges, const gms::gossiper& g)
|
||||
: _token_ranges(std::move(token_ranges))
|
||||
, _gossiper(g) {}
|
||||
static future<ranges_holder_secondary> make(const locator::vnode_effective_replication_map* erm, locator::host_id ep, const gms::gossiper& g) {
|
||||
static future<ranges_holder_secondary> make(const locator::effective_replication_map_ptr& erm, locator::host_id ep, const gms::gossiper& g) {
|
||||
co_return ranges_holder_secondary(co_await get_secondary_ranges(erm, ep), g);
|
||||
}
|
||||
std::size_t size() const { return _token_ranges.size(); }
|
||||
@@ -433,8 +429,6 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
// The token_ranges_owned_by_this_shard class is only used for vnodes, where the vnodes give a partition range for the entire node
|
||||
// and such range still needs to be divided between the shards.
|
||||
template<class primary_or_secondary_t>
|
||||
class token_ranges_owned_by_this_shard {
|
||||
schema_ptr _s;
|
||||
@@ -528,7 +522,7 @@ struct scan_ranges_context {
|
||||
// should be possible (and a must for issue #7751!).
|
||||
lw_shared_ptr<service::pager::paging_state> paging_state = nullptr;
|
||||
auto regular_columns =
|
||||
s->regular_columns() | std::views::transform(&column_definition::id)
|
||||
s->regular_columns() | std::views::transform([] (const column_definition& cdef) { return cdef.id; })
|
||||
| std::ranges::to<query::column_id_vector>();
|
||||
selection = cql3::selection::selection::wildcard(s);
|
||||
query::partition_slice::option_set opts = selection->get_query_options();
|
||||
@@ -661,17 +655,6 @@ static future<> scan_table_ranges(
|
||||
}
|
||||
}
|
||||
|
||||
static future<> scan_tablet(locator::tablet_id tablet, service::storage_proxy& proxy, abort_source& abort_source, named_semaphore& page_sem,
|
||||
expiration_service::stats& expiration_stats, const scan_ranges_context& scan_ctx, const locator::tablet_map& tablet_map) {
|
||||
auto tablet_token_range = tablet_map.get_token_range(tablet);
|
||||
dht::ring_position tablet_start(tablet_token_range.start()->value(), dht::ring_position::token_bound::start),
|
||||
tablet_end(tablet_token_range.end()->value(), dht::ring_position::token_bound::end);
|
||||
auto partition_range = dht::partition_range::make(std::move(tablet_start), std::move(tablet_end));
|
||||
// Note that because of issue #9167 we need to run a separate query on each partition range, and can't pass
|
||||
// several of them into one partition_range_vector that is passed to scan_table_ranges().
|
||||
return scan_table_ranges(proxy, scan_ctx, {partition_range}, abort_source, page_sem, expiration_stats);
|
||||
}
|
||||
|
||||
// scan_table() scans, in one table, data "owned" by this shard, looking for
|
||||
// expired items and deleting them.
|
||||
// We consider each node to "own" its primary token ranges, i.e., the tokens
|
||||
@@ -747,69 +730,34 @@ static future<bool> scan_table(
|
||||
expiration_stats.scan_table++;
|
||||
// FIXME: need to pace the scan, not do it all at once.
|
||||
scan_ranges_context scan_ctx{s, proxy, std::move(column_name), std::move(member)};
|
||||
|
||||
if (s->table().uses_tablets()) {
|
||||
locator::effective_replication_map_ptr erm = s->table().get_effective_replication_map();
|
||||
auto my_host_id = erm->get_topology().my_host_id();
|
||||
const auto &tablet_map = erm->get_token_metadata().tablets().get_tablet_map(s->id());
|
||||
for (std::optional tablet = tablet_map.first_tablet(); tablet; tablet = tablet_map.next_tablet(*tablet)) {
|
||||
auto tablet_primary_replica = tablet_map.get_primary_replica(*tablet);
|
||||
// check if this is the primary replica for the current tablet
|
||||
if (tablet_primary_replica.host == my_host_id && tablet_primary_replica.shard == this_shard_id()) {
|
||||
co_await scan_tablet(*tablet, proxy, abort_source, page_sem, expiration_stats, scan_ctx, tablet_map);
|
||||
} else if(erm->get_replication_factor() > 1) {
|
||||
// Check if this is the secondary replica for the current tablet
|
||||
// and if the primary replica is down which means we will take over this work.
|
||||
// If each node only scans its own primary ranges, then when any node is
|
||||
// down part of the token range will not get scanned. This can be viewed
|
||||
// as acceptable (when the comes back online, it will resume its scan),
|
||||
// but as noted in issue #9787, we can allow more prompt expiration
|
||||
// by tasking another node to take over scanning of the dead node's primary
|
||||
// ranges. What we do here is that this node will also check expiration
|
||||
// on its *secondary* ranges - but only those whose primary owner is down.
|
||||
auto tablet_secondary_replica = tablet_map.get_secondary_replica(*tablet); // throws if no secondary replica
|
||||
if (tablet_secondary_replica.host == my_host_id && tablet_secondary_replica.shard == this_shard_id()) {
|
||||
if (!gossiper.is_alive(tablet_primary_replica.host)) {
|
||||
co_await scan_tablet(*tablet, proxy, abort_source, page_sem, expiration_stats, scan_ctx, tablet_map);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else { // VNodes
|
||||
locator::static_effective_replication_map_ptr ermp =
|
||||
db.real_database().find_keyspace(s->ks_name()).get_static_effective_replication_map();
|
||||
auto* erm = ermp->maybe_as_vnode_effective_replication_map();
|
||||
if (!erm) {
|
||||
on_internal_error(tlogger, format("Keyspace {} is local", s->ks_name()));
|
||||
}
|
||||
auto my_host_id = erm->get_topology().my_host_id();
|
||||
token_ranges_owned_by_this_shard my_ranges(s, co_await ranges_holder_primary::make(erm, my_host_id));
|
||||
while (std::optional<dht::partition_range> range = my_ranges.next_partition_range()) {
|
||||
// Note that because of issue #9167 we need to run a separate
|
||||
// query on each partition range, and can't pass several of
|
||||
// them into one partition_range_vector.
|
||||
dht::partition_range_vector partition_ranges;
|
||||
partition_ranges.push_back(std::move(*range));
|
||||
// FIXME: if scanning a single range fails, including network errors,
|
||||
// we fail the entire scan (and rescan from the beginning). Need to
|
||||
// reconsider this. Saving the scan position might be a good enough
|
||||
// solution for this problem.
|
||||
co_await scan_table_ranges(proxy, scan_ctx, std::move(partition_ranges), abort_source, page_sem, expiration_stats);
|
||||
}
|
||||
// If each node only scans its own primary ranges, then when any node is
|
||||
// down part of the token range will not get scanned. This can be viewed
|
||||
// as acceptable (when the comes back online, it will resume its scan),
|
||||
// but as noted in issue #9787, we can allow more prompt expiration
|
||||
// by tasking another node to take over scanning of the dead node's primary
|
||||
// ranges. What we do here is that this node will also check expiration
|
||||
// on its *secondary* ranges - but only those whose primary owner is down.
|
||||
token_ranges_owned_by_this_shard my_secondary_ranges(s, co_await ranges_holder_secondary::make(erm, my_host_id, gossiper));
|
||||
while (std::optional<dht::partition_range> range = my_secondary_ranges.next_partition_range()) {
|
||||
expiration_stats.secondary_ranges_scanned++;
|
||||
dht::partition_range_vector partition_ranges;
|
||||
partition_ranges.push_back(std::move(*range));
|
||||
co_await scan_table_ranges(proxy, scan_ctx, std::move(partition_ranges), abort_source, page_sem, expiration_stats);
|
||||
}
|
||||
auto erm = db.real_database().find_keyspace(s->ks_name()).get_vnode_effective_replication_map();
|
||||
auto my_host_id = erm->get_topology().my_host_id();
|
||||
token_ranges_owned_by_this_shard my_ranges(s, co_await ranges_holder_primary::make(erm, my_host_id));
|
||||
while (std::optional<dht::partition_range> range = my_ranges.next_partition_range()) {
|
||||
// Note that because of issue #9167 we need to run a separate
|
||||
// query on each partition range, and can't pass several of
|
||||
// them into one partition_range_vector.
|
||||
dht::partition_range_vector partition_ranges;
|
||||
partition_ranges.push_back(std::move(*range));
|
||||
// FIXME: if scanning a single range fails, including network errors,
|
||||
// we fail the entire scan (and rescan from the beginning). Need to
|
||||
// reconsider this. Saving the scan position might be a good enough
|
||||
// solution for this problem.
|
||||
co_await scan_table_ranges(proxy, scan_ctx, std::move(partition_ranges), abort_source, page_sem, expiration_stats);
|
||||
}
|
||||
// If each node only scans its own primary ranges, then when any node is
|
||||
// down part of the token range will not get scanned. This can be viewed
|
||||
// as acceptable (when the comes back online, it will resume its scan),
|
||||
// but as noted in issue #9787, we can allow more prompt expiration
|
||||
// by tasking another node to take over scanning of the dead node's primary
|
||||
// ranges. What we do here is that this node will also check expiration
|
||||
// on its *secondary* ranges - but only those whose primary owner is down.
|
||||
token_ranges_owned_by_this_shard my_secondary_ranges(s, co_await ranges_holder_secondary::make(erm, my_host_id, gossiper));
|
||||
while (std::optional<dht::partition_range> range = my_secondary_ranges.next_partition_range()) {
|
||||
expiration_stats.secondary_ranges_scanned++;
|
||||
dht::partition_range_vector partition_ranges;
|
||||
partition_ranges.push_back(std::move(*range));
|
||||
co_await scan_table_ranges(proxy, scan_ctx, std::move(partition_ranges), abort_source, page_sem, expiration_stats);
|
||||
}
|
||||
co_return true;
|
||||
}
|
||||
|
||||
@@ -246,24 +246,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"sstableinfo":{
|
||||
"id":"sstableinfo",
|
||||
"description":"Compacted sstable information",
|
||||
"properties":{
|
||||
"generation":{
|
||||
"type": "string",
|
||||
"description":"Generation of the sstable"
|
||||
},
|
||||
"origin":{
|
||||
"type":"string",
|
||||
"description":"Origin of the sstable"
|
||||
},
|
||||
"size":{
|
||||
"type":"long",
|
||||
"description":"Size of the sstable"
|
||||
}
|
||||
}
|
||||
},
|
||||
"compaction_info" :{
|
||||
"id": "compaction_info",
|
||||
"description":"A key value mapping",
|
||||
@@ -345,10 +327,6 @@
|
||||
"type":"string",
|
||||
"description":"The UUID"
|
||||
},
|
||||
"shard_id":{
|
||||
"type":"int",
|
||||
"description":"The shard id the compaction was executed on"
|
||||
},
|
||||
"cf":{
|
||||
"type":"string",
|
||||
"description":"The column family name"
|
||||
@@ -357,17 +335,9 @@
|
||||
"type":"string",
|
||||
"description":"The keyspace name"
|
||||
},
|
||||
"compaction_type":{
|
||||
"type":"string",
|
||||
"description":"Type of compaction"
|
||||
},
|
||||
"started_at":{
|
||||
"type":"long",
|
||||
"description":"The time compaction started"
|
||||
},
|
||||
"compacted_at":{
|
||||
"type":"long",
|
||||
"description":"The time compaction completed"
|
||||
"description":"The time of compaction"
|
||||
},
|
||||
"bytes_in":{
|
||||
"type":"long",
|
||||
@@ -383,32 +353,6 @@
|
||||
"type":"row_merged"
|
||||
},
|
||||
"description":"The merged rows"
|
||||
},
|
||||
"sstables_in": {
|
||||
"type":"array",
|
||||
"items":{
|
||||
"type":"sstableinfo"
|
||||
},
|
||||
"description":"List of input sstables for compaction"
|
||||
},
|
||||
"sstables_out": {
|
||||
"type":"array",
|
||||
"items":{
|
||||
"type":"sstableinfo"
|
||||
},
|
||||
"description":"List of output sstables from compaction"
|
||||
},
|
||||
"total_tombstone_purge_attempt":{
|
||||
"type":"long",
|
||||
"description":"Total number of tombstone purge attempts"
|
||||
},
|
||||
"total_tombstone_purge_failure_due_to_overlapping_with_memtable":{
|
||||
"type":"long",
|
||||
"description":"Number of tombstone purge failures due to data overlapping with memtables"
|
||||
},
|
||||
"total_tombstone_purge_failure_due_to_overlapping_with_uncompacting_sstable":{
|
||||
"type":"long",
|
||||
"description":"Number of tombstone purge failures due to data overlapping with non-compacting sstables"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -594,50 +594,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/storage_service/natural_endpoints/v2/{keyspace}",
|
||||
"operations": [
|
||||
{
|
||||
"method": "GET",
|
||||
"summary":"This method returns the N endpoints that are responsible for storing the specified key i.e for replication. the endpoint responsible for this key",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"nickname": "get_natural_endpoints_v2",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "keyspace",
|
||||
"description": "The keyspace to query about.",
|
||||
"required": true,
|
||||
"allowMultiple": false,
|
||||
"type": "string",
|
||||
"paramType": "path"
|
||||
},
|
||||
{
|
||||
"name": "cf",
|
||||
"description": "Column family name.",
|
||||
"required": true,
|
||||
"allowMultiple": false,
|
||||
"type": "string",
|
||||
"paramType": "query"
|
||||
},
|
||||
{
|
||||
"name": "key_component",
|
||||
"description": "Each component of the key for which we need to find the endpoint (e.g. ?key_component=part1&key_component=part2).",
|
||||
"required": true,
|
||||
"allowMultiple": true,
|
||||
"type": "string",
|
||||
"paramType": "query"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
"path":"/storage_service/cdc_streams_check_and_repair",
|
||||
"operations":[
|
||||
@@ -1144,14 +1100,6 @@
|
||||
"allowMultiple":false,
|
||||
"type":"string",
|
||||
"paramType":"query"
|
||||
},
|
||||
{
|
||||
"name": "drop_unfixable_sstables",
|
||||
"description": "When set to true, drop unfixable sstables. Applies only to scrub mode SEGREGATE.",
|
||||
"required":false,
|
||||
"allowMultiple":false,
|
||||
"type":"boolean",
|
||||
"paramType":"query"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -2196,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"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -2973,14 +2896,6 @@
|
||||
"allowMultiple":false,
|
||||
"type":"string",
|
||||
"paramType":"query"
|
||||
},
|
||||
{
|
||||
"name":"incremental_mode",
|
||||
"description":"Set the incremental repair mode. Can be 'disabled', 'incremental', or 'full'. 'incremental': The incremental repair logic is enabled. Unrepaired sstables will be included for repair. Repaired sstables will be skipped. The incremental repair states will be updated after repair. 'full': The incremental repair logic is enabled. Both repaired and unrepaired sstables will be included for repair. The incremental repair states will be updated after repair. 'disabled': The incremental repair logic is disabled completely. The incremental repair states, e.g., repaired_at in sstables and sstables_repaired_at in the system.tablets table, will not be updated after repair. When the option is not provided, it defaults to incremental mode.",
|
||||
"required":false,
|
||||
"allowMultiple":false,
|
||||
"type":"string",
|
||||
"paramType":"query"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -3112,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":[
|
||||
@@ -3221,54 +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":[
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path":"/storage_service/drop_quarantined_sstables",
|
||||
"operations":[
|
||||
{
|
||||
"method":"POST",
|
||||
"summary":"Drops all quarantined sstables in all keyspaces or specified keyspace and tables",
|
||||
"type":"void",
|
||||
"nickname":"drop_quarantined_sstables",
|
||||
"produces":[
|
||||
"application/json"
|
||||
],
|
||||
"parameters":[
|
||||
{
|
||||
"name":"keyspace",
|
||||
"description":"The keyspace name to drop quarantined sstables from.",
|
||||
"required":false,
|
||||
"allowMultiple":false,
|
||||
"type":"string",
|
||||
"paramType":"query"
|
||||
},
|
||||
{
|
||||
"name":"tables",
|
||||
"description":"Comma-separated table names to drop quarantined sstables from.",
|
||||
"required":false,
|
||||
"allowMultiple":false,
|
||||
"type":"string",
|
||||
"paramType":"query"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"models":{
|
||||
@@ -3405,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",
|
||||
@@ -3482,7 +3282,7 @@
|
||||
"version":{
|
||||
"type":"string",
|
||||
"enum":[
|
||||
"ka", "la", "mc", "md", "me", "ms"
|
||||
"ka", "la", "mc", "md", "me"
|
||||
],
|
||||
"description":"SSTable version"
|
||||
},
|
||||
@@ -3528,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)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -447,7 +447,7 @@
|
||||
"description":"The number of units completed so far"
|
||||
},
|
||||
"children_ids":{
|
||||
"type":"chunked_array",
|
||||
"type":"array",
|
||||
"items":{
|
||||
"type":"task_identity"
|
||||
},
|
||||
|
||||
63
api/api.cc
63
api/api.cc
@@ -137,6 +137,14 @@ future<> unset_load_meter(http_context& ctx) {
|
||||
return ctx.http_server.set_routes([&ctx] (routes& r) { unset_load_meter(ctx, r); });
|
||||
}
|
||||
|
||||
future<> set_format_selector(http_context& ctx, db::sstables_format_selector& sel) {
|
||||
return ctx.http_server.set_routes([&ctx, &sel] (routes& r) { set_format_selector(ctx, r, sel); });
|
||||
}
|
||||
|
||||
future<> unset_format_selector(http_context& ctx) {
|
||||
return ctx.http_server.set_routes([&ctx] (routes& r) { unset_format_selector(ctx, r); });
|
||||
}
|
||||
|
||||
future<> set_server_sstables_loader(http_context& ctx, sharded<sstables_loader>& sst_loader) {
|
||||
return ctx.http_server.set_routes([&ctx, &sst_loader] (routes& r) { set_sstables_loader(ctx, r, sst_loader); });
|
||||
}
|
||||
@@ -216,22 +224,15 @@ future<> unset_server_gossip(http_context& ctx) {
|
||||
});
|
||||
}
|
||||
|
||||
future<> set_server_column_family(http_context& ctx, sharded<replica::database>& db) {
|
||||
co_await register_api(ctx, "column_family",
|
||||
"The column family API", [&db] (http_context& ctx, routes& r) {
|
||||
set_column_family(ctx, r, db);
|
||||
});
|
||||
co_await register_api(ctx, "cache_service",
|
||||
"The cache service API", [&db] (http_context& ctx, routes& r) {
|
||||
set_cache_service(ctx, db, r);
|
||||
future<> set_server_column_family(http_context& ctx, sharded<db::system_keyspace>& sys_ks) {
|
||||
return register_api(ctx, "column_family",
|
||||
"The column family API", [&sys_ks] (http_context& ctx, routes& r) {
|
||||
set_column_family(ctx, r, sys_ks);
|
||||
});
|
||||
}
|
||||
|
||||
future<> unset_server_column_family(http_context& ctx) {
|
||||
return ctx.http_server.set_routes([&ctx] (routes& r) {
|
||||
unset_column_family(ctx, r);
|
||||
unset_cache_service(ctx, r);
|
||||
});
|
||||
return ctx.http_server.set_routes([&ctx] (routes& r) { unset_column_family(ctx, r); });
|
||||
}
|
||||
|
||||
future<> set_server_messaging_service(http_context& ctx, sharded<netw::messaging_service>& ms) {
|
||||
@@ -263,6 +264,15 @@ future<> unset_server_stream_manager(http_context& ctx) {
|
||||
return ctx.http_server.set_routes([&ctx] (routes& r) { unset_stream_manager(ctx, r); });
|
||||
}
|
||||
|
||||
future<> set_server_cache(http_context& ctx) {
|
||||
return register_api(ctx, "cache_service",
|
||||
"The cache service API", set_cache_service);
|
||||
}
|
||||
|
||||
future<> unset_server_cache(http_context& ctx) {
|
||||
return ctx.http_server.set_routes([&ctx] (routes& r) { unset_cache_service(ctx, r); });
|
||||
}
|
||||
|
||||
future<> set_hinted_handoff(http_context& ctx, sharded<service::storage_proxy>& proxy, sharded<gms::gossiper>& g) {
|
||||
return register_api(ctx, "hinted_handoff",
|
||||
"The hinted handoff API", [&proxy, &g] (http_context& ctx, routes& r) {
|
||||
@@ -274,7 +284,7 @@ future<> unset_hinted_handoff(http_context& ctx) {
|
||||
return ctx.http_server.set_routes([&ctx] (routes& r) { unset_hinted_handoff(ctx, r); });
|
||||
}
|
||||
|
||||
future<> set_server_compaction_manager(http_context& ctx, sharded<compaction::compaction_manager>& cm) {
|
||||
future<> set_server_compaction_manager(http_context& ctx, sharded<compaction_manager>& cm) {
|
||||
return register_api(ctx, "compaction_manager", "The Compaction manager API", [&cm] (http_context& ctx, routes& r) {
|
||||
set_compaction_manager(ctx, r, cm);
|
||||
});
|
||||
@@ -381,5 +391,32 @@ future<> unset_server_raft(http_context& ctx) {
|
||||
return ctx.http_server.set_routes([&ctx] (routes& r) { unset_raft(ctx, r); });
|
||||
}
|
||||
|
||||
void req_params::process(const request& req) {
|
||||
// Process mandatory parameters
|
||||
for (auto& [name, ent] : params) {
|
||||
if (!ent.is_mandatory) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
ent.value = req.get_path_param(name);
|
||||
} catch (std::out_of_range&) {
|
||||
throw httpd::bad_param_exception(fmt::format("Mandatory parameter '{}' was not provided", name));
|
||||
}
|
||||
}
|
||||
|
||||
// Process optional parameters
|
||||
for (auto& [name, value] : req.query_parameters) {
|
||||
try {
|
||||
auto& ent = params.at(name);
|
||||
if (ent.is_mandatory) {
|
||||
throw httpd::bad_param_exception(fmt::format("Parameter '{}' is expected to be provided as part of the request url", name));
|
||||
}
|
||||
ent.value = value;
|
||||
} catch (std::out_of_range&) {
|
||||
throw httpd::bad_param_exception(fmt::format("Unsupported optional parameter '{}'", name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
91
api/api.hh
91
api/api.hh
@@ -23,6 +23,17 @@
|
||||
|
||||
namespace api {
|
||||
|
||||
template<class T>
|
||||
std::vector<sstring> container_to_vec(const T& container) {
|
||||
std::vector<sstring> res;
|
||||
res.reserve(std::size(container));
|
||||
|
||||
for (const auto& i : container) {
|
||||
res.push_back(fmt::to_string(i));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
std::vector<T> map_to_key_value(const std::map<sstring, sstring>& map) {
|
||||
std::vector<T> res;
|
||||
@@ -56,6 +67,17 @@ T map_sum(T&& dest, const S& src) {
|
||||
return std::move(dest);
|
||||
}
|
||||
|
||||
template <typename MAP>
|
||||
std::vector<sstring> map_keys(const MAP& map) {
|
||||
std::vector<sstring> res;
|
||||
res.reserve(std::size(map));
|
||||
|
||||
for (const auto& i : map) {
|
||||
res.push_back(fmt::to_string(i.first));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* General sstring splitting function
|
||||
*/
|
||||
@@ -73,7 +95,7 @@ inline std::vector<sstring> split(const sstring& text, const char* separator) {
|
||||
*
|
||||
*/
|
||||
template<class T, class F, class V>
|
||||
future<json::json_return_type> sum_stats(sharded<T>& d, V F::*f) {
|
||||
future<json::json_return_type> sum_stats(distributed<T>& d, V F::*f) {
|
||||
return d.map_reduce0([f](const T& p) {return p.get_stats().*f;}, 0,
|
||||
std::plus<V>()).then([](V val) {
|
||||
return make_ready_future<json::json_return_type>(val);
|
||||
@@ -106,7 +128,7 @@ httpd::utils_json::rate_moving_average_and_histogram timer_to_json(const utils::
|
||||
}
|
||||
|
||||
template<class T, class F>
|
||||
future<json::json_return_type> sum_histogram_stats(sharded<T>& d, utils::timed_rate_moving_average_and_histogram F::*f) {
|
||||
future<json::json_return_type> sum_histogram_stats(distributed<T>& d, utils::timed_rate_moving_average_and_histogram F::*f) {
|
||||
|
||||
return d.map_reduce0([f](const T& p) {return (p.get_stats().*f).hist;}, utils::ihistogram(),
|
||||
std::plus<utils::ihistogram>()).then([](const utils::ihistogram& val) {
|
||||
@@ -115,7 +137,7 @@ future<json::json_return_type> sum_histogram_stats(sharded<T>& d, utils::timed_
|
||||
}
|
||||
|
||||
template<class T, class F>
|
||||
future<json::json_return_type> sum_timer_stats(sharded<T>& d, utils::timed_rate_moving_average_and_histogram F::*f) {
|
||||
future<json::json_return_type> sum_timer_stats(distributed<T>& d, utils::timed_rate_moving_average_and_histogram F::*f) {
|
||||
|
||||
return d.map_reduce0([f](const T& p) {return (p.get_stats().*f).rate();}, utils::rate_moving_average_and_histogram(),
|
||||
std::plus<utils::rate_moving_average_and_histogram>()).then([](const utils::rate_moving_average_and_histogram& val) {
|
||||
@@ -124,7 +146,7 @@ future<json::json_return_type> sum_timer_stats(sharded<T>& d, utils::timed_rate
|
||||
}
|
||||
|
||||
template<class T, class F>
|
||||
future<json::json_return_type> sum_timer_stats(sharded<T>& d, utils::timed_rate_moving_average_summary_and_histogram F::*f) {
|
||||
future<json::json_return_type> sum_timer_stats(distributed<T>& d, utils::timed_rate_moving_average_summary_and_histogram F::*f) {
|
||||
return d.map_reduce0([f](const T& p) {return (p.get_stats().*f).rate();}, utils::rate_moving_average_and_histogram(),
|
||||
std::plus<utils::rate_moving_average_and_histogram>()).then([](const utils::rate_moving_average_and_histogram& val) {
|
||||
return make_ready_future<json::json_return_type>(timer_to_json(val));
|
||||
@@ -230,6 +252,67 @@ public:
|
||||
operator T() const { return value; }
|
||||
};
|
||||
|
||||
using mandatory = bool_class<struct mandatory_tag>;
|
||||
|
||||
class req_params {
|
||||
public:
|
||||
struct def {
|
||||
std::optional<sstring> value;
|
||||
mandatory is_mandatory = mandatory::no;
|
||||
|
||||
def(std::optional<sstring> value_ = std::nullopt, mandatory is_mandatory_ = mandatory::no)
|
||||
: value(std::move(value_))
|
||||
, is_mandatory(is_mandatory_)
|
||||
{ }
|
||||
|
||||
def(mandatory is_mandatory_)
|
||||
: is_mandatory(is_mandatory_)
|
||||
{ }
|
||||
};
|
||||
|
||||
private:
|
||||
std::unordered_map<sstring, def> params;
|
||||
|
||||
public:
|
||||
req_params(std::initializer_list<std::pair<sstring, def>> l) {
|
||||
for (const auto& [name, ent] : l) {
|
||||
add(std::move(name), std::move(ent));
|
||||
}
|
||||
}
|
||||
|
||||
void add(sstring name, def ent) {
|
||||
params.emplace(std::move(name), std::move(ent));
|
||||
}
|
||||
|
||||
void process(const request& req);
|
||||
|
||||
const std::optional<sstring>& get(const char* name) const {
|
||||
return params.at(name).value;
|
||||
}
|
||||
|
||||
template <typename T = sstring>
|
||||
const std::optional<T> get_as(const char* name) const {
|
||||
return get(name);
|
||||
}
|
||||
|
||||
template <typename T = sstring>
|
||||
requires std::same_as<T, bool>
|
||||
const std::optional<bool> get_as(const char* name) const {
|
||||
auto value = get(name);
|
||||
if (!value) {
|
||||
return std::nullopt;
|
||||
}
|
||||
std::transform(value->begin(), value->end(), value->begin(), ::tolower);
|
||||
if (value == "true" || value == "yes" || value == "1") {
|
||||
return true;
|
||||
}
|
||||
if (value == "false" || value == "no" || value == "0") {
|
||||
return false;
|
||||
}
|
||||
throw boost::bad_lexical_cast{};
|
||||
}
|
||||
};
|
||||
|
||||
httpd::utils_json::estimated_histogram time_to_json_histogram(const utils::time_estimated_histogram& val);
|
||||
|
||||
}
|
||||
|
||||
@@ -18,9 +18,7 @@
|
||||
using request = http::request;
|
||||
using reply = http::reply;
|
||||
|
||||
namespace compaction {
|
||||
class compaction_manager;
|
||||
}
|
||||
|
||||
namespace service {
|
||||
|
||||
@@ -58,6 +56,7 @@ class sstables_format_selector;
|
||||
namespace view {
|
||||
class view_builder;
|
||||
}
|
||||
class system_keyspace;
|
||||
}
|
||||
namespace netw { class messaging_service; }
|
||||
class repair_service;
|
||||
@@ -84,9 +83,9 @@ struct http_context {
|
||||
sstring api_dir;
|
||||
sstring api_doc;
|
||||
httpd::http_server_control http_server;
|
||||
sharded<replica::database>& db;
|
||||
distributed<replica::database>& db;
|
||||
|
||||
http_context(sharded<replica::database>& _db)
|
||||
http_context(distributed<replica::database>& _db)
|
||||
: db(_db)
|
||||
{
|
||||
}
|
||||
@@ -117,7 +116,7 @@ future<> set_server_token_metadata(http_context& ctx, sharded<locator::shared_to
|
||||
future<> unset_server_token_metadata(http_context& ctx);
|
||||
future<> set_server_gossip(http_context& ctx, sharded<gms::gossiper>& g);
|
||||
future<> unset_server_gossip(http_context& ctx);
|
||||
future<> set_server_column_family(http_context& ctx, sharded<replica::database>& db);
|
||||
future<> set_server_column_family(http_context& ctx, sharded<db::system_keyspace>& sys_ks);
|
||||
future<> unset_server_column_family(http_context& ctx);
|
||||
future<> set_server_messaging_service(http_context& ctx, sharded<netw::messaging_service>& ms);
|
||||
future<> unset_server_messaging_service(http_context& ctx);
|
||||
@@ -127,7 +126,9 @@ future<> set_server_stream_manager(http_context& ctx, sharded<streaming::stream_
|
||||
future<> unset_server_stream_manager(http_context& ctx);
|
||||
future<> set_hinted_handoff(http_context& ctx, sharded<service::storage_proxy>& p, sharded<gms::gossiper>& g);
|
||||
future<> unset_hinted_handoff(http_context& ctx);
|
||||
future<> set_server_compaction_manager(http_context& ctx, sharded<compaction::compaction_manager>& cm);
|
||||
future<> set_server_cache(http_context& ctx);
|
||||
future<> unset_server_cache(http_context& ctx);
|
||||
future<> set_server_compaction_manager(http_context& ctx, sharded<compaction_manager>& cm);
|
||||
future<> unset_server_compaction_manager(http_context& ctx);
|
||||
future<> set_server_done(http_context& ctx);
|
||||
future<> set_server_task_manager(http_context& ctx, sharded<tasks::task_manager>& tm, lw_shared_ptr<db::config> cfg, sharded<gms::gossiper>& gossiper);
|
||||
@@ -140,6 +141,8 @@ future<> set_server_raft(http_context&, sharded<service::raft_group_registry>&);
|
||||
future<> unset_server_raft(http_context&);
|
||||
future<> set_load_meter(http_context& ctx, service::load_meter& lm);
|
||||
future<> unset_load_meter(http_context& ctx);
|
||||
future<> set_format_selector(http_context& ctx, db::sstables_format_selector& sel);
|
||||
future<> unset_format_selector(http_context& ctx);
|
||||
future<> set_server_cql_server_test(http_context& ctx, cql_transport::controller& ctl);
|
||||
future<> unset_server_cql_server_test(http_context& ctx);
|
||||
future<> set_server_service_levels(http_context& ctx, cql_transport::controller& ctl, sharded<cql3::query_processor>& qp);
|
||||
|
||||
@@ -16,7 +16,7 @@ using namespace json;
|
||||
using namespace seastar::httpd;
|
||||
namespace cs = httpd::cache_service_json;
|
||||
|
||||
void set_cache_service(http_context& ctx, sharded<replica::database>& db, routes& r) {
|
||||
void set_cache_service(http_context& ctx, routes& r) {
|
||||
cs::get_row_cache_save_period_in_seconds.set(r, [](std::unique_ptr<http::request> req) {
|
||||
// We never save the cache
|
||||
// Origin uses 0 for never
|
||||
@@ -204,53 +204,53 @@ void set_cache_service(http_context& ctx, sharded<replica::database>& db, routes
|
||||
});
|
||||
});
|
||||
|
||||
cs::get_row_hits.set(r, [&db] (std::unique_ptr<http::request> req) {
|
||||
return map_reduce_cf(db, uint64_t(0), [](const replica::column_family& cf) {
|
||||
cs::get_row_hits.set(r, [&ctx] (std::unique_ptr<http::request> req) {
|
||||
return map_reduce_cf(ctx, uint64_t(0), [](const replica::column_family& cf) {
|
||||
return cf.get_row_cache().stats().hits.count();
|
||||
}, std::plus<uint64_t>());
|
||||
});
|
||||
|
||||
cs::get_row_requests.set(r, [&db] (std::unique_ptr<http::request> req) {
|
||||
return map_reduce_cf(db, uint64_t(0), [](const replica::column_family& cf) {
|
||||
cs::get_row_requests.set(r, [&ctx] (std::unique_ptr<http::request> req) {
|
||||
return map_reduce_cf(ctx, uint64_t(0), [](const replica::column_family& cf) {
|
||||
return cf.get_row_cache().stats().hits.count() + cf.get_row_cache().stats().misses.count();
|
||||
}, std::plus<uint64_t>());
|
||||
});
|
||||
|
||||
cs::get_row_hit_rate.set(r, [&db] (std::unique_ptr<http::request> req) {
|
||||
return map_reduce_cf(db, ratio_holder(), [](const replica::column_family& cf) {
|
||||
cs::get_row_hit_rate.set(r, [&ctx] (std::unique_ptr<http::request> req) {
|
||||
return map_reduce_cf(ctx, ratio_holder(), [](const replica::column_family& cf) {
|
||||
return ratio_holder(cf.get_row_cache().stats().hits.count() + cf.get_row_cache().stats().misses.count(),
|
||||
cf.get_row_cache().stats().hits.count());
|
||||
}, std::plus<ratio_holder>());
|
||||
});
|
||||
|
||||
cs::get_row_hits_moving_avrage.set(r, [&db] (std::unique_ptr<http::request> req) {
|
||||
return map_reduce_cf_raw(db, utils::rate_moving_average(), [](const replica::column_family& cf) {
|
||||
cs::get_row_hits_moving_avrage.set(r, [&ctx] (std::unique_ptr<http::request> req) {
|
||||
return map_reduce_cf_raw(ctx, utils::rate_moving_average(), [](const replica::column_family& cf) {
|
||||
return cf.get_row_cache().stats().hits.rate();
|
||||
}, std::plus<utils::rate_moving_average>()).then([](const utils::rate_moving_average& m) {
|
||||
return make_ready_future<json::json_return_type>(meter_to_json(m));
|
||||
});
|
||||
});
|
||||
|
||||
cs::get_row_requests_moving_avrage.set(r, [&db] (std::unique_ptr<http::request> req) {
|
||||
return map_reduce_cf_raw(db, utils::rate_moving_average(), [](const replica::column_family& cf) {
|
||||
cs::get_row_requests_moving_avrage.set(r, [&ctx] (std::unique_ptr<http::request> req) {
|
||||
return map_reduce_cf_raw(ctx, utils::rate_moving_average(), [](const replica::column_family& cf) {
|
||||
return cf.get_row_cache().stats().hits.rate() + cf.get_row_cache().stats().misses.rate();
|
||||
}, std::plus<utils::rate_moving_average>()).then([](const utils::rate_moving_average& m) {
|
||||
return make_ready_future<json::json_return_type>(meter_to_json(m));
|
||||
});
|
||||
});
|
||||
|
||||
cs::get_row_size.set(r, [&db] (std::unique_ptr<http::request> req) {
|
||||
cs::get_row_size.set(r, [&ctx] (std::unique_ptr<http::request> req) {
|
||||
// In origin row size is the weighted size.
|
||||
// We currently do not support weights, so we use raw size in bytes instead
|
||||
return db.map_reduce0([](replica::database& db) -> uint64_t {
|
||||
return ctx.db.map_reduce0([](replica::database& db) -> uint64_t {
|
||||
return db.row_cache_tracker().region().occupancy().used_space();
|
||||
}, uint64_t(0), std::plus<uint64_t>()).then([](const int64_t& res) {
|
||||
return make_ready_future<json::json_return_type>(res);
|
||||
});
|
||||
});
|
||||
|
||||
cs::get_row_entries.set(r, [&db] (std::unique_ptr<http::request> req) {
|
||||
return db.map_reduce0([](replica::database& db) -> uint64_t {
|
||||
cs::get_row_entries.set(r, [&ctx] (std::unique_ptr<http::request> req) {
|
||||
return ctx.db.map_reduce0([](replica::database& db) -> uint64_t {
|
||||
return db.row_cache_tracker().partitions();
|
||||
}, uint64_t(0), std::plus<uint64_t>()).then([](const int64_t& res) {
|
||||
return make_ready_future<json::json_return_type>(res);
|
||||
|
||||
@@ -7,20 +7,15 @@
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <seastar/core/sharded.hh>
|
||||
|
||||
namespace seastar::httpd {
|
||||
class routes;
|
||||
}
|
||||
|
||||
namespace replica {
|
||||
class database;
|
||||
}
|
||||
|
||||
namespace api {
|
||||
|
||||
struct http_context;
|
||||
void set_cache_service(http_context& ctx, seastar::sharded<replica::database>& db, seastar::httpd::routes& r);
|
||||
void set_cache_service(http_context& ctx, seastar::httpd::routes& r);
|
||||
void unset_cache_service(http_context& ctx, seastar::httpd::routes& r);
|
||||
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -13,25 +13,25 @@
|
||||
#include <any>
|
||||
#include "api/api_init.hh"
|
||||
|
||||
namespace db {
|
||||
class system_keyspace;
|
||||
}
|
||||
|
||||
namespace api {
|
||||
|
||||
void set_column_family(http_context& ctx, httpd::routes& r, sharded<replica::database>& db);
|
||||
void set_column_family(http_context& ctx, httpd::routes& r, sharded<db::system_keyspace>& sys_ks);
|
||||
void unset_column_family(http_context& ctx, httpd::routes& r);
|
||||
|
||||
table_info parse_table_info(const sstring& name, const replica::database& db);
|
||||
|
||||
template<class Mapper, class I, class Reducer>
|
||||
future<I> map_reduce_cf_raw(sharded<replica::database>& db, const sstring& name, I init,
|
||||
future<I> map_reduce_cf_raw(http_context& ctx, const sstring& name, I init,
|
||||
Mapper mapper, Reducer reducer) {
|
||||
auto uuid = parse_table_info(name, db.local()).id;
|
||||
using mapper_type = std::function<future<std::unique_ptr<std::any>>(replica::database&)>;
|
||||
auto uuid = parse_table_info(name, ctx.db.local()).id;
|
||||
using mapper_type = std::function<std::unique_ptr<std::any>(replica::database&)>;
|
||||
using reducer_type = std::function<std::unique_ptr<std::any>(std::unique_ptr<std::any>, std::unique_ptr<std::any>)>;
|
||||
return db.map_reduce0(mapper_type([mapper, uuid](replica::database& db) {
|
||||
return futurize_invoke([mapper, &db, uuid] {
|
||||
return mapper(db.find_column_family(uuid));
|
||||
}).then([] (auto result) {
|
||||
return std::make_unique<std::any>(I(std::move(result)));
|
||||
});
|
||||
return ctx.db.map_reduce0(mapper_type([mapper, uuid](replica::database& db) {
|
||||
return std::make_unique<std::any>(I(mapper(db.find_column_family(uuid))));
|
||||
}), std::make_unique<std::any>(std::move(init)), reducer_type([reducer = std::move(reducer)] (std::unique_ptr<std::any> a, std::unique_ptr<std::any> b) mutable {
|
||||
return std::make_unique<std::any>(I(reducer(std::any_cast<I>(std::move(*a)), std::any_cast<I>(std::move(*b)))));
|
||||
})).then([] (std::unique_ptr<std::any> r) {
|
||||
@@ -41,30 +41,33 @@ future<I> map_reduce_cf_raw(sharded<replica::database>& db, const sstring& name,
|
||||
|
||||
|
||||
template<class Mapper, class I, class Reducer>
|
||||
future<json::json_return_type> map_reduce_cf(sharded<replica::database>& db, const sstring& name, I init,
|
||||
future<json::json_return_type> map_reduce_cf(http_context& ctx, const sstring& name, I init,
|
||||
Mapper mapper, Reducer reducer) {
|
||||
return map_reduce_cf_raw(db, name, init, mapper, reducer).then([](const I& res) {
|
||||
return map_reduce_cf_raw(ctx, name, init, mapper, reducer).then([](const I& res) {
|
||||
return make_ready_future<json::json_return_type>(res);
|
||||
});
|
||||
}
|
||||
|
||||
template<class Mapper, class I, class Reducer, class Result>
|
||||
future<json::json_return_type> map_reduce_cf(sharded<replica::database>& db, const sstring& name, I init,
|
||||
future<json::json_return_type> map_reduce_cf(http_context& ctx, const sstring& name, I init,
|
||||
Mapper mapper, Reducer reducer, Result result) {
|
||||
return map_reduce_cf_raw(db, name, init, mapper, reducer).then([result](const I& res) mutable {
|
||||
return map_reduce_cf_raw(ctx, name, init, mapper, reducer).then([result](const I& res) mutable {
|
||||
result = res;
|
||||
return make_ready_future<json::json_return_type>(result);
|
||||
});
|
||||
}
|
||||
|
||||
future<json::json_return_type> map_reduce_cf_time_histogram(http_context& ctx, const sstring& name, std::function<utils::time_estimated_histogram(const replica::column_family&)> f);
|
||||
|
||||
struct map_reduce_column_families_locally {
|
||||
std::any init;
|
||||
std::function<future<std::unique_ptr<std::any>>(replica::column_family&)> mapper;
|
||||
std::function<std::unique_ptr<std::any>(replica::column_family&)> mapper;
|
||||
std::function<std::unique_ptr<std::any>(std::unique_ptr<std::any>, std::unique_ptr<std::any>)> reducer;
|
||||
future<std::unique_ptr<std::any>> operator()(replica::database& db) const {
|
||||
auto res = seastar::make_lw_shared<std::unique_ptr<std::any>>(std::make_unique<std::any>(init));
|
||||
return db.get_tables_metadata().for_each_table_gently([res, this] (table_id, seastar::lw_shared_ptr<replica::table> table) -> future<> {
|
||||
*res = reducer(std::move(*res), co_await mapper(*table.get()));
|
||||
return db.get_tables_metadata().for_each_table_gently([res, this] (table_id, seastar::lw_shared_ptr<replica::table> table) {
|
||||
*res = reducer(std::move(*res), mapper(*table.get()));
|
||||
return make_ready_future();
|
||||
}).then([res] () {
|
||||
return std::move(*res);
|
||||
});
|
||||
@@ -72,21 +75,17 @@ struct map_reduce_column_families_locally {
|
||||
};
|
||||
|
||||
template<class Mapper, class I, class Reducer>
|
||||
future<I> map_reduce_cf_raw(sharded<replica::database>& db, I init,
|
||||
future<I> map_reduce_cf_raw(http_context& ctx, I init,
|
||||
Mapper mapper, Reducer reducer) {
|
||||
using mapper_type = std::function<future<std::unique_ptr<std::any>>(replica::column_family&)>;
|
||||
using mapper_type = std::function<std::unique_ptr<std::any>(replica::column_family&)>;
|
||||
using reducer_type = std::function<std::unique_ptr<std::any>(std::unique_ptr<std::any>, std::unique_ptr<std::any>)>;
|
||||
auto wrapped_mapper = mapper_type([mapper = std::move(mapper)] (replica::column_family& cf) mutable {
|
||||
return futurize_invoke([&cf, mapper] {
|
||||
return mapper(cf);
|
||||
}).then([] (auto result) {
|
||||
return std::make_unique<std::any>(I(std::move(result)));
|
||||
});
|
||||
return std::make_unique<std::any>(I(mapper(cf)));
|
||||
});
|
||||
auto wrapped_reducer = reducer_type([reducer = std::move(reducer)] (std::unique_ptr<std::any> a, std::unique_ptr<std::any> b) mutable {
|
||||
return std::make_unique<std::any>(I(reducer(std::any_cast<I>(std::move(*a)), std::any_cast<I>(std::move(*b)))));
|
||||
});
|
||||
return db.map_reduce0(map_reduce_column_families_locally{init,
|
||||
return ctx.db.map_reduce0(map_reduce_column_families_locally{init,
|
||||
std::move(wrapped_mapper), wrapped_reducer}, std::make_unique<std::any>(init), wrapped_reducer).then([] (std::unique_ptr<std::any> res) {
|
||||
return std::any_cast<I>(std::move(*res));
|
||||
});
|
||||
@@ -94,13 +93,20 @@ future<I> map_reduce_cf_raw(sharded<replica::database>& db, I init,
|
||||
|
||||
|
||||
template<class Mapper, class I, class Reducer>
|
||||
future<json::json_return_type> map_reduce_cf(sharded<replica::database>& db, I init,
|
||||
future<json::json_return_type> map_reduce_cf(http_context& ctx, I init,
|
||||
Mapper mapper, Reducer reducer) {
|
||||
return map_reduce_cf_raw(db, init, mapper, reducer).then([](const I& res) {
|
||||
return map_reduce_cf_raw(ctx, init, mapper, reducer).then([](const I& res) {
|
||||
return make_ready_future<json::json_return_type>(res);
|
||||
});
|
||||
}
|
||||
|
||||
future<json::json_return_type> get_cf_stats(http_context& ctx, const sstring& name,
|
||||
int64_t replica::column_family_stats::*f);
|
||||
|
||||
future<json::json_return_type> get_cf_stats(http_context& ctx,
|
||||
int64_t replica::column_family_stats::*f);
|
||||
|
||||
|
||||
std::tuple<sstring, sstring> parse_fully_qualified_cf_name(sstring name);
|
||||
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
#include "api/api.hh"
|
||||
#include "api/api-doc/compaction_manager.json.hh"
|
||||
#include "api/api-doc/storage_service.json.hh"
|
||||
#include "db/compaction_history_entry.hh"
|
||||
#include "db/system_keyspace.hh"
|
||||
#include "column_family.hh"
|
||||
#include "unimplemented.hh"
|
||||
@@ -29,9 +28,9 @@ namespace ss = httpd::storage_service_json;
|
||||
using namespace json;
|
||||
using namespace seastar::httpd;
|
||||
|
||||
static future<json::json_return_type> get_cm_stats(sharded<compaction::compaction_manager>& cm,
|
||||
int64_t compaction::compaction_manager::stats::*f) {
|
||||
return cm.map_reduce0([f](compaction::compaction_manager& cm) {
|
||||
static future<json::json_return_type> get_cm_stats(sharded<compaction_manager>& cm,
|
||||
int64_t compaction_manager::stats::*f) {
|
||||
return cm.map_reduce0([f](compaction_manager& cm) {
|
||||
return cm.get_stats().*f;
|
||||
}, int64_t(0), std::plus<int64_t>()).then([](const int64_t& res) {
|
||||
return make_ready_future<json::json_return_type>(res);
|
||||
@@ -47,9 +46,9 @@ static std::unordered_map<std::pair<sstring, sstring>, uint64_t, utils::tuple_ha
|
||||
return std::move(a);
|
||||
}
|
||||
|
||||
void set_compaction_manager(http_context& ctx, routes& r, sharded<compaction::compaction_manager>& cm) {
|
||||
void set_compaction_manager(http_context& ctx, routes& r, sharded<compaction_manager>& cm) {
|
||||
cm::get_compactions.set(r, [&cm] (std::unique_ptr<http::request> req) {
|
||||
return cm.map_reduce0([](compaction::compaction_manager& cm) {
|
||||
return cm.map_reduce0([](compaction_manager& cm) {
|
||||
std::vector<cm::summary> summaries;
|
||||
|
||||
for (const auto& c : cm.get_compactions()) {
|
||||
@@ -58,7 +57,7 @@ void set_compaction_manager(http_context& ctx, routes& r, sharded<compaction::co
|
||||
s.ks = c.ks_name;
|
||||
s.cf = c.cf_name;
|
||||
s.unit = "keys";
|
||||
s.task_type = compaction::compaction_name(c.type);
|
||||
s.task_type = sstables::compaction_name(c.type);
|
||||
s.completed = c.total_keys_written;
|
||||
s.total = c.total_partitions;
|
||||
summaries.push_back(std::move(s));
|
||||
@@ -72,9 +71,10 @@ void set_compaction_manager(http_context& ctx, routes& r, sharded<compaction::co
|
||||
cm::get_pending_tasks_by_table.set(r, [&ctx] (std::unique_ptr<http::request> req) {
|
||||
return ctx.db.map_reduce0([](replica::database& db) {
|
||||
return do_with(std::unordered_map<std::pair<sstring, sstring>, uint64_t, utils::tuple_hash>(), [&db](std::unordered_map<std::pair<sstring, sstring>, uint64_t, utils::tuple_hash>& tasks) {
|
||||
return db.get_tables_metadata().for_each_table_gently([&tasks] (table_id, lw_shared_ptr<replica::table> table) -> future<> {
|
||||
return db.get_tables_metadata().for_each_table_gently([&tasks] (table_id, lw_shared_ptr<replica::table> table) {
|
||||
replica::table& cf = *table.get();
|
||||
tasks[std::make_pair(cf.schema()->ks_name(), cf.schema()->cf_name())] = co_await cf.estimate_pending_compactions();
|
||||
tasks[std::make_pair(cf.schema()->ks_name(), cf.schema()->cf_name())] = cf.estimate_pending_compactions();
|
||||
return make_ready_future<>();
|
||||
}).then([&tasks] {
|
||||
return std::move(tasks);
|
||||
});
|
||||
@@ -103,20 +103,23 @@ void set_compaction_manager(http_context& ctx, routes& r, sharded<compaction::co
|
||||
|
||||
cm::stop_compaction.set(r, [&cm] (std::unique_ptr<http::request> req) {
|
||||
auto type = req->get_query_param("type");
|
||||
return cm.invoke_on_all([type] (compaction::compaction_manager& cm) {
|
||||
return cm.invoke_on_all([type] (compaction_manager& cm) {
|
||||
return cm.stop_compaction(type);
|
||||
}).then([] {
|
||||
return make_ready_future<json::json_return_type>(json_void());
|
||||
});
|
||||
});
|
||||
|
||||
cm::stop_keyspace_compaction.set(r, [&ctx, &cm] (std::unique_ptr<http::request> req) -> future<json::json_return_type> {
|
||||
auto [ks_name, tables] = parse_table_infos(ctx, *req, "tables");
|
||||
cm::stop_keyspace_compaction.set(r, [&ctx] (std::unique_ptr<http::request> req) -> future<json::json_return_type> {
|
||||
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 cm.invoke_on_all([&] (compaction::compaction_manager& cm) {
|
||||
co_await ctx.db.invoke_on_all([&] (replica::database& db) {
|
||||
auto& cm = db.get_compaction_manager();
|
||||
return parallel_for_each(tables, [&] (const table_info& ti) {
|
||||
return cm.stop_compaction(type, [id = ti.id] (const compaction::compaction_group_view* x) {
|
||||
return x->schema()->id() == id;
|
||||
auto& t = db.find_column_family(ti.id);
|
||||
return t.parallel_foreach_table_state([&] (compaction::table_state& ts) {
|
||||
return cm.stop_compaction(type, &ts);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -124,13 +127,13 @@ void set_compaction_manager(http_context& ctx, routes& r, sharded<compaction::co
|
||||
});
|
||||
|
||||
cm::get_pending_tasks.set(r, [&ctx] (std::unique_ptr<http::request> req) {
|
||||
return map_reduce_cf(ctx.db, int64_t(0), [](replica::column_family& cf) {
|
||||
return map_reduce_cf(ctx, int64_t(0), [](replica::column_family& cf) {
|
||||
return cf.estimate_pending_compactions();
|
||||
}, std::plus<int64_t>());
|
||||
});
|
||||
|
||||
cm::get_completed_tasks.set(r, [&cm] (std::unique_ptr<http::request> req) {
|
||||
return get_cm_stats(cm, &compaction::compaction_manager::stats::completed_tasks);
|
||||
return get_cm_stats(cm, &compaction_manager::stats::completed_tasks);
|
||||
});
|
||||
|
||||
cm::get_total_compactions_completed.set(r, [] (std::unique_ptr<http::request> req) {
|
||||
@@ -148,7 +151,7 @@ void set_compaction_manager(http_context& ctx, routes& r, sharded<compaction::co
|
||||
});
|
||||
|
||||
cm::get_compaction_history.set(r, [&cm] (std::unique_ptr<http::request> req) {
|
||||
noncopyable_function<future<>(output_stream<char>&&)> f = [&cm] (output_stream<char>&& out) -> future<> {
|
||||
std::function<future<>(output_stream<char>&&)> f = [&cm] (output_stream<char>&& out) -> future<> {
|
||||
auto s = std::move(out);
|
||||
bool first = true;
|
||||
std::exception_ptr ex;
|
||||
@@ -157,11 +160,8 @@ void set_compaction_manager(http_context& ctx, routes& r, sharded<compaction::co
|
||||
co_await cm.local().get_compaction_history([&s, &first](const db::compaction_history_entry& entry) mutable -> future<> {
|
||||
cm::history h;
|
||||
h.id = fmt::to_string(entry.id);
|
||||
h.shard_id = entry.shard_id;
|
||||
h.ks = std::move(entry.ks);
|
||||
h.cf = std::move(entry.cf);
|
||||
h.compaction_type = entry.compaction_type;
|
||||
h.started_at = entry.started_at;
|
||||
h.compacted_at = entry.compacted_at;
|
||||
h.bytes_in = entry.bytes_in;
|
||||
h.bytes_out = entry.bytes_out;
|
||||
@@ -173,24 +173,6 @@ void set_compaction_manager(http_context& ctx, routes& r, sharded<compaction::co
|
||||
e.value = it.second;
|
||||
h.rows_merged.push(std::move(e));
|
||||
}
|
||||
for (const auto& data : entry.sstables_in) {
|
||||
httpd::compaction_manager_json::sstableinfo sstable;
|
||||
sstable.generation = fmt::to_string(data.generation),
|
||||
sstable.origin = data.origin,
|
||||
sstable.size = data.size,
|
||||
h.sstables_in.push(std::move(sstable));
|
||||
}
|
||||
for (const auto& data : entry.sstables_out) {
|
||||
httpd::compaction_manager_json::sstableinfo sstable;
|
||||
sstable.generation = fmt::to_string(data.generation),
|
||||
sstable.origin = data.origin,
|
||||
sstable.size = data.size,
|
||||
h.sstables_out.push(std::move(sstable));
|
||||
}
|
||||
h.total_tombstone_purge_attempt = entry.total_tombstone_purge_attempt;
|
||||
h.total_tombstone_purge_failure_due_to_overlapping_with_memtable = entry.total_tombstone_purge_failure_due_to_overlapping_with_memtable;
|
||||
h.total_tombstone_purge_failure_due_to_overlapping_with_uncompacting_sstable = entry.total_tombstone_purge_failure_due_to_overlapping_with_uncompacting_sstable;
|
||||
|
||||
if (!first) {
|
||||
co_await s.write(", ");
|
||||
}
|
||||
|
||||
@@ -13,13 +13,11 @@ namespace seastar::httpd {
|
||||
class routes;
|
||||
}
|
||||
|
||||
namespace compaction {
|
||||
class compaction_manager;
|
||||
}
|
||||
|
||||
namespace api {
|
||||
struct http_context;
|
||||
void set_compaction_manager(http_context& ctx, seastar::httpd::routes& r, seastar::sharded<compaction::compaction_manager>& cm);
|
||||
void set_compaction_manager(http_context& ctx, seastar::httpd::routes& r, seastar::sharded<compaction_manager>& cm);
|
||||
void unset_compaction_manager(http_context& ctx, seastar::httpd::routes& r);
|
||||
|
||||
}
|
||||
|
||||
@@ -23,6 +23,22 @@ using namespace seastar::httpd;
|
||||
namespace sp = httpd::storage_proxy_json;
|
||||
namespace ss = httpd::storage_service_json;
|
||||
|
||||
template<class T>
|
||||
json::json_return_type get_json_return_type(const T& val) {
|
||||
return json::json_return_type(val);
|
||||
}
|
||||
|
||||
/*
|
||||
* As commented on db::seed_provider_type is not used
|
||||
* and probably never will.
|
||||
*
|
||||
* Just in case, we will return its name
|
||||
*/
|
||||
template<>
|
||||
json::json_return_type get_json_return_type(const db::seed_provider_type& val) {
|
||||
return json::json_return_type(val.class_name);
|
||||
}
|
||||
|
||||
std::string_view format_type(std::string_view type) {
|
||||
if (type == "int") {
|
||||
return "integer";
|
||||
@@ -171,7 +187,7 @@ void set_config(std::shared_ptr < api_registry_builder20 > rb, http_context& ctx
|
||||
});
|
||||
|
||||
ss::get_all_data_file_locations.set(r, [&cfg](const_req req) {
|
||||
return cfg.data_file_directories();
|
||||
return container_to_vec(cfg.data_file_directories());
|
||||
});
|
||||
|
||||
ss::get_saved_caches_location.set(r, [&cfg](const_req req) {
|
||||
|
||||
@@ -21,10 +21,10 @@ namespace hf = httpd::error_injection_json;
|
||||
|
||||
void set_error_injection(http_context& ctx, routes& r) {
|
||||
|
||||
hf::enable_injection.set(r, [](std::unique_ptr<request> req) -> future<json::json_return_type> {
|
||||
hf::enable_injection.set(r, [](std::unique_ptr<request> req) {
|
||||
sstring injection = req->get_path_param("injection");
|
||||
bool one_shot = req->get_query_param("one_shot") == "True";
|
||||
auto params = co_await util::read_entire_stream_contiguous(*req->content_stream);
|
||||
auto params = req->content;
|
||||
|
||||
const size_t max_params_size = 1024 * 1024;
|
||||
if (params.size() > max_params_size) {
|
||||
@@ -39,11 +39,12 @@ void set_error_injection(http_context& ctx, routes& r) {
|
||||
: rjson::parse_to_map<utils::error_injection_parameters>(params);
|
||||
|
||||
auto& errinj = utils::get_local_injector();
|
||||
co_await errinj.enable_on_all(injection, one_shot, std::move(parameters));
|
||||
return errinj.enable_on_all(injection, one_shot, std::move(parameters)).then([] {
|
||||
return make_ready_future<json::json_return_type>(json::json_void());
|
||||
});
|
||||
} catch (const rjson::error& e) {
|
||||
throw httpd::bad_param_exception(format("Failed to parse injections parameters: {}", e.what()));
|
||||
}
|
||||
co_return json::json_void();
|
||||
});
|
||||
|
||||
hf::get_enabled_injections_on_all.set(r, [](std::unique_ptr<request> req) {
|
||||
|
||||
@@ -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();
|
||||
@@ -40,9 +40,7 @@ void set_failure_detector(http_context& ctx, routes& r, gms::gossiper& g) {
|
||||
}
|
||||
res.emplace_back(std::move(val));
|
||||
});
|
||||
return make_ready_future<json::json_return_type>(json::stream_range_as_array(res, [](const fd::endpoint_state& i){
|
||||
return i;
|
||||
}));
|
||||
return make_ready_future<json::json_return_type>(res);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -66,15 +64,11 @@ 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::vector<fd::mapper> nodes_status;
|
||||
nodes_status.reserve(g.num_endpoints());
|
||||
g.for_each_endpoint_state([&] (const gms::endpoint_state& es) {
|
||||
fd::mapper val;
|
||||
val.key = fmt::to_string(es.get_ip());
|
||||
val.value = g.is_alive(es.get_host_id()) ? "UP" : "DOWN";
|
||||
nodes_status.emplace_back(std::move(val));
|
||||
std::map<sstring, sstring> nodes_status;
|
||||
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>(std::move(nodes_status));
|
||||
return make_ready_future<json::json_return_type>(map_to_key_value<fd::mapper>(nodes_status));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -87,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")));
|
||||
}
|
||||
|
||||
@@ -21,45 +21,51 @@ using namespace json;
|
||||
void set_gossiper(http_context& ctx, routes& r, gms::gossiper& g) {
|
||||
httpd::gossiper_json::get_down_endpoint.set(r, [&g] (std::unique_ptr<request> req) -> future<json::json_return_type> {
|
||||
auto res = co_await g.get_unreachable_members_synchronized();
|
||||
co_return json::json_return_type(res | std::views::transform([] (auto& ep) { return fmt::to_string(ep); }) | std::ranges::to<std::vector>());
|
||||
co_return json::json_return_type(container_to_vec(res));
|
||||
});
|
||||
|
||||
|
||||
httpd::gossiper_json::get_live_endpoint.set(r, [&g] (std::unique_ptr<request> req) -> future<json::json_return_type> {
|
||||
auto res = co_await g.get_live_members_synchronized();
|
||||
co_return json::json_return_type(res | std::views::transform([] (auto& ep) { return fmt::to_string(ep); }) | std::ranges::to<std::vector>());
|
||||
httpd::gossiper_json::get_live_endpoint.set(r, [&g] (std::unique_ptr<request> req) {
|
||||
return g.get_live_members_synchronized().then([] (auto res) {
|
||||
return make_ready_future<json::json_return_type>(container_to_vec(res));
|
||||
});
|
||||
});
|
||||
|
||||
httpd::gossiper_json::get_endpoint_downtime.set(r, [&g] (std::unique_ptr<request> req) -> future<json::json_return_type> {
|
||||
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();
|
||||
});
|
||||
|
||||
@@ -71,7 +71,7 @@ void set_raft(http_context&, httpd::routes& r, sharded<service::raft_group_regis
|
||||
co_return json_void{};
|
||||
});
|
||||
r::get_leader_host.set(r, [&raft_gr] (std::unique_ptr<http::request> req) -> future<json_return_type> {
|
||||
if (req->get_query_param("group_id").empty()) {
|
||||
if (!req->query_parameters.contains("group_id")) {
|
||||
const auto leader_id = co_await raft_gr.invoke_on(0, [] (service::raft_group_registry& raft_gr) {
|
||||
auto& srv = raft_gr.group0();
|
||||
return srv.current_leader();
|
||||
@@ -100,7 +100,7 @@ void set_raft(http_context&, httpd::routes& r, sharded<service::raft_group_regis
|
||||
r::read_barrier.set(r, [&raft_gr] (std::unique_ptr<http::request> req) -> future<json_return_type> {
|
||||
auto timeout = get_request_timeout(*req);
|
||||
|
||||
if (req->get_query_param("group_id").empty()) {
|
||||
if (!req->query_parameters.contains("group_id")) {
|
||||
// Read barrier on group 0 by default
|
||||
co_await raft_gr.invoke_on(0, [timeout] (service::raft_group_registry& raft_gr) -> future<> {
|
||||
co_await raft_gr.group0_with_timeouts().read_barrier(nullptr, timeout);
|
||||
@@ -131,7 +131,7 @@ void set_raft(http_context&, httpd::routes& r, sharded<service::raft_group_regis
|
||||
const auto stepdown_timeout_ticks = dur / service::raft_tick_interval;
|
||||
auto timeout_dur = raft::logical_clock::duration(stepdown_timeout_ticks);
|
||||
|
||||
if (req->get_query_param("group_id").empty()) {
|
||||
if (!req->query_parameters.contains("group_id")) {
|
||||
// Stepdown on group 0 by default
|
||||
co_await raft_gr.invoke_on(0, [timeout_dur] (service::raft_group_registry& raft_gr) {
|
||||
apilog.info("Triggering stepdown for group0");
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ utils::time_estimated_histogram timed_rate_moving_average_summary_merge(utils::t
|
||||
* @return A future that resolves to the result of the aggregation.
|
||||
*/
|
||||
template<typename V, typename Reducer, typename InnerMapper>
|
||||
future<V> two_dimensional_map_reduce(sharded<service::storage_proxy>& d,
|
||||
future<V> two_dimensional_map_reduce(distributed<service::storage_proxy>& d,
|
||||
InnerMapper mapper, Reducer reducer, V initial_value) {
|
||||
return d.map_reduce0( [mapper, reducer, initial_value] (const service::storage_proxy& sp) {
|
||||
return map_reduce_scheduling_group_specific<service::storage_proxy_stats::stats>(
|
||||
@@ -59,7 +59,7 @@ future<V> two_dimensional_map_reduce(sharded<service::storage_proxy>& d,
|
||||
* @return A future that resolves to the result of the aggregation.
|
||||
*/
|
||||
template<typename V, typename Reducer, typename F, typename C>
|
||||
future<V> two_dimensional_map_reduce(sharded<service::storage_proxy>& d,
|
||||
future<V> two_dimensional_map_reduce(distributed<service::storage_proxy>& d,
|
||||
C F::*f, Reducer reducer, V initial_value) {
|
||||
return two_dimensional_map_reduce(d, [f] (F& stats) -> V {
|
||||
return stats.*f;
|
||||
@@ -75,20 +75,20 @@ future<V> two_dimensional_map_reduce(sharded<service::storage_proxy>& d,
|
||||
*
|
||||
*/
|
||||
template<typename V, typename F>
|
||||
future<json::json_return_type> sum_stats_storage_proxy(sharded<proxy>& d, V F::*f) {
|
||||
future<json::json_return_type> sum_stats_storage_proxy(distributed<proxy>& d, V F::*f) {
|
||||
return two_dimensional_map_reduce(d, [f] (F& stats) { return stats.*f; }, std::plus<V>(), V(0)).then([] (V val) {
|
||||
return make_ready_future<json::json_return_type>(val);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
static future<utils::rate_moving_average> sum_timed_rate(sharded<proxy>& d, utils::timed_rate_moving_average service::storage_proxy_stats::stats::*f) {
|
||||
static future<utils::rate_moving_average> sum_timed_rate(distributed<proxy>& d, utils::timed_rate_moving_average service::storage_proxy_stats::stats::*f) {
|
||||
return two_dimensional_map_reduce(d, [f] (service::storage_proxy_stats::stats& stats) {
|
||||
return (stats.*f).rate();
|
||||
}, std::plus<utils::rate_moving_average>(), utils::rate_moving_average());
|
||||
}
|
||||
|
||||
static future<json::json_return_type> sum_timed_rate_as_obj(sharded<proxy>& d, utils::timed_rate_moving_average service::storage_proxy_stats::stats::*f) {
|
||||
static future<json::json_return_type> sum_timed_rate_as_obj(distributed<proxy>& d, utils::timed_rate_moving_average service::storage_proxy_stats::stats::*f) {
|
||||
return sum_timed_rate(d, f).then([](const utils::rate_moving_average& val) {
|
||||
httpd::utils_json::rate_moving_average m;
|
||||
m = val;
|
||||
@@ -100,7 +100,7 @@ httpd::utils_json::rate_moving_average_and_histogram get_empty_moving_average()
|
||||
return timer_to_json(utils::rate_moving_average_and_histogram());
|
||||
}
|
||||
|
||||
static future<json::json_return_type> sum_timed_rate_as_long(sharded<proxy>& d, utils::timed_rate_moving_average service::storage_proxy_stats::stats::*f) {
|
||||
static future<json::json_return_type> sum_timed_rate_as_long(distributed<proxy>& d, utils::timed_rate_moving_average service::storage_proxy_stats::stats::*f) {
|
||||
return sum_timed_rate(d, f).then([](const utils::rate_moving_average& val) {
|
||||
return make_ready_future<json::json_return_type>(val.count);
|
||||
});
|
||||
@@ -152,7 +152,7 @@ static future<json::json_return_type> total_latency(sharded<service::storage_pr
|
||||
*/
|
||||
template<typename F>
|
||||
future<json::json_return_type>
|
||||
sum_histogram_stats_storage_proxy(sharded<proxy>& d,
|
||||
sum_histogram_stats_storage_proxy(distributed<proxy>& d,
|
||||
utils::timed_rate_moving_average_summary_and_histogram F::*f) {
|
||||
return two_dimensional_map_reduce(d, [f] (service::storage_proxy_stats::stats& stats) {
|
||||
return (stats.*f).hist;
|
||||
@@ -172,7 +172,7 @@ sum_histogram_stats_storage_proxy(sharded<proxy>& d,
|
||||
*/
|
||||
template<typename F>
|
||||
future<json::json_return_type>
|
||||
sum_timer_stats_storage_proxy(sharded<proxy>& d,
|
||||
sum_timer_stats_storage_proxy(distributed<proxy>& d,
|
||||
utils::timed_rate_moving_average_summary_and_histogram F::*f) {
|
||||
|
||||
return two_dimensional_map_reduce(d, [f] (service::storage_proxy_stats::stats& stats) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -52,19 +52,17 @@ 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 {
|
||||
compaction::compaction_type_options::scrub opts;
|
||||
sstables::compaction_type_options::scrub opts;
|
||||
sstring keyspace;
|
||||
std::vector<sstring> column_families;
|
||||
sstring snapshot_tag;
|
||||
};
|
||||
|
||||
scrub_info parse_scrub_options(const http_context& ctx, std::unique_ptr<http::request> req);
|
||||
future<scrub_info> parse_scrub_options(const http_context& ctx, sharded<db::snapshot_ctl>& snap_ctl, std::unique_ptr<http::request> req);
|
||||
|
||||
void set_storage_service(http_context& ctx, httpd::routes& r, sharded<service::storage_service>& ss, service::raft_group0_client&);
|
||||
void unset_storage_service(http_context& ctx, httpd::routes& r);
|
||||
@@ -82,13 +80,6 @@ void set_snapshot(http_context& ctx, httpd::routes& r, sharded<db::snapshot_ctl>
|
||||
void unset_snapshot(http_context& ctx, httpd::routes& r);
|
||||
void set_load_meter(http_context& ctx, httpd::routes& r, service::load_meter& lm);
|
||||
void unset_load_meter(http_context& ctx, httpd::routes& r);
|
||||
seastar::future<json::json_return_type> run_toppartitions_query(db::toppartitions_query& q, bool legacy_request = false);
|
||||
|
||||
// converts string value of boolean parameter into bool
|
||||
// maps (case insensitively)
|
||||
// "true", "yes" and "1" into true
|
||||
// "false", "no" and "0" into false
|
||||
// otherwise throws runtime_error
|
||||
bool validate_bool_x(const sstring& param, bool default_value);
|
||||
seastar::future<json::json_return_type> run_toppartitions_query(db::toppartitions_query& q, http_context &ctx, bool legacy_request = false);
|
||||
|
||||
} // namespace api
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#include "api/api-doc/system.json.hh"
|
||||
#include "api/api-doc/metrics.json.hh"
|
||||
#include "replica/database.hh"
|
||||
#include "sstables/sstables_manager.hh"
|
||||
#include "db/sstables-format-selector.hh"
|
||||
|
||||
#include <rapidjson/document.h>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
@@ -54,8 +54,7 @@ void set_system(http_context& ctx, routes& r) {
|
||||
|
||||
hm::set_metrics_config.set(r, [](std::unique_ptr<http::request> req) -> future<json::json_return_type> {
|
||||
rapidjson::Document doc;
|
||||
auto content = co_await util::read_entire_stream_contiguous(*req->content_stream);
|
||||
doc.Parse(content.c_str());
|
||||
doc.Parse(req->content.c_str());
|
||||
if (!doc.IsArray()) {
|
||||
throw bad_param_exception("Expected a json array");
|
||||
}
|
||||
@@ -88,19 +87,21 @@ void set_system(http_context& ctx, routes& r) {
|
||||
relabels[i].expr = element["regex"].GetString();
|
||||
}
|
||||
}
|
||||
bool failed = false;
|
||||
co_await smp::invoke_on_all([&relabels, &failed] {
|
||||
return metrics::set_relabel_configs(relabels).then([&failed](const metrics::metric_relabeling_result& result) {
|
||||
if (result.metrics_relabeled_due_to_collision > 0) {
|
||||
failed = true;
|
||||
return do_with(std::move(relabels), false, [](const std::vector<seastar::metrics::relabel_config>& relabels, bool& failed) {
|
||||
return smp::invoke_on_all([&relabels, &failed] {
|
||||
return metrics::set_relabel_configs(relabels).then([&failed](const metrics::metric_relabeling_result& result) {
|
||||
if (result.metrics_relabeled_due_to_collision > 0) {
|
||||
failed = true;
|
||||
}
|
||||
return;
|
||||
});
|
||||
}).then([&failed](){
|
||||
if (failed) {
|
||||
throw bad_param_exception("conflicts found during relabeling");
|
||||
}
|
||||
return;
|
||||
return make_ready_future<json::json_return_type>(seastar::json::json_void());
|
||||
});
|
||||
});
|
||||
if (failed) {
|
||||
throw bad_param_exception("conflicts found during relabeling");
|
||||
}
|
||||
co_return seastar::json::json_void();
|
||||
});
|
||||
|
||||
hs::get_system_uptime.set(r, [](const_req req) {
|
||||
@@ -183,13 +184,18 @@ void set_system(http_context& ctx, routes& r) {
|
||||
apilog.info("Profile dumped to {}", profile_dest);
|
||||
return make_ready_future<json::json_return_type>(json::json_return_type(json::json_void()));
|
||||
}) ;
|
||||
}
|
||||
|
||||
hs::get_highest_supported_sstable_version.set(r, [&ctx] (std::unique_ptr<request> req) {
|
||||
return smp::submit_to(0, [&ctx] {
|
||||
auto format = ctx.db.local().get_user_sstables_manager().get_highest_supported_format();
|
||||
return make_ready_future<json::json_return_type>(seastar::to_sstring(format));
|
||||
void set_format_selector(http_context& ctx, routes& r, db::sstables_format_selector& sel) {
|
||||
hs::get_highest_supported_sstable_version.set(r, [&sel] (std::unique_ptr<request> req) {
|
||||
return smp::submit_to(0, [&sel] {
|
||||
return make_ready_future<json::json_return_type>(seastar::to_sstring(sel.selected_format()));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void unset_format_selector(http_context& ctx, routes& r) {
|
||||
hs::get_highest_supported_sstable_version.unset(r);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -12,9 +12,14 @@ namespace seastar::httpd {
|
||||
class routes;
|
||||
}
|
||||
|
||||
namespace db { class sstables_format_selector; }
|
||||
|
||||
namespace api {
|
||||
|
||||
struct http_context;
|
||||
void set_system(http_context& ctx, seastar::httpd::routes& r);
|
||||
|
||||
void set_format_selector(http_context& ctx, seastar::httpd::routes& r, db::sstables_format_selector& sel);
|
||||
void unset_format_selector(http_context& ctx, seastar::httpd::routes& r);
|
||||
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
* SPDX-License-Identifier: LicenseRef-ScyllaDB-Source-Available-1.0
|
||||
*/
|
||||
|
||||
#include <seastar/core/chunked_fifo.hh>
|
||||
#include <seastar/core/coroutine.hh>
|
||||
#include <seastar/coroutine/exception.hh>
|
||||
#include <seastar/http/exception.hh>
|
||||
@@ -35,9 +34,8 @@ static ::tm get_time(db_clock::time_point tp) {
|
||||
}
|
||||
|
||||
tm::task_status make_status(tasks::task_status status, sharded<gms::gossiper>& gossiper) {
|
||||
chunked_fifo<tm::task_identity> tis;
|
||||
tis.reserve(status.children.size());
|
||||
for (const auto& child : status.children) {
|
||||
std::vector<tm::task_identity> tis{status.children.size()};
|
||||
std::ranges::transform(status.children, tis.begin(), [&gossiper] (const auto& child) {
|
||||
tm::task_identity ident;
|
||||
gms::inet_address addr{};
|
||||
if (gossiper.local_is_initialized()) {
|
||||
@@ -45,8 +43,8 @@ tm::task_status make_status(tasks::task_status status, sharded<gms::gossiper>& g
|
||||
}
|
||||
ident.task_id = child.task_id.to_sstring();
|
||||
ident.node = fmt::format("{}", addr);
|
||||
tis.push_back(std::move(ident));
|
||||
}
|
||||
return ident;
|
||||
});
|
||||
|
||||
tm::task_status res{};
|
||||
res.id = status.task_id.to_sstring();
|
||||
@@ -107,11 +105,11 @@ void set_task_manager(http_context& ctx, routes& r, sharded<tasks::task_manager>
|
||||
throw bad_param_exception(fmt::format("{}", std::current_exception()));
|
||||
}
|
||||
|
||||
if (auto param = req->get_query_param("keyspace"); !param.empty()) {
|
||||
keyspace = param;
|
||||
if (auto it = req->query_parameters.find("keyspace"); it != req->query_parameters.end()) {
|
||||
keyspace = it->second;
|
||||
}
|
||||
if (auto param = req->get_query_param("table"); !param.empty()) {
|
||||
table = param;
|
||||
if (auto it = req->query_parameters.find("table"); it != req->query_parameters.end()) {
|
||||
table = it->second;
|
||||
}
|
||||
|
||||
return module->get_stats(internal, [keyspace = std::move(keyspace), table = std::move(table)] (std::string& ks, std::string& t) {
|
||||
@@ -119,7 +117,7 @@ void set_task_manager(http_context& ctx, routes& r, sharded<tasks::task_manager>
|
||||
});
|
||||
});
|
||||
|
||||
noncopyable_function<future<>(output_stream<char>&&)> f = [r = std::move(res)] (output_stream<char>&& os) -> future<> {
|
||||
std::function<future<>(output_stream<char>&&)> f = [r = std::move(res)] (output_stream<char>&& os) -> future<> {
|
||||
auto s = std::move(os);
|
||||
std::exception_ptr ex;
|
||||
try {
|
||||
@@ -175,8 +173,8 @@ void set_task_manager(http_context& ctx, routes& r, sharded<tasks::task_manager>
|
||||
auto id = tasks::task_id{utils::UUID{req->get_path_param("task_id")}};
|
||||
tasks::task_status status;
|
||||
std::optional<std::chrono::seconds> timeout = std::nullopt;
|
||||
if (auto param = req->get_query_param("timeout"); !param.empty()) {
|
||||
timeout = std::chrono::seconds(boost::lexical_cast<uint32_t>(param));
|
||||
if (auto it = req->query_parameters.find("timeout"); it != req->query_parameters.end()) {
|
||||
timeout = std::chrono::seconds(boost::lexical_cast<uint32_t>(it->second));
|
||||
}
|
||||
try {
|
||||
auto task = tasks::task_handler{tm.local(), id};
|
||||
@@ -196,7 +194,7 @@ void set_task_manager(http_context& ctx, routes& r, sharded<tasks::task_manager>
|
||||
auto task = tasks::task_handler{tm.local(), id};
|
||||
auto res = co_await task.get_status_recursively(true);
|
||||
|
||||
noncopyable_function<future<>(output_stream<char>&&)> f = [r = std::move(res), &gossiper] (output_stream<char>&& os) -> future<> {
|
||||
std::function<future<>(output_stream<char>&&)> f = [r = std::move(res), &gossiper] (output_stream<char>&& os) -> future<> {
|
||||
auto s = std::move(os);
|
||||
auto res = std::move(r);
|
||||
co_await s.write("[");
|
||||
@@ -217,7 +215,7 @@ void set_task_manager(http_context& ctx, routes& r, sharded<tasks::task_manager>
|
||||
tm::get_and_update_ttl.set(r, [&cfg] (std::unique_ptr<http::request> req) -> future<json::json_return_type> {
|
||||
uint32_t ttl = cfg.task_ttl_seconds();
|
||||
try {
|
||||
co_await cfg.task_ttl_seconds.set_value_on_all_shards(req->get_query_param("ttl"), utils::config_file::config_source::API);
|
||||
co_await cfg.task_ttl_seconds.set_value_on_all_shards(req->query_parameters["ttl"], utils::config_file::config_source::API);
|
||||
} catch (...) {
|
||||
throw bad_param_exception(fmt::format("{}", std::current_exception()));
|
||||
}
|
||||
@@ -232,7 +230,7 @@ void set_task_manager(http_context& ctx, routes& r, sharded<tasks::task_manager>
|
||||
tm::get_and_update_user_ttl.set(r, [&cfg] (std::unique_ptr<http::request> req) -> future<json::json_return_type> {
|
||||
uint32_t user_ttl = cfg.user_task_ttl_seconds();
|
||||
try {
|
||||
co_await cfg.user_task_ttl_seconds.set_value_on_all_shards(req->get_query_param("user_ttl"), utils::config_file::config_source::API);
|
||||
co_await cfg.user_task_ttl_seconds.set_value_on_all_shards(req->query_parameters["user_ttl"], utils::config_file::config_source::API);
|
||||
} catch (...) {
|
||||
throw bad_param_exception(fmt::format("{}", std::current_exception()));
|
||||
}
|
||||
|
||||
@@ -57,16 +57,20 @@ void set_task_manager_test(http_context& ctx, routes& r, sharded<tasks::task_man
|
||||
|
||||
tmt::register_test_task.set(r, [&tm] (std::unique_ptr<http::request> req) -> future<json::json_return_type> {
|
||||
sharded<tasks::task_manager>& tms = tm;
|
||||
const auto id_param = req->get_query_param("task_id");
|
||||
auto id = !id_param.empty() ? tasks::task_id{utils::UUID{id_param}} : tasks::task_id::create_null_id();
|
||||
const auto shard_param = req->get_query_param("shard");
|
||||
unsigned shard = shard_param.empty() ? 0 : boost::lexical_cast<unsigned>(shard_param);
|
||||
std::string keyspace = req->get_query_param("keyspace");
|
||||
std::string table = req->get_query_param("table");
|
||||
std::string entity = req->get_query_param("entity");
|
||||
auto it = req->query_parameters.find("task_id");
|
||||
auto id = it != req->query_parameters.end() ? tasks::task_id{utils::UUID{it->second}} : tasks::task_id::create_null_id();
|
||||
it = req->query_parameters.find("shard");
|
||||
unsigned shard = it != req->query_parameters.end() ? boost::lexical_cast<unsigned>(it->second) : 0;
|
||||
it = req->query_parameters.find("keyspace");
|
||||
std::string keyspace = it != req->query_parameters.end() ? it->second : "";
|
||||
it = req->query_parameters.find("table");
|
||||
std::string table = it != req->query_parameters.end() ? it->second : "";
|
||||
it = req->query_parameters.find("entity");
|
||||
std::string entity = it != req->query_parameters.end() ? it->second : "";
|
||||
it = req->query_parameters.find("parent_id");
|
||||
tasks::task_info data;
|
||||
if (auto parent_id = req->get_query_param("parent_id"); !parent_id.empty()) {
|
||||
data.id = tasks::task_id{utils::UUID{parent_id}};
|
||||
if (it != req->query_parameters.end()) {
|
||||
data.id = tasks::task_id{utils::UUID{it->second}};
|
||||
auto parent_ptr = co_await tasks::task_manager::lookup_task_on_all_shards(tm, data.id);
|
||||
data.shard = parent_ptr->get_status().shard;
|
||||
}
|
||||
@@ -84,7 +88,7 @@ void set_task_manager_test(http_context& ctx, routes& r, sharded<tasks::task_man
|
||||
});
|
||||
|
||||
tmt::unregister_test_task.set(r, [&tm] (std::unique_ptr<http::request> req) -> future<json::json_return_type> {
|
||||
auto id = tasks::task_id{utils::UUID{req->get_query_param("task_id")}};
|
||||
auto id = tasks::task_id{utils::UUID{req->query_parameters["task_id"]}};
|
||||
try {
|
||||
co_await tasks::task_manager::invoke_on_task(tm, id, [] (tasks::task_manager::task_variant task_v, tasks::virtual_task_hint) -> future<> {
|
||||
return std::visit(overloaded_functor{
|
||||
@@ -105,8 +109,9 @@ void set_task_manager_test(http_context& ctx, routes& r, sharded<tasks::task_man
|
||||
|
||||
tmt::finish_test_task.set(r, [&tm] (std::unique_ptr<http::request> req) -> future<json::json_return_type> {
|
||||
auto id = tasks::task_id{utils::UUID{req->get_path_param("task_id")}};
|
||||
std::string error = req->get_query_param("error");
|
||||
bool fail = !error.empty();
|
||||
auto it = req->query_parameters.find("error");
|
||||
bool fail = it != req->query_parameters.end();
|
||||
std::string error = fail ? it->second : "";
|
||||
|
||||
try {
|
||||
co_await tasks::task_manager::invoke_on_task(tm, id, [fail, error = std::move(error)] (tasks::task_manager::task_variant task_v, tasks::virtual_task_hint) -> future<> {
|
||||
|
||||
123
api/tasks.cc
123
api/tasks.cc
@@ -12,7 +12,6 @@
|
||||
#include "api/api.hh"
|
||||
#include "api/storage_service.hh"
|
||||
#include "api/api-doc/tasks.json.hh"
|
||||
#include "api/api-doc/storage_service.json.hh"
|
||||
#include "compaction/compaction_manager.hh"
|
||||
#include "compaction/task_manager_module.hh"
|
||||
#include "service/storage_service.hh"
|
||||
@@ -26,14 +25,14 @@ extern logging::logger apilog;
|
||||
namespace api {
|
||||
|
||||
namespace t = httpd::tasks_json;
|
||||
namespace ss = httpd::storage_service_json;
|
||||
using namespace json;
|
||||
|
||||
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, 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));
|
||||
};
|
||||
}
|
||||
@@ -41,93 +40,52 @@ static auto wrap_ks_cf(http_context &ctx, ks_cf_func f) {
|
||||
void set_tasks_compaction_module(http_context& ctx, routes& r, sharded<service::storage_service>& ss, sharded<db::snapshot_ctl>& snap_ctl) {
|
||||
t::force_keyspace_compaction_async.set(r, [&ctx](std::unique_ptr<http::request> req) -> future<json::json_return_type> {
|
||||
auto& db = ctx.db;
|
||||
auto [ keyspace, table_infos ] = parse_table_infos(ctx, *req, "cf");
|
||||
auto flush = validate_bool_x(req->get_query_param("flush_memtables"), true);
|
||||
auto params = req_params({
|
||||
std::pair("keyspace", mandatory::yes),
|
||||
std::pair("cf", mandatory::no),
|
||||
std::pair("flush_memtables", mandatory::no),
|
||||
});
|
||||
params.process(*req);
|
||||
auto keyspace = validate_keyspace(ctx, *params.get("keyspace"));
|
||||
auto table_infos = parse_table_infos(keyspace, ctx, params.get("cf").value_or(""));
|
||||
auto flush = params.get_as<bool>("flush_memtables").value_or(true);
|
||||
apilog.debug("force_keyspace_compaction_async: keyspace={} tables={}, flush={}", keyspace, table_infos, flush);
|
||||
|
||||
auto& compaction_module = db.local().get_compaction_manager().get_task_manager_module();
|
||||
std::optional<compaction::flush_mode> fmopt;
|
||||
std::optional<flush_mode> fmopt;
|
||||
if (!flush) {
|
||||
fmopt = compaction::flush_mode::skip;
|
||||
fmopt = flush_mode::skip;
|
||||
}
|
||||
auto task = co_await compaction_module.make_and_start_task<compaction::major_keyspace_compaction_task_impl>({}, std::move(keyspace), tasks::task_id::create_null_id(), db, table_infos, fmopt);
|
||||
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);
|
||||
|
||||
co_return json::json_return_type(task->get_status().id.to_sstring());
|
||||
});
|
||||
|
||||
ss::force_keyspace_compaction.set(r, [&ctx](std::unique_ptr<http::request> req) -> future<json::json_return_type> {
|
||||
auto& db = ctx.db;
|
||||
auto [ keyspace, table_infos ] = parse_table_infos(ctx, *req, "cf");
|
||||
auto flush = validate_bool_x(req->get_query_param("flush_memtables"), true);
|
||||
auto consider_only_existing_data = validate_bool_x(req->get_query_param("consider_only_existing_data"), false);
|
||||
apilog.info("force_keyspace_compaction: keyspace={} tables={}, flush={} consider_only_existing_data={}", keyspace, table_infos, flush, consider_only_existing_data);
|
||||
|
||||
auto& compaction_module = db.local().get_compaction_manager().get_task_manager_module();
|
||||
std::optional<compaction::flush_mode> fmopt;
|
||||
if (!flush && !consider_only_existing_data) {
|
||||
fmopt = compaction::flush_mode::skip;
|
||||
}
|
||||
auto task = co_await compaction_module.make_and_start_task<compaction::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();
|
||||
co_return json_void();
|
||||
});
|
||||
|
||||
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_vnodes_cleanup_allowed(keyspace)) {
|
||||
if (!co_await ss.local().is_cleanup_allowed(keyspace)) {
|
||||
auto msg = "Can not perform cleanup operation when topology changes";
|
||||
apilog.warn("force_keyspace_cleanup_async: keyspace={} tables={}: {}", keyspace, table_infos, msg);
|
||||
co_await coroutine::return_exception(std::runtime_error(msg));
|
||||
}
|
||||
|
||||
auto& compaction_module = db.local().get_compaction_manager().get_task_manager_module();
|
||||
auto task = co_await compaction_module.make_and_start_task<compaction::cleanup_keyspace_compaction_task_impl>({}, std::move(keyspace), db, table_infos, compaction::flush_mode::all_tables, tasks::is_user_task::yes);
|
||||
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_return json::json_return_type(task->get_status().id.to_sstring());
|
||||
});
|
||||
|
||||
ss::force_keyspace_cleanup.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);
|
||||
const auto& rs = db.local().find_keyspace(keyspace).get_replication_strategy();
|
||||
if (rs.is_local() || !rs.is_vnode_based()) {
|
||||
auto reason = rs.is_local() ? "require" : "support";
|
||||
apilog.info("Keyspace {} does not {} cleanup", keyspace, reason);
|
||||
co_return json::json_return_type(0);
|
||||
}
|
||||
apilog.info("force_keyspace_cleanup: keyspace={} tables={}", keyspace, table_infos);
|
||||
if (!co_await ss.local().is_vnodes_cleanup_allowed(keyspace)) {
|
||||
auto msg = "Can not perform cleanup operation when topology changes";
|
||||
apilog.warn("force_keyspace_cleanup: keyspace={} tables={}: {}", keyspace, table_infos, msg);
|
||||
co_await coroutine::return_exception(std::runtime_error(msg));
|
||||
}
|
||||
|
||||
auto& compaction_module = db.local().get_compaction_manager().get_task_manager_module();
|
||||
auto task = co_await compaction_module.make_and_start_task<compaction::cleanup_keyspace_compaction_task_impl>(
|
||||
{}, std::move(keyspace), db, table_infos, compaction::flush_mode::all_tables, tasks::is_user_task::yes);
|
||||
co_await task->done();
|
||||
co_return json::json_return_type(0);
|
||||
});
|
||||
|
||||
t::perform_keyspace_offstrategy_compaction_async.set(r, wrap_ks_cf(ctx, [] (http_context& ctx, std::unique_ptr<http::request> req, sstring keyspace, std::vector<table_info> table_infos) -> future<json::json_return_type> {
|
||||
apilog.info("perform_keyspace_offstrategy_compaction: keyspace={} tables={}", keyspace, table_infos);
|
||||
auto& compaction_module = ctx.db.local().get_compaction_manager().get_task_manager_module();
|
||||
auto task = co_await compaction_module.make_and_start_task<compaction::offstrategy_keyspace_compaction_task_impl>({}, std::move(keyspace), ctx.db, table_infos, nullptr);
|
||||
auto task = co_await compaction_module.make_and_start_task<offstrategy_keyspace_compaction_task_impl>({}, std::move(keyspace), ctx.db, table_infos, nullptr);
|
||||
|
||||
co_return json::json_return_type(task->get_status().id.to_sstring());
|
||||
}));
|
||||
|
||||
ss::perform_keyspace_offstrategy_compaction.set(r, wrap_ks_cf(ctx, [] (http_context& ctx, std::unique_ptr<http::request> req, sstring keyspace, std::vector<table_info> table_infos) -> future<json::json_return_type> {
|
||||
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<compaction::offstrategy_keyspace_compaction_task_impl>({}, std::move(keyspace), ctx.db, table_infos, &res);
|
||||
co_await task->done();
|
||||
co_return json::json_return_type(res);
|
||||
}));
|
||||
|
||||
t::upgrade_sstables_async.set(r, wrap_ks_cf(ctx, [] (http_context& ctx, std::unique_ptr<http::request> req, sstring keyspace, std::vector<table_info> table_infos) -> future<json::json_return_type> {
|
||||
auto& db = ctx.db;
|
||||
bool exclude_current_version = req_param<bool>(*req, "exclude_current_version", false);
|
||||
@@ -135,65 +93,28 @@ void set_tasks_compaction_module(http_context& ctx, routes& r, sharded<service::
|
||||
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<compaction::upgrade_sstables_compaction_task_impl>({}, std::move(keyspace), db, table_infos, exclude_current_version);
|
||||
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_return json::json_return_type(task->get_status().id.to_sstring());
|
||||
}));
|
||||
|
||||
ss::upgrade_sstables.set(r, wrap_ks_cf(ctx, [] (http_context& ctx, std::unique_ptr<http::request> req, sstring keyspace, std::vector<table_info> table_infos) -> future<json::json_return_type> {
|
||||
auto& db = ctx.db;
|
||||
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<compaction::upgrade_sstables_compaction_task_impl>({}, std::move(keyspace), db, table_infos, exclude_current_version);
|
||||
co_await task->done();
|
||||
co_return json::json_return_type(0);
|
||||
}));
|
||||
|
||||
t::scrub_async.set(r, [&ctx, &snap_ctl] (std::unique_ptr<http::request> req) -> future<json::json_return_type> {
|
||||
auto& db = ctx.db;
|
||||
auto info = parse_scrub_options(ctx, std::move(req));
|
||||
|
||||
if (!info.snapshot_tag.empty()) {
|
||||
co_await snap_ctl.local().take_column_family_snapshot(info.keyspace, info.column_families, info.snapshot_tag, db::snapshot_ctl::skip_flush::no);
|
||||
}
|
||||
auto info = co_await parse_scrub_options(ctx, snap_ctl, std::move(req));
|
||||
|
||||
auto& compaction_module = db.local().get_compaction_manager().get_task_manager_module();
|
||||
auto task = co_await compaction_module.make_and_start_task<compaction::scrub_sstables_compaction_task_impl>({}, std::move(info.keyspace), db, std::move(info.column_families), info.opts, nullptr);
|
||||
auto task = co_await compaction_module.make_and_start_task<scrub_sstables_compaction_task_impl>({}, std::move(info.keyspace), db, std::move(info.column_families), info.opts, nullptr);
|
||||
|
||||
co_return json::json_return_type(task->get_status().id.to_sstring());
|
||||
});
|
||||
|
||||
ss::force_compaction.set(r, [&ctx] (std::unique_ptr<http::request> req) -> future<json::json_return_type> {
|
||||
auto& db = ctx.db;
|
||||
auto flush = validate_bool_x(req->get_query_param("flush_memtables"), true);
|
||||
auto consider_only_existing_data = validate_bool_x(req->get_query_param("consider_only_existing_data"), false);
|
||||
apilog.info("force_compaction: flush={} consider_only_existing_data={}", flush, consider_only_existing_data);
|
||||
|
||||
auto& compaction_module = db.local().get_compaction_manager().get_task_manager_module();
|
||||
std::optional<compaction::flush_mode> fmopt;
|
||||
if (!flush && !consider_only_existing_data) {
|
||||
fmopt = compaction::flush_mode::skip;
|
||||
}
|
||||
auto task = co_await compaction_module.make_and_start_task<compaction::global_major_compaction_task_impl>({}, db, fmopt, consider_only_existing_data);
|
||||
co_await task->done();
|
||||
co_return json_void();
|
||||
});
|
||||
}
|
||||
|
||||
void unset_tasks_compaction_module(http_context& ctx, httpd::routes& r) {
|
||||
t::force_keyspace_compaction_async.unset(r);
|
||||
ss::force_keyspace_compaction.unset(r);
|
||||
t::force_keyspace_cleanup_async.unset(r);
|
||||
ss::force_keyspace_cleanup.unset(r);
|
||||
t::perform_keyspace_offstrategy_compaction_async.unset(r);
|
||||
ss::perform_keyspace_offstrategy_compaction.unset(r);
|
||||
t::upgrade_sstables_async.unset(r);
|
||||
ss::upgrade_sstables.unset(r);
|
||||
t::scrub_async.unset(r);
|
||||
ss::force_compaction.unset(r);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -54,12 +54,12 @@ void set_token_metadata(http_context& ctx, routes& r, sharded<locator::shared_to
|
||||
for (const auto host_id: leaving_host_ids) {
|
||||
eps.insert(g.local().get_address_map().get(host_id));
|
||||
}
|
||||
return eps | std::views::transform([] (auto& i) { return fmt::to_string(i); }) | std::ranges::to<std::vector>();
|
||||
return container_to_vec(eps);
|
||||
});
|
||||
|
||||
ss::get_moving_nodes.set(r, [](const_req req) {
|
||||
std::unordered_set<sstring> addr;
|
||||
return addr | std::ranges::to<std::vector>();
|
||||
return container_to_vec(addr);
|
||||
});
|
||||
|
||||
ss::get_joining_nodes.set(r, [&tm, &g](const_req req) {
|
||||
@@ -70,21 +70,15 @@ void set_token_metadata(http_context& ctx, routes& r, sharded<locator::shared_to
|
||||
for (const auto& [token, host_id]: points) {
|
||||
eps.insert(g.local().get_address_map().get(host_id));
|
||||
}
|
||||
return eps | std::views::transform([] (auto& i) { return fmt::to_string(i); }) | std::ranges::to<std::vector>();
|
||||
return container_to_vec(eps);
|
||||
});
|
||||
|
||||
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");
|
||||
}
|
||||
return tm.local().get()->get_host_ids()
|
||||
| std::views::transform([&g] (locator::host_id id) {
|
||||
ss::mapper m;
|
||||
m.key = fmt::to_string(g.local().get_address_map().get(id));
|
||||
m.value = fmt::to_string(id);
|
||||
return m;
|
||||
})
|
||||
| std::ranges::to<std::vector<ss::mapper>>();
|
||||
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); }) |
|
||||
std::ranges::to<std::unordered_map>();
|
||||
return map_to_key_value(std::move(map), res);
|
||||
});
|
||||
|
||||
static auto host_or_broadcast = [&tm](const_req req) {
|
||||
|
||||
@@ -209,11 +209,6 @@ future<> audit::log(const audit_info* audit_info, service::query_state& query_st
|
||||
static const sstring anonymous_username("anonymous");
|
||||
const sstring& username = client_state.user() ? client_state.user()->name.value_or(anonymous_username) : no_username;
|
||||
socket_address client_ip = client_state.get_client_address().addr();
|
||||
if (logger.is_enabled(logging::log_level::debug)) {
|
||||
logger.debug("Log written: node_ip {} category {} cl {} error {} keyspace {} query '{}' client_ip {} table {} username {}",
|
||||
node_ip, audit_info->category_string(), cl, error, audit_info->keyspace(),
|
||||
audit_info->query(), client_ip, audit_info->table(), username);
|
||||
}
|
||||
return futurize_invoke(std::mem_fn(&storage_helper::write), _storage_helper_ptr, audit_info, node_ip, client_ip, cl, username, error)
|
||||
.handle_exception([audit_info, node_ip, client_ip, cl, username, error] (auto ep) {
|
||||
logger.error("Unexpected exception when writing log with: node_ip {} category {} cl {} error {} keyspace {} query '{}' client_ip {} table {} username {} exception {}",
|
||||
@@ -224,10 +219,6 @@ future<> audit::log(const audit_info* audit_info, service::query_state& query_st
|
||||
|
||||
future<> audit::log_login(const sstring& username, socket_address client_ip, bool error) noexcept {
|
||||
socket_address node_ip = _token_metadata.get()->get_topology().my_address().addr();
|
||||
if (logger.is_enabled(logging::log_level::debug)) {
|
||||
logger.debug("Login log written: node_ip {}, client_ip {}, username {}, error {}",
|
||||
node_ip, client_ip, username, error ? "true" : "false");
|
||||
}
|
||||
return futurize_invoke(std::mem_fn(&storage_helper::write_login), _storage_helper_ptr, username, node_ip, client_ip, error)
|
||||
.handle_exception([username, node_ip, client_ip, error] (auto ep) {
|
||||
logger.error("Unexpected exception when writing login log with: node_ip {} client_ip {} username {} error {} exception {}",
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
#include "cql3/statements/ks_prop_defs.hh"
|
||||
#include "service/migration_manager.hh"
|
||||
#include "service/storage_proxy.hh"
|
||||
#include "locator/abstract_replication_strategy.hh"
|
||||
|
||||
namespace audit {
|
||||
|
||||
@@ -65,8 +64,8 @@ future<> audit_cf_storage_helper::migrate_audit_table(service::group0_guard grou
|
||||
data_dictionary::database db = _qp.db();
|
||||
cql3::statements::ks_prop_defs old_ks_prop_defs;
|
||||
auto old_ks_metadata = old_ks_prop_defs.as_ks_metadata_update(
|
||||
ks->metadata(), *_qp.proxy().get_token_metadata_ptr(), db.features(), db.get_config());
|
||||
locator::replication_strategy_config_options strategy_opts;
|
||||
ks->metadata(), *_qp.proxy().get_token_metadata_ptr(), db.features());
|
||||
std::map<sstring, sstring> strategy_opts;
|
||||
for (const auto &dc: _qp.proxy().get_token_metadata_ptr()->get_topology().get_datacenters())
|
||||
strategy_opts[dc] = "3";
|
||||
|
||||
@@ -74,7 +73,6 @@ future<> audit_cf_storage_helper::migrate_audit_table(service::group0_guard grou
|
||||
"org.apache.cassandra.locator.NetworkTopologyStrategy",
|
||||
strategy_opts,
|
||||
std::nullopt, // initial_tablets
|
||||
std::nullopt, // consistency_option
|
||||
old_ks_metadata->durable_writes(),
|
||||
old_ks_metadata->get_storage_options(),
|
||||
old_ks_metadata->tables());
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
#include "mutation/canonical_mutation.hh"
|
||||
#include "schema/schema_fwd.hh"
|
||||
#include "mutation/timestamp.hh"
|
||||
#include "timestamp.hh"
|
||||
#include "utils/assert.hh"
|
||||
#include "utils/exponential_backoff_retry.hh"
|
||||
#include "cql3/query_processor.hh"
|
||||
@@ -119,14 +119,9 @@ 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,
|
||||
utils::chunked_vector<canonical_mutation> muts,
|
||||
std::vector<canonical_mutation> muts,
|
||||
::service::group0_guard group0_guard,
|
||||
seastar::abort_source& as,
|
||||
std::optional<::service::raft_timeout> timeout) {
|
||||
@@ -154,7 +149,7 @@ future<> announce_mutations_with_batching(
|
||||
});
|
||||
|
||||
size_t memory_usage = 0;
|
||||
utils::chunked_vector<canonical_mutation> muts;
|
||||
std::vector<canonical_mutation> muts;
|
||||
|
||||
// guard has to be taken before we execute code in gen as
|
||||
// it can do read-before-write and we want announce_mutations
|
||||
@@ -204,7 +199,7 @@ future<> announce_mutations(
|
||||
internal_distributed_query_state(),
|
||||
timestamp,
|
||||
std::move(values));
|
||||
utils::chunked_vector<canonical_mutation> cmuts = {muts.begin(), muts.end()};
|
||||
std::vector<canonical_mutation> cmuts = {muts.begin(), muts.end()};
|
||||
co_await announce_mutations_with_guard(group0_client, std::move(cmuts), std::move(group0_guard), as, timeout);
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
@@ -102,11 +101,7 @@ future<> password_authenticator::migrate_legacy_metadata() const {
|
||||
return do_for_each(*results, [this](const cql3::untyped_result_set_row& row) {
|
||||
auto username = row.get_as<sstring>("username");
|
||||
auto salted_hash = row.get_as<sstring>(SALTED_HASH);
|
||||
static const auto query = seastar::format("UPDATE {}.{} SET {} = ? WHERE {} = ?",
|
||||
meta::legacy::AUTH_KS,
|
||||
meta::roles_table::name,
|
||||
SALTED_HASH,
|
||||
meta::roles_table::role_col_name);
|
||||
static const auto query = update_row_query();
|
||||
return _qp.execute_internal(
|
||||
query,
|
||||
consistency_for_user(username),
|
||||
@@ -122,111 +117,42 @@ future<> password_authenticator::migrate_legacy_metadata() const {
|
||||
});
|
||||
}
|
||||
|
||||
future<> password_authenticator::legacy_create_default_if_missing() {
|
||||
const auto exists = co_await legacy::default_role_row_satisfies(_qp, &has_salted_hash, _superuser);
|
||||
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 = seastar::format("UPDATE {}.{} SET {} = ? WHERE {} = ?",
|
||||
meta::legacy::AUTH_KS,
|
||||
meta::roles_table::name,
|
||||
SALTED_HASH,
|
||||
meta::roles_table::role_col_name);
|
||||
co_await _qp.execute_internal(
|
||||
const auto query = update_row_query();
|
||||
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)) {
|
||||
if (!_superuser_created_promise.available()) {
|
||||
// Counterintuitively, we mark promise as ready before any startup work
|
||||
// because wait_for_schema_agreement() below will block indefinitely
|
||||
// without cluster majority. In that case, blocking node startup
|
||||
// would lead to a cluster deadlock.
|
||||
_superuser_created_promise.set_value();
|
||||
}
|
||||
_migration_manager.wait_for_schema_agreement(_qp.db().real_database(), db::timeout_clock::time_point::max(), &_as).get();
|
||||
|
||||
if (legacy::any_nondefault_role_row_satisfies(_qp, &has_salted_hash, _superuser).get()) {
|
||||
if (any_nondefault_role_row_satisfies(_qp, &has_salted_hash, _superuser).get()) {
|
||||
if (legacy_metadata_exists()) {
|
||||
plogger.warn("Ignoring legacy authentication metadata since nondefault data already exist.");
|
||||
}
|
||||
@@ -238,34 +164,20 @@ 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();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (legacy_mode(_qp)) {
|
||||
static const sstring create_roles_query = fmt::format(
|
||||
"CREATE TABLE {}.{} ("
|
||||
" {} text PRIMARY KEY,"
|
||||
" can_login boolean,"
|
||||
" is_superuser boolean,"
|
||||
" member_of set<text>,"
|
||||
" salted_hash text"
|
||||
")",
|
||||
meta::legacy::AUTH_KS,
|
||||
meta::roles_table::name,
|
||||
meta::roles_table::role_col_name);
|
||||
return create_legacy_metadata_table_if_missing(
|
||||
meta::roles_table::name,
|
||||
_qp,
|
||||
create_roles_query,
|
||||
meta::roles_table::creation_query(),
|
||||
_migration_manager);
|
||||
}
|
||||
return make_ready_future<>();
|
||||
@@ -316,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;
|
||||
@@ -346,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;
|
||||
@@ -389,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));
|
||||
}
|
||||
|
||||
///
|
||||
|
||||
@@ -36,8 +36,7 @@ static const std::unordered_map<sstring, auth::permission> permission_names({
|
||||
{"MODIFY", auth::permission::MODIFY},
|
||||
{"AUTHORIZE", auth::permission::AUTHORIZE},
|
||||
{"DESCRIBE", auth::permission::DESCRIBE},
|
||||
{"EXECUTE", auth::permission::EXECUTE},
|
||||
{"VECTOR_SEARCH_INDEXING", auth::permission::VECTOR_SEARCH_INDEXING}});
|
||||
{"EXECUTE", auth::permission::EXECUTE}});
|
||||
|
||||
const sstring& auth::permissions::to_string(permission p) {
|
||||
for (auto& v : permission_names) {
|
||||
|
||||
@@ -33,7 +33,6 @@ enum class permission {
|
||||
// data access
|
||||
SELECT, // required for SELECT.
|
||||
MODIFY, // required for INSERT, UPDATE, DELETE, TRUNCATE.
|
||||
VECTOR_SEARCH_INDEXING, // required for SELECT from tables with vector indexes if SELECT permission is not granted.
|
||||
|
||||
// permission management
|
||||
AUTHORIZE, // required for GRANT and REVOKE.
|
||||
@@ -55,8 +54,7 @@ typedef enum_set<
|
||||
permission::MODIFY,
|
||||
permission::AUTHORIZE,
|
||||
permission::DESCRIBE,
|
||||
permission::EXECUTE,
|
||||
permission::VECTOR_SEARCH_INDEXING>> permission_set;
|
||||
permission::EXECUTE>> permission_set;
|
||||
|
||||
bool operator<(const permission_set&, const permission_set&);
|
||||
|
||||
|
||||
@@ -41,26 +41,22 @@ static const std::unordered_map<resource_kind, std::size_t> max_parts{
|
||||
{resource_kind::functions, 2}};
|
||||
|
||||
static permission_set applicable_permissions(const data_resource_view& dv) {
|
||||
|
||||
// We only support VECTOR_SEARCH_INDEXING permission for ALL KEYSPACES.
|
||||
|
||||
auto set = permission_set::of<
|
||||
if (dv.table()) {
|
||||
return permission_set::of<
|
||||
permission::ALTER,
|
||||
permission::DROP,
|
||||
permission::SELECT,
|
||||
permission::MODIFY,
|
||||
permission::AUTHORIZE>();
|
||||
|
||||
if (!dv.table()) {
|
||||
set.add(permission_set::of<permission::CREATE>());
|
||||
}
|
||||
|
||||
if (!dv.table() && !dv.keyspace()) {
|
||||
set.add(permission_set::of<permission::VECTOR_SEARCH_INDEXING>());
|
||||
}
|
||||
|
||||
return set;
|
||||
|
||||
return permission_set::of<
|
||||
permission::CREATE,
|
||||
permission::ALTER,
|
||||
permission::DROP,
|
||||
permission::SELECT,
|
||||
permission::MODIFY,
|
||||
permission::AUTHORIZE>();
|
||||
}
|
||||
|
||||
static permission_set applicable_permissions(const role_resource_view& rv) {
|
||||
@@ -197,7 +193,9 @@ service_level_resource_view::service_level_resource_view(const resource &r) {
|
||||
|
||||
sstring encode_signature(std::string_view name, std::vector<data_type> args) {
|
||||
return seastar::format("{}[{}]", name,
|
||||
fmt::join(args | std::views::transform(&abstract_type::name), "^"));
|
||||
fmt::join(args | std::views::transform([] (const data_type t) {
|
||||
return t->name();
|
||||
}), "^"));
|
||||
}
|
||||
|
||||
std::pair<sstring, std::vector<data_type>> decode_signature(std::string_view encoded_signature) {
|
||||
@@ -223,7 +221,9 @@ std::pair<sstring, std::vector<data_type>> decode_signature(std::string_view enc
|
||||
static sstring decoded_signature_string(std::string_view encoded_signature) {
|
||||
auto [function_name, arg_types] = decode_signature(encoded_signature);
|
||||
return seastar::format("{}({})", cql3::util::maybe_quote(sstring(function_name)),
|
||||
fmt::join(arg_types | std::views::transform(&abstract_type::cql3_type_name), ", "));
|
||||
fmt::join(arg_types | std::views::transform([] (data_type t) {
|
||||
return t->cql3_type_name();
|
||||
}), ", "));
|
||||
}
|
||||
|
||||
resource make_functions_resource(const cql3::functions::function& f) {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -18,21 +18,43 @@
|
||||
|
||||
namespace auth {
|
||||
|
||||
namespace legacy {
|
||||
namespace meta {
|
||||
|
||||
namespace roles_table {
|
||||
|
||||
std::string_view creation_query() {
|
||||
static const sstring instance = fmt::format(
|
||||
"CREATE TABLE {}.{} ("
|
||||
" {} text PRIMARY KEY,"
|
||||
" can_login boolean,"
|
||||
" is_superuser boolean,"
|
||||
" member_of set<text>,"
|
||||
" salted_hash text"
|
||||
")",
|
||||
meta::legacy::AUTH_KS,
|
||||
name,
|
||||
role_col_name);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
} // namespace roles_table
|
||||
|
||||
} // namespace meta
|
||||
|
||||
future<bool> default_role_row_satisfies(
|
||||
cql3::query_processor& qp,
|
||||
std::function<bool(const cql3::untyped_result_set_row&)> p,
|
||||
std::optional<std::string> rolename) {
|
||||
const sstring query = seastar::format("SELECT * FROM {}.{} WHERE {} = ?",
|
||||
auth::meta::legacy::AUTH_KS,
|
||||
get_auth_ks_name(qp),
|
||||
meta::roles_table::name,
|
||||
meta::roles_table::role_col_name);
|
||||
|
||||
for (auto cl : { db::consistency_level::ONE, db::consistency_level::QUORUM }) {
|
||||
auto results = co_await qp.execute_internal(query, cl
|
||||
, internal_distributed_query_state()
|
||||
, {rolename.value_or(std::string(auth::meta::DEFAULT_SUPERUSER_NAME))}
|
||||
, {rolename.value_or(std::string(meta::DEFAULT_SUPERUSER_NAME))}
|
||||
, cql3::query_processor::cache_internal::yes
|
||||
);
|
||||
if (!results->empty()) {
|
||||
@@ -46,7 +68,7 @@ future<bool> any_nondefault_role_row_satisfies(
|
||||
cql3::query_processor& qp,
|
||||
std::function<bool(const cql3::untyped_result_set_row&)> p,
|
||||
std::optional<std::string> rolename) {
|
||||
const sstring query = seastar::format("SELECT * FROM {}.{}", auth::meta::legacy::AUTH_KS, meta::roles_table::name);
|
||||
const sstring query = seastar::format("SELECT * FROM {}.{}", get_auth_ks_name(qp), meta::roles_table::name);
|
||||
|
||||
auto results = co_await qp.execute_internal(query, db::consistency_level::QUORUM
|
||||
, internal_distributed_query_state(), cql3::query_processor::cache_internal::no
|
||||
@@ -63,6 +85,4 @@ future<bool> any_nondefault_role_row_satisfies(
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace legacy
|
||||
|
||||
} // namespace auth
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user