Merge branch 'apache:trunk' into liuisaac/CASSANDRA-19831/trunk

This commit is contained in:
Isaac Liu 2026-08-01 11:48:39 -04:00 committed by GitHub
commit da943add35
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
185 changed files with 4389 additions and 790 deletions

View File

@ -78,7 +78,7 @@
<exclude name="test/data/jmxdump/cassandra-*-jmx.yaml"/> <exclude name="test/data/jmxdump/cassandra-*-jmx.yaml"/>
<!-- Documentation files --> <!-- Documentation files -->
<exclude name=".github/pull_request_template.md"/> <exclude name=".github/pull_request_template.md"/>
<exclude name=".github/workflows/code-check.yaml"/> <exclude name=".github/workflows/**.yaml"/>
<exclude NAME="doc/modules/**/*"/> <exclude NAME="doc/modules/**/*"/>
<exclude NAME="src/java/**/*.md"/> <exclude NAME="src/java/**/*.md"/>
<exclude NAME="**/README*"/> <exclude NAME="**/README*"/>

View File

@ -25,7 +25,7 @@ Python dependencies are found in .build/run-ci.d/requirements.txt
Custom environment variables can be set in .build/.run-ci.env Custom environment variables can be set in .build/.run-ci.env
lint with: lint with:
`pylint --disable=C0301,W0511,C0103,W0702,C0415,C0116,C0115,R0914,W0603,R0915,R0913,R0911 run-ci` `pylint --disable=C0301,W0511,C0103,W0702,C0415,C0116,C0115,R0914,W0603,R0915,R0913,R0917,R0911,W0212,W0621 run-ci`
test with: test with:
`python .build/run-ci.d/run-ci-test.py` `python .build/run-ci.d/run-ci-test.py`
@ -38,6 +38,7 @@ import gzip
import itertools import itertools
import os import os
import shutil import shutil
import socket
import subprocess import subprocess
import sys import sys
import tarfile import tarfile
@ -50,9 +51,10 @@ from urllib.request import urlretrieve
from typing import Optional, Tuple from typing import Optional, Tuple
# External Libraries (`pip install -r .build/run-ci.d/requirements.txt`) # External Libraries (`pip install -r .build/run-ci.d/requirements.txt`)
import requests
import yaml
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from kubernetes import client, config, stream from kubernetes import client, config, stream
import requests
try: try:
import jenkins import jenkins
@ -72,9 +74,9 @@ def base_job_name(args) -> str:
""" """
if not hasattr(base_job_name, "_cached_result"): if not hasattr(base_job_name, "_cached_result"):
raw_url = args.repository.replace("https://github.com/", "https://raw.githubusercontent.com/").removesuffix(".git") + f"/{args.branch}/build.xml" raw_url = args.repository.replace("https://github.com/", "https://raw.githubusercontent.com/").removesuffix(".git") + f"/{args.branch}/build.xml"
if 200 != requests.head(raw_url).status_code: if 200 != requests.head(raw_url, timeout=30).status_code:
raise ValueError(f"GitHub unavailable, or this branch has not been pushed yet: {args.repository} @ {args.branch} (or remote tracking not setup up: `git config --get branch.{args.branch}.remote` and `git config --get branch.{args.branch}.merge`)") raise ValueError(f"GitHub unavailable, or this branch has not been pushed yet: {args.repository} @ {args.branch} (or remote tracking not setup up: `git config --get branch.{args.branch}.remote` and `git config --get branch.{args.branch}.merge`)")
response = requests.get(raw_url) response = requests.get(raw_url, timeout=30)
response.raise_for_status() response.raise_for_status()
for line in response.text.splitlines(): for line in response.text.splitlines():
if 'property' in line and 'name="base.version"' in line: if 'property' in line and 'name="base.version"' in line:
@ -99,7 +101,7 @@ def is_local_git_dirty(args) -> bool:
# use base_job_name to verify the remote branch exists # use base_job_name to verify the remote branch exists
base_job_name(args) base_job_name(args)
# check if the working directory is clean # check if the working directory is clean
clean = subprocess.run(["git", "-C", str(CASSANDRA_DIR), "diff-index", "--quiet", "HEAD", "--"]).returncode clean = subprocess.run(["git", "-C", str(CASSANDRA_DIR), "diff-index", "--quiet", "HEAD", "--"], check=False).returncode
# check if there are unpushed committed changes # check if there are unpushed committed changes
unpushed_commits = bool(subprocess.run(["git", "-C", str(CASSANDRA_DIR), "log", "@{u}..HEAD", "--name-only"], unpushed_commits = bool(subprocess.run(["git", "-C", str(CASSANDRA_DIR), "log", "@{u}..HEAD", "--name-only"],
capture_output=True, text=True, check=False).stdout.strip()) capture_output=True, text=True, check=False).stdout.strip())
@ -192,6 +194,7 @@ def argument_parser() -> argparse.ArgumentParser:
parser.add_argument("-k", "--dtest-branch", default=DEFAULT_DTEST_REPO_BRANCH, help="DTest repository branch.") parser.add_argument("-k", "--dtest-branch", default=DEFAULT_DTEST_REPO_BRANCH, help="DTest repository branch.")
parser.add_argument("-s", "--setup", action="store_true", help="Set up Jenkins before the build.") parser.add_argument("-s", "--setup", action="store_true", help="Set up Jenkins before the build.")
parser.add_argument("--only-setup", action="store_true", help="Only install Jenkins into the k8s cluster.") parser.add_argument("--only-setup", action="store_true", help="Only install Jenkins into the k8s cluster.")
parser.add_argument("-f", "--values-override", help="Path to an additional helm values file, applied over .jenkins/k8s/jenkins-deployment.yaml. Required when the target cluster carries site customisations, see .jenkins/k8s/README.md")
parser.add_argument("--tear-down", action="store_true", help="Tear down Jenkins after the build.") parser.add_argument("--tear-down", action="store_true", help="Tear down Jenkins after the build.")
parser.add_argument("--only-tear-down", action="store_true", help="Only tear down Jenkins.") parser.add_argument("--only-tear-down", action="store_true", help="Only tear down Jenkins.")
parser.add_argument("--only-node-cleaner", action="store_true", help="Only run the node cleaner. The node cleaner scans the k8s nodes, eagerly terminating those unused.") parser.add_argument("--only-node-cleaner", action="store_true", help="Only run the node cleaner. The node cleaner scans the k8s nodes, eagerly terminating those unused.")
@ -212,6 +215,8 @@ def parse_arguments() -> argparse.Namespace:
assert not (args.setup and args.only_setup), "Both --setup or --only-setup cannot be specified." assert not (args.setup and args.only_setup), "Both --setup or --only-setup cannot be specified."
assert not (args.tear_down and args.only_tear_down), "Both --tear-down or --only-tear-down cannot be specified." assert not (args.tear_down and args.only_tear_down), "Both --tear-down or --only-tear-down cannot be specified."
assert not ("custom" == args.profile and not args.profile_custom_regexp), "Custom profile requires --profile-custom-regexp." assert not ("custom" == args.profile and not args.profile_custom_regexp), "Custom profile requires --profile-custom-regexp."
assert not (args.values_override and not (args.setup or args.only_setup)), "--values-override requires --setup or --only-setup."
assert not (args.values_override and not Path(args.values_override).is_file()), f"No such values override file: {args.values_override}"
if not args.url and os.environ.get("JENKINS_URL"): if not args.url and os.environ.get("JENKINS_URL"):
args.url = os.environ.get("JENKINS_URL") args.url = os.environ.get("JENKINS_URL")
@ -253,19 +258,113 @@ def run_kubectl_command(kubeconfig: Optional[str], kubecontext: Optional[str], k
cmd += command cmd += command
return subprocess.run(cmd, capture_output=True, text=True, check=True).stdout.strip() return subprocess.run(cmd, capture_output=True, text=True, check=True).stdout.strip()
def install_jenkins(kubeconfig: Optional[str], kubecontext: Optional[str], kube_ns: str): def run_helm_command(kubeconfig: Optional[str], kubecontext: Optional[str], kube_ns: str, command: list,
"""Installs Jenkins Operator using Helm in the specified K8s namespace.""" capture_output: bool = True, check: bool = True) -> subprocess.CompletedProcess:
print("Adding Helm repository for Jenkins Operator...") """Runs a helm command with the specified kubeconfig, context and namespace."""
subprocess.run(["helm", "repo", "add", "jenkins", "https://charts.jenkins.io"], check=True)
subprocess.run(["helm", "repo", "update"], check=True)
cmd = ["helm"] cmd = ["helm"]
if kubeconfig: if kubeconfig:
cmd += ["--kubeconfig", kubeconfig] cmd += ["--kubeconfig", kubeconfig]
if kubecontext: if kubecontext:
cmd += ["--kube-context", kubecontext] cmd += ["--kube-context", kubecontext]
cmd += ["--namespace", kube_ns, "upgrade", "--install", "-f", DEPLOY_YAML, "cassius", "jenkins/jenkins", "--wait"] cmd += ["--namespace", kube_ns]
result = subprocess.run(cmd, capture_output=True, check=True) cmd += command
return subprocess.run(cmd, capture_output=capture_output, text=True, check=check)
def install_jenkins(kubeconfig: Optional[str], kubecontext: Optional[str], kube_ns: str,
values_override: Optional[str] = None):
"""Installs Jenkins Operator using Helm in the specified K8s namespace."""
def confirm_helm_updates():
"""Prompts before an upgrade drops any values the deployed jenkins currently has."""
def helm_values() -> dict:
"""
The values a deployed jenkins was last installed with, or an empty dict when there is no release.
These are the user-supplied values, whatever files they came from, so they include any customisations
made to the site outside of `.jenkins/k8s/jenkins-deployment.yaml`.
"""
result = run_helm_command(kubeconfig, kubecontext, kube_ns,
["get", "values", "cassius", "-o", "yaml"], check=False)
if result.returncode != 0:
debug(f"No existing cassius release found in namespace {kube_ns}: {result.stderr.strip()}")
return {}
return yaml.safe_load(result.stdout) or {}
def merge_values(base: dict, override: dict) -> dict:
"""Merges two helm values files the way helm does: maps key by key, everything else replaced."""
merged = dict(base)
for key, value in override.items():
if isinstance(value, dict) and isinstance(merged.get(key), dict):
merged[key] = merge_values(merged[key], value)
else:
merged[key] = value
return merged
def detect_lost_values(live: dict, proposed: dict) -> dict:
"""
Values the deployed jenkins has that an upgrade would drop, as {dotted.key.path: live value}.
A key held live but absent from what is about to be applied is either a customisation made to this site,
or a key that `.jenkins/k8s/jenkins-deployment.yaml` has removed since the site was last deployed.
Note also what this cannot see: a key that exists in both but was given a different value locally, such
as an edited `agent.podTemplates` entry, is silently overwritten.
Read the diff of `helm template` before deploying an unfamiliar site.
"""
def leaf_values(values, path: str = "") -> dict:
"""Flattens a values map to {dotted.key.path: value}, lists are leaves (helm replaces them)."""
if not isinstance(values, dict):
return {path: values}
leaves = {}
for key, value in values.items():
leaves.update(leaf_values(value, f"{path}.{key}" if path else str(key)))
return leaves
live_leaves, proposed_leaves = leaf_values(live), leaf_values(proposed)
lost = {path: value for path, value in live_leaves.items() if path not in proposed_leaves}
# lists are replaced wholesale, so also report items dropped from a list that is otherwise still there
for path, value in live_leaves.items():
if isinstance(value, list) and isinstance(proposed_leaves.get(path), list):
dropped = [item for item in value if item not in proposed_leaves[path]]
if dropped:
lost[f"{path}[]"] = dropped
return lost
with open(DEPLOY_YAML, encoding="utf-8") as deploy_yaml:
proposed = yaml.safe_load(deploy_yaml) or {}
if values_override:
with open(values_override, encoding="utf-8") as override_yaml:
proposed = merge_values(proposed, yaml.safe_load(override_yaml) or {})
lost = detect_lost_values(helm_values(), proposed)
if not lost:
return
print(f"\nWARNING: {len(lost)} value(s) the deployed jenkins has are absent from what is about to be applied.")
print("Each is either a customisation of this site, or a key removed from jenkins-deployment.yaml since"
" the site was last deployed. Upgrading drops them:\n")
for path in sorted(lost):
value = str(lost[path]).replace("\n", " ")
print(f" {path}: {value[:100] + '…' if len(value) > 100 else value}")
print(f"\nTo keep any of them, add them to a values override file (see .jenkins/k8s/README.md) and pass"
f" `--values-override`{' (the file passed does not contain them)' if values_override else ''}.")
if not sys.stdin.isatty():
print("Refusing to drop them when running non-interactively.")
sys.exit(1)
if input("\nDrop these values and continue? [y/N] ").strip().lower() not in ("y", "yes"):
print("Aborted, nothing was deployed.")
sys.exit(1)
confirm_helm_updates()
print("Adding Helm repository for Jenkins Operator...")
subprocess.run(["helm", "repo", "add", "jenkins", "https://charts.jenkins.io"], check=True)
subprocess.run(["helm", "repo", "update"], check=True)
# site customisations are applied last, helm merges each -f over the previous
values_files = ["-f", DEPLOY_YAML] + (["-f", values_override] if values_override else [])
result = run_helm_command(kubeconfig, kubecontext, kube_ns,
["upgrade", "--install"] + values_files + ["cassius", "jenkins/jenkins", "--wait"])
run_kubectl_command(kubeconfig, kubecontext, kube_ns, run_kubectl_command(kubeconfig, kubecontext, kube_ns,
["exec", DEFAULT_POD_NAME, "--", ["exec", DEFAULT_POD_NAME, "--",
@ -277,6 +376,19 @@ def install_jenkins(kubeconfig: Optional[str], kubecontext: Optional[str], kube_
sys.exit(1) sys.exit(1)
def wait_for_jenkins_http(ip: str):
host, port = (ip.rsplit(":", 1)[0], int(ip.rsplit(":", 1)[1])) if ":" in ip else (ip, 80)
spin_while(f"Waiting for Jenkins HTTP at {host}:{port}… ", lambda: _tcp_connect_ok(host, port))
def _tcp_connect_ok(host: str, port: int) -> bool:
try:
with socket.create_connection((host, port), timeout=2):
return True
except OSError:
return False
def get_jenkins(k8s_client: client.CoreV1Api, args, kube_ns: str) -> Tuple[str, jenkins.Jenkins]: def get_jenkins(k8s_client: client.CoreV1Api, args, kube_ns: str) -> Tuple[str, jenkins.Jenkins]:
"""Authenticates to Jenkins and returns the Jenkins ip and server objects.""" """Authenticates to Jenkins and returns the Jenkins ip and server objects."""
@ -782,13 +894,10 @@ def cleanup_and_maybe_teardown(kubeconfig: Optional[str], kubecontext: Optional[
IS_RUNNING = False IS_RUNNING = False
if tear_down: if tear_down:
print("Cleaning up Jenkins and all resources.") print("Cleaning up Jenkins and all resources.")
cmd = ["helm"] run_helm_command(kubeconfig, kubecontext, kube_ns, ["uninstall", "cassius"], capture_output=False)
if kubeconfig: # the pvc is annotated `helm.sh/resource-policy: keep`, see .jenkins/k8s/jenkins-deployment.yaml
cmd += ["--kubeconfig", kubeconfig] print(f"Jenkins uninstalled. The jenkins-home volume was kept, delete it with:\n"
if kubecontext: f" kubectl --namespace {kube_ns} delete pvc cassius-jenkins")
cmd += ["--kube-context", kubecontext]
cmd += ["--namespace", kube_ns, "uninstall", "cassius"]
subprocess.run(cmd, check=True)
@contextmanager @contextmanager
@ -826,10 +935,11 @@ def main():
if args.setup or args.only_setup: if args.setup or args.only_setup:
init_k8s_namespace(k8s_client, DEFAULT_KUBE_NS) init_k8s_namespace(k8s_client, DEFAULT_KUBE_NS)
with helm_installation_lock(Path("/tmp/.cassandra-run-ci.lock")): with helm_installation_lock(Path("/tmp/.cassandra-run-ci.lock")):
install_jenkins(args.kubeconfig, args.kubecontext, DEFAULT_KUBE_NS) install_jenkins(args.kubeconfig, args.kubecontext, DEFAULT_KUBE_NS, args.values_override)
(ip, server) = get_jenkins(k8s_client, args, DEFAULT_KUBE_NS) (ip, server) = get_jenkins(k8s_client, args, DEFAULT_KUBE_NS)
if args.setup or args.only_setup: if args.setup or args.only_setup:
wait_for_jenkins_http(ip)
ensure_cassandra_job_parameters_visible(server) ensure_cassandra_job_parameters_visible(server)
if args.only_setup: if args.only_setup:
return return

View File

@ -3,7 +3,7 @@
``` ```
➤ .build/run-ci --help ➤ .build/run-ci --help
usage: run-ci [-h] [-c KUBECONFIG] [-x KUBECONTEXT] [-i URL] [-u USER] [-r REPOSITORY] [-b BRANCH] [-p {packaging,skinny,pre-commit,pre-commit w/ upgrades,post-commit,custom}] [-e PROFILE_CUSTOM_REGEXP] [-j JDK] [-d DTEST_REPOSITORY] [-k DTEST_BRANCH] usage: run-ci [-h] [-c KUBECONFIG] [-x KUBECONTEXT] [-i URL] [-u USER] [-r REPOSITORY] [-b BRANCH] [-p {packaging,skinny,pre-commit,pre-commit w/ upgrades,post-commit,custom}] [-e PROFILE_CUSTOM_REGEXP] [-j JDK] [-d DTEST_REPOSITORY] [-k DTEST_BRANCH]
[-s] [--only-setup] [--tear-down] [--only-tear-down] [--only-node-cleaner] [-o DOWNLOAD_RESULTS] [-s] [--only-setup] [-v VALUES_OVERRIDE] [--tear-down] [--only-tear-down] [--only-node-cleaner] [-o DOWNLOAD_RESULTS]
Run CI pipeline for Cassandra on K8s using Jenkins. Run CI pipeline for Cassandra on K8s using Jenkins.
@ -30,6 +30,8 @@ options:
DTest repository branch. DTest repository branch.
-s, --setup Set up Jenkins before the build. -s, --setup Set up Jenkins before the build.
--only-setup Only install Jenkins into the k8s cluster. --only-setup Only install Jenkins into the k8s cluster.
-v VALUES_OVERRIDE, --values-override VALUES_OVERRIDE
Path to an additional helm values file, applied over .jenkins/k8s/jenkins-deployment.yaml. Required when the target cluster carries site customisations, see .jenkins/k8s/README.md
--tear-down Tear down Jenkins after the build. --tear-down Tear down Jenkins after the build.
--only-tear-down Only tear down Jenkins. --only-tear-down Only tear down Jenkins.
--only-node-cleaner Only run the node cleaner. The node cleaner scans the k8s nodes, eagerly terminating those unused. --only-node-cleaner Only run the node cleaner. The node cleaner scans the k8s nodes, eagerly terminating those unused.
@ -63,7 +65,15 @@ Setup/Update Jenkins Helm into your current kubeconfig
.build/run-ci --only-setup .build/run-ci --only-setup
``` ```
Uninstall Jenkins from your current kubeconfig Setup/Update Jenkins Helm into a cluster that carries site customisations, e.g. pre-ci.cassandra.apache.org
```
.build/run-ci --only-setup --values-override ~/.cassandra-ci/pre-ci-overrides.yaml
```
Before any setup, the values already deployed are compared against those about to be applied. Any value the deployed jenkins holds that the new files lack is listed, and confirmation is asked for before it is dropped; running non-interactively aborts instead. See `.jenkins/k8s/README.md` for what this can and cannot catch.
Uninstall Jenkins from your current kubeconfig.
``` ```
.build/run-ci --only-tear-down .build/run-ci --only-tear-down
``` ```
The jenkins-home volume is kept; delete it separately with `kubectl delete pvc cassius-jenkins`

View File

@ -18,6 +18,7 @@ bs4
dotenv dotenv
kubernetes kubernetes
python-jenkins python-jenkins
pyyaml
requests requests
# optional for different clouds # optional for different clouds

View File

@ -27,12 +27,14 @@
import argparse import argparse
from pathlib import Path from pathlib import Path
import tempfile
import unittest import unittest
from unittest.mock import patch, MagicMock from unittest.mock import patch, MagicMock
# Import the functions from the script # Import the functions from the script
from run_ci import ( from run_ci import (
DEPLOY_YAML,
debug, debug,
install_jenkins, install_jenkins,
get_jenkins, get_jenkins,
@ -55,13 +57,84 @@ class TestCIPipeline(unittest.TestCase):
debug("Test message") debug("Test message")
mock_print.assert_called_with("Test message") mock_print.assert_called_with("Test message")
# the pre-flight check is nested inside install_jenkins, so it is exercised through it: the mocked
# `helm get values` stdout stands in for the values the site already has deployed
LIVE_STORAGE_CLASS = "persistence:\n storageClass: gp2\n"
@patch('run_ci.subprocess.run') @patch('run_ci.subprocess.run')
def test_install_jenkins(self, mock_run): def test_install_jenkins(self, mock_run):
mock_run.return_value = MagicMock(returncode=0) # empty stdout, i.e. nothing deployed yet, so the pre-flight check has nothing to warn about
mock_run.return_value = MagicMock(returncode=0, stdout="")
install_jenkins("test-namespace", Path("/fake/cassandra/dir"), "default") install_jenkins("test-namespace", Path("/fake/cassandra/dir"), "default")
mock_run.assert_any_call(["helm", "repo", "add", "jenkins", "https://charts.jenkins.io"], check=True) mock_run.assert_any_call(["helm", "repo", "add", "jenkins", "https://charts.jenkins.io"], check=True)
mock_run.assert_any_call(["helm", "repo", "update"], check=True) mock_run.assert_any_call(["helm", "repo", "update"], check=True)
@patch('run_ci.subprocess.run')
def test_install_jenkins_values_override(self, mock_run):
mock_run.return_value = MagicMock(returncode=0, stdout="")
with tempfile.NamedTemporaryFile("w", suffix=".yaml") as override:
override.write(self.LIVE_STORAGE_CLASS)
override.flush()
install_jenkins(None, None, "default", override.name)
upgrade_cmd = [c for c in [call.args[0] for call in mock_run.call_args_list] if "upgrade" in c][0]
# the site's overrides must come after, and never replace, the repo's deployment yaml
self.assertEqual(["-f", DEPLOY_YAML, "-f", override.name], upgrade_cmd[5:9])
@patch('run_ci.sys.stdin.isatty')
@patch('run_ci.print')
@patch('run_ci.subprocess.run')
def test_install_jenkins_aborts_non_interactively(self, mock_run, mock_print, mock_isatty):
mock_isatty.return_value = False
mock_run.return_value = MagicMock(returncode=0, stdout=self.LIVE_STORAGE_CLASS)
with self.assertRaises(SystemExit):
install_jenkins(None, None, "default")
self.assertEqual([], [c for c in [call.args[0] for call in mock_run.call_args_list] if "upgrade" in c])
# and continues when the customisation is passed back in as an override. This also proves the merge is
# per key: were the override to replace the whole persistence map, its other keys would now be reported lost
with tempfile.NamedTemporaryFile("w", suffix=".yaml") as override:
override.write(self.LIVE_STORAGE_CLASS)
override.flush()
install_jenkins(None, None, "default", override.name)
self.assertEqual(1, len([c for c in [call.args[0] for call in mock_run.call_args_list] if "upgrade" in c]))
@patch('run_ci.input')
@patch('run_ci.sys.stdin.isatty')
@patch('run_ci.print')
@patch('run_ci.subprocess.run')
def test_install_jenkins_prompts(self, mock_run, mock_print, mock_isatty, mock_input):
mock_isatty.return_value = True
mock_run.return_value = MagicMock(returncode=0, stdout=self.LIVE_STORAGE_CLASS)
mock_input.return_value = "n"
with self.assertRaises(SystemExit):
install_jenkins(None, None, "default")
mock_input.return_value = "y"
install_jenkins(None, None, "default")
@patch('run_ci.sys.stdin.isatty')
@patch('run_ci.print')
@patch('run_ci.subprocess.run')
def test_install_jenkins_reports_only_detectable_losses(self, mock_run, mock_print, mock_isatty):
mock_isatty.return_value = False
# a plugin only this site installs is reported, as is a key the site alone holds; a key held in both but
# locally edited (persistence.size) cannot be seen, and must not be claimed
mock_run.return_value = MagicMock(returncode=0, stdout="persistence:\n size: 1Ti\n"
"controller:\n installPlugins:\n - site-only-plugin\n")
with self.assertRaises(SystemExit):
install_jenkins(None, None, "default")
printed = " ".join(str(call.args[0]) for call in mock_print.call_args_list if call.args)
self.assertIn("controller.installPlugins[]", printed)
self.assertIn("site-only-plugin", printed)
self.assertNotIn("persistence.size", printed)
@patch('run_ci.print')
@patch('run_ci.subprocess.run')
def test_install_jenkins_when_nothing_deployed(self, mock_run, mock_print):
# `helm get values` fails when there is no release, and nothing is then warned about
mock_run.side_effect = lambda cmd, **kwargs: MagicMock(returncode=1 if "get" in cmd else 0,
stdout="", stderr="release: not found")
install_jenkins(None, None, "default")
self.assertEqual([], [call.args[0] for call in mock_print.call_args_list if "WARNING" in str(call.args)])
@patch('run_ci.subprocess.run') @patch('run_ci.subprocess.run')
@patch('run_ci.jenkins.Jenkins') @patch('run_ci.jenkins.Jenkins')
def test_get_jenkins(self, mock_jenkins, mock_run): def test_get_jenkins(self, mock_jenkins, mock_run):
@ -91,13 +164,14 @@ class TestCIPipeline(unittest.TestCase):
@patch('run_ci.stream.stream') @patch('run_ci.stream.stream')
def test_delete_remote_junit_files(self, mock_stream): def test_delete_remote_junit_files(self, mock_stream):
mock_k8s_client = MagicMock() mock_k8s_client = MagicMock()
delete_remote_junit_files(mock_k8s_client, "test-pod", "test-namespace", 456) delete_remote_junit_files(mock_k8s_client, "test-pod", "test-namespace", "test-job", 456)
mock_stream.assert_called() mock_stream.assert_called()
@patch('run_ci.subprocess.run') @patch('run_ci.subprocess.run')
def test_cleanup_and_maybe_teardown(self, mock_run): def test_cleanup_and_maybe_teardown(self, mock_run):
cleanup_and_maybe_teardown(None, None, "test-namespace", True) cleanup_and_maybe_teardown(None, None, "test-namespace", True)
mock_run.assert_called_with(["helm", "--namespace", "test-namespace", "uninstall", "cassius"], check=True) mock_run.assert_called_with(["helm", "--namespace", "test-namespace", "uninstall", "cassius"],
capture_output=False, text=True, check=True)
@patch('run_ci.fcntl.flock') @patch('run_ci.fcntl.flock')
def test_helm_installation_lock(self, mock_flock): def test_helm_installation_lock(self, mock_flock):

58
.github/workflows/jenkins-check.yaml vendored Normal file
View File

@ -0,0 +1,58 @@
name: CI Check
# Checks the CI tooling itself: the run-ci script and everything under .jenkins/
# Nothing here touches a k8s cluster; the helm and groovy steps only render and parse.
on:
push:
branches:
- '**'
paths:
- '.build/run-ci'
- '.build/run-ci.d/**'
- '.jenkins/**'
- '.github/workflows/jenkins-check.yaml'
pull_request:
paths:
- '.build/run-ci'
- '.build/run-ci.d/**'
- '.jenkins/**'
- '.github/workflows/jenkins-check.yaml'
jobs:
run-ci:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Python 3
uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: Install dependencies
run: pip install pylint -r .build/run-ci.d/requirements.txt
- name: Lint run-ci
run: pylint --disable=C0301,W0511,C0103,W0702,C0415,C0116,C0115,R0914,W0603,R0915,R0913,R0917,R0911,W0212,W0621 .build/run-ci
- name: Run run-ci unit tests
run: python .build/run-ci.d/run-ci-test.py
- name: Check run-ci argument parsing
run: python .build/run-ci --help
jenkins-validate:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Python 3
uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: Install dependencies
run: pip install pyyaml
- name: Helm
uses: azure/setup-helm@v4
# validate.sh runs groovy from docker when it is not on the PATH
- name: Validate .jenkins/
run: .jenkins/k8s/jenkins-test.sh

107
.jenkins/Jenkinsfile vendored
View File

@ -355,7 +355,7 @@ def build(command, cell) {
ws("workspace/${JOB_NAME}/${BUILD_NUMBER}/${cell.step}/${cell.arch}/jdk-${cell.jdk}") { ws("workspace/${JOB_NAME}/${BUILD_NUMBER}/${cell.step}/${cell.arch}/jdk-${cell.jdk}") {
try { try {
fetchSource(cell.step, cell.arch, cell.jdk) fetchSource(cell.step, cell.arch, cell.jdk)
sh """ sh label: "checking Jenkinsfile validity", script: """
test -f .jenkins/Jenkinsfile || { echo "Invalid git fork/branch"; exit 1; } test -f .jenkins/Jenkinsfile || { echo "Invalid git fork/branch"; exit 1; }
grep -q "Jenkins CI declaration" .jenkins/Jenkinsfile || { echo "Only Cassandra 5.0+ supported"; exit 1; } grep -q "Jenkins CI declaration" .jenkins/Jenkinsfile || { echo "Only Cassandra 5.0+ supported"; exit 1; }
""" """
@ -373,7 +373,7 @@ def build(command, cell) {
} }
if (0 != status) { error("Stage ${cell.step}${cell_suffix} failed with exit status ${status}") } if (0 != status) { error("Stage ${cell.step}${cell_suffix} failed with exit status ${status}") }
if ("jar" == cell.step) { if ("jar" == cell.step) {
stash name: "${cell.arch}_${cell.jdk}" _stash(cell)
} }
} catch (exc) { } catch (exc) {
if ("org.jenkinsci.plugins.workflow.steps.FlowInterruptedException" == exc.getClass().getName()) { if ("org.jenkinsci.plugins.workflow.steps.FlowInterruptedException" == exc.getClass().getName()) {
@ -438,8 +438,8 @@ def test(command, cell) {
def descriptions = [] def descriptions = []
for (def cause in exc.getCauses()) { for (def cause in exc.getCauses()) {
echo "CauseOfInterruption: ${cause.getClass().getName()} - ${cause.getShortDescription()}" echo "CauseOfInterruption: ${cause.getClass().getName()} - ${cause.getShortDescription()}"
if (cause.getClass().getName().contains('CauseOfInterruption$UserInterruption')) { if (cause.getClass().getName().contains('CauseOfInterruption$UserInterruption') || cause.getClass().getName().contains('ParallelStep$FailFastCause')) {
throw exc // user explicitly aborted — do not retry throw exc // user abort or fail-fast — do not retry
} }
descriptions.add(cause.getShortDescription()) descriptions.add(cause.getShortDescription())
} }
@ -453,24 +453,12 @@ def test(command, cell) {
} }
} }
dir("build") { dir("build") {
sh """ organiseTestResultFiles(cell, cell_suffix)
mkdir -p test/output/${cell.step}
find test/output -type f -name "TEST*.xml" -execdir mkdir -p jdk_${cell.jdk}/${cell.arch} ';' -execdir mv {} jdk_${cell.jdk}/${cell.arch}/{} ';'
find test/output -name cqlshlib.xml -execdir mv cqlshlib.xml ${cell.step}/cqlshlib${cell_suffix}.xml ';'
find test/output -name nosetests.xml -execdir mv nosetests.xml ${cell.step}/nosetests${cell_suffix}.xml ';'
"""
if (!cell.step.startsWith("microbench")) { if (!cell.step.startsWith("microbench")) {
junit testResults: "test/**/TEST-*.xml,test/**/cqlshlib*.xml,test/**/nosetests*.xml", testDataPublishers: [[$class: 'StabilityTestDataPublisher']] junit testResults: "test/**/TEST-*.xml,test/**/cqlshlib*.xml,test/**/nosetests*.xml", testDataPublishers: [[$class: 'StabilityTestDataPublisher']]
} }
// check if we had Linux OOM killer active within the test container which could kill forked JUnit JVM processes debugOomKiller()
sh """ compressTestResultFiles()
echo "docker memory/oomkiller debug:"
cat /sys/fs/cgroup/docker/memory.events || true
"""
sh """
find test/output -type f -name "*.xml" -print0 | xargs -0 -r -n1 -P"\$(nproc)" xz -f
echo "test result files compressed"; find test/output -type f -name "*.xml.xz" | wc -l
"""
archiveArtifacts artifacts: "test/logs/**,test/**/TEST-*.xml.xz,test/**/cqlshlib*.xml.xz,test/**/nosetests*.xml.xz,test/**/jmh-result.json", fingerprint: true archiveArtifacts artifacts: "test/logs/**,test/**/TEST-*.xml.xz,test/**/cqlshlib*.xml.xz,test/**/nosetests*.xml.xz,test/**/jmh-result.json", fingerprint: true
copyToNightlies("${logfile},test/logs/**,test/**/jmh-result.json", "${cell.step}/${cell.arch}/jdk${cell.jdk}/python${cell.python}/cython_${cell.cython}/" + "split_${cell.split}_${splits}".replace("/", "_")) copyToNightlies("${logfile},test/logs/**,test/**/jmh-result.json", "${cell.step}/${cell.arch}/jdk${cell.jdk}/python${cell.python}/cython_${cell.cython}/" + "split_${cell.split}_${splits}".replace("/", "_"))
} }
@ -517,7 +505,7 @@ def fetchDockerImages(dockerfiles) {
// prefetch, from apache jfrog, reduces risking dockerhub pull rate limits // prefetch, from apache jfrog, reduces risking dockerhub pull rate limits
// also prefetch alpine:latest as its used as a utility in the scripts // also prefetch alpine:latest as its used as a utility in the scripts
def dockerfilesVar = dockerfiles.join(' ') def dockerfilesVar = dockerfiles.join(' ')
sh """#!/bin/bash sh label: "fetching docker images...", script: """#!/bin/bash
for dockerfile in ${dockerfilesVar} ; do for dockerfile in ${dockerfilesVar} ; do
image_tag="\$(md5sum .build/docker/\${dockerfile}.docker | cut -d' ' -f1)" image_tag="\$(md5sum .build/docker/\${dockerfile}.docker | cut -d' ' -f1)"
image_name="apache/cassandra-\${dockerfile}:\${image_tag}" image_name="apache/cassandra-\${dockerfile}:\${image_tag}"
@ -527,6 +515,14 @@ def fetchDockerImages(dockerfiles) {
done done
docker pull -q apache.jfrog.io/cassan-docker/alpine:3.19.1 & docker pull -q apache.jfrog.io/cassan-docker/alpine:3.19.1 &
wait wait
# debug how much space the images have taken. df reports the node's filesystem: an emptyDir has no
# size of its own, so this sees node pressure coming, never the docker-storage sizeLimit
{ set +x; } 2>/dev/null
free_gib=\$(df --output=avail -B 1073741824 / | tail -1 | tr -d ' ')
echo "docker images total: \$(docker system df --format '{{.Size}}' | head -1) (node free: \${free_gib}Gi)"
if [ "\${free_gib}" -le 10 ] ; then
echo "WARNING: only \${free_gib}Gi free on the node after pulling images — review the agents' ephemeral-storage budget in .jenkins/k8s/jenkins-deployment.yaml"
fi
""" """
} }
@ -564,15 +560,15 @@ def copyToNightlies(sourceFiles, remoteDirectory='') {
def cleanAgent(job_name) { def cleanAgent(job_name) {
// get any public IP which is more helpful correlating back to the cloud instance // get any public IP which is more helpful correlating back to the cloud instance
sh script: 'hostname; curl -sm 10 ifconfig.me', returnStatus: true sh script: 'hostname; curl -sm 10 ifconfig.me', returnStatus: true
def agentScriptsUrl = "https://raw.githubusercontent.com/apache/cassandra-builds/trunk/jenkins-dsl/agent_scripts/"
if (isCanonical()) { if (isCanonical()) {
def agentScriptsUrl = "https://raw.githubusercontent.com/apache/cassandra-builds/trunk/jenkins-dsl/agent_scripts/"
cleanAgentDocker(job_name, agentScriptsUrl) cleanAgentDocker(job_name, agentScriptsUrl)
logAgentInfo(job_name, agentScriptsUrl)
} }
logAgentInfo(job_name, agentScriptsUrl)
cleanWs() cleanWs()
if (isCanonical()) { if (isCanonical()) {
// in the workspace prune any abandoned or uncleaned builds (CASSANDRA-20436) // in the workspace prune any abandoned or uncleaned builds (CASSANDRA-20436)
sh """#!/bin/bash sh label: "prune abandoned and uncleaned workspace builds files...", script: """#!/bin/bash
set +e set +e
find /home/jenkins/jenkins-*/workspace/ -mindepth 2 -maxdepth 2 -type d -regextype posix-extended -regex '.*/[0-9]+' -mtime +31 -print -exec rm -rf {} + find /home/jenkins/jenkins-*/workspace/ -mindepth 2 -maxdepth 2 -type d -regextype posix-extended -regex '.*/[0-9]+' -mtime +31 -print -exec rm -rf {} +
""" """
@ -582,8 +578,7 @@ def cleanAgent(job_name) {
def cleanAgentDocker(job_name, agentScriptsUrl) { def cleanAgentDocker(job_name, agentScriptsUrl) {
// we don't expect any build to have been running for longer than maxBuildHours // we don't expect any build to have been running for longer than maxBuildHours
def maxBuildHours = 12 def maxBuildHours = 12
echo "Pruning docker for '${job_name}' on ${NODE_NAME}…" ; sh label: "Pruning docker for '${job_name}' on ${NODE_NAME}...", script: """#!/bin/bash
sh """#!/bin/bash
set +e set +e
wget -q ${agentScriptsUrl}/docker_image_pruner.py wget -q ${agentScriptsUrl}/docker_image_pruner.py
wget -q ${agentScriptsUrl}/docker_agent_cleaner.sh wget -q ${agentScriptsUrl}/docker_agent_cleaner.sh
@ -592,12 +587,58 @@ def cleanAgentDocker(job_name, agentScriptsUrl) {
} }
def logAgentInfo(job_name, agentScriptsUrl) { def logAgentInfo(job_name, agentScriptsUrl) {
sh """#!/bin/bash // post-run remaining build/ and docker usage. used to validate the agents' ephemeral-storage budget
set +e -o pipefail sh label: "log build usage...", script: """
wget -q ${agentScriptsUrl}/agent_report.sh { set +x; } 2>/dev/null
bash -x agent_report.sh | tee -a \$(date +"%Y%m%d%H%M")-disk-usage-stats.txt du -sh ${WORKSPACE}/build/m2 ${WORKSPACE}/build/tmp ${WORKSPACE}/build/test ${WORKSPACE}/build 2>/dev/null || true
""" df -h / || true
copyToNightlies("*-disk-usage-stats.txt", "cassandra/ci-cassandra.apache.org/agents/${NODE_NAME}/disk-usage/") """
if (isCanonical()) {
sh label: "running agent_report.sh for disk usage stats (and more)...", script: """#!/bin/bash
set +e -o pipefail
wget -q ${agentScriptsUrl}/agent_report.sh
bash -x agent_report.sh | tee -a \$(date +"%Y%m%d%H%M")-disk-usage-stats.txt
"""
copyToNightlies("*-disk-usage-stats.txt", "cassandra/ci-cassandra.apache.org/agents/${NODE_NAME}/disk-usage/")
}
}
def _stash(cell) {
sh label: "check stash size...", script: """
{ set +x; } 2>/dev/null
free_gib=\$(df --output=avail -B 1073741824 / | tail -1 | tr -d ' ')
stash_gb=\$(du -sb ${WORKSPACE} | awk '{printf "%.1f", \$1/1024/1024/1024}')
echo "stash size: \${stash_gb}G (node free: \${free_gib}Gi)"
if [ "\${free_gib}" -le 10 ] ; then
echo "WARNING: only \${free_gib}Gi free on the node after building stash (\${stash_gb}G) — review jnlp's resourceLimitEphemeralStorage in .jenkins/k8s/jenkins-deployment.yaml"
fi
"""
stash name: "${cell.arch}_${cell.jdk}"
}
def organiseTestResultFiles(cell, cell_suffix) {
sh label: "organise test result files...", script: """
mkdir -p test/output/${cell.step}
find test/output -type f -name "TEST*.xml" -execdir mkdir -p jdk_${cell.jdk}/${cell.arch} ';' -execdir mv {} jdk_${cell.jdk}/${cell.arch}/{} ';'
find test/output -name cqlshlib.xml -execdir mv cqlshlib.xml ${cell.step}/cqlshlib${cell_suffix}.xml ';'
find test/output -name nosetests.xml -execdir mv nosetests.xml ${cell.step}/nosetests${cell_suffix}.xml ';'
"""
}
def debugOomKiller() {
// check if we had Linux OOM killer active within the test container which could kill forked JUnit JVM processes
sh label: "checking for oom kills...", script: """
# docker memory/oomkiller debug:
cat /sys/fs/cgroup/docker/memory.events || true
"""
}
def compressTestResultFiles() {
sh label: "compress test result files...", script: """
{ set +x; } 2>/dev/null
find test/output -type f -name "*.xml" -print0 | xargs -0 -r -n1 -P"\$(nproc)" xz -f
echo "\$(find test/output -type f -name "*.xml.xz" | wc -l) test result files compressed"
"""
} }
///////////////////////////////////////// /////////////////////////////////////////
@ -614,7 +655,7 @@ def generateTestReports() {
def script_vars = "#!/bin/bash -x \n " def script_vars = "#!/bin/bash -x \n "
if (isCanonical()) { if (isCanonical()) {
// copyArtifacts takes >4hrs, hack with manual download // copyArtifacts takes >4hrs, hack with manual download
sh """${script_vars} sh label: "manual download (instead of copyArtifacts)...", script: """${script_vars}
( mkdir -p build/test ( mkdir -p build/test
wget -q ${BUILD_URL}/artifact/test/output/*zip*/output.zip wget -q ${BUILD_URL}/artifact/test/output/*zip*/output.zip
unzip -x -d build/test -q output.zip ) ${teeSuffix} unzip -x -d build/test -q output.zip ) ${teeSuffix}
@ -627,7 +668,7 @@ def generateTestReports() {
// merge splits for each target's test report, other axes are kept separate // merge splits for each target's test report, other axes are kept separate
// TODO parallelised for loop // TODO parallelised for loop
// TODO results_details.tar.xz needs to include all logs for failed tests // TODO results_details.tar.xz needs to include all logs for failed tests
sh """${script_vars} ( sh label: "merging splits test reports...", script: """${script_vars} (
echo "test result files to decompress"; find build/test/output -type f -name "*.xml.xz" | wc -l echo "test result files to decompress"; find build/test/output -type f -name "*.xml.xz" | wc -l
find build/test/output -type f -name "*.xml.xz" -print0 | xargs -0 -r -n1 -P"\$(nproc)" xz -f --decompress find build/test/output -type f -name "*.xml.xz" -print0 | xargs -0 -r -n1 -P"\$(nproc)" xz -f --decompress

View File

@ -24,14 +24,14 @@ ZONE="us-central1-c"
gcloud container clusters create ${CLUSTER_NAME} --machine-type e2-standard-8 --disk-type=pd-ssd --num-nodes 1 --node-labels=cassandra.jenkins.controller=true --autoscaling-profile optimize-utilization --zone ${ZONE} gcloud container clusters create ${CLUSTER_NAME} --machine-type e2-standard-8 --disk-type=pd-ssd --num-nodes 1 --node-labels=cassandra.jenkins.controller=true --autoscaling-profile optimize-utilization --zone ${ZONE}
# small resource nodes # small resource nodes
gcloud container node-pools create agents-small --cluster ${CLUSTER_NAME} --machine-type e2-highcpu-8 --disk-type=pd-ssd --enable-autoscaling --spot --num-nodes=0 --min-nodes=0 --max-nodes=50 --node-labels=cassandra.jenkins.agent=true,cassandra.jenkins.agent.small=true --zone ${ZONE} gcloud container node-pools create agents-small --cluster ${CLUSTER_NAME} --machine-type e2-highcpu-8 --disk-type=pd-ssd --disk-size=107 --enable-autoscaling --spot --num-nodes=0 --min-nodes=0 --max-nodes=50 --node-labels=cassandra.jenkins.agent=true,cassandra.jenkins.agent.small=true --zone ${ZONE}
# medium resource nodes # medium resource nodes
# preference (by cost): n2-highcpu-8, c3-highcpu-8, n4-highcpu-8, n1-highcpu-16 # preference (by cost): n2-highcpu-8, c3-highcpu-8, n4-highcpu-8, n1-highcpu-16
gcloud container node-pools create agents-medium --cluster ${CLUSTER_NAME} --machine-type n2-highcpu-8 --disk-type=pd-ssd --enable-autoscaling --spot --num-nodes=0 --min-nodes=0 --max-nodes=100 --node-labels=cassandra.jenkins.agent=true,cassandra.jenkins.agent.medium=true --zone ${ZONE} gcloud container node-pools create agents-medium --cluster ${CLUSTER_NAME} --machine-type n2-highcpu-8 --disk-type=pd-ssd --disk-size=107 --enable-autoscaling --spot --num-nodes=0 --min-nodes=0 --max-nodes=100 --node-labels=cassandra.jenkins.agent=true,cassandra.jenkins.agent.medium=true --zone ${ZONE}
# large resource nodes # large resource nodes
gcloud container node-pools create agents-large --cluster ${CLUSTER_NAME} --machine-type n2-standard-8 --disk-type=pd-ssd --enable-autoscaling --spot --num-nodes=0 --min-nodes=0 --max-nodes=160 --node-labels=cassandra.jenkins.agent=true,cassandra.jenkins.agent.large=true --zone ${ZONE} gcloud container node-pools create agents-large --cluster ${CLUSTER_NAME} --machine-type n2-standard-8 --disk-type=pd-ssd --disk-size=107 --enable-autoscaling --spot --num-nodes=0 --min-nodes=0 --max-nodes=160 --node-labels=cassandra.jenkins.agent=true,cassandra.jenkins.agent.large=true --zone ${ZONE}
# For each sized resource nodes, pick any machine type that fits, those listed above should work and be the most cost-effective, but this can change region to region # For each sized resource nodes, pick any machine type that fits, those listed above should work and be the most cost-effective, but this can change region to region
# See https://github.com/apache/cassandra/blob/cassandra-6.0/.jenkins/Jenkinsfile#L35-L38 # See https://github.com/apache/cassandra/blob/cassandra-6.0/.jenkins/Jenkinsfile#L35-L38
@ -64,17 +64,81 @@ kubectl exec -it svc/cassius-jenkins -c jenkins -- /bin/cat /run/secrets/additio
This leaves the controller running, a single e2-standard-8 instance. All other node-pools downscale to zero. This leaves the controller running, a single e2-standard-8 instance. All other node-pools downscale to zero.
### Local-only Access ## Upgrading an existing instance (e.g. pre-ci.cassandra.apache.org)
A long-lived site like pre-ci.cassandra.apache.org may carry customisations: hostname, cloud load-balancer, storage class; that are deliberately absent from `jenkins-deployment.yaml`.
Running `helm upgrade -f jenkins-deployment.yaml` or `.build/run-ci --only-setup` will drop those customisations.
Instead keep the customisations in separate overrides file, using it like
```
.build/run-ci --only-setup --values-override <file>
```
or as a second `-f` argument to `helm upgrade`.
##### To collect overridden values
To get and diff currently deployed values against the version of `jenkins-deployment.yaml` that is deployed.
```
RELEASE=cassius
NS=default
kubectl config current-context # confirm correct context
DEPLOYED_COMMIT=cassandra-5.0 # the last jenkins-deployment.yaml deployed git commit sha
helm get values ${RELEASE} -n ${NS} -o yaml > /tmp/cassandra-ci-live-values.yaml
git show ${DEPLOYED_COMMIT}:.jenkins/k8s/jenkins-deployment.yaml > /tmp/deployed.yaml
for f in /tmp/deployed.yaml /tmp/cassandra-ci-live-values.yaml ; do
python3 -c 'import sys,yaml;print(yaml.safe_dump(yaml.safe_load(open(sys.argv[1])),sort_keys=True,width=10000))' ${f} > ${f}.sorted
done
diff -u /tmp/deployed.yaml.sorted /tmp/cassandra-ci-live-values.yaml.sorted
```
Copy the genuine customisations you need to keep into `~/.cassandra-ci/<site>-overrides.yaml`, keeping the full key path.
For example:
```
controller:
ingress:
hostName: pre-ci.cassandra.apache.org # note the capital N, `hostname` is silently ignored
serviceAnnotations: # AWS load-balancer-controller: static EIP, public subnet
service.beta.kubernetes.io/aws-load-balancer-name: pre-ci-apache-cassandra
...
persistence:
storageClass: gp2
```
Beware how Helm merges: maps are merged key by key, but lists and strings are *replaced* wholesale. Each `agent.podTemplates.*` entry is one multi-line string, so overriding a pod template masks every repo-side change to that template. Prefer keeping pod-template customisations out of the overrides file; where that is unavoidable, re-apply the repo's changes to the overridden copy by hand at each upgrade.
### Rolling back a bad Helm upgrade
```
helm history ${RELEASE} -n ${NS}
helm rollback ${RELEASE} <last-good-revision> -n ${NS} --wait --timeout 15m
```
`helm rollback` restores the previous chart *and* values, so the site's customisations come back with it. If the release history itself is unusable, the `/tmp/cassandra-ci-live-values.yaml` from above can be used:
```
helm upgrade ${RELEASE} jenkins/jenkins --version ${CHART_VERSION} -n ${NS} \
-f /tmp/cassandra-ci-live-values.yaml --wait --timeout 15m
```
### Configuring Local-only Access
If you want only local private access to Jenkins, do the following. If you want only local private access to Jenkins, do the following.
Comment these lines before running `heml upgrade …` Comment these lines before running `helm upgrade …`
``` ```
# serviceType: LoadBalancer # serviceType: LoadBalancer
# ingress: # ingress:
# enabled: "true" # enabled: "true"
``` ```
Run the heml upgrade and get the password as usual Run the helm upgrade and get the password as usual
``` ```
helm upgrade --install -f values.yaml cassius jenkins/jenkins --wait helm upgrade --install -f values.yaml cassius jenkins/jenkins --wait

View File

@ -22,7 +22,11 @@
persistence: persistence:
enabled: true enabled: true
size: "500Gi" size: "500Gi"
# aws needs gp2, gke can be left commented # keep the claim (jobs, credentials, build history) when the release is uninstalled.
# deleting it then takes a deliberate `kubectl delete pvc cassius-jenkins`
annotations:
helm.sh/resource-policy: keep
# aws needs gp2, gke can be left commented (add it to your override yaml)
#storageClass: "gp2" #storageClass: "gp2"
controller: controller:
# To get URL run `kubectl describe svc cassius-jenkins | grep 'LoadBalancer Ingress'` # To get URL run `kubectl describe svc cassius-jenkins | grep 'LoadBalancer Ingress'`
@ -31,7 +35,7 @@ controller:
targetPort: 8080 targetPort: 8080
ingress: ingress:
enabled: "true" enabled: "true"
# uncomment and set if you have a "ci-cassandra" dns entry for the jenkins controller # if you have a "ci-cassandra" dns entry for the jenkins controller add the following value to your override yaml
#hostName: ci-cassandra.<your-domain> #hostName: ci-cassandra.<your-domain>
customJenkinsLabels: customJenkinsLabels:
- controller - controller
@ -69,13 +73,14 @@ controller:
- "staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods inspect java.lang.Object" - "staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods inspect java.lang.Object"
- "staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods max java.util.Collection" - "staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods max java.util.Collection"
- "staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods putAt java.util.List java.util.List java.lang.Object" - "staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods putAt java.util.List java.util.List java.lang.Object"
- "field hudson.plugins.git.GitSCMBackwardCompatibility branch"
- "method org.jenkinsci.plugins.workflow.steps.FlowInterruptedException getCauses" - "method org.jenkinsci.plugins.workflow.steps.FlowInterruptedException getCauses"
JCasC: JCasC:
configScripts: configScripts:
welcome-message: | welcome-message: |
jenkins: jenkins:
systemMessage: Welcome to Apache Cassandra systemMessage: Welcome to Apache Cassandra
# we still need separate jobs because Jenkinsfile can differ (and are read before parameters are applied). # Separate jobs are needed because Jenkinsfiles differ, and are read before parameters are applied.
# if a dev branch alters the Jenkinsfile, it will not be picked up by the job you need to edit the job configuration # if a dev branch alters the Jenkinsfile, it will not be picked up by the job you need to edit the job configuration
# see the CAUTION warning in .jenkins/Jenkinsfile # see the CAUTION warning in .jenkins/Jenkinsfile
# TODO: add new version each release branching # TODO: add new version each release branching
@ -154,6 +159,23 @@ agent:
node-selector: node-selector:
cassandra.jenkins.agent: true cassandra.jenkins.agent: true
waitForPodSec: "180" waitForPodSec: "180"
#
# Each template below is an opaque string to the helm chart, parsed only by the kubernetes plugin,
# so the chart (nor .jenkins/k8s/jenkins-test.sh) can validate it.
#
# Two traps then to pay attention to:
# - a volume declared under `volumes:` is generated as `volume-N`, and the generated copy wins any
# merge with a raw `yaml:` entry of the same name, so a field set only there is silently dropped.
# Volumes wanting a field the plugin lacks, a sizeLimit for instance, are declared in `yaml:` alone.
# - ephemeral-storage is charged to the pod: every emptyDir, the workspace included, along with the
# containers' writable layers and logs, all against the containers' limits summed. Declare no
# request and the pod is BestEffort for storage, which the scheduler ignores and the node evicts
# first, reporting only `request is 0`. Each template below budgets 80Gi (of the ~89Gi that a
# 100GiB node allocates) leaving the rest to the node's own image cache and daemonsets.
#
# After any deploy, validate changes like:
# kubectl get pod -l jenkins/cassius-jenkins-agent -o json | jq '.items[0].spec | {volumes, containers: [.containers[] | {name, resources, volumeMounts}]}'
#
podTemplates: podTemplates:
agent-dind-small: | agent-dind-small: |
- name: agent-dind-small - name: agent-dind-small
@ -201,6 +223,9 @@ agent:
resourceLimitCpu: 2 resourceLimitCpu: 2
resourceRequestMemory: 1G resourceRequestMemory: 1G
resourceLimitMemory: 1G resourceLimitMemory: 1G
# the workspace emptyDir
resourceRequestEphemeralStorage: 10Gi
resourceLimitEphemeralStorage: 20Gi
ttyEnabled: 'true' ttyEnabled: 'true'
workingDir: /home/jenkins/agent workingDir: /home/jenkins/agent
- name: dind - name: dind
@ -225,12 +250,13 @@ agent:
resourceLimitCpu: 4 resourceLimitCpu: 4
resourceRequestMemory: 1G resourceRequestMemory: 1G
resourceLimitMemory: 2400M resourceLimitMemory: 2400M
# docker's images and containers, in the docker-storage emptyDir
resourceRequestEphemeralStorage: 40Gi
resourceLimitEphemeralStorage: 60Gi
ttyEnabled: 'true' ttyEnabled: 'true'
workingDir: /home/jenkins/agent workingDir: /home/jenkins/agent
volumes: volumes:
- emptyDirVolume: # /var/lib/docker is not here but in `yaml:` below, the only place it can carry a sizeLimit
memory: 'false'
mountPath: /var/lib/docker
- emptyDirVolume: - emptyDirVolume:
memory: 'false' memory: 'false'
mountPath: /certs mountPath: /certs
@ -247,6 +273,18 @@ agent:
values: values:
- "true" - "true"
topologyKey: kubernetes.io/hostname topologyKey: kubernetes.io/hostname
# docker's storage, named and mounted here so that the sizeLimit survives the plugin's merge.
# 60Gi bounds the images and their containers alone, of the pod's 80Gi
# fetchDockerImages in Jenkinsfile warns as the node fills as our image sizes grow.
volumes:
- name: docker-storage
emptyDir:
sizeLimit: 60Gi
containers:
- name: dind
volumeMounts:
- name: docker-storage
mountPath: /var/lib/docker
agent-dind-medium: | agent-dind-medium: |
- name: agent-dind-medium - name: agent-dind-medium
label: agent-dind cassandra-medium cassandra-amd64-medium label: agent-dind cassandra-medium cassandra-amd64-medium
@ -291,6 +329,9 @@ agent:
resourceLimitCpu: 3 resourceLimitCpu: 3
resourceRequestMemory: 1G resourceRequestMemory: 1G
resourceLimitMemory: 2400M resourceLimitMemory: 2400M
# the workspace emptyDir
resourceRequestEphemeralStorage: 10Gi
resourceLimitEphemeralStorage: 20Gi
ttyEnabled: 'true' ttyEnabled: 'true'
workingDir: /home/jenkins/agent workingDir: /home/jenkins/agent
- name: dind - name: dind
@ -315,12 +356,13 @@ agent:
resourceLimitCpu: 4 resourceLimitCpu: 4
resourceRequestMemory: 3400M resourceRequestMemory: 3400M
resourceLimitMemory: 5G resourceLimitMemory: 5G
# docker's images and containers, in the docker-storage emptyDir
resourceRequestEphemeralStorage: 40Gi
resourceLimitEphemeralStorage: 60Gi
ttyEnabled: 'true' ttyEnabled: 'true'
workingDir: /home/jenkins/agent workingDir: /home/jenkins/agent
volumes: volumes:
- emptyDirVolume: # /var/lib/docker is not here but in `yaml:` below, the only place it can carry a sizeLimit
memory: 'false'
mountPath: /var/lib/docker
- emptyDirVolume: - emptyDirVolume:
memory: 'false' memory: 'false'
mountPath: /certs mountPath: /certs
@ -337,6 +379,18 @@ agent:
values: values:
- "true" - "true"
topologyKey: kubernetes.io/hostname topologyKey: kubernetes.io/hostname
# docker's storage, named and mounted here so that the sizeLimit survives the plugin's merge.
# 60Gi bounds the images and their containers alone, of the pod's 80Gi
# fetchDockerImages in Jenkinsfile warns as the node fills as our image sizes grow.
volumes:
- name: docker-storage
emptyDir:
sizeLimit: 60Gi
containers:
- name: dind
volumeMounts:
- name: docker-storage
mountPath: /var/lib/docker
agent-dind-large: | agent-dind-large: |
- name: agent-dind-large - name: agent-dind-large
label: agent-dind cassandra-large cassandra-amd64-large cassandra-amd64-large-dedicated label: agent-dind cassandra-large cassandra-amd64-large cassandra-amd64-large-dedicated
@ -381,6 +435,9 @@ agent:
resourceLimitCpu: 3 resourceLimitCpu: 3
resourceRequestMemory: 1G resourceRequestMemory: 1G
resourceLimitMemory: 2G resourceLimitMemory: 2G
# the workspace emptyDir
resourceRequestEphemeralStorage: 10Gi
resourceLimitEphemeralStorage: 20Gi
ttyEnabled: 'true' ttyEnabled: 'true'
workingDir: /home/jenkins/agent workingDir: /home/jenkins/agent
- name: dind - name: dind
@ -405,12 +462,13 @@ agent:
resourceLimitCpu: 7 resourceLimitCpu: 7
resourceRequestMemory: 16G resourceRequestMemory: 16G
resourceLimitMemory: 30G resourceLimitMemory: 30G
# docker's images and containers, in the docker-storage emptyDir
resourceRequestEphemeralStorage: 40Gi
resourceLimitEphemeralStorage: 60Gi
ttyEnabled: 'true' ttyEnabled: 'true'
workingDir: /home/jenkins/agent workingDir: /home/jenkins/agent
volumes: volumes:
- emptyDirVolume: # /var/lib/docker is not here but in `yaml:` below, the only place it can carry a sizeLimit
memory: 'false'
mountPath: /var/lib/docker
- emptyDirVolume: - emptyDirVolume:
memory: 'false' memory: 'false'
mountPath: /certs mountPath: /certs
@ -427,5 +485,17 @@ agent:
values: values:
- "true" - "true"
topologyKey: kubernetes.io/hostname topologyKey: kubernetes.io/hostname
# docker's storage, named and mounted here so that the sizeLimit survives the plugin's merge.
# 60Gi bounds the images and their containers alone, of the pod's 80Gi
# fetchDockerImages in Jenkinsfile warns as the node fills as our image sizes grow.
volumes:
- name: docker-storage
emptyDir:
sizeLimit: 60Gi
containers:
- name: dind
volumeMounts:
- name: docker-storage
mountPath: /var/lib/docker

117
.jenkins/k8s/jenkins-test.sh Executable file
View File

@ -0,0 +1,117 @@
#!/bin/bash
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Validates the CI declarations under .jenkins/ without deploying anything:
# - the Jenkinsfile parses as groovy
# - jenkins-deployment.yaml renders through the jenkins helm chart
# - the yaml embedded in it (agent pod templates, JCasC config scripts) parses
#
# Requires: helm, python3 with pyyaml, and either groovy or docker.
# Run from anywhere: .jenkins/k8s/jenkins-test.sh
set -e
CASSANDRA_DIR="$(cd "$(dirname "$0")/../.." > /dev/null && pwd)"
JENKINS_DIR="${CASSANDRA_DIR}/.jenkins"
status=0
command -v helm > /dev/null || { echo "helm must be installed and in the PATH"; exit 1; }
command -v python3 > /dev/null || { echo "python3 must be installed and in the PATH"; exit 1; }
python3 -c "import yaml" 2> /dev/null || { echo "python3 pyyaml must be installed: pip install pyyaml"; exit 1; }
echo "== Jenkinsfile groovy syntax"
# Phases.CONVERSION parses and builds the AST without resolving the pipeline DSL or @NonCPS,
# neither of which exist outside a jenkins controller
syntax_check_dir="$(mktemp -d)"
syntax_check="${syntax_check_dir}/syntax-check.groovy"
# mktemp gives 0700, which the unprivileged user inside the groovy image cannot traverse
chmod 755 "${syntax_check_dir}"
cat > "${syntax_check}" << 'EOF'
import org.codehaus.groovy.control.CompilationUnit
import org.codehaus.groovy.control.Phases
def cu = new CompilationUnit()
args.each { cu.addSource(new File(it)) }
cu.compile(Phases.CONVERSION)
println " ${args.join(', ')} parses"
EOF
if command -v groovy > /dev/null ; then
groovy "${syntax_check}" "${JENKINS_DIR}/Jenkinsfile" || status=1
elif command -v docker > /dev/null ; then
# absolute paths, the image's working directory is not where the script was mounted
docker run --rm -v "${syntax_check_dir}:/check:ro" -v "${JENKINS_DIR}:/jenkins:ro" \
groovy:4.0-jdk17 groovy /check/syntax-check.groovy /jenkins/Jenkinsfile || status=1
else
echo " SKIPPED: neither groovy nor docker found"
fi
echo "== jenkins-deployment.yaml renders through the helm chart"
helm repo add jenkins https://charts.jenkins.io > /dev/null
helm repo update > /dev/null
# --namespace and a release name only so the chart's templates have something to interpolate
helm template cassius jenkins/jenkins --namespace default -f "${JENKINS_DIR}/k8s/jenkins-deployment.yaml" > /dev/null \
&& echo " jenkins-deployment.yaml renders" || status=1
echo "== yaml embedded in jenkins-deployment.yaml"
python3 - "${JENKINS_DIR}/k8s" << 'EOF' || status=1
import sys, yaml
from pathlib import Path
k8s_dir = Path(sys.argv[1])
errors = 0
for path in sorted(k8s_dir.glob("*.yaml")):
try:
values = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
except yaml.YAMLError as error:
print(f" INVALID {path.name}: {error}")
errors += 1
continue
print(f" {path.name} parses")
# the values in these two maps are themselves yaml documents, and a chart never validates them —
# a misindented pod template reaches the kubernetes plugin and silently loses its agents
for keys in (("agent", "podTemplates"), ("controller", "JCasC", "configScripts")):
embedded = values
for key in keys:
embedded = embedded.get(key, {}) if isinstance(embedded, dict) else {}
for name, document in (embedded or {}).items():
location = f"{path.name} {'.'.join(keys)}.{name}"
try:
parsed = yaml.safe_load(document)
except yaml.YAMLError as error:
print(f" INVALID {location}: {error}")
errors += 1
continue
print(f" {location} parses")
# each pod template may carry a raw kubernetes pod spec in a nested `yaml` key
for template in parsed if isinstance(parsed, list) else []:
if isinstance(template, dict) and "yaml" in template:
try:
yaml.safe_load(template["yaml"])
except yaml.YAMLError as error:
print(f" INVALID {location}.yaml: {error}")
errors += 1
else:
print(f" {location}.yaml parses")
sys.exit(1 if errors else 0)
EOF
[ 0 -eq ${status} ] && echo "== all .jenkins/ checks passed" || echo "== FAILED"
exit ${status}

View File

@ -1,9 +1,21 @@
7.0 7.0
* Don't increment client metrics on messaging service connection unpause (CASSANDRA-21491)
* Add nodetool getreplicas (CASSANDRA-17665)
* Implementation of CEP-49: Hardware-accelerated compression (CASSANDRA-20975) * Implementation of CEP-49: Hardware-accelerated compression (CASSANDRA-20975)
* Avoid using ObjectUtils.getFirstNonNull in Schema (CASSANDRA-21394) * Avoid using ObjectUtils.getFirstNonNull in Schema (CASSANDRA-21394)
* Allow nodetool garbagecollect to take a user defined list of SSTables (CASSANDRA-16767) * Allow nodetool garbagecollect to take a user defined list of SSTables (CASSANDRA-16767)
* Add a guardrail for misprepared statements (CASSANDRA-21139) * Add a guardrail for misprepared statements (CASSANDRA-21139)
Merged from 6.0: Merged from 6.0:
* Apply performance optimizations for rows merging logic (CASSANDRA-21524)
* Fix operationMode reporting DECOMMISSION_FAILED instead of LEAVING when resuming a failed decommission (CASSANDRA-21493)
* Avoid megamorphic calls when serializing and deserializing fixed-length values (CASSANDRA-21536)
* Avoid megamorphic calls for Cell.timestamp/ttl/path/localDeletionTimeAsUnsignedInt methods (CASSANDRA-21526)
* Reduce allocations in DefaultQueryOptions (CASSANDRA-21467)
* Allow unreserved keywords as user and identity names in USER and IDENTITY statements (CASSANDRA-21510)
* Reduce allocations in DefaultQueryOptions (CASSANDRA-21467)
* Reduce number of scheduledTasks on metric id release in ThreadLocalMetrics (CASSANDRA-21475)
* Cache various Enum.values() used in deserialization to avoid per-read array allocation (CASSANDRA-21528)
* Fix Accord transaction error message when altering a table (CASSANDRA-20580)
* Depend only on platform-specific Zstd JNI native libraries (CASSANDRA-21483) * Depend only on platform-specific Zstd JNI native libraries (CASSANDRA-21483)
* Expose immediately-executed tasks in the queries virtual table (CASSANDRA-21471) * Expose immediately-executed tasks in the queries virtual table (CASSANDRA-21471)
* Avoid potential deadlock between GlobalLogFollower and GossipStage (CASSANDRA-21384) * Avoid potential deadlock between GlobalLogFollower and GossipStage (CASSANDRA-21384)
@ -21,7 +33,7 @@ Merged from 6.0:
* Always send TCM commit failures as Messaging failures (CASSANDRA-21457) * Always send TCM commit failures as Messaging failures (CASSANDRA-21457)
* Fix ReadCommand serializedSize() using incorrect epoch (CASSANDRA-21438) * Fix ReadCommand serializedSize() using incorrect epoch (CASSANDRA-21438)
* Allocation improvements in ProtocolVersion, StorageProxy and MerkleTree (CASSANDRA-21199) * Allocation improvements in ProtocolVersion, StorageProxy and MerkleTree (CASSANDRA-21199)
* Dont leave autocompaction disabled during bootstrap and replace (CASSANDRA-21236) * Don't leave autocompaction disabled during bootstrap and replace (CASSANDRA-21236)
* Make nodetool abortbootstrap more robust (CASSANDRA-21235) * Make nodetool abortbootstrap more robust (CASSANDRA-21235)
* Don't clear prepared statement cache on nodetool cms initialize (CASSANDRA-21234) * Don't clear prepared statement cache on nodetool cms initialize (CASSANDRA-21234)
* Improve performance when deserializing cluster metadata (CASSANDRA-21224) * Improve performance when deserializing cluster metadata (CASSANDRA-21224)
@ -64,6 +76,8 @@ Merged from 5.0:
* Use estimated compressed size for tables to check if there is enough free space for a compaction (CASSANDRA-21245) * Use estimated compressed size for tables to check if there is enough free space for a compaction (CASSANDRA-21245)
* Fix failing select on system_views.settings for non-string keys (CASSANDRA-21348) * Fix failing select on system_views.settings for non-string keys (CASSANDRA-21348)
Merged from 4.0: Merged from 4.0:
* Bound declared value length against readable bytes in CBUtil (CASSANDRA-21521)
* Verify extension type before initializing reflectively-loaded classes (CASSANDRA-21525)
* Rename conflicting nodetool import --copy-data short option from -p to -cd (CASSANDRA-20214) * Rename conflicting nodetool import --copy-data short option from -p to -cd (CASSANDRA-20214)
* Fix PasswordObfuscator failing to obfuscate certain passwords (CASSANDRA-21113) * Fix PasswordObfuscator failing to obfuscate certain passwords (CASSANDRA-21113)
* Fix negative memtable allocator ownership when an update is shadowed by an existing row deletion (CASSANDRA-21469) * Fix negative memtable allocator ownership when an update is shadowed by an existing row deletion (CASSANDRA-21469)
@ -86,6 +100,7 @@ Merged from 4.0:
* Fix a removed TTLed row re-appearance in a materialized view after a cursor compaction (CASSANDRA-21152) * Fix a removed TTLed row re-appearance in a materialized view after a cursor compaction (CASSANDRA-21152)
* Rework ZSTD dictionary compression logic to create a trainer per training (CASSANDRA-21209) * Rework ZSTD dictionary compression logic to create a trainer per training (CASSANDRA-21209)
Merged from 5.0: Merged from 5.0:
* SAI Component Checksum Validation Should be Segment-Aware (CASSANDRA-21516)
* Ensure SAI sends range tombstones to the coordinator for queries on static columns (CASSANDRA-21332) * Ensure SAI sends range tombstones to the coordinator for queries on static columns (CASSANDRA-21332)
Merged from 4.1: Merged from 4.1:
* Add Paxos v2 option and informatin in cassandra.yaml (CASSANDRA-21316) * Add Paxos v2 option and informatin in cassandra.yaml (CASSANDRA-21316)
@ -8655,4 +8670,3 @@ Full list of issues resolved in 0.4 is at https://issues.apache.org/jira/secure/
* Added FlushPeriodInMinutes configuration parameter to force * Added FlushPeriodInMinutes configuration parameter to force
flushing of infrequently-updated ColumnFamilies flushing of infrequently-updated ColumnFamilies

View File

@ -31,7 +31,7 @@ Getting started
--------------- ---------------
This short guide will walk you through getting a basic one node cluster up This short guide will walk you through getting a basic one node cluster up
and running, and demonstrate some simple reads and writes. For a more-complete guide, please see the Apache Cassandra website's https://cassandra.apache.org/doc/trunk/cassandra/getting-started/index.html[Getting Started Guide]. and running, and demonstrate some simple reads and writes. For a more-complete guide, please see the Apache Cassandra website's https://cassandra.apache.org/doc/latest/cassandra/getting-started/index.html[Getting Started Guide].
First, we'll unpack our archive: First, we'll unpack our archive:

View File

@ -651,6 +651,64 @@
<jflex file="${build.src.java}/org/apache/cassandra/index/sasi/analyzer/StandardTokenizerImpl.jflex" destdir="${build.src.gen-java}/" /> <jflex file="${build.src.java}/org/apache/cassandra/index/sasi/analyzer/StandardTokenizerImpl.jflex" destdir="${build.src.gen-java}/" />
</target> </target>
<!--
Copies a source class into ${build.src.gen-java} under a NEW class name while keeping it
in its ORIGINAL package, so it compiles into an independent class that still enjoys
package-private access to its neighbours.
This lets a hot call site use its own dedicated copy of a class (e.g. a RowMergeIterator
copy of MergeIterator) so that the virtual calls inside it stay mono-/bi-morphic and
remain inlinable, instead of turning megamorphic once every caller in the code base
funnels the shared original through the same bytecode.
The transformation is purely textual: every whole-word occurrence of the original simple
class name is rewritten to the new name. Matching on word boundaries keeps substrings
such as IMergeIterator intact. The package declaration is left untouched, so the copy
stays in the same package as the original and no imports need to change.
-->
<macrodef name="gen-java-copy">
<!-- Fully-qualified name of the class to copy, e.g. org.apache.cassandra.utils.MergeIterator -->
<attribute name="class"/>
<!-- New simple class name for the copy, e.g. RowMergeIterator -->
<attribute name="newName"/>
<sequential>
<local name="gen.src.rel"/>
<local name="gen.simple.name"/>
<!-- source file path relative to ${build.src.java}: dots -> slashes -->
<loadresource property="gen.src.rel">
<string value="@{class}"/>
<filterchain><tokenfilter><replaceregex pattern="\." replace="/" flags="g"/></tokenfilter></filterchain>
</loadresource>
<!-- original simple class name = last segment of the fully-qualified name -->
<loadresource property="gen.simple.name">
<string value="@{class}"/>
<filterchain><tokenfilter><replaceregex pattern="^.*\.([^.]+)$" replace="\1"/></tokenfilter></filterchain>
</loadresource>
<copy todir="${build.src.gen-java}" preservelastmodified="true">
<fileset dir="${build.src.java}" includes="${gen.src.rel}.java"/>
<!-- keep the original package directory, only rename the file -->
<mapper type="regexp" from="^(.*[\\/])[^\\/]+$" to="\1@{newName}.java"/>
<filterchain>
<tokenfilter>
<replaceregex pattern="\b${gen.simple.name}\b" replace="@{newName}" flags="g"/>
</tokenfilter>
</filterchain>
</copy>
</sequential>
</macrodef>
<!--
Generate the copies of hand-picked classes before compilation. Add a
<gen-java-copy class="..." newName="..."/> line here for every class that needs a
dedicated copy.
-->
<target name="gen-java-copies" depends="init"
description="Copy selected classes under new names (e.g. MergeIterator -> RowMergeIterator) into src/gen-java to avoid megamorphic calls">
<gen-java-copy class="org.apache.cassandra.utils.MergeIterator" newName="RowMergeIterator"/>
<gen-java-copy class="org.apache.cassandra.utils.MergeIterator" newName="UnfilteredMergeIterator"/>
<gen-java-copy class="org.apache.cassandra.utils.MergeIterator" newName="ComplexCellMergeIterator"/>
</target>
<!-- create properties file with C version --> <!-- create properties file with C version -->
<target name="_createVersionPropFile" depends="_get-git-sha,set-cqlsh-version,_set-build-date"> <target name="_createVersionPropFile" depends="_get-git-sha,set-cqlsh-version,_set-build-date">
<taskdef name="propertyfile" classname="org.apache.tools.ant.taskdefs.optional.PropertyFile"/> <taskdef name="propertyfile" classname="org.apache.tools.ant.taskdefs.optional.PropertyFile"/>
@ -710,7 +768,7 @@
</javac> </javac>
</target> </target>
<target depends="init,gen-cql3-grammar,generate-cql-html,generate-jflex-java" <target depends="init,gen-cql3-grammar,generate-cql-html,generate-jflex-java,gen-java-copies"
name="build-project"> name="build-project">
<echo message="${ant.project.name}: ${ant.file}"/> <echo message="${ant.project.name}: ${ant.file}"/>
<!-- Order matters! --> <!-- Order matters! -->
@ -2173,7 +2231,7 @@
</target> </target>
<!-- Generate IDEA project description files --> <!-- Generate IDEA project description files -->
<target name="generate-idea-files" depends="init,resolver-dist-lib,gen-cql3-grammar,generate-jflex-java,_createVersionPropFile" description="Generate IDEA files"> <target name="generate-idea-files" depends="init,resolver-dist-lib,gen-cql3-grammar,generate-jflex-java,gen-java-copies,_createVersionPropFile" description="Generate IDEA files">
<delete dir=".idea"/> <delete dir=".idea"/>
<delete file="${eclipse.project.name}.iml"/> <delete file="${eclipse.project.name}.iml"/>
<mkdir dir=".idea"/> <mkdir dir=".idea"/>

View File

@ -31,11 +31,6 @@ In a multi-instance deployment, multiple Cassandra instances will
independently assume that all CPU processors are available to it. This independently assume that all CPU processors are available to it. This
setting allows you to specify a smaller set of processors. setting allows you to specify a smaller set of processors.
== `cassandra.boot_without_jna=true`
If JNA fails to initialize, Cassandra fails to boot. Use this command to
boot Cassandra without JNA.
== `cassandra.config=<directory>` == `cassandra.config=<directory>`
The directory location of the `cassandra.yaml file`. The default The directory location of the `cassandra.yaml file`. The default

View File

@ -265,11 +265,11 @@ management, caching, and training behavior.
=== Dictionary Refresh Settings === Dictionary Refresh Settings
* `compression_dictionary_refresh_interval` (default: `3600`): How often * `compression_dictionary_refresh_interval` (default: `3600s`): How often
(in seconds) to check for and refresh compression dictionaries (in seconds) to check for and refresh compression dictionaries
cluster-wide. Newly trained dictionaries will be picked up by all nodes cluster-wide. Newly trained dictionaries will be picked up by all nodes
within this interval. within this interval.
* `compression_dictionary_refresh_initial_delay` (default: `10`): Initial * `compression_dictionary_refresh_initial_delay` (default: `10s`): Initial
delay (in seconds) before the first dictionary refresh check after node delay (in seconds) before the first dictionary refresh check after node
startup. startup.
@ -278,7 +278,7 @@ startup.
* `compression_dictionary_cache_size` (default: `10`): Maximum number of * `compression_dictionary_cache_size` (default: `10`): Maximum number of
compression dictionaries to cache per table. Higher values reduce lookup compression dictionaries to cache per table. Higher values reduce lookup
overhead but increase memory usage. overhead but increase memory usage.
* `compression_dictionary_cache_expire` (default: `3600`): Dictionary * `compression_dictionary_cache_expire` (default: `24h`): Dictionary
cache entry TTL in seconds. Expired entries are evicted and reloaded on cache entry TTL in seconds. Expired entries are evicted and reloaded on
next access. next access.
@ -289,10 +289,10 @@ Example configuration:
[source,yaml] [source,yaml]
---- ----
# Dictionary refresh and caching # Dictionary refresh and caching
compression_dictionary_refresh_interval: 3600 compression_dictionary_refresh_interval: 3600s
compression_dictionary_refresh_initial_delay: 10 compression_dictionary_refresh_initial_delay: 10s
compression_dictionary_cache_size: 10 compression_dictionary_cache_size: 10
compression_dictionary_cache_expire: 3600 compression_dictionary_cache_expire: 24h
---- ----
=== CQL training parameters: === CQL training parameters:

View File

@ -2363,12 +2363,14 @@ vector_type returns [CQL3Type.Raw vt]
username username
: IDENT : IDENT
| STRING_LITERAL | STRING_LITERAL
| unreserved_keyword
| QUOTED_NAME { addRecognitionError("Quoted strings are are not supported for user names and USER is deprecated, please use ROLE");} | QUOTED_NAME { addRecognitionError("Quoted strings are are not supported for user names and USER is deprecated, please use ROLE");}
; ;
identity identity
: IDENT : IDENT
| STRING_LITERAL | STRING_LITERAL
| unreserved_keyword
| QUOTED_NAME { addRecognitionError("Quoted strings are are not supported for identity");} | QUOTED_NAME { addRecognitionError("Quoted strings are are not supported for identity");}
; ;

View File

@ -63,7 +63,7 @@ public final class AuthConfig
/* Authentication, authorization and role management backend, implementing IAuthenticator, I*Authorizer & IRoleManager */ /* Authentication, authorization and role management backend, implementing IAuthenticator, I*Authorizer & IRoleManager */
IAuthenticator authenticator = authInstantiate(conf.authenticator, AllowAllAuthenticator.class); IAuthenticator authenticator = authInstantiate(conf.authenticator, IAuthenticator.class, AllowAllAuthenticator.class);
// the configuration options regarding credentials caching are only guaranteed to // the configuration options regarding credentials caching are only guaranteed to
// work with PasswordAuthenticator, so log a message if some other authenticator // work with PasswordAuthenticator, so log a message if some other authenticator
@ -82,7 +82,7 @@ public final class AuthConfig
// authorizer // authorizer
IAuthorizer authorizer = authInstantiate(conf.authorizer, AllowAllAuthorizer.class); IAuthorizer authorizer = authInstantiate(conf.authorizer, IAuthorizer.class, AllowAllAuthorizer.class);
if (!authenticator.requireAuthentication() && authorizer.requireAuthorization()) if (!authenticator.requireAuthentication() && authorizer.requireAuthorization())
{ {
@ -94,7 +94,7 @@ public final class AuthConfig
// role manager // role manager
IRoleManager roleManager = authInstantiate(conf.role_manager, CassandraRoleManager.class); IRoleManager roleManager = authInstantiate(conf.role_manager, IRoleManager.class, CassandraRoleManager.class);
if (authenticator instanceof PasswordAuthenticator && !(roleManager instanceof CassandraRoleManager)) if (authenticator instanceof PasswordAuthenticator && !(roleManager instanceof CassandraRoleManager))
throw new ConfigurationException(authenticator.getClass().getName() + " requires " + CassandraRoleManager.class.getName(), false); throw new ConfigurationException(authenticator.getClass().getName() + " requires " + CassandraRoleManager.class.getName(), false);
@ -104,12 +104,15 @@ public final class AuthConfig
// authenticator // authenticator
IInternodeAuthenticator internodeAuthenticator = authInstantiate(conf.internode_authenticator, IInternodeAuthenticator internodeAuthenticator = authInstantiate(conf.internode_authenticator,
IInternodeAuthenticator.class,
AllowAllInternodeAuthenticator.class); AllowAllInternodeAuthenticator.class);
DatabaseDescriptor.setInternodeAuthenticator(internodeAuthenticator); DatabaseDescriptor.setInternodeAuthenticator(internodeAuthenticator);
// network authorizer // network authorizer
INetworkAuthorizer networkAuthorizer = authInstantiate(conf.network_authorizer, AllowAllNetworkAuthorizer.class); INetworkAuthorizer networkAuthorizer = authInstantiate(conf.network_authorizer,
INetworkAuthorizer.class,
AllowAllNetworkAuthorizer.class);
if (networkAuthorizer.requireAuthorization() && !authenticator.requireAuthentication()) if (networkAuthorizer.requireAuthorization() && !authenticator.requireAuthentication())
{ {
@ -120,7 +123,9 @@ public final class AuthConfig
// cidr authorizer // cidr authorizer
ICIDRAuthorizer cidrAuthorizer = authInstantiate(conf.cidr_authorizer, AllowAllCIDRAuthorizer.class); ICIDRAuthorizer cidrAuthorizer = authInstantiate(conf.cidr_authorizer,
ICIDRAuthorizer.class,
AllowAllCIDRAuthorizer.class);
if (cidrAuthorizer.requireAuthorization() && !authenticator.requireAuthentication()) if (cidrAuthorizer.requireAuthorization() && !authenticator.requireAuthentication())
{ {
@ -140,11 +145,11 @@ public final class AuthConfig
DatabaseDescriptor.getInternodeAuthenticator().validateConfiguration(); DatabaseDescriptor.getInternodeAuthenticator().validateConfiguration();
} }
private static <T> T authInstantiate(ParameterizedClass authCls, Class<T> defaultCls) { private static <T> T authInstantiate(ParameterizedClass authCls, Class<T> expectedType, Class<? extends T> defaultCls) {
if (authCls != null && authCls.class_name != null) if (authCls != null && authCls.class_name != null)
{ {
String authPackage = AuthConfig.class.getPackage().getName(); String authPackage = AuthConfig.class.getPackage().getName();
return ParameterizedClass.newInstance(authCls, List.of("", authPackage)); return ParameterizedClass.newInstance(authCls, List.of("", authPackage), expectedType);
} }
// for now, this has to stay and can not be replaced by ParameterizedClass.newInstance as above // for now, this has to stay and can not be replaced by ParameterizedClass.newInstance as above

View File

@ -101,7 +101,8 @@ public class MutualTlsAuthenticator implements IAuthenticator
throw new ConfigurationException(message); throw new ConfigurationException(message);
} }
certificateValidator = ParameterizedClass.newInstance(new ParameterizedClass(certificateValidatorClassName), certificateValidator = ParameterizedClass.newInstance(new ParameterizedClass(certificateValidatorClassName),
Arrays.asList("", AuthConfig.class.getPackage().getName())); Arrays.asList("", AuthConfig.class.getPackage().getName()),
MutualTlsCertificateValidator.class);
Config config = DatabaseDescriptor.getRawConfig(); Config config = DatabaseDescriptor.getRawConfig();
certificateValidityPeriodValidator = new MutualTlsCertificateValidityPeriodValidator(config.client_encryption_options.max_certificate_validity_period); certificateValidityPeriodValidator = new MutualTlsCertificateValidityPeriodValidator(config.client_encryption_options.max_certificate_validity_period);

View File

@ -104,7 +104,8 @@ public class MutualTlsInternodeAuthenticator implements IInternodeAuthenticator
} }
certificateValidator = ParameterizedClass.newInstance(new ParameterizedClass(certificateValidatorClassName), certificateValidator = ParameterizedClass.newInstance(new ParameterizedClass(certificateValidatorClassName),
Arrays.asList("", AuthConfig.class.getPackage().getName())); Arrays.asList("", AuthConfig.class.getPackage().getName()),
MutualTlsCertificateValidator.class);
Config config = DatabaseDescriptor.getRawConfig(); Config config = DatabaseDescriptor.getRawConfig();
if (parameters.containsKey(TRUSTED_PEER_IDENTITIES)) if (parameters.containsKey(TRUSTED_PEER_IDENTITIES))

View File

@ -85,6 +85,7 @@ import org.apache.cassandra.config.Config.DiskAccessMode;
import org.apache.cassandra.config.Config.PaxosOnLinearizabilityViolation; import org.apache.cassandra.config.Config.PaxosOnLinearizabilityViolation;
import org.apache.cassandra.config.Config.PaxosStatePurging; import org.apache.cassandra.config.Config.PaxosStatePurging;
import org.apache.cassandra.config.DurationSpec.IntMillisecondsBound; import org.apache.cassandra.config.DurationSpec.IntMillisecondsBound;
import org.apache.cassandra.cql3.QueryOptions;
import org.apache.cassandra.db.ConsistencyLevel; import org.apache.cassandra.db.ConsistencyLevel;
import org.apache.cassandra.db.commitlog.AbstractCommitLogSegmentManager; import org.apache.cassandra.db.commitlog.AbstractCommitLogSegmentManager;
import org.apache.cassandra.db.commitlog.CommitLog; import org.apache.cassandra.db.commitlog.CommitLog;
@ -273,6 +274,8 @@ public class DatabaseDescriptor
public static volatile boolean allowUnlimitedConcurrentValidations = ALLOW_UNLIMITED_CONCURRENT_VALIDATIONS.getBoolean(); public static volatile boolean allowUnlimitedConcurrentValidations = ALLOW_UNLIMITED_CONCURRENT_VALIDATIONS.getBoolean();
private static volatile QueryOptions.DefaultReadThresholds defaultReadThresholds;
/** /**
* RetryStrategy which provides exponential backoff with full jitter, for use by both CMS and non-CMS members * RetryStrategy which provides exponential backoff with full jitter, for use by both CMS and non-CMS members
* when submitting a Commit request. The range and increments of the backoff times are defined by * when submitting a Commit request. The range and increments of the backoff times are defined by
@ -332,6 +335,7 @@ public class DatabaseDescriptor
private static void clear() private static void clear()
{ {
sstableFormats = null; sstableFormats = null;
defaultReadThresholds = null;
clearMBean("org.apache.cassandra.db:type=DynamicEndpointSnitch"); clearMBean("org.apache.cassandra.db:type=DynamicEndpointSnitch");
clearMBean("org.apache.cassandra.db:type=EndpointSnitchInfo"); clearMBean("org.apache.cassandra.db:type=EndpointSnitchInfo");
clearMBean("org.apache.cassandra.db:type=LocationInfo"); clearMBean("org.apache.cassandra.db:type=LocationInfo");
@ -515,7 +519,7 @@ public class DatabaseDescriptor
String loaderClass = CONFIG_LOADER.getString(); String loaderClass = CONFIG_LOADER.getString();
ConfigurationLoader loader = loaderClass == null ConfigurationLoader loader = loaderClass == null
? new YamlConfigurationLoader() ? new YamlConfigurationLoader()
: FBUtilities.construct(loaderClass, "configuration loading"); : FBUtilities.construct(loaderClass, "configuration loading", ConfigurationLoader.class);
Config config = loader.loadConfig(); Config config = loader.loadConfig();
if (!hasLoggedConfig) if (!hasLoggedConfig)
@ -1655,7 +1659,8 @@ public class DatabaseDescriptor
} }
try try
{ {
Class<?> seedProviderClass = Class.forName(conf.seed_provider.class_name); Class<? extends SeedProvider> seedProviderClass =
FBUtilities.classForNameWithoutInitialization(conf.seed_provider.class_name, "seed provider", SeedProvider.class);
seedProvider = (SeedProvider) seedProviderClass.getConstructor(Map.class).newInstance(conf.seed_provider.parameters); seedProvider = (SeedProvider) seedProviderClass.getConstructor(Map.class).newInstance(conf.seed_provider.parameters);
} }
// there are about 5 checked exceptions that could be thrown here. // there are about 5 checked exceptions that could be thrown here.
@ -2032,7 +2037,7 @@ public class DatabaseDescriptor
{ {
if (!snitchClassName.contains(".")) if (!snitchClassName.contains("."))
snitchClassName = "org.apache.cassandra.locator." + snitchClassName; snitchClassName = "org.apache.cassandra.locator." + snitchClassName;
IEndpointSnitch snitch = FBUtilities.construct(snitchClassName, "snitch"); IEndpointSnitch snitch = FBUtilities.construct(snitchClassName, "snitch", IEndpointSnitch.class);
return snitch; return snitch;
} }
@ -2040,7 +2045,7 @@ public class DatabaseDescriptor
{ {
if (!className.contains(".")) if (!className.contains("."))
className = "org.apache.cassandra.locator." + className; className = "org.apache.cassandra.locator." + className;
NodeProximity sorter = FBUtilities.construct(className, "node proximity measurement"); NodeProximity sorter = FBUtilities.construct(className, "node proximity measurement", NodeProximity.class);
return sorter; return sorter;
} }
@ -2048,7 +2053,7 @@ public class DatabaseDescriptor
{ {
if (!className.contains(".")) if (!className.contains("."))
className = "org.apache.cassandra.locator." + className; className = "org.apache.cassandra.locator." + className;
InitialLocationProvider provider = FBUtilities.construct(className, "initial location provider"); InitialLocationProvider provider = FBUtilities.construct(className, "initial location provider", InitialLocationProvider.class);
return provider; return provider;
} }
@ -2056,7 +2061,7 @@ public class DatabaseDescriptor
{ {
if (!className.contains(".")) if (!className.contains("."))
className = "org.apache.cassandra.locator." + className; className = "org.apache.cassandra.locator." + className;
NodeAddressConfig config = FBUtilities.construct(className, "node address config"); NodeAddressConfig config = FBUtilities.construct(className, "node address config", NodeAddressConfig.class);
return config; return config;
} }
@ -2064,7 +2069,7 @@ public class DatabaseDescriptor
{ {
if (!detectorClassName.contains(".")) if (!detectorClassName.contains("."))
detectorClassName = "org.apache.cassandra.gms." + detectorClassName; detectorClassName = "org.apache.cassandra.gms." + detectorClassName;
IFailureDetector detector = FBUtilities.construct(detectorClassName, "failure detector"); IFailureDetector detector = FBUtilities.construct(detectorClassName, "failure detector", IFailureDetector.class);
return detector; return detector;
} }
@ -5570,6 +5575,14 @@ public class DatabaseDescriptor
return conf.invalid_legacy_protocol_magic_no_spam_enabled; return conf.invalid_legacy_protocol_magic_no_spam_enabled;
} }
public static QueryOptions.DefaultReadThresholds getDefaultReadThresholds()
{
if (defaultReadThresholds == null)
defaultReadThresholds = new QueryOptions.DefaultReadThresholds(getCoordinatorReadSizeWarnThreshold(),
getCoordinatorReadSizeFailThreshold());
return defaultReadThresholds;
}
public static boolean getReadThresholdsEnabled() public static boolean getReadThresholdsEnabled()
{ {
return conf.read_thresholds_enabled; return conf.read_thresholds_enabled;
@ -5594,6 +5607,7 @@ public class DatabaseDescriptor
{ {
logger.info("updating coordinator_read_size_warn_threshold to {}", value); logger.info("updating coordinator_read_size_warn_threshold to {}", value);
conf.coordinator_read_size_warn_threshold = value; conf.coordinator_read_size_warn_threshold = value;
defaultReadThresholds = null;
} }
@Nullable @Nullable
@ -5606,6 +5620,7 @@ public class DatabaseDescriptor
{ {
logger.info("updating coordinator_read_size_fail_threshold to {}", value); logger.info("updating coordinator_read_size_fail_threshold to {}", value);
conf.coordinator_read_size_fail_threshold = value; conf.coordinator_read_size_fail_threshold = value;
defaultReadThresholds = null;
} }
@Nullable @Nullable

View File

@ -64,7 +64,17 @@ public class ParameterizedClass
p.containsKey(PARAMETERS) ? (Map<String, String>)((List<?>)p.get(PARAMETERS)).get(0) : null); p.containsKey(PARAMETERS) ? (Map<String, String>)((List<?>)p.get(PARAMETERS)).get(0) : null);
} }
/**
* Prefer {@link #newInstance(ParameterizedClass, List, Class)}: passing an {@code expectedType} verifies the
* resolved class is the intended extension type before it is instantiated. This overload performs no type
* check (it still loads without initialization, so a wrong class name cannot run its static initializer here).
*/
static public <K> K newInstance(ParameterizedClass parameterizedClass, List<String> searchPackages) static public <K> K newInstance(ParameterizedClass parameterizedClass, List<String> searchPackages)
{
return newInstance(parameterizedClass, searchPackages, null);
}
static public <K> K newInstance(ParameterizedClass parameterizedClass, List<String> searchPackages, Class<K> expectedType)
{ {
Class<?> providerClass = null; Class<?> providerClass = null;
if (searchPackages == null || searchPackages.isEmpty()) if (searchPackages == null || searchPackages.isEmpty())
@ -76,9 +86,12 @@ public class ParameterizedClass
if (!searchPackage.isEmpty() && !searchPackage.endsWith(".")) if (!searchPackage.isEmpty() && !searchPackage.endsWith("."))
searchPackage = searchPackage + '.'; searchPackage = searchPackage + '.';
String name = searchPackage + parameterizedClass.class_name; String name = searchPackage + parameterizedClass.class_name;
providerClass = Class.forName(name); // Load without initialization so a wrong class name does not run its static initializer here. The
// type is verified below (once the search has resolved a class) and the class is only initialized
// later, when it is constructed.
providerClass = Class.forName(name, false, ParameterizedClass.class.getClassLoader());
} }
catch (ClassNotFoundException e) catch (ClassNotFoundException | NoClassDefFoundError e)
{ {
//no-op //no-op
} }
@ -91,6 +104,13 @@ public class ParameterizedClass
throw new ConfigurationException(error); throw new ConfigurationException(error);
} }
// Verify the resolved class is the expected extension type before it is initialized/instantiated. Done once,
// after the package search, so a wrong-type match under an earlier search package does not abort the search
// before a valid class under a later package is found.
if (expectedType != null && !expectedType.isAssignableFrom(providerClass))
throw new ConfigurationException("Invalid parameterized class " + providerClass.getName() +
": must extend or implement " + expectedType.getName());
try try
{ {
Constructor<?> mapConstructor = filterConstructor(providerClass, c -> c.getParameterTypes().length == 1 && c.getParameterTypes()[0].equals(Map.class)); Constructor<?> mapConstructor = filterConstructor(providerClass, c -> c.getParameterTypes().length == 1 && c.getParameterTypes()[0].equals(Map.class));

View File

@ -322,7 +322,7 @@ public abstract class QueryOptions implements RealTimeFunctionContext
// if daemon initialization hasn't happened yet (very common in tests) then ignore // if daemon initialization hasn't happened yet (very common in tests) then ignore
if (!DatabaseDescriptor.isDaemonInitialized() || !DatabaseDescriptor.getReadThresholdsEnabled()) if (!DatabaseDescriptor.isDaemonInitialized() || !DatabaseDescriptor.getReadThresholdsEnabled())
return DisabledReadThresholds.INSTANCE; return DisabledReadThresholds.INSTANCE;
return new DefaultReadThresholds(DatabaseDescriptor.getCoordinatorReadSizeWarnThreshold(), DatabaseDescriptor.getCoordinatorReadSizeFailThreshold()); return DatabaseDescriptor.getDefaultReadThresholds();
} }
} }
@ -349,7 +349,7 @@ public abstract class QueryOptions implements RealTimeFunctionContext
} }
} }
private static class DefaultReadThresholds implements ReadThresholds public static class DefaultReadThresholds implements ReadThresholds
{ {
private final long warnThresholdBytes; private final long warnThresholdBytes;
private final long abortThresholdBytes; private final long abortThresholdBytes;

View File

@ -96,12 +96,19 @@ public abstract class Selector
SLICE_SELECTOR(ElementsSelector.SliceSelector.deserializer), SLICE_SELECTOR(ElementsSelector.SliceSelector.deserializer),
VECTOR_SELECTOR(VectorSelector.deserializer); VECTOR_SELECTOR(VectorSelector.deserializer);
private static final Kind[] VALUES = values();
private final SelectorDeserializer deserializer; private final SelectorDeserializer deserializer;
Kind(SelectorDeserializer deserializer) Kind(SelectorDeserializer deserializer)
{ {
this.deserializer = deserializer; this.deserializer = deserializer;
} }
public static Kind fromOrdinal(int ordinal)
{
return VALUES[ordinal];
}
} }
/** /**
@ -260,7 +267,7 @@ public abstract class Selector
public Selector deserialize(DataInputPlus in, int version, TableMetadata metadata) throws IOException public Selector deserialize(DataInputPlus in, int version, TableMetadata metadata) throws IOException
{ {
Kind kind = Kind.values()[in.readUnsignedByte()]; Kind kind = Kind.fromOrdinal(in.readUnsignedByte());
return kind.deserializer.deserialize(in, version, metadata); return kind.deserializer.deserialize(in, version, metadata);
} }

View File

@ -653,7 +653,7 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
boolean forceMigrationChange = modeChange && explicitlySetMigrationFrom && next.transactionalMigrationFrom != newMigrateFrom; boolean forceMigrationChange = modeChange && explicitlySetMigrationFrom && next.transactionalMigrationFrom != newMigrateFrom;
if (modeChange && next.transactionalMode.accordIsEnabled && !DatabaseDescriptor.getAccordTransactionsEnabled()) if (modeChange && next.transactionalMode.accordIsEnabled && !DatabaseDescriptor.getAccordTransactionsEnabled())
throw ire(format("Cannot change transactional mode to %s for %s.%s with accord_transactions_enabled set to false", throw ire(format("Cannot change transactional mode to %s for %s.%s with accord.enabled set to false",
next.transactionalMode, keyspaceName, tableName)); next.transactionalMode, keyspaceName, tableName));
// user is manually updating migration mode, don't interfere // user is manually updating migration mode, don't interfere

View File

@ -116,7 +116,7 @@ public interface ClusteringBoundOrBoundary<V> extends ClusteringPrefix<V>
public ClusteringBoundOrBoundary<byte[]> deserialize(DataInputPlus in, int version, List<AbstractType<?>> types) throws IOException public ClusteringBoundOrBoundary<byte[]> deserialize(DataInputPlus in, int version, List<AbstractType<?>> types) throws IOException
{ {
Kind kind = Kind.values()[in.readByte()]; Kind kind = Kind.fromOrdinal(in.readByte());
return deserializeValues(in, kind, version, types); return deserializeValues(in, kind, version, types);
} }

View File

@ -84,6 +84,8 @@ public interface ClusteringPrefix<V> extends IMeasurableMemory, Clusterable<V>
SSTABLE_UPPER_BOUND ( 4, 1, v -> ByteSource.GTGT_NEXT_COMPONENT); SSTABLE_UPPER_BOUND ( 4, 1, v -> ByteSource.GTGT_NEXT_COMPONENT);
// @formatter:on // @formatter:on
private static final Kind[] VALUES = values();
private final int comparison; private final int comparison;
/** /**
@ -101,6 +103,11 @@ public interface ClusteringPrefix<V> extends IMeasurableMemory, Clusterable<V>
this.asByteComparable = asByteComparable; this.asByteComparable = asByteComparable;
} }
public static Kind fromOrdinal(int ordinal)
{
return VALUES[ordinal];
}
/** /**
* Compares the 2 provided kind. * Compares the 2 provided kind.
* <p> * <p>
@ -476,7 +483,7 @@ public interface ClusteringPrefix<V> extends IMeasurableMemory, Clusterable<V>
public void skip(DataInputPlus in, int version, List<AbstractType<?>> types) throws IOException public void skip(DataInputPlus in, int version, List<AbstractType<?>> types) throws IOException
{ {
Kind kind = Kind.values()[in.readByte()]; Kind kind = Kind.fromOrdinal(in.readByte());
// We shouldn't serialize static clusterings // We shouldn't serialize static clusterings
assert kind != Kind.STATIC_CLUSTERING; assert kind != Kind.STATIC_CLUSTERING;
if (kind == Kind.CLUSTERING) if (kind == Kind.CLUSTERING)
@ -487,7 +494,7 @@ public interface ClusteringPrefix<V> extends IMeasurableMemory, Clusterable<V>
public ClusteringPrefix<byte[]> deserialize(DataInputPlus in, int version, List<AbstractType<?>> types) throws IOException public ClusteringPrefix<byte[]> deserialize(DataInputPlus in, int version, List<AbstractType<?>> types) throws IOException
{ {
Kind kind = Kind.values()[in.readByte()]; Kind kind = Kind.fromOrdinal(in.readByte());
// We shouldn't serialize static clusterings // We shouldn't serialize static clusterings
assert kind != Kind.STATIC_CLUSTERING; assert kind != Kind.STATIC_CLUSTERING;
if (kind == Kind.CLUSTERING) if (kind == Kind.CLUSTERING)
@ -657,7 +664,7 @@ public interface ClusteringPrefix<V> extends IMeasurableMemory, Clusterable<V>
throw new IOException("Corrupt flags value for clustering prefix (isStatic flag set): " + flags); throw new IOException("Corrupt flags value for clustering prefix (isStatic flag set): " + flags);
this.nextIsRow = UnfilteredSerializer.kind(flags) == Unfiltered.Kind.ROW; this.nextIsRow = UnfilteredSerializer.kind(flags) == Unfiltered.Kind.ROW;
this.nextKind = nextIsRow ? Kind.CLUSTERING : ClusteringPrefix.Kind.values()[in.readByte()]; this.nextKind = nextIsRow ? Kind.CLUSTERING : Kind.fromOrdinal(in.readByte());
this.nextSize = nextIsRow ? comparator.size() : in.readUnsignedShort(); this.nextSize = nextIsRow ? comparator.size() : in.readUnsignedShort();
this.deserializedSize = 0; this.deserializedSize = 0;

View File

@ -178,7 +178,8 @@ public abstract class DeletionTime implements Comparable<DeletionTime>, IMeasura
public boolean deletes(Cell<?> cell) public boolean deletes(Cell<?> cell)
{ {
return deletes(cell.timestamp()); // check for LIVE first to avoid a potential cell megamorphic call
return markedForDeleteAt() != MARKED_FOR_DELETE_AT_LIVE && deletes(cell.timestamp());
} }
public boolean deletes(long timestamp) public boolean deletes(long timestamp)

View File

@ -198,6 +198,8 @@ public abstract class ReadCommand extends AbstractReadQuery
SINGLE_PARTITION (SinglePartitionReadCommand.selectionDeserializer, SinglePartitionReadCommand.accordSelectionDeserializer), SINGLE_PARTITION (SinglePartitionReadCommand.selectionDeserializer, SinglePartitionReadCommand.accordSelectionDeserializer),
PARTITION_RANGE (PartitionRangeReadCommand.selectionDeserializer, ignore -> PartitionRangeReadCommand.selectionDeserializer); PARTITION_RANGE (PartitionRangeReadCommand.selectionDeserializer, ignore -> PartitionRangeReadCommand.selectionDeserializer);
private static final Kind[] VALUES = values();
private final SelectionDeserializer selectionDeserializer; private final SelectionDeserializer selectionDeserializer;
private final Function<Seekable, SelectionDeserializer> accordSelectionDeserializer; private final Function<Seekable, SelectionDeserializer> accordSelectionDeserializer;
@ -206,6 +208,11 @@ public abstract class ReadCommand extends AbstractReadQuery
this.selectionDeserializer = selectionDeserializer; this.selectionDeserializer = selectionDeserializer;
this.accordSelectionDeserializer = accordSelectionDeserializer; this.accordSelectionDeserializer = accordSelectionDeserializer;
} }
public static Kind fromOrdinal(int ordinal)
{
return VALUES[ordinal];
}
} }
protected ReadCommand(Epoch serializedAtEpoch, protected ReadCommand(Epoch serializedAtEpoch,
@ -1450,7 +1457,7 @@ public abstract class ReadCommand extends AbstractReadQuery
public ReadCommand deserialize(DataInputPlus in, int version) throws IOException public ReadCommand deserialize(DataInputPlus in, int version) throws IOException
{ {
Kind kind = Kind.values()[in.readByte()]; Kind kind = Kind.fromOrdinal(in.readByte());
int flags = in.readByte(); int flags = in.readByte();
// Shouldn't happen or it's a user error (see comment above) but // Shouldn't happen or it's a user error (see comment above) but
// better complain loudly than doing the wrong thing. // better complain loudly than doing the wrong thing.
@ -1488,7 +1495,7 @@ public abstract class ReadCommand extends AbstractReadQuery
public ReadCommand deserializeForAccord(Seekable key, TableMetadatas tables, DataInputPlus in, int version) throws IOException public ReadCommand deserializeForAccord(Seekable key, TableMetadatas tables, DataInputPlus in, int version) throws IOException
{ {
Kind kind = Kind.values()[in.readByte()]; Kind kind = Kind.fromOrdinal(in.readByte());
int flags = in.readByte(); int flags = in.readByte();
if (isDigest(flags) || isForThrift(flags) || acceptsTransient(flags)) if (isDigest(flags) || isForThrift(flags) || acceptsTransient(flags))
throw new IllegalStateException("Received an Accord command with a digest/thrift/transient flag set."); throw new IllegalStateException("Received an Accord command with a digest/thrift/transient flag set.");

View File

@ -55,7 +55,7 @@ public interface StorageHook
String className = STORAGE_HOOK.getString(); String className = STORAGE_HOOK.getString();
if (className != null) if (className != null)
{ {
return FBUtilities.construct(className, StorageHook.class.getSimpleName()); return FBUtilities.construct(className, StorageHook.class.getSimpleName(), StorageHook.class);
} }
return new StorageHook() return new StorageHook()

View File

@ -65,7 +65,14 @@ public abstract class AggregationSpecification
*/ */
public enum Kind public enum Kind
{ {
AGGREGATE_EVERYTHING, AGGREGATE_BY_PK_PREFIX, AGGREGATE_BY_PK_PREFIX_WITH_SELECTOR AGGREGATE_EVERYTHING, AGGREGATE_BY_PK_PREFIX, AGGREGATE_BY_PK_PREFIX_WITH_SELECTOR;
private static final Kind[] VALUES = values();
public static Kind fromOrdinal(int ordinal)
{
return VALUES[ordinal];
}
} }
/** /**
@ -253,7 +260,7 @@ public abstract class AggregationSpecification
public AggregationSpecification deserialize(DataInputPlus in, int version, TableMetadata metadata) throws IOException public AggregationSpecification deserialize(DataInputPlus in, int version, TableMetadata metadata) throws IOException
{ {
Kind kind = Kind.values()[in.readUnsignedByte()]; Kind kind = Kind.fromOrdinal(in.readUnsignedByte());
switch (kind) switch (kind)
{ {
case AGGREGATE_EVERYTHING: case AGGREGATE_EVERYTHING:

View File

@ -80,7 +80,7 @@ public abstract class AbstractClusteringIndexFilter implements ClusteringIndexFi
public ClusteringIndexFilter deserialize(DataInputPlus in, int version, TableMetadata metadata) throws IOException public ClusteringIndexFilter deserialize(DataInputPlus in, int version, TableMetadata metadata) throws IOException
{ {
Kind kind = Kind.values()[in.readUnsignedByte()]; Kind kind = Kind.fromOrdinal(in.readUnsignedByte());
boolean reversed = in.readBoolean(); boolean reversed = in.readBoolean();
return kind.deserializer.deserialize(in, version, metadata, reversed); return kind.deserializer.deserialize(in, version, metadata, reversed);

View File

@ -46,12 +46,19 @@ public interface ClusteringIndexFilter
SLICE (ClusteringIndexSliceFilter.deserializer), SLICE (ClusteringIndexSliceFilter.deserializer),
NAMES (ClusteringIndexNamesFilter.deserializer); NAMES (ClusteringIndexNamesFilter.deserializer);
private static final Kind[] VALUES = values();
protected final InternalDeserializer deserializer; protected final InternalDeserializer deserializer;
private Kind(InternalDeserializer deserializer) private Kind(InternalDeserializer deserializer)
{ {
this.deserializer = deserializer; this.deserializer = deserializer;
} }
public static Kind fromOrdinal(int ordinal)
{
return VALUES[ordinal];
}
} }
static interface InternalDeserializer static interface InternalDeserializer

View File

@ -44,7 +44,17 @@ public abstract class ColumnSubselection implements Comparable<ColumnSubselectio
public static final Serializer serializer = new Serializer(); public static final Serializer serializer = new Serializer();
/* this enum is used in serialization; preserve order for compatibility */ /* this enum is used in serialization; preserve order for compatibility */
private enum Kind { SLICE, ELEMENT } private enum Kind
{
SLICE, ELEMENT;
private static final Kind[] VALUES = values();
static Kind fromOrdinal(int ordinal)
{
return VALUES[ordinal];
}
}
protected final ColumnMetadata column; protected final ColumnMetadata column;
@ -229,7 +239,7 @@ public abstract class ColumnSubselection implements Comparable<ColumnSubselectio
} }
} }
Kind kind = Kind.values()[in.readUnsignedByte()]; Kind kind = Kind.fromOrdinal(in.readUnsignedByte());
switch (kind) switch (kind)
{ {
case SLICE: case SLICE:

View File

@ -102,7 +102,14 @@ public abstract class DataLimits
/** @deprecated See CASSANDRA-16582 */ /** @deprecated See CASSANDRA-16582 */
@Deprecated(since = "4.0") SUPER_COLUMN_COUNTING_LIMIT, //Deprecated and unused in 4.0, stop publishing in 5.0, reclaim in 6.0 @Deprecated(since = "4.0") SUPER_COLUMN_COUNTING_LIMIT, //Deprecated and unused in 4.0, stop publishing in 5.0, reclaim in 6.0
CQL_GROUP_BY_LIMIT, CQL_GROUP_BY_LIMIT,
CQL_GROUP_BY_PAGING_LIMIT, CQL_GROUP_BY_PAGING_LIMIT;
private static final Kind[] VALUES = values();
public static Kind fromOrdinal(int ordinal)
{
return VALUES[ordinal];
}
} }
public static DataLimits cqlLimits(int cqlRowLimit) public static DataLimits cqlLimits(int cqlRowLimit)
@ -1191,7 +1198,7 @@ public abstract class DataLimits
public DataLimits deserialize(DataInputPlus in, int version, TableMetadata metadata) throws IOException public DataLimits deserialize(DataInputPlus in, int version, TableMetadata metadata) throws IOException
{ {
Kind kind = Kind.values()[in.readUnsignedByte()]; Kind kind = Kind.fromOrdinal(in.readUnsignedByte());
switch (kind) switch (kind)
{ {
case CQL_LIMIT: case CQL_LIMIT:

View File

@ -493,7 +493,17 @@ public class RowFilter implements Iterable<RowFilter.Expression>
// and this is why we have some UNUSEDX for values we don't use anymore // and this is why we have some UNUSEDX for values we don't use anymore
// (we could clean those on a major protocol update, but it's not worth // (we could clean those on a major protocol update, but it's not worth
// the trouble for now) // the trouble for now)
protected enum Kind { SIMPLE, MAP_ELEMENT, UNUSED1, CUSTOM, USER } protected enum Kind
{
SIMPLE, MAP_ELEMENT, UNUSED1, CUSTOM, USER;
private static final Kind[] VALUES = values();
static Kind fromOrdinal(int ordinal)
{
return VALUES[ordinal];
}
}
protected abstract Kind kind(); protected abstract Kind kind();
protected final ColumnMetadata column; protected final ColumnMetadata column;
@ -685,7 +695,7 @@ public class RowFilter implements Iterable<RowFilter.Expression>
public Expression deserialize(DataInputPlus in, int version, TableMetadata metadata) throws IOException public Expression deserialize(DataInputPlus in, int version, TableMetadata metadata) throws IOException
{ {
Kind kind = Kind.values()[in.readByte()]; Kind kind = Kind.fromOrdinal(in.readByte());
// custom expressions (3.0+ only) do not contain a column or operator, only a value // custom expressions (3.0+ only) do not contain a column or operator, only a value
if (kind == Kind.CUSTOM) if (kind == Kind.CUSTOM)

View File

@ -65,7 +65,7 @@ public interface GuardrailsConfigProvider
*/ */
static GuardrailsConfigProvider build(String customImpl) static GuardrailsConfigProvider build(String customImpl)
{ {
return FBUtilities.construct(customImpl, "custom guardrails config provider"); return FBUtilities.construct(customImpl, "custom guardrails config provider", GuardrailsConfigProvider.class);
} }
/** /**

View File

@ -143,8 +143,11 @@ public abstract class ValueGenerator<VALUE>
try try
{ {
Class<? extends ValueGenerator> rawGeneratorClass =
FBUtilities.classForNameWithoutInitialization(className, "generator", ValueGenerator.class);
@SuppressWarnings("unchecked")
Class<? extends ValueGenerator<VALUE>> generatorClass = Class<? extends ValueGenerator<VALUE>> generatorClass =
FBUtilities.classForName(className, "generator"); (Class<? extends ValueGenerator<VALUE>>) rawGeneratorClass;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
ValueGenerator<VALUE> generator = generatorClass.getConstructor(CustomGuardrailConfig.class) ValueGenerator<VALUE> generator = generatorClass.getConstructor(CustomGuardrailConfig.class)

View File

@ -127,8 +127,11 @@ public abstract class ValueValidator<VALUE>
try try
{ {
Class<? extends ValueValidator> rawValidatorClass =
FBUtilities.classForNameWithoutInitialization(className, "validator", ValueValidator.class);
@SuppressWarnings("unchecked")
Class<? extends ValueValidator<VALUE>> validatorClass = Class<? extends ValueValidator<VALUE>> validatorClass =
FBUtilities.classForName(className, "validator"); (Class<? extends ValueValidator<VALUE>>) rawValidatorClass;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
ValueValidator<VALUE> validator = validatorClass.getConstructor(CustomGuardrailConfig.class) ValueValidator<VALUE> validator = validatorClass.getConstructor(CustomGuardrailConfig.class)

View File

@ -39,7 +39,7 @@ public abstract class AbstractTimeUUIDType<T> extends TemporalType<T>
{ {
AbstractTimeUUIDType() AbstractTimeUUIDType()
{ {
super(ComparisonType.CUSTOM); super(ComparisonType.CUSTOM, 16);
} // singleton } // singleton
@Override @Override
@ -193,12 +193,6 @@ public abstract class AbstractTimeUUIDType<T> extends TemporalType<T>
return super.decomposeUntyped(value); return super.decomposeUntyped(value);
} }
@Override
public int valueLengthIfFixed()
{
return 16;
}
@Override @Override
public long toTimeInMillis(ByteBuffer value) public long toTimeInMillis(ByteBuffer value)
{ {

View File

@ -90,11 +90,18 @@ public abstract class AbstractType<T> implements Comparator<ByteBuffer>, Assignm
public final ComparisonType comparisonType; public final ComparisonType comparisonType;
public final boolean isByteOrderComparable; public final boolean isByteOrderComparable;
public final ValueComparators comparatorSet; public final ValueComparators comparatorSet;
private final int valueLengthIfFixed;
protected AbstractType(ComparisonType comparisonType) protected AbstractType(ComparisonType comparisonType)
{
this(comparisonType, VARIABLE_LENGTH);
}
protected AbstractType(ComparisonType comparisonType, int valueLengthIfFixed)
{ {
this.comparisonType = comparisonType; this.comparisonType = comparisonType;
this.isByteOrderComparable = comparisonType == ComparisonType.BYTE_ORDER; this.isByteOrderComparable = comparisonType == ComparisonType.BYTE_ORDER;
this.valueLengthIfFixed = valueLengthIfFixed;
reverseComparator = (o1, o2) -> AbstractType.this.compare(o2, o1); reverseComparator = (o1, o2) -> AbstractType.this.compare(o2, o1);
try try
{ {
@ -384,12 +391,12 @@ public abstract class AbstractType<T> implements Comparator<ByteBuffer>, Assignm
/** /**
* Similar to {@link #isValueCompatibleWith(AbstractType)}, but takes into account {@link Cell} encoding. * Similar to {@link #isValueCompatibleWith(AbstractType)}, but takes into account {@link Cell} encoding.
* In particular, this method doesn't consider two types serialization compatible if one of them has fixed * In particular, this method doesn't consider two types serialization compatible if one of them has fixed
* length (overrides {@link #valueLengthIfFixed()}, and the other one doesn't. * length, and the other one doesn't.
*/ */
public boolean isSerializationCompatibleWith(AbstractType<?> previous) public boolean isSerializationCompatibleWith(AbstractType<?> previous)
{ {
return isValueCompatibleWith(previous) return isValueCompatibleWith(previous)
&& valueLengthIfFixed() == previous.valueLengthIfFixed() && valueLengthIfFixed == previous.valueLengthIfFixed
&& isMultiCell() == previous.isMultiCell(); && isMultiCell() == previous.isMultiCell();
} }
@ -498,7 +505,7 @@ public abstract class AbstractType<T> implements Comparator<ByteBuffer>, Assignm
*/ */
public int valueLengthIfFixed() public int valueLengthIfFixed()
{ {
return VARIABLE_LENGTH; return valueLengthIfFixed;
} }
/** /**
@ -508,7 +515,7 @@ public abstract class AbstractType<T> implements Comparator<ByteBuffer>, Assignm
*/ */
public final boolean isValueLengthFixed() public final boolean isValueLengthFixed()
{ {
return valueLengthIfFixed() != VARIABLE_LENGTH; return valueLengthIfFixed != VARIABLE_LENGTH;
} }
/** /**
@ -570,7 +577,7 @@ public abstract class AbstractType<T> implements Comparator<ByteBuffer>, Assignm
public <V> void writeValue(V value, ValueAccessor<V> accessor, DataOutputPlus out) throws IOException public <V> void writeValue(V value, ValueAccessor<V> accessor, DataOutputPlus out) throws IOException
{ {
assert !isNull(value, accessor) : "bytes should not be null for type " + this; assert !isNull(value, accessor) : "bytes should not be null for type " + this;
int expectedValueLength = valueLengthIfFixed(); int expectedValueLength = valueLengthIfFixed;
if (expectedValueLength >= 0) if (expectedValueLength >= 0)
{ {
int actualValueLength = accessor.size(value); int actualValueLength = accessor.size(value);
@ -589,7 +596,7 @@ public abstract class AbstractType<T> implements Comparator<ByteBuffer>, Assignm
public <V> void writeValue(IndexedValueHolder<V> valueHolder, int i, ValueAccessor<V> accessor, DataOutputPlus out) throws IOException public <V> void writeValue(IndexedValueHolder<V> valueHolder, int i, ValueAccessor<V> accessor, DataOutputPlus out) throws IOException
{ {
assert !valueHolder.isNull(i) : "bytes should not be null for type " + this; assert !valueHolder.isNull(i) : "bytes should not be null for type " + this;
int expectedValueLength = valueLengthIfFixed(); int expectedValueLength = valueLengthIfFixed;
if (expectedValueLength >= 0) if (expectedValueLength >= 0)
{ {
int actualValueLength = valueHolder.size(i); int actualValueLength = valueHolder.size(i);
@ -613,7 +620,7 @@ public abstract class AbstractType<T> implements Comparator<ByteBuffer>, Assignm
public <V> long writtenLength(V value, ValueAccessor<V> accessor) public <V> long writtenLength(V value, ValueAccessor<V> accessor)
{ {
assert !accessor.isEmpty(value) : "bytes should not be empty for type " + this; assert !accessor.isEmpty(value) : "bytes should not be empty for type " + this;
return valueLengthIfFixed() >= 0 return valueLengthIfFixed >= 0
? accessor.size(value) // if the size is wrong, this will be detected in writeValue ? accessor.size(value) // if the size is wrong, this will be detected in writeValue
: accessor.sizeWithVIntLength(value); : accessor.sizeWithVIntLength(value);
} }
@ -621,7 +628,7 @@ public abstract class AbstractType<T> implements Comparator<ByteBuffer>, Assignm
public <V> long writtenLength(IndexedValueHolder<V> valueHolder, int i, ValueAccessor<V> accessor) public <V> long writtenLength(IndexedValueHolder<V> valueHolder, int i, ValueAccessor<V> accessor)
{ {
assert !valueHolder.isNull(i) : "bytes should not be null for type " + this; assert !valueHolder.isNull(i) : "bytes should not be null for type " + this;
return valueLengthIfFixed() >= 0 return valueLengthIfFixed >= 0
? valueHolder.size(i) // if the size is wrong, this will be detected in writeValue ? valueHolder.size(i) // if the size is wrong, this will be detected in writeValue
: accessor.sizeWithVIntLength(valueHolder, i); : accessor.sizeWithVIntLength(valueHolder, i);
} }
@ -643,7 +650,7 @@ public abstract class AbstractType<T> implements Comparator<ByteBuffer>, Assignm
public <V> V read(ValueAccessor<V> accessor, DataInputPlus in, int maxValueSize) throws IOException public <V> V read(ValueAccessor<V> accessor, DataInputPlus in, int maxValueSize) throws IOException
{ {
int length = valueLengthIfFixed(); int length = valueLengthIfFixed;
if (length >= 0) if (length >= 0)
return accessor.read(in, length); return accessor.read(in, length);
@ -664,7 +671,7 @@ public abstract class AbstractType<T> implements Comparator<ByteBuffer>, Assignm
public void skipValue(DataInputPlus in) throws IOException public void skipValue(DataInputPlus in) throws IOException
{ {
int length = valueLengthIfFixed(); int length = valueLengthIfFixed;
if (length >= 0) if (length >= 0)
in.skipBytesFully(length); in.skipBytesFully(length);
else else

View File

@ -36,7 +36,7 @@ public class BooleanType extends AbstractType<Boolean>
private static final ArgumentDeserializer ARGUMENT_DESERIALIZER = new DefaultArgumentDeserializer(instance); private static final ArgumentDeserializer ARGUMENT_DESERIALIZER = new DefaultArgumentDeserializer(instance);
private static final ByteBuffer MASKED_VALUE = instance.decompose(false); private static final ByteBuffer MASKED_VALUE = instance.decompose(false);
BooleanType() {super(ComparisonType.CUSTOM);} // singleton BooleanType() {super(ComparisonType.CUSTOM, 1);} // singleton
@Override @Override
public boolean allowsEmpty() public boolean allowsEmpty()
@ -127,12 +127,6 @@ public class BooleanType extends AbstractType<Boolean>
return ARGUMENT_DESERIALIZER; return ARGUMENT_DESERIALIZER;
} }
@Override
public int valueLengthIfFixed()
{
return 1;
}
@Override @Override
public ByteBuffer getMaskedValue() public ByteBuffer getMaskedValue()
{ {

View File

@ -49,7 +49,7 @@ public class DateType extends AbstractType<Date>
private static final ArgumentDeserializer ARGUMENT_DESERIALIZER = new DefaultArgumentDeserializer(instance); private static final ArgumentDeserializer ARGUMENT_DESERIALIZER = new DefaultArgumentDeserializer(instance);
private static final ByteBuffer MASKED_VALUE = instance.decompose(new Date(0)); private static final ByteBuffer MASKED_VALUE = instance.decompose(new Date(0));
DateType() {super(ComparisonType.BYTE_ORDER);} // singleton DateType() {super(ComparisonType.BYTE_ORDER, 8);} // singleton
public boolean isEmptyValueMeaningless() public boolean isEmptyValueMeaningless()
{ {
@ -143,12 +143,6 @@ public class DateType extends AbstractType<Date>
return ARGUMENT_DESERIALIZER; return ARGUMENT_DESERIALIZER;
} }
@Override
public int valueLengthIfFixed()
{
return 8;
}
@Override @Override
public ByteBuffer getMaskedValue() public ByteBuffer getMaskedValue()
{ {

View File

@ -40,7 +40,7 @@ public class DoubleType extends NumberType<Double>
private static final ByteBuffer MASKED_VALUE = instance.decompose(0d); private static final ByteBuffer MASKED_VALUE = instance.decompose(0d);
DoubleType() {super(ComparisonType.CUSTOM);} // singleton DoubleType() {super(ComparisonType.CUSTOM, 8);} // singleton
@Override @Override
public boolean allowsEmpty() public boolean allowsEmpty()
@ -145,12 +145,6 @@ public class DoubleType extends NumberType<Double>
}; };
} }
@Override
public int valueLengthIfFixed()
{
return 8;
}
@Override @Override
public ByteBuffer add(Number left, Number right) public ByteBuffer add(Number left, Number right)
{ {

View File

@ -71,7 +71,7 @@ public class EmptyType extends AbstractType<Void>
public static final EmptyType instance = new EmptyType(); public static final EmptyType instance = new EmptyType();
private EmptyType() {super(ComparisonType.CUSTOM);} // singleton private EmptyType() {super(ComparisonType.CUSTOM, 0);} // singleton
@Override @Override
public <V> ByteSource asComparableBytes(ValueAccessor<V> accessor, V data, ByteComparable.Version version) public <V> ByteSource asComparableBytes(ValueAccessor<V> accessor, V data, ByteComparable.Version version)
@ -137,12 +137,6 @@ public class EmptyType extends AbstractType<Void>
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@Override
public int valueLengthIfFixed()
{
return 0;
}
@Override @Override
public <V> long writtenLength(V value, ValueAccessor<V> accessor) public <V> long writtenLength(V value, ValueAccessor<V> accessor)
{ {

View File

@ -41,7 +41,7 @@ public class FloatType extends NumberType<Float>
private static final ByteBuffer MASKED_VALUE = instance.decompose(0f); private static final ByteBuffer MASKED_VALUE = instance.decompose(0f);
FloatType() {super(ComparisonType.CUSTOM);} // singleton FloatType() {super(ComparisonType.CUSTOM, 4);} // singleton
@Override @Override
public boolean allowsEmpty() public boolean allowsEmpty()
@ -146,12 +146,6 @@ public class FloatType extends NumberType<Float>
}; };
} }
@Override
public int valueLengthIfFixed()
{
return 4;
}
@Override @Override
public ByteBuffer add(Number left, Number right) public ByteBuffer add(Number left, Number right)
{ {

View File

@ -43,7 +43,7 @@ public class Int32Type extends NumberType<Integer>
Int32Type() Int32Type()
{ {
super(ComparisonType.CUSTOM); super(ComparisonType.CUSTOM, 4);
} // singleton } // singleton
@Override @Override
@ -152,12 +152,6 @@ public class Int32Type extends NumberType<Integer>
}; };
} }
@Override
public int valueLengthIfFixed()
{
return 4;
}
@Override @Override
public ByteBuffer add(Number left, Number right) public ByteBuffer add(Number left, Number right)
{ {

View File

@ -44,7 +44,7 @@ public class LexicalUUIDType extends AbstractType<UUID>
LexicalUUIDType() LexicalUUIDType()
{ {
super(ComparisonType.CUSTOM); super(ComparisonType.CUSTOM, 16);
} // singleton } // singleton
@Override @Override
@ -148,12 +148,6 @@ public class LexicalUUIDType extends AbstractType<UUID>
return ARGUMENT_DESERIALIZER; return ARGUMENT_DESERIALIZER;
} }
@Override
public int valueLengthIfFixed()
{
return 16;
}
@Override @Override
public ByteBuffer getMaskedValue() public ByteBuffer getMaskedValue()
{ {

View File

@ -41,7 +41,7 @@ public class LongType extends NumberType<Long>
private static final ByteBuffer MASKED_VALUE = instance.decompose(0L); private static final ByteBuffer MASKED_VALUE = instance.decompose(0L);
LongType() {super(ComparisonType.CUSTOM);} // singleton LongType() {super(ComparisonType.CUSTOM, 8);} // singleton
@Override @Override
public boolean allowsEmpty() public boolean allowsEmpty()
@ -170,12 +170,6 @@ public class LongType extends NumberType<Long>
}; };
} }
@Override
public int valueLengthIfFixed()
{
return 8;
}
@Override @Override
public ByteBuffer add(Number left, Number right) public ByteBuffer add(Number left, Number right)
{ {

View File

@ -38,6 +38,11 @@ public abstract class MultiElementType<T> extends AbstractType<T>
super(comparisonType); super(comparisonType);
} }
protected MultiElementType(ComparisonType comparisonType, int valueLengthIfFixed)
{
super(comparisonType, valueLengthIfFixed);
}
/** /**
* Returns the serialized representation of the value composed of the specified elements. * Returns the serialized representation of the value composed of the specified elements.
* *
@ -133,4 +138,3 @@ public abstract class MultiElementType<T> extends AbstractType<T>
throw new UnsupportedOperationException(this + " does not support retrieving elements by key or index"); throw new UnsupportedOperationException(this + " does not support retrieving elements by key or index");
} }
} }

View File

@ -34,6 +34,11 @@ public abstract class NumberType<T extends Number> extends AbstractType<T>
super(comparisonType); super(comparisonType);
} }
protected NumberType(ComparisonType comparisonType, int valueLengthIfFixed)
{
super(comparisonType, valueLengthIfFixed);
}
/** /**
* Checks if this type support floating point numbers. * Checks if this type support floating point numbers.
* @return {@code true} if this type support floating point numbers, {@code false} otherwise. * @return {@code true} if this type support floating point numbers, {@code false} otherwise.

View File

@ -57,7 +57,7 @@ public class ReversedType<T> extends AbstractType<T>
private ReversedType(AbstractType<T> baseType) private ReversedType(AbstractType<T> baseType)
{ {
super(ComparisonType.CUSTOM); super(ComparisonType.CUSTOM, baseType.valueLengthIfFixed());
this.baseType = baseType; this.baseType = baseType;
} }
@ -181,12 +181,6 @@ public class ReversedType<T> extends AbstractType<T>
return getInstance(baseType.withUpdatedUserType(udt)); return getInstance(baseType.withUpdatedUserType(udt));
} }
@Override
public int valueLengthIfFixed()
{
return baseType.valueLengthIfFixed();
}
@Override @Override
public boolean isReversed() public boolean isReversed()
{ {

View File

@ -37,6 +37,11 @@ public abstract class TemporalType<T> extends AbstractType<T>
super(comparisonType); super(comparisonType);
} }
protected TemporalType(ComparisonType comparisonType, int valueLengthIfFixed)
{
super(comparisonType, valueLengthIfFixed);
}
/** /**
* Returns the current temporal value. * Returns the current temporal value.
* @return the current temporal value. * @return the current temporal value.

View File

@ -53,7 +53,7 @@ public class TimestampType extends TemporalType<Date>
private static final ByteBuffer MASKED_VALUE = instance.decompose(new Date(0)); private static final ByteBuffer MASKED_VALUE = instance.decompose(new Date(0));
private TimestampType() {super(ComparisonType.CUSTOM);} // singleton private TimestampType() {super(ComparisonType.CUSTOM, 8);} // singleton
@Override @Override
public boolean allowsEmpty() public boolean allowsEmpty()
@ -166,12 +166,6 @@ public class TimestampType extends TemporalType<Date>
return TimestampSerializer.instance; return TimestampSerializer.instance;
} }
@Override
public int valueLengthIfFixed()
{
return 8;
}
@Override @Override
protected void validateDuration(Duration duration) protected void validateDuration(Duration duration)
{ {

View File

@ -449,8 +449,7 @@ public class TypeParser
private static AbstractType<?> getAbstractType(String compareWith) throws ConfigurationException private static AbstractType<?> getAbstractType(String compareWith) throws ConfigurationException
{ {
String className = compareWith.contains(".") ? compareWith : "org.apache.cassandra.db.marshal." + compareWith; Class<? extends AbstractType<?>> typeClass = getAbstractTypeClass(compareWith);
Class<? extends AbstractType<?>> typeClass = FBUtilities.<AbstractType<?>>classForName(className, "abstract-type");
try try
{ {
Field field = typeClass.getDeclaredField("instance"); Field field = typeClass.getDeclaredField("instance");
@ -465,8 +464,7 @@ public class TypeParser
private static AbstractType<?> getAbstractType(String compareWith, TypeParser parser) throws SyntaxException, ConfigurationException private static AbstractType<?> getAbstractType(String compareWith, TypeParser parser) throws SyntaxException, ConfigurationException
{ {
String className = compareWith.contains(".") ? compareWith : "org.apache.cassandra.db.marshal." + compareWith; Class<? extends AbstractType<?>> typeClass = getAbstractTypeClass(compareWith);
Class<? extends AbstractType<?>> typeClass = FBUtilities.<AbstractType<?>>classForName(className, "abstract-type");
if (PseudoUtf8Type.class.isAssignableFrom(typeClass)) if (PseudoUtf8Type.class.isAssignableFrom(typeClass))
{ {
if (StorageService.instance.isDaemonSetupCompleted()) if (StorageService.instance.isDaemonSetupCompleted())
@ -491,6 +489,19 @@ public class TypeParser
} }
} }
private static Class<? extends AbstractType<?>> getAbstractTypeClass(String compareWith) throws ConfigurationException
{
String className = compareWith.contains(".") ? compareWith : "org.apache.cassandra.db.marshal." + compareWith;
// Defer class initialization until after confirming this is an AbstractType. The static instance field
// access or getInstance(TypeParser) invocation below performs the initialization for valid types.
@SuppressWarnings("unchecked")
Class<? extends AbstractType<?>> typeClass =
(Class<? extends AbstractType<?>>) FBUtilities.classForNameWithoutInitialization(className,
"abstract-type",
AbstractType.class);
return typeClass;
}
private static AbstractType<?> getRawAbstractType(Class<? extends AbstractType<?>> typeClass) throws ConfigurationException private static AbstractType<?> getRawAbstractType(Class<? extends AbstractType<?>> typeClass) throws ConfigurationException
{ {
try try

View File

@ -56,7 +56,7 @@ public class UUIDType extends AbstractType<UUID>
UUIDType() UUIDType()
{ {
super(ComparisonType.CUSTOM); super(ComparisonType.CUSTOM, 16);
} }
@Override @Override
@ -252,12 +252,6 @@ public class UUIDType extends AbstractType<UUID>
return (uuid.get(6) & 0xf0) >> 4; return (uuid.get(6) & 0xf0) >> 4;
} }
@Override
public int valueLengthIfFixed()
{
return 16;
}
@Override @Override
public ByteBuffer getMaskedValue() public ByteBuffer getMaskedValue()
{ {

View File

@ -82,20 +82,16 @@ public final class VectorType<T> extends MultiElementType<List<T>>
public final AbstractType<T> elementType; public final AbstractType<T> elementType;
public final int dimension; public final int dimension;
private final TypeSerializer<T> elementSerializer; private final TypeSerializer<T> elementSerializer;
private final int valueLengthIfFixed;
private final VectorSerializer serializer; private final VectorSerializer serializer;
private VectorType(AbstractType<T> elementType, int dimension) private VectorType(AbstractType<T> elementType, int dimension)
{ {
super(ComparisonType.CUSTOM); super(ComparisonType.CUSTOM, valueLengthIfFixed(elementType, dimension));
if (dimension <= 0) if (dimension <= 0)
throw new InvalidRequestException(String.format("vectors may only have positive dimensions; given %d", dimension)); throw new InvalidRequestException(String.format("vectors may only have positive dimensions; given %d", dimension));
this.elementType = elementType; this.elementType = elementType;
this.dimension = dimension; this.dimension = dimension;
this.elementSerializer = elementType.getSerializer(); this.elementSerializer = elementType.getSerializer();
this.valueLengthIfFixed = elementType.isValueLengthFixed() ?
elementType.valueLengthIfFixed() * dimension :
super.valueLengthIfFixed();
this.serializer = elementType.isValueLengthFixed() ? this.serializer = elementType.isValueLengthFixed() ?
new FixedLengthSerializer() : new FixedLengthSerializer() :
new VariableLengthSerializer(); new VariableLengthSerializer();
@ -126,10 +122,10 @@ public final class VectorType<T> extends MultiElementType<List<T>>
return getSerializer().compareCustom(left, accessorL, right, accessorR); return getSerializer().compareCustom(left, accessorL, right, accessorR);
} }
@Override private static int valueLengthIfFixed(AbstractType<?> elementType, int dimension)
public int valueLengthIfFixed()
{ {
return valueLengthIfFixed; int elementLength = elementType.valueLengthIfFixed();
return elementLength >= 0 ? elementLength * dimension : elementLength;
} }
@Override @Override

View File

@ -61,7 +61,18 @@ public abstract class AbstractCell<V> extends Cell<V>
public boolean isTombstone() public boolean isTombstone()
{ {
return localDeletionTime() != NO_DELETION_TIME && ttl() == NO_TTL; return isTombstone(localDeletionTime());
}
public long minDeletionTime()
{
long localDeletionTime = localDeletionTime();
return isTombstone(localDeletionTime) ? Long.MIN_VALUE : localDeletionTime;
}
private boolean isTombstone(long localDeletionTime)
{
return localDeletionTime != NO_DELETION_TIME && ttl() == NO_TTL;
} }
public boolean isExpiring() public boolean isExpiring()

View File

@ -31,17 +31,12 @@ import org.apache.cassandra.utils.memory.ByteBufferCloner;
import static org.apache.cassandra.utils.ByteArrayUtil.EMPTY_BYTE_ARRAY; import static org.apache.cassandra.utils.ByteArrayUtil.EMPTY_BYTE_ARRAY;
public class ArrayCell extends AbstractCell<byte[]> public class ArrayCell extends HeapAbstractCell<byte[]>
{ {
private static final long EMPTY_SIZE = ObjectSizes.measure(new ArrayCell(ColumnMetadata.regularColumn("", "", "", ByteType.instance, ColumnMetadata.NO_UNIQUE_ID), 0L, 0, 0, EMPTY_BYTE_ARRAY, null)); private static final long EMPTY_SIZE = ObjectSizes.measure(new ArrayCell(ColumnMetadata.regularColumn("", "", "", ByteType.instance, ColumnMetadata.NO_UNIQUE_ID), 0L, 0, 0, EMPTY_BYTE_ARRAY, null));
// Careful: Adding vars here has an impact on memtable size // Careful: Adding vars here has an impact on memtable size
private final long timestamp;
private final int ttl;
private final int localDeletionTimeUnsignedInteger;
private final byte[] value; private final byte[] value;
private final CellPath path;
// Please keep both int/long overloaded ctros public. Otherwise silent casts will mess timestamps when one is not // Please keep both int/long overloaded ctros public. Otherwise silent casts will mess timestamps when one is not
// available. // available.
@ -52,12 +47,8 @@ public class ArrayCell extends AbstractCell<byte[]>
public ArrayCell(ColumnMetadata column, long timestamp, int ttl, int localDeletionTimeUnsignedInteger, byte[] value, CellPath path) public ArrayCell(ColumnMetadata column, long timestamp, int ttl, int localDeletionTimeUnsignedInteger, byte[] value, CellPath path)
{ {
super(column); super(column, timestamp, ttl, localDeletionTimeUnsignedInteger, path);
this.timestamp = timestamp;
this.ttl = ttl;
this.localDeletionTimeUnsignedInteger = localDeletionTimeUnsignedInteger;
this.value = value; this.value = value;
this.path = path;
} }
public static ArrayCell live(ColumnMetadata column, long timestamp, byte[] value, CellPath path) public static ArrayCell live(ColumnMetadata column, long timestamp, byte[] value, CellPath path)
@ -71,16 +62,6 @@ public class ArrayCell extends AbstractCell<byte[]>
return new ArrayCell(column, timestamp, ttl, ExpirationDateOverflowHandling.computeLocalExpirationTime(nowInSec, ttl), value, path); return new ArrayCell(column, timestamp, ttl, ExpirationDateOverflowHandling.computeLocalExpirationTime(nowInSec, ttl), value, path);
} }
public long timestamp()
{
return timestamp;
}
public int ttl()
{
return ttl;
}
public byte[] value() public byte[] value()
{ {
return value; return value;
@ -91,10 +72,6 @@ public class ArrayCell extends AbstractCell<byte[]>
return ByteArrayAccessor.instance; return ByteArrayAccessor.instance;
} }
public CellPath path()
{
return path;
}
public Cell<?> withUpdatedColumn(ColumnMetadata newColumn) public Cell<?> withUpdatedColumn(ColumnMetadata newColumn)
{ {
@ -144,9 +121,4 @@ public class ArrayCell extends AbstractCell<byte[]>
return EMPTY_SIZE + ObjectSizes.sizeOfArray(value) - value.length + (path == null ? 0 : path.unsharedHeapSizeExcludingData()); return EMPTY_SIZE + ObjectSizes.sizeOfArray(value) - value.length + (path == null ? 0 : path.unsharedHeapSizeExcludingData());
} }
@Override
protected int localDeletionTimeAsUnsignedInt()
{
return localDeletionTimeUnsignedInteger;
}
} }

View File

@ -172,7 +172,7 @@ public class BTreeRow extends AbstractRow
private static long minDeletionTime(Cell<?> cell) private static long minDeletionTime(Cell<?> cell)
{ {
return cell.isTombstone() ? Long.MIN_VALUE : cell.localDeletionTime(); return cell.minDeletionTime();
} }
private static long minDeletionTime(LivenessInfo info) private static long minDeletionTime(LivenessInfo info)
@ -439,6 +439,11 @@ public class BTreeRow extends AbstractRow
return nowInSec >= minLocalDeletionTime; return nowInSec >= minLocalDeletionTime;
} }
public long minLocalDeletionTime()
{
return minLocalDeletionTime;
}
public boolean hasInvalidDeletions() public boolean hasInvalidDeletions()
{ {
if (primaryKeyLivenessInfo().isExpiring() && (primaryKeyLivenessInfo().ttl() < 0 || primaryKeyLivenessInfo().localExpirationTime() < 0)) if (primaryKeyLivenessInfo().isExpiring() && (primaryKeyLivenessInfo().ttl() < 0 || primaryKeyLivenessInfo().localExpirationTime() < 0))

View File

@ -30,17 +30,12 @@ import org.apache.cassandra.utils.memory.ByteBufferCloner;
import static java.lang.String.format; import static java.lang.String.format;
public class BufferCell extends AbstractCell<ByteBuffer> public class BufferCell extends HeapAbstractCell<ByteBuffer>
{ {
private static final long EMPTY_SIZE = ObjectSizes.measure(new BufferCell(ColumnMetadata.regularColumn("", "", "", ByteType.instance, ColumnMetadata.NO_UNIQUE_ID), 0L, 0, 0, ByteBufferUtil.EMPTY_BYTE_BUFFER, null)); private static final long EMPTY_SIZE = ObjectSizes.measure(new BufferCell(ColumnMetadata.regularColumn("", "", "", ByteType.instance, ColumnMetadata.NO_UNIQUE_ID), 0L, 0, 0, ByteBufferUtil.EMPTY_BYTE_BUFFER, null));
// Careful: Adding vars here has an impact on memtable size // Careful: Adding vars here has an impact on memtable size
private final long timestamp;
private final int ttl;
private final int localDeletionTimeUnsignedInteger;
private final ByteBuffer value; private final ByteBuffer value;
private final CellPath path;
// Please keep both int/long overloaded ctros public. Otherwise silent casts will mess timestamps when one is not // Please keep both int/long overloaded ctros public. Otherwise silent casts will mess timestamps when one is not
// available. // available.
@ -51,14 +46,10 @@ public class BufferCell extends AbstractCell<ByteBuffer>
public BufferCell(ColumnMetadata column, long timestamp, int ttl, int localDeletionTimeUnsignedInteger, ByteBuffer value, CellPath path) public BufferCell(ColumnMetadata column, long timestamp, int ttl, int localDeletionTimeUnsignedInteger, ByteBuffer value, CellPath path)
{ {
super(column); super(column, timestamp, ttl, localDeletionTimeUnsignedInteger, path);
assert !column.isPrimaryKeyColumn(); assert !column.isPrimaryKeyColumn();
assert column.isComplex() == (path != null) : format("Column %s.%s(%s: %s) isComplex: %b with cellpath: %s", column.ksName, column.cfName, column.name, column.type.toString(), column.isComplex(), path); assert column.isComplex() == (path != null) : format("Column %s.%s(%s: %s) isComplex: %b with cellpath: %s", column.ksName, column.cfName, column.name, column.type.toString(), column.isComplex(), path);
this.timestamp = timestamp;
this.ttl = ttl;
this.localDeletionTimeUnsignedInteger = localDeletionTimeUnsignedInteger;
this.value = value; this.value = value;
this.path = path;
} }
public static BufferCell live(ColumnMetadata column, long timestamp, ByteBuffer value) public static BufferCell live(ColumnMetadata column, long timestamp, ByteBuffer value)
@ -92,16 +83,6 @@ public class BufferCell extends AbstractCell<ByteBuffer>
return new BufferCell(column, timestamp, NO_TTL, nowInSec, ByteBufferUtil.EMPTY_BYTE_BUFFER, path); return new BufferCell(column, timestamp, NO_TTL, nowInSec, ByteBufferUtil.EMPTY_BYTE_BUFFER, path);
} }
public long timestamp()
{
return timestamp;
}
public int ttl()
{
return ttl;
}
public ByteBuffer value() public ByteBuffer value()
{ {
return value; return value;
@ -112,10 +93,6 @@ public class BufferCell extends AbstractCell<ByteBuffer>
return ByteBufferAccessor.instance; return ByteBufferAccessor.instance;
} }
public CellPath path()
{
return path;
}
public Cell<?> withUpdatedColumn(ColumnMetadata newColumn) public Cell<?> withUpdatedColumn(ColumnMetadata newColumn)
{ {
@ -163,10 +140,4 @@ public class BufferCell extends AbstractCell<ByteBuffer>
{ {
return EMPTY_SIZE + ObjectSizes.sizeOnHeapExcludingDataOf(value) + (path == null ? 0 : path.unsharedHeapSizeExcludingData()); return EMPTY_SIZE + ObjectSizes.sizeOnHeapExcludingDataOf(value) + (path == null ? 0 : path.unsharedHeapSizeExcludingData());
} }
@Override
protected int localDeletionTimeAsUnsignedInt()
{
return localDeletionTimeUnsignedInteger;
}
} }

View File

@ -150,6 +150,8 @@ public abstract class Cell<V> extends ColumnData
return deletionTimeUnsignedIntegerToLong(localDeletionTimeAsUnsignedInt()); return deletionTimeUnsignedIntegerToLong(localDeletionTimeAsUnsignedInt());
} }
public abstract long minDeletionTime();
/** /**
* Whether the cell is a tombstone or not. * Whether the cell is a tombstone or not.
* *

View File

@ -0,0 +1,64 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.db.rows;
import org.apache.cassandra.schema.ColumnMetadata;
public abstract class HeapAbstractCell<V> extends AbstractCell<V>
{
// Careful: Adding vars here has an impact on memtable size
protected final long timestamp;
protected final int ttl;
protected final int localDeletionTimeUnsignedInteger;
protected final CellPath path;
protected HeapAbstractCell(ColumnMetadata column, long timestamp, int ttl, int localDeletionTimeUnsignedInteger, CellPath path)
{
super(column);
this.timestamp = timestamp;
this.ttl = ttl;
this.localDeletionTimeUnsignedInteger = localDeletionTimeUnsignedInteger;
this.path = path;
}
@Override
public long timestamp()
{
return timestamp;
}
@Override
public int ttl()
{
return ttl;
}
@Override
protected int localDeletionTimeAsUnsignedInt()
{
return localDeletionTimeUnsignedInteger;
}
@Override
public CellPath path()
{
return path;
}
}

View File

@ -20,7 +20,6 @@ package org.apache.cassandra.db.rows;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
@ -44,9 +43,10 @@ import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.paxos.Commit; import org.apache.cassandra.service.paxos.Commit;
import org.apache.cassandra.utils.BiLongAccumulator; import org.apache.cassandra.utils.BiLongAccumulator;
import org.apache.cassandra.utils.BulkIterator; import org.apache.cassandra.utils.BulkIterator;
import org.apache.cassandra.utils.ComplexCellMergeIterator;
import org.apache.cassandra.utils.LongAccumulator; import org.apache.cassandra.utils.LongAccumulator;
import org.apache.cassandra.utils.MergeIterator;
import org.apache.cassandra.utils.ObjectSizes; import org.apache.cassandra.utils.ObjectSizes;
import org.apache.cassandra.utils.RowMergeIterator;
import org.apache.cassandra.utils.SearchIterator; import org.apache.cassandra.utils.SearchIterator;
import org.apache.cassandra.utils.btree.BTree; import org.apache.cassandra.utils.btree.BTree;
import org.apache.cassandra.utils.btree.UpdateFunction; import org.apache.cassandra.utils.btree.UpdateFunction;
@ -221,6 +221,15 @@ public interface Row extends Unfiltered, Iterable<ColumnData>, IMeasurableMemory
*/ */
public boolean hasDeletion(long nowInSec); public boolean hasDeletion(long nowInSec);
/**
* The smallest local deletion time of all the data in this row (row deletion, primary key liveness, cells and
* complex deletions), or {@link Cell#MAX_DELETION_TIME} if the row has no deletion nor expiring data.
* <p>
* Unlike {@link #hasDeletion(long)}, this value is independent of the current time. In particular, a value of
* {@link Cell#MAX_DELETION_TIME} guarantees the row carries neither tombstones nor expiring data.
*/
public long minLocalDeletionTime();
/** /**
* An iterator to efficiently search data for a given column. * An iterator to efficiently search data for a given column.
* *
@ -762,6 +771,11 @@ public interface Row extends Unfiltered, Iterable<ColumnData>, IMeasurableMemory
LivenessInfo rowInfo = LivenessInfo.EMPTY; LivenessInfo rowInfo = LivenessInfo.EMPTY;
Deletion rowDeletion = Deletion.LIVE; Deletion rowDeletion = Deletion.LIVE;
int columnsCountEstimation = 0;
// Track the smallest local deletion time across all inputs: if none of them carries any deletion or
// expiring data (i.e. this stays at MAX_DELETION_TIME), the merged row can't either, so we can hand the
// value to BTreeRow.create() below and skip the full btree scan it would otherwise do to recompute it.
long minDeletionTime = Cell.MAX_DELETION_TIME;
for (Row row : rows) for (Row row : rows)
{ {
if (row == null) if (row == null)
@ -771,6 +785,11 @@ public interface Row extends Unfiltered, Iterable<ColumnData>, IMeasurableMemory
rowInfo = row.primaryKeyLivenessInfo(); rowInfo = row.primaryKeyLivenessInfo();
if (row.deletion().supersedes(rowDeletion)) if (row.deletion().supersedes(rowDeletion))
rowDeletion = row.deletion(); rowDeletion = row.deletion();
minDeletionTime = Math.min(minDeletionTime, row.minLocalDeletionTime());
columnDataIterators.add(row.iterator());
columnsCountEstimation = Math.max(columnsCountEstimation, row.columnCount());
} }
if (rowDeletion.isShadowedBy(rowInfo)) if (rowDeletion.isShadowedBy(rowInfo))
@ -784,25 +803,12 @@ public interface Row extends Unfiltered, Iterable<ColumnData>, IMeasurableMemory
if (activeDeletion.deletes(rowInfo)) if (activeDeletion.deletes(rowInfo))
rowInfo = LivenessInfo.EMPTY; rowInfo = LivenessInfo.EMPTY;
int columnsCountEstimation = 0;
for (Row row : rows)
{
if (row != null)
{
columnDataIterators.add(row.iterator());
columnsCountEstimation = Math.max(columnsCountEstimation, row.columnCount());
}
else
{
columnDataIterators.add(Collections.emptyIterator());
}
}
// try to estimate and set a potential target capacity // try to estimate and set a potential target capacity
if (dataBuffer.length < columnsCountEstimation) if (dataBuffer.length < columnsCountEstimation)
dataBuffer = new ColumnData[columnsCountEstimation]; dataBuffer = new ColumnData[columnsCountEstimation];
columnDataReducer.setActiveDeletion(activeDeletion); columnDataReducer.setActiveDeletion(activeDeletion);
Iterator<ColumnData> merged = MergeIterator.get(columnDataIterators, ColumnData.comparator, columnDataReducer); Iterator<ColumnData> merged = RowMergeIterator.get(columnDataIterators, ColumnData.comparator, columnDataReducer);
while (merged.hasNext()) while (merged.hasNext())
{ {
ColumnData data = merged.next(); ColumnData data = merged.next();
@ -819,8 +825,12 @@ public interface Row extends Unfiltered, Iterable<ColumnData>, IMeasurableMemory
try (BulkIterator<ColumnData> it = BulkIterator.of(dataBuffer)) try (BulkIterator<ColumnData> it = BulkIterator.of(dataBuffer))
{ {
return BTreeRow.create(clustering, rowInfo, rowDeletion, Object[] tree = BTree.build(it, dataBufferSize, UpdateFunction.noOp());
BTree.build(it, dataBufferSize, UpdateFunction.noOp())); // If none of the merged rows had any deletion or expiring data, neither does the result, so we can
// pass the already-known min local deletion time and avoid rescanning the whole btree to recompute it.
return minDeletionTime == Cell.MAX_DELETION_TIME
? BTreeRow.create(clustering, rowInfo, rowDeletion, tree, Cell.MAX_DELETION_TIME)
: BTreeRow.create(clustering, rowInfo, rowDeletion, tree);
} }
} }
@ -841,10 +851,11 @@ public interface Row extends Unfiltered, Iterable<ColumnData>, IMeasurableMemory
return rows; return rows;
} }
private static class ColumnDataReducer extends MergeIterator.Reducer<ColumnData, ColumnData> private static class ColumnDataReducer extends RowMergeIterator.Reducer<ColumnData, ColumnData>
{ {
private ColumnMetadata column; private ColumnMetadata column;
private final List<ColumnData> versions; private final ColumnData[] versions;
private int versionsSize;
private DeletionTime activeDeletion; private DeletionTime activeDeletion;
@ -854,7 +865,7 @@ public interface Row extends Unfiltered, Iterable<ColumnData>, IMeasurableMemory
public ColumnDataReducer(int size, boolean hasComplex) public ColumnDataReducer(int size, boolean hasComplex)
{ {
this.versions = new ArrayList<>(size); this.versions = new ColumnData[size];
this.complexBuilder = hasComplex ? ComplexColumnData.builder() : null; this.complexBuilder = hasComplex ? ComplexColumnData.builder() : null;
this.complexCells = hasComplex ? new ArrayList<>(size) : null; this.complexCells = hasComplex ? new ArrayList<>(size) : null;
this.cellReducer = new CellReducer(); this.cellReducer = new CellReducer();
@ -870,20 +881,24 @@ public interface Row extends Unfiltered, Iterable<ColumnData>, IMeasurableMemory
if (useColumnMetadata(data.column())) if (useColumnMetadata(data.column()))
column = data.column(); column = data.column();
versions.add(data); versions[versionsSize++] = data;
} }
/** /**
* Determines it the {@code ColumnMetadata} is the one that should be used. * Determines whether {@code dataColumn} should replace the currently selected column metadata,
* @param dataColumn the {@code ColumnMetadata} to use. * i.e. whether no column has been selected yet or {@code dataColumn} is a newer version.
* @return {@code true} if the {@code ColumnMetadata} is the one that should be used, {@code false} otherwise. * @param dataColumn the candidate {@code ColumnMetadata} to evaluate.
* @return {@code true} if {@code dataColumn} should be used, {@code false} otherwise.
*/ */
private boolean useColumnMetadata(ColumnMetadata dataColumn) private boolean useColumnMetadata(ColumnMetadata dataColumn)
{ {
if (column == null) ColumnMetadata currentColumn = column;
if (currentColumn == null)
return true; return true;
if (currentColumn == dataColumn)
return false;
return ColumnMetadataVersionComparator.INSTANCE.compare(column, dataColumn) < 0; return ColumnMetadataVersionComparator.INSTANCE.compare(currentColumn, dataColumn) < 0;
} }
protected ColumnData getReduced() protected ColumnData getReduced()
@ -891,9 +906,9 @@ public interface Row extends Unfiltered, Iterable<ColumnData>, IMeasurableMemory
if (column.isSimple()) if (column.isSimple())
{ {
Cell<?> merged = null; Cell<?> merged = null;
for (int i=0, isize=versions.size(); i<isize; i++) for (int i = 0; i < versionsSize; i++)
{ {
Cell<?> cell = (Cell<?>) versions.get(i); Cell<?> cell = (Cell<?>) versions[i];
if (!activeDeletion.deletes(cell)) if (!activeDeletion.deletes(cell))
merged = merged == null ? cell : Cells.reconcile(merged, cell); merged = merged == null ? cell : Cells.reconcile(merged, cell);
} }
@ -904,9 +919,9 @@ public interface Row extends Unfiltered, Iterable<ColumnData>, IMeasurableMemory
complexBuilder.newColumn(column); complexBuilder.newColumn(column);
complexCells.clear(); complexCells.clear();
DeletionTime complexDeletion = DeletionTime.LIVE; DeletionTime complexDeletion = DeletionTime.LIVE;
for (int i=0, isize=versions.size(); i<isize; i++) for (int i = 0; i < versionsSize; i++)
{ {
ColumnData data = versions.get(i); ColumnData data = versions[i];
ComplexColumnData cd = (ComplexColumnData)data; ComplexColumnData cd = (ComplexColumnData)data;
if (cd.complexDeletion().supersedes(complexDeletion)) if (cd.complexDeletion().supersedes(complexDeletion))
complexDeletion = cd.complexDeletion(); complexDeletion = cd.complexDeletion();
@ -923,7 +938,7 @@ public interface Row extends Unfiltered, Iterable<ColumnData>, IMeasurableMemory
cellReducer.setActiveDeletion(activeDeletion); cellReducer.setActiveDeletion(activeDeletion);
} }
Iterator<Cell<?>> cells = MergeIterator.get(complexCells, Cell.comparator, cellReducer); Iterator<Cell<?>> cells = ComplexCellMergeIterator.get(complexCells, Cell.comparator, cellReducer);
while (cells.hasNext()) while (cells.hasNext())
{ {
Cell<?> merged = cells.next(); Cell<?> merged = cells.next();
@ -937,11 +952,12 @@ public interface Row extends Unfiltered, Iterable<ColumnData>, IMeasurableMemory
protected void onKeyChange() protected void onKeyChange()
{ {
column = null; column = null;
versions.clear(); Arrays.fill(versions, 0, versionsSize, null);
versionsSize = 0;
} }
} }
private static class CellReducer extends MergeIterator.Reducer<Cell<?>, Cell<?>> private static class CellReducer extends ComplexCellMergeIterator.Reducer<Cell<?>, Cell<?>>
{ {
private DeletionTime activeDeletion; private DeletionTime activeDeletion;
private Cell<?> merged; private Cell<?> merged;

View File

@ -38,7 +38,7 @@ import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.serializers.MarshalException; import org.apache.cassandra.serializers.MarshalException;
import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.IMergeIterator; import org.apache.cassandra.utils.IMergeIterator;
import org.apache.cassandra.utils.MergeIterator; import org.apache.cassandra.utils.UnfilteredMergeIterator;
/** /**
* Static methods to work with atom iterators. * Static methods to work with atom iterators.
@ -415,7 +415,7 @@ public abstract class UnfilteredRowIterators
reversed, reversed,
EncodingStats.merge(iterators, UnfilteredRowIterator::stats)); EncodingStats.merge(iterators, UnfilteredRowIterator::stats));
this.mergeIterator = MergeIterator.get(iterators, this.mergeIterator = UnfilteredMergeIterator.get(iterators,
reversed ? metadata.comparator.reversed() : metadata.comparator, reversed ? metadata.comparator.reversed() : metadata.comparator,
new MergeReducer(iterators.size(), reversed, listener)); new MergeReducer(iterators.size(), reversed, listener));
this.listener = listener; this.listener = listener;
@ -540,7 +540,7 @@ public abstract class UnfilteredRowIterators
listener.close(); listener.close();
} }
private class MergeReducer extends MergeIterator.Reducer<Unfiltered, Unfiltered> private class MergeReducer extends UnfilteredMergeIterator.Reducer<Unfiltered, Unfiltered>
{ {
private final MergeListener listener; private final MergeListener listener;

View File

@ -128,6 +128,7 @@ public final class DiagnosticEventPersistence
LastEventIdBroadcaster.instance().setLastEventId(event.getClass().getName(), store.getLastEventId()); LastEventIdBroadcaster.instance().setLastEventId(event.getClass().getName(), store.getLastEventId());
} }
@SuppressWarnings("unchecked")
private Class<DiagnosticEvent> getEventClass(String eventClazz) throws ClassNotFoundException, InvalidClassException private Class<DiagnosticEvent> getEventClass(String eventClazz) throws ClassNotFoundException, InvalidClassException
{ {
// get class by eventClazz argument name // get class by eventClazz argument name
@ -135,12 +136,12 @@ public final class DiagnosticEventPersistence
if (!eventClazz.startsWith("org.apache.cassandra.")) if (!eventClazz.startsWith("org.apache.cassandra."))
throw new RuntimeException("Not a Cassandra event class: " + eventClazz); throw new RuntimeException("Not a Cassandra event class: " + eventClazz);
Class<DiagnosticEvent> clazz = (Class<DiagnosticEvent>) Class.forName(eventClazz); Class<?> clazz = Class.forName(eventClazz, false, DiagnosticEventPersistence.class.getClassLoader());
if (!(DiagnosticEvent.class.isAssignableFrom(clazz))) if (!(DiagnosticEvent.class.isAssignableFrom(clazz)))
throw new InvalidClassException("Event class must be of type DiagnosticEvent"); throw new InvalidClassException("Event class must be of type DiagnosticEvent");
return clazz; return (Class<DiagnosticEvent>) clazz.asSubclass(DiagnosticEvent.class);
} }
private DiagnosticEventStore<Long> getStore(Class cls) private DiagnosticEventStore<Long> getStore(Class cls)

View File

@ -921,6 +921,11 @@ public class SecondaryIndexManager implements IndexRegistry, INotificationConsum
return indexes.get(indexName); return indexes.get(indexName);
} }
static Class<? extends Index> loadIndexClass(String className)
{
return FBUtilities.classForNameWithoutInitialization(className, "Index", Index.class);
}
private Index createInstance(IndexMetadata indexDef) private Index createInstance(IndexMetadata indexDef)
{ {
Index newIndex; Index newIndex;
@ -933,7 +938,7 @@ public class SecondaryIndexManager implements IndexRegistry, INotificationConsum
try try
{ {
Class<? extends Index> indexClass = FBUtilities.classForName(className, "Index"); Class<? extends Index> indexClass = loadIndexClass(className);
Constructor<? extends Index> ctor = indexClass.getConstructor(ColumnFamilyStore.class, IndexMetadata.class); Constructor<? extends Index> ctor = indexClass.getConstructor(ColumnFamilyStore.class, IndexMetadata.class);
newIndex = ctor.newInstance(baseCfs, indexDef); newIndex = ctor.newInstance(baseCfs, indexDef);
} }

View File

@ -20,6 +20,8 @@ package org.apache.cassandra.index.sai.disk.io;
import java.io.IOException; import java.io.IOException;
import javax.annotation.concurrent.NotThreadSafe;
import org.apache.lucene.store.DataInput; import org.apache.lucene.store.DataInput;
import org.apache.lucene.store.IndexInput; import org.apache.lucene.store.IndexInput;
@ -30,10 +32,13 @@ import org.apache.cassandra.io.util.RandomAccessReader;
* This is a wrapper over a Cassandra {@link RandomAccessReader} that provides an {@link IndexInput} * This is a wrapper over a Cassandra {@link RandomAccessReader} that provides an {@link IndexInput}
* interface for Lucene classes that need {@link IndexInput}. This is an optimisation because the * interface for Lucene classes that need {@link IndexInput}. This is an optimisation because the
* Lucene {@link DataInput} reads bytes one at a time whereas the {@link RandomAccessReader} is * Lucene {@link DataInput} reads bytes one at a time whereas the {@link RandomAccessReader} is
* optimised to read multibyte objects faster. * optimized to read multibyte objects faster.
*/ */
@NotThreadSafe
public class IndexInputReader extends IndexInput public class IndexInputReader extends IndexInput
{ {
public static final Runnable NO_OP_ON_CLOSE = () -> {};
/** /**
* the byte order of `input`'s native readX operations doesn't matter, * the byte order of `input`'s native readX operations doesn't matter,
* because we only use `readFully` and `readByte` methods. IndexInput calls these * because we only use `readFully` and `readByte` methods. IndexInput calls these
@ -42,27 +47,47 @@ public class IndexInputReader extends IndexInput
private final RandomAccessReader input; private final RandomAccessReader input;
private final Runnable doOnClose; private final Runnable doOnClose;
private IndexInputReader(RandomAccessReader input, Runnable doOnClose) /** Absolute offset in the underlying file that this input's position 0 refers to. */
private final long offset;
/** Bounded length of this input, in bytes. */
private final long length;
private IndexInputReader(RandomAccessReader input, Runnable doOnClose, long offset, long length)
{ {
super(input.getPath()); super(input.getPath());
this.input = input; this.input = input;
this.doOnClose = doOnClose; this.doOnClose = doOnClose;
this.offset = offset;
this.length = length;
} }
public static IndexInputReader create(RandomAccessReader input) public static IndexInputReader create(RandomAccessReader input)
{ {
return new IndexInputReader(input, () -> {}); // Top-level inputs own the underlying reader; folding its close into doOnClose lets us
// avoid a separate ownership flag on the class.
return new IndexInputReader(input, input::close, 0L, input.length());
} }
public static IndexInputReader create(RandomAccessReader input, Runnable doOnClose) public static IndexInputReader create(RandomAccessReader input, Runnable doOnClose)
{ {
return new IndexInputReader(input, doOnClose); Runnable close = () -> {
try
{
input.close();
}
finally
{
doOnClose.run();
}
};
return new IndexInputReader(input, close, 0L, input.length());
} }
public static IndexInputReader create(FileHandle handle) public static IndexInputReader create(FileHandle handle)
{ {
RandomAccessReader reader = handle.createReader(); RandomAccessReader reader = handle.createReader();
return new IndexInputReader(reader, () -> {}); return new IndexInputReader(reader, reader::close, 0L, reader.length());
} }
@Override @Override
@ -80,37 +105,42 @@ public class IndexInputReader extends IndexInput
@Override @Override
public void close() public void close()
{ {
try doOnClose.run();
{
input.close();
}
finally
{
doOnClose.run();
}
} }
@Override @Override
public long getFilePointer() public long getFilePointer()
{ {
return input.getFilePointer(); return input.getFilePointer() - offset;
} }
@Override @Override
public void seek(long position) public void seek(long position)
{ {
input.seek(position); if (position > length)
throw new IllegalArgumentException("Cannot seek to position " + position + " past length of " + length);
input.seek(offset + position);
} }
@Override @Override
public long length() public long length()
{ {
return input.length(); return length;
} }
@Override @Override
public IndexInput slice(String sliceDescription, long offset, long length) public IndexInput slice(String sliceDescription, long offset, long length)
{ {
throw new UnsupportedOperationException("Slice operations are not supported"); if (offset < 0 || length < 0 || offset + length > this.length)
throw new IllegalArgumentException("Invalid slice: offset=" + offset + ", length=" + length + ", parent length=" + this.length + " for " + sliceDescription);
// Slices share the underlying reader with their parent; the no-op close keeps the parent's lifecycle intact.
IndexInputReader slice = new IndexInputReader(input, NO_OP_ON_CLOSE, this.offset + offset, length);
// Seek to the beginning of the slice...
slice.seek(0);
return slice;
} }
} }

View File

@ -21,11 +21,14 @@ package org.apache.cassandra.index.sai.disk.v1;
import java.io.IOException; import java.io.IOException;
import java.io.UncheckedIOException; import java.io.UncheckedIOException;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.List;
import java.util.Set; import java.util.Set;
import com.codahale.metrics.Gauge; import com.codahale.metrics.Gauge;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import org.apache.lucene.codecs.CodecUtil;
import org.apache.lucene.index.CorruptIndexException;
import org.apache.lucene.store.IndexInput; import org.apache.lucene.store.IndexInput;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -44,6 +47,7 @@ import org.apache.cassandra.index.sai.disk.format.IndexComponent;
import org.apache.cassandra.index.sai.disk.format.IndexDescriptor; import org.apache.cassandra.index.sai.disk.format.IndexDescriptor;
import org.apache.cassandra.index.sai.disk.format.OnDiskFormat; import org.apache.cassandra.index.sai.disk.format.OnDiskFormat;
import org.apache.cassandra.index.sai.disk.v1.segment.SegmentBuilder; import org.apache.cassandra.index.sai.disk.v1.segment.SegmentBuilder;
import org.apache.cassandra.index.sai.disk.v1.segment.SegmentMetadata;
import org.apache.cassandra.index.sai.metrics.AbstractMetrics; import org.apache.cassandra.index.sai.metrics.AbstractMetrics;
import org.apache.cassandra.index.sai.utils.IndexIdentifier; import org.apache.cassandra.index.sai.utils.IndexIdentifier;
import org.apache.cassandra.index.sai.utils.IndexTermType; import org.apache.cassandra.index.sai.utils.IndexTermType;
@ -96,6 +100,18 @@ public class V1OnDiskFormat implements OnDiskFormat
IndexComponent.TERMS_DATA, IndexComponent.TERMS_DATA,
IndexComponent.POSTING_LISTS); IndexComponent.POSTING_LISTS);
/**
* Per-column components whose files are written in append mode with one SAI codec footer
* per segment (see {@link org.apache.cassandra.index.sai.disk.v1.bbtree.NumericIndexWriter},
* {@link org.apache.cassandra.index.sai.disk.v1.trie.TrieTermsDictionaryWriter},
* {@link org.apache.cassandra.index.sai.disk.v1.postings.PostingsWriter}, and
* {@link org.apache.cassandra.index.sai.disk.v1.vector.OnHeapGraph}).
*/
private static final Set<IndexComponent> SEGMENTED_COMPONENTS = EnumSet.of(IndexComponent.BALANCED_TREE,
IndexComponent.POSTING_LISTS,
IndexComponent.TERMS_DATA,
IndexComponent.COMPRESSED_VECTORS);
/** /**
* Global limit on heap consumed by all index segment building that occurs outside the context of Memtable flush. * Global limit on heap consumed by all index segment building that occurs outside the context of Memtable flush.
* <p> * <p>
@ -219,15 +235,90 @@ public class V1OnDiskFormat implements OnDiskFormat
} }
} }
if (isEmptyIndex)
return;
// Safely read the segment metadata so we can validate per-segment checksums below...
List<SegmentMetadata> segments = null;
if (checksum)
{
validateIndexComponent(indexDescriptor, indexIdentifier, IndexComponent.META, true);
try
{
segments = SegmentMetadata.load(MetadataSource.loadColumnMetadata(indexDescriptor, indexIdentifier), indexDescriptor.primaryKeyFactory);
}
catch (IOException e)
{
rethrowIOException(e);
}
}
for (IndexComponent indexComponent : perColumnIndexComponents(indexTermType)) for (IndexComponent indexComponent : perColumnIndexComponents(indexTermType))
{ {
if (!isEmptyIndex && isNotBuildCompletionMarker(indexComponent)) if (isNotBuildCompletionMarker(indexComponent))
{ {
validateIndexComponent(indexDescriptor, indexIdentifier, indexComponent, checksum); // META was validated up-front in CHECKSUM mode; don't validate it twice.
if (checksum && indexComponent == IndexComponent.META)
continue;
if (checksum && SEGMENTED_COMPONENTS.contains(indexComponent))
{
assert segments != null : "No segment metadata available!";
validateSegmentedIndexComponent(indexDescriptor, indexIdentifier, indexComponent, segments, indexTermType.isVector());
}
else
validateIndexComponent(indexDescriptor, indexIdentifier, indexComponent, checksum);
} }
} }
} }
private static void validateSegmentedIndexComponent(IndexDescriptor indexDescriptor,
IndexIdentifier indexIdentifier,
IndexComponent indexComponent,
List<SegmentMetadata> segments,
boolean payloadOnlyMetadata)
{
try (IndexInput input = indexDescriptor.openPerIndexInput(indexComponent, indexIdentifier))
{
long fileLength = input.length();
long frameStart = 0;
for (SegmentMetadata segment : segments)
{
SegmentMetadata.ComponentMetadata cm = segment.componentMetadatas.get(indexComponent);
// Non-vector writers record offsets as the codec-framed segment starts (before
// the header) and length as the full framed length (through the footer). The vector
// writer (OnHeapGraph#writeData) instead records the offset as the payload start (after
// the header) and length as just the payload length, because vector readers seek
// directly at the payload. Segments are written contiguously in append mode, so we can
// recover the vector-path frame extent by walking segment ends and adding the trailing
// 16-byte codec footer.
long frameEnd = payloadOnlyMetadata ? cm.offset + cm.length + CodecUtil.footerLength() : cm.offset + cm.length;
if (frameEnd > fileLength || frameEnd < frameStart)
throw new CorruptIndexException(String.format("Segment frame [%d, %d) is inconsistent with component file length %d",
frameStart, frameEnd, fileLength),
indexComponent.name + '@' + frameStart);
IndexInput slice = input.slice(indexComponent.name + '@' + frameStart, frameStart, frameEnd - frameStart);
SAICodecUtils.validateChecksum(slice);
frameStart = frameEnd;
}
if (frameStart != fileLength)
throw new CorruptIndexException(String.format("Component file length %d does not match combined frame length of all segments %d",
fileLength, frameStart),
indexComponent.name);
}
catch (Exception e)
{
logger.warn(indexDescriptor.logMessage("Segmented checksum validation failed for index component {} on SSTable {}"),
indexComponent, indexDescriptor.sstableDescriptor);
rethrowIOException(e);
}
}
private static void validateIndexComponent(IndexDescriptor indexDescriptor, private static void validateIndexComponent(IndexDescriptor indexDescriptor,
IndexIdentifier indexContext, IndexIdentifier indexContext,
IndexComponent indexComponent, IndexComponent indexComponent,
@ -245,9 +336,7 @@ public class V1OnDiskFormat implements OnDiskFormat
catch (Exception e) catch (Exception e)
{ {
logger.warn(indexDescriptor.logMessage("{} failed for index component {} on SSTable {}"), logger.warn(indexDescriptor.logMessage("{} failed for index component {} on SSTable {}"),
checksum ? "Checksum validation" : "Validation", checksum ? "Checksum validation" : "Validation", indexComponent, indexDescriptor.sstableDescriptor);
indexComponent,
indexDescriptor.sstableDescriptor);
rethrowIOException(e); rethrowIOException(e);
} }
} }

View File

@ -103,6 +103,12 @@ public class CellWithSource<T> extends Cell<T>
return cell.localDeletionTime(); return cell.localDeletionTime();
} }
@Override
public long minDeletionTime()
{
return cell.minDeletionTime();
}
@Override @Override
public boolean isTombstone() public boolean isTombstone()
{ {

View File

@ -213,6 +213,12 @@ public class RowWithSource implements Row
return row.hasDeletion(nowInSec); return row.hasDeletion(nowInSec);
} }
@Override
public long minLocalDeletionTime()
{
return row.minLocalDeletionTime();
}
@Override @Override
public SearchIterator<ColumnMetadata, ColumnData> searchIterator() public SearchIterator<ColumnMetadata, ColumnData> searchIterator()
{ {

View File

@ -37,6 +37,7 @@ import org.apache.cassandra.index.sasi.disk.OnDiskIndexBuilder.Mode;
import org.apache.cassandra.index.sasi.plan.Expression.Op; import org.apache.cassandra.index.sasi.plan.Expression.Op;
import org.apache.cassandra.schema.ColumnMetadata; import org.apache.cassandra.schema.ColumnMetadata;
import org.apache.cassandra.schema.IndexMetadata; import org.apache.cassandra.schema.IndexMetadata;
import org.apache.cassandra.utils.FBUtilities;
public class IndexMode public class IndexMode
{ {
@ -60,10 +61,10 @@ public class IndexMode
public final Mode mode; public final Mode mode;
public final boolean isAnalyzed, isLiteral; public final boolean isAnalyzed, isLiteral;
public final Class analyzerClass; public final Class<? extends AbstractAnalyzer> analyzerClass;
public final long maxCompactionFlushMemoryInBytes; public final long maxCompactionFlushMemoryInBytes;
private IndexMode(Mode mode, boolean isLiteral, boolean isAnalyzed, Class analyzerClass, long maxMemBytes) private IndexMode(Mode mode, boolean isLiteral, boolean isAnalyzed, Class<? extends AbstractAnalyzer> analyzerClass, long maxMemBytes)
{ {
this.mode = mode; this.mode = mode;
this.isLiteral = isLiteral; this.isLiteral = isLiteral;
@ -81,7 +82,7 @@ public class IndexMode
if (isAnalyzed) if (isAnalyzed)
{ {
if (analyzerClass != null) if (analyzerClass != null)
analyzer = (AbstractAnalyzer) analyzerClass.newInstance(); analyzer = analyzerClass.newInstance();
else if (TOKENIZABLE_TYPES.contains(validator)) else if (TOKENIZABLE_TYPES.contains(validator))
analyzer = new StandardAnalyzer(); analyzer = new StandardAnalyzer();
} }
@ -99,21 +100,14 @@ public class IndexMode
// validate that a valid analyzer class was provided if specified // validate that a valid analyzer class was provided if specified
if (indexOptions.containsKey(INDEX_ANALYZER_CLASS_OPTION)) if (indexOptions.containsKey(INDEX_ANALYZER_CLASS_OPTION))
{ {
Class<?> analyzerClass; Class<? extends AbstractAnalyzer> analyzerClass = FBUtilities.classForNameWithoutInitialization(indexOptions.get(INDEX_ANALYZER_CLASS_OPTION),
try "analyzer",
{ AbstractAnalyzer.class);
analyzerClass = Class.forName(indexOptions.get(INDEX_ANALYZER_CLASS_OPTION));
}
catch (ClassNotFoundException e)
{
throw new ConfigurationException(String.format("Invalid analyzer class option specified [%s]",
indexOptions.get(INDEX_ANALYZER_CLASS_OPTION)));
}
AbstractAnalyzer analyzer; AbstractAnalyzer analyzer;
try try
{ {
analyzer = (AbstractAnalyzer) analyzerClass.newInstance(); analyzer = analyzerClass.newInstance();
analyzer.validate(indexOptions, cd); analyzer.validate(indexOptions, cd);
} }
catch (InstantiationException | IllegalAccessException e) catch (InstantiationException | IllegalAccessException e)
@ -148,25 +142,30 @@ public class IndexMode
} }
boolean isAnalyzed = false; boolean isAnalyzed = false;
Class analyzerClass = null; Class<? extends AbstractAnalyzer> analyzerClass = null;
try if (indexOptions.get(INDEX_ANALYZER_CLASS_OPTION) != null)
{ {
if (indexOptions.get(INDEX_ANALYZER_CLASS_OPTION) != null) try
{ {
analyzerClass = Class.forName(indexOptions.get(INDEX_ANALYZER_CLASS_OPTION)); analyzerClass = FBUtilities.classForNameWithoutInitialization(indexOptions.get(INDEX_ANALYZER_CLASS_OPTION),
"analyzer",
AbstractAnalyzer.class);
isAnalyzed = indexOptions.get(INDEX_ANALYZED_OPTION) == null isAnalyzed = indexOptions.get(INDEX_ANALYZED_OPTION) == null
? true : Boolean.parseBoolean(indexOptions.get(INDEX_ANALYZED_OPTION)); ? true : Boolean.parseBoolean(indexOptions.get(INDEX_ANALYZED_OPTION));
} }
else if (indexOptions.get(INDEX_ANALYZED_OPTION) != null) catch (ConfigurationException e)
{ {
isAnalyzed = Boolean.parseBoolean(indexOptions.get(INDEX_ANALYZED_OPTION)); if (!(e.getCause() instanceof ClassNotFoundException))
throw e;
// Should not happen as we already validated we could instantiate an instance in validateAnalyzer().
logger.error("Failed to find specified analyzer class [{}]. Falling back to default analyzer",
indexOptions.get(INDEX_ANALYZER_CLASS_OPTION));
} }
} }
catch (ClassNotFoundException e) else if (indexOptions.get(INDEX_ANALYZED_OPTION) != null)
{ {
// should not happen as we already validated we could instantiate an instance in validateAnalyzer() isAnalyzed = Boolean.parseBoolean(indexOptions.get(INDEX_ANALYZED_OPTION));
logger.error("Failed to find specified analyzer class [{}]. Falling back to default analyzer",
indexOptions.get(INDEX_ANALYZER_CLASS_OPTION));
} }
boolean isLiteral = false; boolean isLiteral = false;

View File

@ -61,7 +61,7 @@ public class ClusteringDescriptor extends ResizableByteBuffer
protected void loadClustering(RandomAccessReader dataReader, byte clusteringKind, int clusteringColumnsBound) throws IOException protected void loadClustering(RandomAccessReader dataReader, byte clusteringKind, int clusteringColumnsBound) throws IOException
{ {
set(ClusteringPrefix.Kind.values()[clusteringKind], clusteringKind, clusteringColumnsBound); set(ClusteringPrefix.Kind.fromOrdinal(clusteringKind), clusteringKind, clusteringColumnsBound);
if (clusteringKind != STATIC_CLUSTERING_KIND) if (clusteringKind != STATIC_CLUSTERING_KIND)
readUnfilteredClustering(dataReader, clusteringTypes, this.clusteringColumnsBound, this); readUnfilteredClustering(dataReader, clusteringTypes, this.clusteringColumnsBound, this);
else else
@ -103,7 +103,7 @@ public class ClusteringDescriptor extends ResizableByteBuffer
} }
private void set(byte clusteringKindEncoded, int clusteringColumnsBound) { private void set(byte clusteringKindEncoded, int clusteringColumnsBound) {
set(ClusteringPrefix.Kind.values()[clusteringKindEncoded], clusteringKindEncoded, clusteringColumnsBound); set(ClusteringPrefix.Kind.fromOrdinal(clusteringKindEncoded), clusteringKindEncoded, clusteringColumnsBound);
} }
private void set(ClusteringPrefix.Kind clusteringKind, byte clusteringKindEncoded, int clusteringColumnsBound) private void set(ClusteringPrefix.Kind clusteringKind, byte clusteringKindEncoded, int clusteringColumnsBound)

View File

@ -513,7 +513,7 @@ public class SSTableCursorWriter implements AutoCloseable
public void writeRangeTombstone(UnfilteredDescriptor rangeTombstone, boolean updateClusteringMetadata) throws IOException public void writeRangeTombstone(UnfilteredDescriptor rangeTombstone, boolean updateClusteringMetadata) throws IOException
{ {
int tombstoneKind = rangeTombstone.clusteringKindEncoded(); int tombstoneKind = rangeTombstone.clusteringKindEncoded();
ClusteringPrefix.Kind kind = ClusteringPrefix.Kind.values()[tombstoneKind]; ClusteringPrefix.Kind kind = ClusteringPrefix.Kind.fromOrdinal(tombstoneKind);
long unfilteredStartPosition = getPosition(); long unfilteredStartPosition = getPosition();
/** See: {@link org.apache.cassandra.db.rows.UnfilteredSerializer#serialize */ /** See: {@link org.apache.cassandra.db.rows.UnfilteredSerializer#serialize */
dataWriter.writeByte((byte)IS_MARKER); dataWriter.writeByte((byte)IS_MARKER);

View File

@ -340,11 +340,11 @@ public abstract class AbstractReplicationStrategy
if ("org.apache.cassandra.locator.OldNetworkTopologyStrategy".equals(className)) // see CASSANDRA-16301 if ("org.apache.cassandra.locator.OldNetworkTopologyStrategy".equals(className)) // see CASSANDRA-16301
throw new ConfigurationException("The support for the OldNetworkTopologyStrategy has been removed in C* version 4.0. The keyspace strategy should be switch to NetworkTopologyStrategy"); throw new ConfigurationException("The support for the OldNetworkTopologyStrategy has been removed in C* version 4.0. The keyspace strategy should be switch to NetworkTopologyStrategy");
Class<AbstractReplicationStrategy> strategyClass = FBUtilities.classForName(className, "replication strategy"); @SuppressWarnings("unchecked")
if (!AbstractReplicationStrategy.class.isAssignableFrom(strategyClass)) Class<AbstractReplicationStrategy> strategyClass =
{ (Class<AbstractReplicationStrategy>) FBUtilities.classForNameWithoutInitialization(className,
throw new ConfigurationException(String.format("Specified replication strategy class (%s) is not derived from AbstractReplicationStrategy", className)); "replication strategy",
} AbstractReplicationStrategy.class);
return strategyClass; return strategyClass;
} }

View File

@ -26,6 +26,8 @@ import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
@ -41,6 +43,7 @@ import org.apache.cassandra.concurrent.ScheduledExecutors;
import org.apache.cassandra.concurrent.Shutdownable; import org.apache.cassandra.concurrent.Shutdownable;
import io.netty.util.concurrent.FastThreadLocal; import io.netty.util.concurrent.FastThreadLocal;
import io.netty.util.concurrent.FastThreadLocalThread;
import static com.google.common.collect.ImmutableList.of; import static com.google.common.collect.ImmutableList.of;
import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory;
@ -64,10 +67,8 @@ public class ThreadLocalMetrics
static final AtomicInteger idGenerator = new AtomicInteger(); static final AtomicInteger idGenerator = new AtomicInteger();
private static final Object freeMetricIdSetGuard = new Object();
@VisibleForTesting @VisibleForTesting
static final BitSet freeMetricIdSet = new BitSet(); static final FreeMetricIdSetTracker freeMetricIdSetTracker = new FreeMetricIdSetTracker();
private static final List<ThreadLocalMetrics> allThreadLocalMetrics = new CopyOnWriteArrayList<>(); private static final List<ThreadLocalMetrics> allThreadLocalMetrics = new CopyOnWriteArrayList<>();
@ -101,7 +102,13 @@ public class ThreadLocalMetrics
{ {
ThreadLocalMetrics result = new ThreadLocalMetrics(); ThreadLocalMetrics result = new ThreadLocalMetrics();
allThreadLocalMetrics.add(result); allThreadLocalMetrics.add(result);
destroyWhenUnreachable(Thread.currentThread(), result::release);
Thread thread = Thread.currentThread();
// use phantom references ony if needed
// CassandraThread is FastThreadLocalThread too
if (!(thread instanceof FastThreadLocalThread))
destroyWhenUnreachable(thread, result::release);
return result; return result;
} }
@ -317,13 +324,7 @@ public class ThreadLocalMetrics
static int allocateMetricId() static int allocateMetricId()
{ {
int metricId; int metricId = freeMetricIdSetTracker.getFreeMetricId();
synchronized (freeMetricIdSetGuard)
{
metricId = freeMetricIdSet.nextSetBit(0);
if (metricId >= 0)
freeMetricIdSet.clear(metricId);
}
if (metricId < 0) if (metricId < 0)
metricId = idGenerator.getAndIncrement(); metricId = idGenerator.getAndIncrement();
@ -374,26 +375,92 @@ public class ThreadLocalMetrics
lock.unlock(); lock.unlock();
} }
// there's no an obvious happens-before relation between currentCounterValues[metricId] = 0 write we just did freeMetricIdSetTracker.markAsFree(metricId);
// and an initial read of the entry by a thread which updates the reused metric }
// as a workaround we introduce a delay in recyling to provide the write visibility in practice
// even if it is not formally guaranteed by the JMM @VisibleForTesting
ScheduledExecutors.scheduledTasks.schedule(() -> { static class FreeMetricIdSetTracker
synchronized (freeMetricIdSetGuard) {
private final BitSet freeMetricIdSet = new BitSet();
private final BitSet tickDelayedToFreeMetricIdSet = new BitSet();
private final BitSet tockDelayedToFreeMetricIdSet = new BitSet();
private BitSet delayedToFreeMetricIdSet = tickDelayedToFreeMetricIdSet;
private ScheduledFuture<?> cleanupTask;
@VisibleForTesting
synchronized void triggerRecycling()
{
cleanupTask = null;
BitSet toProcess = otherSet(delayedToFreeMetricIdSet);
freeMetricIdSet.or(toProcess);
toProcess.clear();
if (!delayedToFreeMetricIdSet.isEmpty())
scheduleCleanupTask();
delayedToFreeMetricIdSet = toProcess;
}
private BitSet otherSet(BitSet set)
{
return set == tickDelayedToFreeMetricIdSet ? tockDelayedToFreeMetricIdSet : tickDelayedToFreeMetricIdSet;
}
public synchronized int getFreeMetricId()
{
int metricId = freeMetricIdSet.nextSetBit(0);
if (metricId >= 0)
freeMetricIdSet.clear(metricId);
return metricId;
}
public synchronized void markAsFree(int metricId)
{
// there's no an obvious happens-before relation between currentCounterValues[metricId] = 0 write we just did
// and an initial read of the entry by a thread which updates the reused metric
// as a workaround we introduce a delay in recyling to provide the write visibility in practice
// even if it is not formally guaranteed by the JMM
delayedToFreeMetricIdSet.set(metricId);
scheduleCleanupTask();
}
// must be called while holding this monitor (from a synchronized method)
@VisibleForTesting
protected void scheduleCleanupTask()
{
try
{ {
freeMetricIdSet.set(metricId); if (cleanupTask == null)
cleanupTask = ScheduledExecutors.scheduledTasks.schedule(this::triggerRecycling, 5, TimeUnit.SECONDS);
} }
}, 5, TimeUnit.SECONDS); catch (RejectedExecutionException e)
{
// ignore theoretically possible rejections during a shutdown
}
}
public synchronized int getFreeMetricSetCardinality()
{
return freeMetricIdSet.cardinality();
}
@Override
public synchronized String toString()
{
return "FreeMetricIdSetTracker{" +
"freeMetricIdSet=" + freeMetricIdSet +
", tickDelayedToFreeMetricIdSet=" + tickDelayedToFreeMetricIdSet +
", tockDelayedToFreeMetricIdSet=" + tockDelayedToFreeMetricIdSet +
", delayedToFreeMetricIdSet=" + (delayedToFreeMetricIdSet == tickDelayedToFreeMetricIdSet ? "tick" : "tock") +
'}';
}
} }
@VisibleForTesting @VisibleForTesting
static int getAllocatedMetricsCount() static int getAllocatedMetricsCount()
{ {
int freeCount; int freeCount = freeMetricIdSetTracker.getFreeMetricSetCardinality();
synchronized (freeMetricIdSetGuard)
{
freeCount = freeMetricIdSet.cardinality();
}
return idGenerator.get() - freeCount; return idGenerator.get() - freeCount;
} }

View File

@ -31,7 +31,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.apache.cassandra.concurrent.ManyToOneConcurrentLinkedQueue; import org.apache.cassandra.concurrent.ManyToOneConcurrentLinkedQueue;
import org.apache.cassandra.metrics.ClientMetrics;
import org.apache.cassandra.net.FrameDecoder.CorruptFrame; import org.apache.cassandra.net.FrameDecoder.CorruptFrame;
import org.apache.cassandra.net.FrameDecoder.Frame; import org.apache.cassandra.net.FrameDecoder.Frame;
import org.apache.cassandra.net.FrameDecoder.FrameProcessor; import org.apache.cassandra.net.FrameDecoder.FrameProcessor;
@ -314,7 +313,7 @@ public abstract class AbstractMessageHandler extends ChannelInboundHandlerAdapte
decoder.reactivate(); decoder.reactivate();
if (decoder.isActive()) if (decoder.isActive())
ClientMetrics.instance.unpauseConnection(); onConnectionUnpaused();
} }
} }
catch (Throwable t) catch (Throwable t)
@ -323,6 +322,10 @@ public abstract class AbstractMessageHandler extends ChannelInboundHandlerAdapte
} }
} }
protected void onConnectionUnpaused()
{
}
protected abstract void fatalExceptionCaught(Throwable t); protected abstract void fatalExceptionCaught(Throwable t);
// return true if the handler should be reactivated - if no new hurdles were encountered, // return true if the handler should be reactivated - if no new hurdles were encountered,

View File

@ -395,7 +395,10 @@ public class AutoRepairConfig implements Serializable
className = parameterizedClass.class_name.contains(".") ? className = parameterizedClass.class_name.contains(".") ?
parameterizedClass.class_name : parameterizedClass.class_name :
"org.apache.cassandra.repair.autorepair." + parameterizedClass.class_name; "org.apache.cassandra.repair.autorepair." + parameterizedClass.class_name;
tokenRangeSplitterClass = FBUtilities.classForName(className, "token_range_splitter"); tokenRangeSplitterClass =
FBUtilities.classForNameWithoutInitialization(className,
"token_range_splitter",
IAutoRepairTokenRangeSplitter.class);
} }
else else
{ {

View File

@ -311,15 +311,7 @@ public final class CompactionParams
String className = name.contains(".") String className = name.contains(".")
? name ? name
: "org.apache.cassandra.db.compaction." + name; : "org.apache.cassandra.db.compaction." + name;
Class<AbstractCompactionStrategy> strategyClass = FBUtilities.classForName(className, "compaction strategy"); return FBUtilities.classForNameWithoutInitialization(className, "compaction strategy", AbstractCompactionStrategy.class);
if (!AbstractCompactionStrategy.class.isAssignableFrom(strategyClass))
{
throw new ConfigurationException(format("Compaction strategy class %s is not derived from AbstractReplicationStrategy",
className));
}
return strategyClass;
} }
/* /*

View File

@ -46,6 +46,7 @@ import org.apache.cassandra.io.compress.ZstdDictionaryCompressor;
import org.apache.cassandra.io.util.DataInputPlus; import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus; import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.net.MessagingService; import org.apache.cassandra.net.MessagingService;
import org.apache.cassandra.utils.FBUtilities;
import static java.lang.String.format; import static java.lang.String.format;
@ -302,7 +303,7 @@ public final class CompressionParams
return maxCompressedLength; return maxCompressedLength;
} }
private static Class<?> parseCompressorClass(String className) throws ConfigurationException private static Class<? extends ICompressor> parseCompressorClass(String className) throws ConfigurationException
{ {
if (className == null || className.isEmpty()) if (className == null || className.isEmpty())
return null; return null;
@ -310,15 +311,17 @@ public final class CompressionParams
className = className.contains(".") ? className : "org.apache.cassandra.io.compress." + className; className = className.contains(".") ? className : "org.apache.cassandra.io.compress." + className;
try try
{ {
return Class.forName(className); return FBUtilities.classForNameWithoutInitialization(className, "compression", ICompressor.class);
} }
catch (Exception e) catch (ConfigurationException e)
{ {
throw new ConfigurationException("Could not create Compression for type " + className, e); if (e.getCause() instanceof ClassNotFoundException || e.getCause() instanceof NoClassDefFoundError)
throw new ConfigurationException("Could not create Compression for type " + className, e);
throw e;
} }
} }
private static ICompressor createCompressor(Class<?> compressorClass, Map<String, String> compressionOptions) throws ConfigurationException private static ICompressor createCompressor(Class<? extends ICompressor> compressorClass, Map<String, String> compressionOptions) throws ConfigurationException
{ {
if (compressorClass == null) if (compressorClass == null)
{ {

View File

@ -148,9 +148,7 @@ public final class IndexMetadata
// Get the fully qualified class name: // Get the fully qualified class name:
String className = getIndexClassName(); String className = getIndexClassName();
Class<Index> indexerClass = FBUtilities.classForName(className, "custom indexer"); Class<? extends Index> indexerClass = FBUtilities.classForNameWithoutInitialization(className, "custom indexer", Index.class);
if (!Index.class.isAssignableFrom(indexerClass))
throw new ConfigurationException(String.format("Specified Indexer class (%s) does not implement the Indexer interface", className));
validateCustomIndexOptions(table, indexerClass, options); validateCustomIndexOptions(table, indexerClass, options);
} }
} }

View File

@ -228,19 +228,25 @@ public final class MemtableParams
try try
{ {
Memtable.Factory factory; Memtable.Factory factory;
Class<?> clazz = Class.forName(className); Class<?> clazz = Class.forName(className, false, MemtableParams.class.getClassLoader());
final Map<String, String> parametersCopy = options.parameters != null final Map<String, String> parametersCopy = options.parameters != null
? new HashMap<>(options.parameters) ? new HashMap<>(options.parameters)
: new HashMap<>(); : new HashMap<>();
try try
{ {
Method factoryMethod = clazz.getDeclaredMethod("factory", Map.class); Method factoryMethod = clazz.getDeclaredMethod("factory", Map.class);
if (!Memtable.Factory.class.isAssignableFrom(factoryMethod.getReturnType()))
throw new ClassCastException("Memtable factory method on " + className +
" must return " + Memtable.Factory.class.getName());
factory = (Memtable.Factory) factoryMethod.invoke(null, parametersCopy); factory = (Memtable.Factory) factoryMethod.invoke(null, parametersCopy);
} }
catch (NoSuchMethodException e) catch (NoSuchMethodException e)
{ {
// continue with FACTORY field // continue with FACTORY field
Field factoryField = clazz.getDeclaredField("FACTORY"); Field factoryField = clazz.getDeclaredField("FACTORY");
if (!Memtable.Factory.class.isAssignableFrom(factoryField.getType()))
throw new ClassCastException("Memtable FACTORY field on " + className +
" must be of type " + Memtable.Factory.class.getName());
factory = (Memtable.Factory) factoryField.get(null); factory = (Memtable.Factory) factoryField.get(null);
} }
if (!parametersCopy.isEmpty()) if (!parametersCopy.isEmpty())

View File

@ -286,7 +286,7 @@ public final class ReplicationParams
Map<String, String> options = new HashMap<>(size); Map<String, String> options = new HashMap<>(size);
for (int i = 0; i < size; i++) for (int i = 0; i < size; i++)
options.put(in.readUTF(), in.readUTF()); options.put(in.readUTF(), in.readUTF());
return new ReplicationParams(FBUtilities.classForName(klassName, "ReplicationStrategy"), options); return new ReplicationParams(FBUtilities.classForNameWithoutInitialization(klassName, "ReplicationStrategy", AbstractReplicationStrategy.class), options);
} }
public long serializedSize(ReplicationParams t, Version version) public long serializedSize(ReplicationParams t, Version version)
@ -322,7 +322,7 @@ public final class ReplicationParams
Map<String, String> options = new HashMap<>(size); Map<String, String> options = new HashMap<>(size);
for (int i=0; i<size; i++) for (int i=0; i<size; i++)
options.put(in.readUTF(), in.readUTF()); options.put(in.readUTF(), in.readUTF());
return new ReplicationParams(FBUtilities.classForName(klassName, "ReplicationStrategy"), options); return new ReplicationParams(FBUtilities.classForNameWithoutInitialization(klassName, "ReplicationStrategy", AbstractReplicationStrategy.class), options);
} }
public long serializedSize(ReplicationParams t, int version) public long serializedSize(ReplicationParams t, int version)

View File

@ -105,7 +105,7 @@ public abstract class AbstractCryptoProvider
return; return;
} }
FBUtilities.classForName(getProviderClassAsString(), "crypto provider"); FBUtilities.classForNameWithoutInitialization(getProviderClassAsString(), "crypto provider", Provider.class);
String providerName = getProviderName(); String providerName = getProviderName();
int providerPosition = getProviderPosition(providerName); int providerPosition = getProviderPosition(providerName);

View File

@ -40,6 +40,7 @@ import org.slf4j.LoggerFactory;
import org.apache.cassandra.concurrent.ImmediateExecutor; import org.apache.cassandra.concurrent.ImmediateExecutor;
import org.apache.cassandra.config.TransparentDataEncryptionOptions; import org.apache.cassandra.config.TransparentDataEncryptionOptions;
import org.apache.cassandra.utils.FBUtilities;
import io.netty.util.concurrent.FastThreadLocal; import io.netty.util.concurrent.FastThreadLocal;
@ -71,9 +72,10 @@ public class CipherFactory
try try
{ {
secureRandom = SecureRandom.getInstance("SHA1PRNG"); secureRandom = SecureRandom.getInstance("SHA1PRNG");
Class<KeyProvider> keyProviderClass = (Class<KeyProvider>)Class.forName(options.key_provider.class_name); Class<? extends KeyProvider> keyProviderClass =
Constructor ctor = keyProviderClass.getConstructor(TransparentDataEncryptionOptions.class); FBUtilities.classForNameWithoutInitialization(options.key_provider.class_name, "key provider", KeyProvider.class);
keyProvider = (KeyProvider)ctor.newInstance(options); Constructor<? extends KeyProvider> ctor = keyProviderClass.getConstructor(TransparentDataEncryptionOptions.class);
keyProvider = ctor.newInstance(options);
} }
catch (Exception e) catch (Exception e)
{ {

View File

@ -147,9 +147,11 @@ public class CacheService implements CacheServiceMBean
? DatabaseDescriptor.getRowCacheClassName() : "org.apache.cassandra.cache.NopCacheProvider"; ? DatabaseDescriptor.getRowCacheClassName() : "org.apache.cassandra.cache.NopCacheProvider";
try try
{ {
Class<CacheProvider<RowCacheKey, IRowCacheEntry>> cacheProviderClass = Class<? extends CacheProvider> cacheProviderClass =
(Class<CacheProvider<RowCacheKey, IRowCacheEntry>>) Class.forName(cacheProviderClassName); FBUtilities.classForNameWithoutInitialization(cacheProviderClassName, "row cache provider", CacheProvider.class);
cacheProvider = cacheProviderClass.newInstance(); @SuppressWarnings("unchecked")
CacheProvider<RowCacheKey, IRowCacheEntry> typedCacheProvider = cacheProviderClass.newInstance();
cacheProvider = typedCacheProvider;
} }
catch (Exception e) catch (Exception e)
{ {

View File

@ -124,7 +124,7 @@ public class ClientState
{ {
try try
{ {
handler = FBUtilities.construct(customHandlerClass, "QueryHandler"); handler = FBUtilities.construct(customHandlerClass, "QueryHandler", QueryHandler.class);
logger.info("Using {} as a query handler for native protocol queries (as requested by the {} system property)", logger.info("Using {} as a query handler for native protocol queries (as requested by the {} system property)",
customHandlerClass, CUSTOM_QUERY_HANDLER_CLASS.getKey()); customHandlerClass, CUSTOM_QUERY_HANDLER_CLASS.getKey());
} }

View File

@ -78,7 +78,7 @@ public class DiskErrorsHandlerService
String fsErrorHandlerClass = CassandraRelevantProperties.CUSTOM_DISK_ERROR_HANDLER.getString(); String fsErrorHandlerClass = CassandraRelevantProperties.CUSTOM_DISK_ERROR_HANDLER.getString();
DiskErrorsHandler fsErrorHandler = fsErrorHandlerClass == null DiskErrorsHandler fsErrorHandler = fsErrorHandlerClass == null
? new DefaultDiskErrorsHandler() ? new DefaultDiskErrorsHandler()
: FBUtilities.construct(fsErrorHandlerClass, "disk error handler"); : FBUtilities.construct(fsErrorHandlerClass, "disk error handler", DiskErrorsHandler.class);
DiskErrorsHandlerService.set(fsErrorHandler); DiskErrorsHandlerService.set(fsErrorHandler);
} }
} }

View File

@ -3403,6 +3403,11 @@ public class StorageService extends NotificationBroadcasterSupport implements IE
*/ */
@Deprecated(since = "4.0") @Deprecated(since = "4.0")
public List<InetAddress> getNaturalEndpoints(String keyspaceName, String cf, String key) public List<InetAddress> getNaturalEndpoints(String keyspaceName, String cf, String key)
{
return getNaturalReplicas(keyspaceName, cf, key);
}
public List<InetAddress> getNaturalReplicas(String keyspaceName, String cf, String key)
{ {
EndpointsForToken replicas = getNaturalReplicasForToken(keyspaceName, cf, key); EndpointsForToken replicas = getNaturalReplicasForToken(keyspaceName, cf, key);
List<InetAddress> inetList = new ArrayList<>(replicas.size()); List<InetAddress> inetList = new ArrayList<>(replicas.size());
@ -3410,11 +3415,16 @@ public class StorageService extends NotificationBroadcasterSupport implements IE
return inetList; return inetList;
} }
public List<String> getNaturalEndpointsWithPort(String keyspaceName, String cf, String key) public List<String> getNaturalReplicasWithPort(String keyspaceName, String cf, String key)
{ {
return Replicas.stringify(getNaturalReplicasForToken(keyspaceName, cf, key), true); return Replicas.stringify(getNaturalReplicasForToken(keyspaceName, cf, key), true);
} }
public List<String> getNaturalEndpointsWithPort(String keyspaceName, String cf, String key)
{
return getNaturalReplicasWithPort(keyspaceName, cf, key);
}
/** @deprecated See CASSANDRA-7544 */ /** @deprecated See CASSANDRA-7544 */
@Deprecated(since = "4.0") @Deprecated(since = "4.0")
public List<InetAddress> getNaturalEndpoints(String keyspaceName, ByteBuffer key) public List<InetAddress> getNaturalEndpoints(String keyspaceName, ByteBuffer key)

View File

@ -262,12 +262,28 @@ public interface StorageServiceMBean extends NotificationEmitter
* @param key - key for which we need to find the endpoint return value - * @param key - key for which we need to find the endpoint return value -
* the endpoint responsible for this key * the endpoint responsible for this key
* @deprecated See CASSANDRA-7544 * @deprecated See CASSANDRA-7544
* @link getNaturalReplicas
* @link getNaturalReplicasWithPort
*/ */
@Deprecated(since = "4.0") public List<InetAddress> getNaturalEndpoints(String keyspaceName, String cf, String key); @Deprecated(since = "4.0") public List<InetAddress> getNaturalEndpoints(String keyspaceName, String cf, String key);
public List<String> getNaturalEndpointsWithPort(String keyspaceName, String cf, String key); /** @deprecated See CASSANDRA-17665 */
@Deprecated(since = "7.0") public List<String> getNaturalEndpointsWithPort(String keyspaceName, String cf, String key);
/** @deprecated See CASSANDRA-7544 */ /** @deprecated See CASSANDRA-7544 */
@Deprecated(since = "4.0") public List<InetAddress> getNaturalEndpoints(String keyspaceName, ByteBuffer key); @Deprecated(since = "4.0") public List<InetAddress> getNaturalEndpoints(String keyspaceName, ByteBuffer key);
public List<String> getNaturalEndpointsWithPort(String keysapceName, ByteBuffer key); /** @deprecated See CASSANDRA-17665 */
@Deprecated(since = "7.0") public List<String> getNaturalEndpointsWithPort(String keysapceName, ByteBuffer key);
/**
* This method returns the N replicas that are responsible for storing the
* specified key i.e for replication.
*
* @param keyspaceName keyspace name
* @param cf Column family name
* @param key - key for which we need to find the replica return value -
* the replica responsible for this key
*/
public List<InetAddress> getNaturalReplicas(String keyspaceName, String cf, String key);
public List<String> getNaturalReplicasWithPort(String keyspaceName, String cf, String key);
/** /**
* @deprecated use {@link #takeSnapshot(String tag, Map options, String... entities)} instead. See CASSANDRA-10907 * @deprecated use {@link #takeSnapshot(String tag, Map options, String... entities)} instead. See CASSANDRA-10907

View File

@ -458,7 +458,9 @@ public class AccordService implements IAccordService, Shutdownable
{ {
Invariants.require(localId != null, "static localId must be set before instantiating AccordService"); Invariants.require(localId != null, "static localId must be set before instantiating AccordService");
logger.info("Starting accord with nodeId {}", localId); logger.info("Starting accord with nodeId {}", localId);
AccordAgent agent = FBUtilities.construct(CassandraRelevantProperties.ACCORD_AGENT_CLASS.getString(AccordAgent.class.getName()), "AccordAgent"); AccordAgent agent = FBUtilities.construct(CassandraRelevantProperties.ACCORD_AGENT_CLASS.getString(AccordAgent.class.getName()),
"AccordAgent",
AccordAgent.class);
agent.setup(localId); agent.setup(localId);
AccordTimeService time = new AccordTimeService(); AccordTimeService time = new AccordTimeService();
this.scheduler = new AccordScheduler(); this.scheduler = new AccordScheduler();

View File

@ -37,7 +37,7 @@ public interface StreamHook
String className = STREAM_HOOK.getString(); String className = STREAM_HOOK.getString();
if (className != null) if (className != null)
{ {
return FBUtilities.construct(className, StreamHook.class.getSimpleName()); return FBUtilities.construct(className, StreamHook.class.getSimpleName(), StreamHook.class);
} }
else else
{ {

View File

@ -41,7 +41,7 @@ public class ExtensionKey<V, K extends ExtensionValue<V>> extends MetadataKey
public K newValue() public K newValue()
{ {
return valueType.cast(FBUtilities.construct(valueType.getName(), "extension value")); return FBUtilities.construct(valueType.getName(), "extension value", valueType);
} }
public static final class Serializer implements MetadataSerializer<ExtensionKey<?, ?>> public static final class Serializer implements MetadataSerializer<ExtensionKey<?, ?>>
@ -58,7 +58,9 @@ public class ExtensionKey<V, K extends ExtensionValue<V>> extends MetadataKey
{ {
String id = in.readUTF(); String id = in.readUTF();
String valType = in.readUTF(); String valType = in.readUTF();
return new ExtensionKey(id, FBUtilities.classForName(valType, "value type")); Class<? extends ExtensionValue> valueType =
FBUtilities.classForNameWithoutInitialization(valType, "value type", ExtensionValue.class);
return new ExtensionKey(id, valueType);
} }
@Override @Override
@ -68,4 +70,3 @@ public class ExtensionKey<V, K extends ExtensionValue<V>> extends MetadataKey
} }
} }
} }

View File

@ -92,6 +92,7 @@ public interface SingleNodeSequences
else if (InProgressSequences.isLeave(inProgress)) else if (InProgressSequences.isLeave(inProgress))
{ {
logger.info("Resuming decommission @ {} (current epoch = {}): {}", inProgress.latestModification, metadata.epoch, inProgress.status()); logger.info("Resuming decommission @ {} (current epoch = {}): {}", inProgress.latestModification, metadata.epoch, inProgress.status());
StorageService.instance.clearTransientMode();
} }
else else
{ {

View File

@ -1200,14 +1200,14 @@ public class NodeProbe implements AutoCloseable
ssProxy.setHintedHandoffThrottleInKB(throttleInKB); ssProxy.setHintedHandoffThrottleInKB(throttleInKB);
} }
public List<String> getEndpointsWithPort(String keyspace, String cf, String key) public List<String> getReplicasWithPort(String keyspace, String cf, String key)
{ {
return ssProxy.getNaturalEndpointsWithPort(keyspace, cf, key); return ssProxy.getNaturalReplicasWithPort(keyspace, cf, key);
} }
public List<InetAddress> getEndpoints(String keyspace, String cf, String key) public List<InetAddress> getReplicas(String keyspace, String cf, String key)
{ {
return ssProxy.getNaturalEndpoints(keyspace, cf, key); return ssProxy.getNaturalReplicas(keyspace, cf, key);
} }
public List<String> getSSTables(String keyspace, String cf, String key, boolean hexFormat) public List<String> getSSTables(String keyspace, String cf, String key, boolean hexFormat)

View File

@ -17,62 +17,10 @@
*/ */
package org.apache.cassandra.tools.nodetool; package org.apache.cassandra.tools.nodetool;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.List;
import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.nodetool.layout.CassandraUsage;
import picocli.CommandLine.Command; import picocli.CommandLine.Command;
import picocli.CommandLine.Mixin;
import picocli.CommandLine.Parameters;
import static com.google.common.base.Preconditions.checkArgument; @Deprecated(since = "7.0") // this is alias to getreplicas
import static org.apache.cassandra.tools.nodetool.CommandUtils.concatArgs; @Command(name = "getendpoints", description = "Print the end points that owns the key, deprecated, use getreplicas instead")
public class GetEndpoints extends GetReplicas
@Command(name = "getendpoints", description = "Print the end points that owns the key")
public class GetEndpoints extends AbstractCommand
{ {
@CassandraUsage(usage = "<keyspace> <table> <key>", description = "The keyspace, the table, and the partition key for which we need to find the endpoint")
private List<String> args = new ArrayList<>();
@Parameters(index = "0", arity = "0..1", description = "The keyspace for which we need to find the endpoint")
private String keyspace;
@Parameters(index = "1", arity = "0..1", description = "The table for which we need to find the endpoint")
private String table;
@Parameters(index = "2", arity = "0..1", description = "The partition key for which we need to find the endpoint")
private String key;
@Mixin
private PrintPortMixin printPortMixin = new PrintPortMixin();
@Override
public void execute(NodeProbe probe)
{
args = concatArgs(keyspace, table, key);
checkArgument(args.size() == 3, "getendpoints requires keyspace, table and partition key arguments");
String ks = args.get(0);
String table = args.get(1);
String key = args.get(2);
if (printPortMixin.printPort)
{
for (String endpoint : probe.getEndpointsWithPort(ks, table, key))
{
probe.output().out.println(endpoint);
}
}
else
{
List<InetAddress> endpoints = probe.getEndpoints(ks, table, key);
for (InetAddress endpoint : endpoints)
{
probe.output().out.println(endpoint.getHostAddress());
}
}
}
} }

View File

@ -0,0 +1,79 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.tools.nodetool;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.List;
import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.nodetool.layout.CassandraUsage;
import picocli.CommandLine.Command;
import picocli.CommandLine.Mixin;
import picocli.CommandLine.Parameters;
import static com.google.common.base.Preconditions.checkArgument;
import static org.apache.cassandra.tools.nodetool.CommandUtils.concatArgs;
@Command(name = "getreplicas", description = "Print the replicas that own the key")
public class GetReplicas extends AbstractCommand
{
@CassandraUsage(usage = "<keyspace> <table> <key>", description = "The keyspace, the table, and the partition key for which we need to find the replica (e.g., pk1:pk2:pk3 for compound keys)")
private List<String> args = new ArrayList<>();
@Parameters(index = "0", arity = "0..1", description = "The keyspace for which we need to find the replica")
private String keyspace;
@Parameters(index = "1", arity = "0..1", description = "The table for which we need to find the replica")
private String table;
@Parameters(index = "2", arity = "0..1", description = "The partition key for which we need to find the replica (e.g., pk1:pk2:pk3 for compound keys)")
private String key;
@Mixin
private PrintPortMixin printPortMixin = new PrintPortMixin();
@Override
public void execute(NodeProbe probe)
{
args = concatArgs(keyspace, table, key);
checkArgument(args.size() == 3, "requires keyspace, table and partition key arguments");
String ks = args.get(0);
String table = args.get(1);
String key = args.get(2);
if (printPortMixin.printPort)
{
for (String replica : probe.getReplicasWithPort(ks, table, key))
{
probe.output().out.println(replica);
}
}
else
{
List<InetAddress> replicas = probe.getReplicas(ks, table, key);
for (InetAddress replica : replicas)
{
probe.output().out.println(replica.getHostAddress());
}
}
}
}

View File

@ -38,7 +38,7 @@ import static com.google.common.base.Preconditions.checkArgument;
@Command(name = "invalidatepermissionscache", description = "Invalidate the permissions cache") @Command(name = "invalidatepermissionscache", description = "Invalidate the permissions cache")
public class InvalidatePermissionsCache extends AbstractCommand public class InvalidatePermissionsCache extends AbstractCommand
{ {
@Parameters(paramLabel = "role", description = "A role for which permissions to specified resources need to be invalidated", arity = "0..1", index = "0") @Parameters(paramLabel = "role_name", description = "A role for which permissions to specified resources need to be invalidated", arity = "0..1", index = "0")
private String roleName; private String roleName;
// Data Resources // Data Resources

View File

@ -120,6 +120,7 @@ import static org.apache.cassandra.tools.nodetool.Help.printTopCommandUsage;
GetInterDCStreamThroughput.class, GetInterDCStreamThroughput.class,
GetLoggingLevels.class, GetLoggingLevels.class,
GetMaxHintWindow.class, GetMaxHintWindow.class,
GetReplicas.class,
GetSSTables.class, GetSSTables.class,
GetSeeds.class, GetSeeds.class,
GetSnapshotThrottle.class, GetSnapshotThrottle.class,

View File

@ -393,6 +393,7 @@ public class Sjk extends AbstractCommand
List<Class<?>> result = new ArrayList<>(); List<Class<?>> result = new ArrayList<>();
try try
{ {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
String path = packageName.replace('.', '/'); String path = packageName.replace('.', '/');
for (String f : findFiles(path)) for (String f : findFiles(path))
{ {
@ -400,7 +401,7 @@ public class Sjk extends AbstractCommand
{ {
f = f.substring(0, f.length() - ".class".length()); f = f.substring(0, f.length() - ".class".length());
f = f.replace('/', '.'); f = f.replace('/', '.');
result.add(Class.forName(f)); result.add(Class.forName(f, false, cl));
} }
} }
return result; return result;

View File

@ -117,7 +117,7 @@ public abstract class Tracing extends ExecutorLocals.Impl
{ {
try try
{ {
tracing = FBUtilities.construct(customTracingClass, "Tracing"); tracing = FBUtilities.construct(customTracingClass, "Tracing", Tracing.class);
logger.info("Using the {} class to trace queries (as requested by the {} system property)", logger.info("Using the {} class to trace queries (as requested by the {} system property)",
customTracingClass, CUSTOM_TRACING_CLASS.getKey()); customTracingClass, CUSTOM_TRACING_CLASS.getKey());
} }

Some files were not shown because too many files have changed in this diff Show More