mirror of https://github.com/apache/cassandra
Merge branch 'apache:trunk' into liuisaac/CASSANDRA-19831/trunk
This commit is contained in:
commit
da943add35
|
|
@ -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*"/>
|
||||||
|
|
|
||||||
152
.build/run-ci
152
.build/run-ci
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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`
|
||||||
|
|
@ -18,6 +18,7 @@ bs4
|
||||||
dotenv
|
dotenv
|
||||||
kubernetes
|
kubernetes
|
||||||
python-jenkins
|
python-jenkins
|
||||||
|
pyyaml
|
||||||
requests
|
requests
|
||||||
|
|
||||||
# optional for different clouds
|
# optional for different clouds
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
if (isCanonical()) {
|
|
||||||
def agentScriptsUrl = "https://raw.githubusercontent.com/apache/cassandra-builds/trunk/jenkins-dsl/agent_scripts/"
|
def agentScriptsUrl = "https://raw.githubusercontent.com/apache/cassandra-builds/trunk/jenkins-dsl/agent_scripts/"
|
||||||
|
if (isCanonical()) {
|
||||||
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,13 +587,59 @@ 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
|
||||||
|
sh label: "log build usage...", script: """
|
||||||
|
{ set +x; } 2>/dev/null
|
||||||
|
du -sh ${WORKSPACE}/build/m2 ${WORKSPACE}/build/tmp ${WORKSPACE}/build/test ${WORKSPACE}/build 2>/dev/null || true
|
||||||
|
df -h / || true
|
||||||
|
"""
|
||||||
|
if (isCanonical()) {
|
||||||
|
sh label: "running agent_report.sh for disk usage stats (and more)...", script: """#!/bin/bash
|
||||||
set +e -o pipefail
|
set +e -o pipefail
|
||||||
wget -q ${agentScriptsUrl}/agent_report.sh
|
wget -q ${agentScriptsUrl}/agent_report.sh
|
||||||
bash -x agent_report.sh | tee -a \$(date +"%Y%m%d%H%M")-disk-usage-stats.txt
|
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/")
|
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"
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
|
||||||
/////////////////////////////////////////
|
/////////////////////////////////////////
|
||||||
////// scripting support for summary ////
|
////// scripting support for summary ////
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
18
CHANGES.txt
18
CHANGES.txt
|
|
@ -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)
|
||||||
* Don’t 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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
||||||
|
|
|
||||||
62
build.xml
62
build.xml
|
|
@ -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"/>
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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");}
|
||||||
;
|
;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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));
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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.");
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -79,38 +104,43 @@ public class IndexInputReader extends IndexInput
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close()
|
public void close()
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
input.close();
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
{
|
||||||
doOnClose.run();
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
{
|
{
|
||||||
|
// 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);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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,26 +142,31 @@ 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)
|
||||||
{
|
{
|
||||||
analyzerClass = Class.forName(indexOptions.get(INDEX_ANALYZER_CLASS_OPTION));
|
try
|
||||||
|
{
|
||||||
|
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));
|
||||||
}
|
}
|
||||||
|
catch (ConfigurationException e)
|
||||||
|
{
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
else if (indexOptions.get(INDEX_ANALYZED_OPTION) != null)
|
else if (indexOptions.get(INDEX_ANALYZED_OPTION) != null)
|
||||||
{
|
{
|
||||||
isAnalyzed = Boolean.parseBoolean(indexOptions.get(INDEX_ANALYZED_OPTION));
|
isAnalyzed = Boolean.parseBoolean(indexOptions.get(INDEX_ANALYZED_OPTION));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
catch (ClassNotFoundException 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));
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean isLiteral = false;
|
boolean isLiteral = false;
|
||||||
try
|
try
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
freeMetricIdSetTracker.markAsFree(metricId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
static class FreeMetricIdSetTracker
|
||||||
|
{
|
||||||
|
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
|
// 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
|
// 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
|
// 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
|
// even if it is not formally guaranteed by the JMM
|
||||||
ScheduledExecutors.scheduledTasks.schedule(() -> {
|
delayedToFreeMetricIdSet.set(metricId);
|
||||||
synchronized (freeMetricIdSetGuard)
|
scheduleCleanupTask();
|
||||||
{
|
}
|
||||||
freeMetricIdSet.set(metricId);
|
|
||||||
|
// must be called while holding this monitor (from a synchronized method)
|
||||||
|
@VisibleForTesting
|
||||||
|
protected void scheduleCleanupTask()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (cleanupTask == null)
|
||||||
|
cleanupTask = ScheduledExecutors.scheduledTasks.schedule(this::triggerRecycling, 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") +
|
||||||
|
'}';
|
||||||
}
|
}
|
||||||
}, 5, TimeUnit.SECONDS);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
if (e.getCause() instanceof ClassNotFoundException || e.getCause() instanceof NoClassDefFoundError)
|
||||||
throw new ConfigurationException("Could not create Compression for type " + className, e);
|
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)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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())
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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
Loading…
Reference in New Issue