mirror of https://github.com/apache/cassandra
Compare commits
73 Commits
df1756aae2
...
3e38c44fd0
| Author | SHA1 | Date |
|---|---|---|
|
|
3e38c44fd0 | |
|
|
e28556f71c | |
|
|
10557d7ffe | |
|
|
8fd77ffea3 | |
|
|
e0e4feb120 | |
|
|
1e3d43b2dd | |
|
|
bcd9e662d2 | |
|
|
feef3fcf51 | |
|
|
5d1830775d | |
|
|
aa1447571b | |
|
|
565c366ee1 | |
|
|
725c61c1f7 | |
|
|
bdbdf8d710 | |
|
|
6a5bb352af | |
|
|
209ef2b6c4 | |
|
|
63c0072e0c | |
|
|
e874e3a90b | |
|
|
d411c9a2c7 | |
|
|
f6fa474948 | |
|
|
b5f2a54210 | |
|
|
74bb6b5eca | |
|
|
4c1d57e6f3 | |
|
|
5e1d691818 | |
|
|
2ddb92091c | |
|
|
dba6b7df80 | |
|
|
f8b887e4df | |
|
|
d7da71a88a | |
|
|
bde65f163e | |
|
|
172099f4a8 | |
|
|
57dabaea8c | |
|
|
247289d7b6 | |
|
|
ecc0a3e77b | |
|
|
2507eceb29 | |
|
|
251b0e9b91 | |
|
|
4412926e4f | |
|
|
3cae8aa6f6 | |
|
|
5bf01cc7c0 | |
|
|
c4c3048b13 | |
|
|
af202bddc2 | |
|
|
8fc52f5d2f | |
|
|
a3ec88632d | |
|
|
f7f52421c7 | |
|
|
79c8669b84 | |
|
|
9ddfe0fb22 | |
|
|
b3acdfda08 | |
|
|
ddfdf5d69f | |
|
|
3bee9024c2 | |
|
|
03304bce53 | |
|
|
db0871ce8b | |
|
|
3fad760416 | |
|
|
f51c29c196 | |
|
|
7666b19ac6 | |
|
|
564d30ad8e | |
|
|
0840eb35af | |
|
|
0040482e26 | |
|
|
ca42cfe68d | |
|
|
e805b8aa32 | |
|
|
4066139d3f | |
|
|
57cfd6b123 | |
|
|
fb46c8126f | |
|
|
1920fffad3 | |
|
|
2293c58805 | |
|
|
28ec2730bd | |
|
|
87df717392 | |
|
|
e9cab5ebb1 | |
|
|
d75d603281 | |
|
|
15a6bac06d | |
|
|
bd345213c1 | |
|
|
1c702382de | |
|
|
26d7b166ba | |
|
|
f989d96b4b | |
|
|
27cc24bc79 | |
|
|
4bd98de6ee |
|
|
@ -78,7 +78,7 @@
|
|||
<exclude name="test/data/jmxdump/cassandra-*-jmx.yaml"/>
|
||||
<!-- Documentation files -->
|
||||
<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="src/java/**/*.md"/>
|
||||
<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
|
||||
|
||||
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:
|
||||
`python .build/run-ci.d/run-ci-test.py`
|
||||
|
|
@ -38,6 +38,7 @@ import gzip
|
|||
import itertools
|
||||
import os
|
||||
import shutil
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
import tarfile
|
||||
|
|
@ -50,9 +51,10 @@ from urllib.request import urlretrieve
|
|||
from typing import Optional, Tuple
|
||||
|
||||
# External Libraries (`pip install -r .build/run-ci.d/requirements.txt`)
|
||||
import requests
|
||||
import yaml
|
||||
from bs4 import BeautifulSoup
|
||||
from kubernetes import client, config, stream
|
||||
import requests
|
||||
|
||||
try:
|
||||
import jenkins
|
||||
|
|
@ -72,9 +74,9 @@ def base_job_name(args) -> str:
|
|||
"""
|
||||
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"
|
||||
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`)")
|
||||
response = requests.get(raw_url)
|
||||
response = requests.get(raw_url, timeout=30)
|
||||
response.raise_for_status()
|
||||
for line in response.text.splitlines():
|
||||
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
|
||||
base_job_name(args)
|
||||
# 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
|
||||
unpushed_commits = bool(subprocess.run(["git", "-C", str(CASSANDRA_DIR), "log", "@{u}..HEAD", "--name-only"],
|
||||
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("-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("-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("--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.")
|
||||
|
|
@ -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.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 (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"):
|
||||
args.url = os.environ.get("JENKINS_URL")
|
||||
|
|
@ -253,19 +258,113 @@ def run_kubectl_command(kubeconfig: Optional[str], kubecontext: Optional[str], k
|
|||
cmd += command
|
||||
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):
|
||||
"""Installs Jenkins Operator using Helm in the specified K8s namespace."""
|
||||
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)
|
||||
|
||||
def run_helm_command(kubeconfig: Optional[str], kubecontext: Optional[str], kube_ns: str, command: list,
|
||||
capture_output: bool = True, check: bool = True) -> subprocess.CompletedProcess:
|
||||
"""Runs a helm command with the specified kubeconfig, context and namespace."""
|
||||
cmd = ["helm"]
|
||||
if kubeconfig:
|
||||
cmd += ["--kubeconfig", kubeconfig]
|
||||
if kubecontext:
|
||||
cmd += ["--kube-context", kubecontext]
|
||||
cmd += ["--namespace", kube_ns, "upgrade", "--install", "-f", DEPLOY_YAML, "cassius", "jenkins/jenkins", "--wait"]
|
||||
result = subprocess.run(cmd, capture_output=True, check=True)
|
||||
cmd += ["--namespace", kube_ns]
|
||||
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,
|
||||
["exec", DEFAULT_POD_NAME, "--",
|
||||
|
|
@ -277,6 +376,19 @@ def install_jenkins(kubeconfig: Optional[str], kubecontext: Optional[str], kube_
|
|||
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]:
|
||||
"""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
|
||||
if tear_down:
|
||||
print("Cleaning up Jenkins and all resources.")
|
||||
cmd = ["helm"]
|
||||
if kubeconfig:
|
||||
cmd += ["--kubeconfig", kubeconfig]
|
||||
if kubecontext:
|
||||
cmd += ["--kube-context", kubecontext]
|
||||
cmd += ["--namespace", kube_ns, "uninstall", "cassius"]
|
||||
subprocess.run(cmd, check=True)
|
||||
run_helm_command(kubeconfig, kubecontext, kube_ns, ["uninstall", "cassius"], capture_output=False)
|
||||
# the pvc is annotated `helm.sh/resource-policy: keep`, see .jenkins/k8s/jenkins-deployment.yaml
|
||||
print(f"Jenkins uninstalled. The jenkins-home volume was kept, delete it with:\n"
|
||||
f" kubectl --namespace {kube_ns} delete pvc cassius-jenkins")
|
||||
|
||||
|
||||
@contextmanager
|
||||
|
|
@ -826,10 +935,11 @@ def main():
|
|||
if args.setup or args.only_setup:
|
||||
init_k8s_namespace(k8s_client, DEFAULT_KUBE_NS)
|
||||
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)
|
||||
if args.setup or args.only_setup:
|
||||
wait_for_jenkins_http(ip)
|
||||
ensure_cassandra_job_parameters_visible(server)
|
||||
if args.only_setup:
|
||||
return
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
```
|
||||
➤ .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]
|
||||
[-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.
|
||||
|
||||
|
|
@ -30,6 +30,8 @@ options:
|
|||
DTest repository branch.
|
||||
-s, --setup Set up Jenkins before the build.
|
||||
--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.
|
||||
--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.
|
||||
|
|
@ -63,7 +65,15 @@ Setup/Update Jenkins Helm into your current kubeconfig
|
|||
.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
|
||||
```
|
||||
```
|
||||
The jenkins-home volume is kept; delete it separately with `kubectl delete pvc cassius-jenkins`
|
||||
|
|
@ -18,6 +18,7 @@ bs4
|
|||
dotenv
|
||||
kubernetes
|
||||
python-jenkins
|
||||
pyyaml
|
||||
requests
|
||||
|
||||
# optional for different clouds
|
||||
|
|
|
|||
|
|
@ -27,12 +27,14 @@
|
|||
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
|
||||
# Import the functions from the script
|
||||
from run_ci import (
|
||||
DEPLOY_YAML,
|
||||
debug,
|
||||
install_jenkins,
|
||||
get_jenkins,
|
||||
|
|
@ -55,13 +57,84 @@ class TestCIPipeline(unittest.TestCase):
|
|||
debug("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')
|
||||
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")
|
||||
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)
|
||||
|
||||
@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.jenkins.Jenkins')
|
||||
def test_get_jenkins(self, mock_jenkins, mock_run):
|
||||
|
|
@ -91,13 +164,14 @@ class TestCIPipeline(unittest.TestCase):
|
|||
@patch('run_ci.stream.stream')
|
||||
def test_delete_remote_junit_files(self, mock_stream):
|
||||
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()
|
||||
|
||||
@patch('run_ci.subprocess.run')
|
||||
def test_cleanup_and_maybe_teardown(self, mock_run):
|
||||
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')
|
||||
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}") {
|
||||
try {
|
||||
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; }
|
||||
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 ("jar" == cell.step) {
|
||||
stash name: "${cell.arch}_${cell.jdk}"
|
||||
_stash(cell)
|
||||
}
|
||||
} catch (exc) {
|
||||
if ("org.jenkinsci.plugins.workflow.steps.FlowInterruptedException" == exc.getClass().getName()) {
|
||||
|
|
@ -438,8 +438,8 @@ def test(command, cell) {
|
|||
def descriptions = []
|
||||
for (def cause in exc.getCauses()) {
|
||||
echo "CauseOfInterruption: ${cause.getClass().getName()} - ${cause.getShortDescription()}"
|
||||
if (cause.getClass().getName().contains('CauseOfInterruption$UserInterruption')) {
|
||||
throw exc // user explicitly aborted — do not retry
|
||||
if (cause.getClass().getName().contains('CauseOfInterruption$UserInterruption') || cause.getClass().getName().contains('ParallelStep$FailFastCause')) {
|
||||
throw exc // user abort or fail-fast — do not retry
|
||||
}
|
||||
descriptions.add(cause.getShortDescription())
|
||||
}
|
||||
|
|
@ -453,24 +453,12 @@ def test(command, cell) {
|
|||
}
|
||||
}
|
||||
dir("build") {
|
||||
sh """
|
||||
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 ';'
|
||||
"""
|
||||
organiseTestResultFiles(cell, cell_suffix)
|
||||
if (!cell.step.startsWith("microbench")) {
|
||||
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
|
||||
sh """
|
||||
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
|
||||
"""
|
||||
debugOomKiller()
|
||||
compressTestResultFiles()
|
||||
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("/", "_"))
|
||||
}
|
||||
|
|
@ -517,7 +505,7 @@ def fetchDockerImages(dockerfiles) {
|
|||
// prefetch, from apache jfrog, reduces risking dockerhub pull rate limits
|
||||
// also prefetch alpine:latest as its used as a utility in the scripts
|
||||
def dockerfilesVar = dockerfiles.join(' ')
|
||||
sh """#!/bin/bash
|
||||
sh label: "fetching docker images...", script: """#!/bin/bash
|
||||
for dockerfile in ${dockerfilesVar} ; do
|
||||
image_tag="\$(md5sum .build/docker/\${dockerfile}.docker | cut -d' ' -f1)"
|
||||
image_name="apache/cassandra-\${dockerfile}:\${image_tag}"
|
||||
|
|
@ -527,6 +515,14 @@ def fetchDockerImages(dockerfiles) {
|
|||
done
|
||||
docker pull -q apache.jfrog.io/cassan-docker/alpine:3.19.1 &
|
||||
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) {
|
||||
// get any public IP which is more helpful correlating back to the cloud instance
|
||||
sh script: 'hostname; curl -sm 10 ifconfig.me', returnStatus: true
|
||||
def agentScriptsUrl = "https://raw.githubusercontent.com/apache/cassandra-builds/trunk/jenkins-dsl/agent_scripts/"
|
||||
if (isCanonical()) {
|
||||
def agentScriptsUrl = "https://raw.githubusercontent.com/apache/cassandra-builds/trunk/jenkins-dsl/agent_scripts/"
|
||||
cleanAgentDocker(job_name, agentScriptsUrl)
|
||||
logAgentInfo(job_name, agentScriptsUrl)
|
||||
}
|
||||
logAgentInfo(job_name, agentScriptsUrl)
|
||||
cleanWs()
|
||||
if (isCanonical()) {
|
||||
// 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
|
||||
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) {
|
||||
// we don't expect any build to have been running for longer than maxBuildHours
|
||||
def maxBuildHours = 12
|
||||
echo "Pruning docker for '${job_name}' on ${NODE_NAME}…" ;
|
||||
sh """#!/bin/bash
|
||||
sh label: "Pruning docker for '${job_name}' on ${NODE_NAME}...", script: """#!/bin/bash
|
||||
set +e
|
||||
wget -q ${agentScriptsUrl}/docker_image_pruner.py
|
||||
wget -q ${agentScriptsUrl}/docker_agent_cleaner.sh
|
||||
|
|
@ -592,12 +587,58 @@ def cleanAgentDocker(job_name, agentScriptsUrl) {
|
|||
}
|
||||
|
||||
def logAgentInfo(job_name, agentScriptsUrl) {
|
||||
sh """#!/bin/bash
|
||||
set +e -o pipefail
|
||||
wget -q ${agentScriptsUrl}/agent_report.sh
|
||||
bash -x agent_report.sh | tee -a \$(date +"%Y%m%d%H%M")-disk-usage-stats.txt
|
||||
"""
|
||||
copyToNightlies("*-disk-usage-stats.txt", "cassandra/ci-cassandra.apache.org/agents/${NODE_NAME}/disk-usage/")
|
||||
// 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
|
||||
wget -q ${agentScriptsUrl}/agent_report.sh
|
||||
bash -x agent_report.sh | tee -a \$(date +"%Y%m%d%H%M")-disk-usage-stats.txt
|
||||
"""
|
||||
copyToNightlies("*-disk-usage-stats.txt", "cassandra/ci-cassandra.apache.org/agents/${NODE_NAME}/disk-usage/")
|
||||
}
|
||||
}
|
||||
|
||||
def _stash(cell) {
|
||||
sh label: "check stash size...", script: """
|
||||
{ set +x; } 2>/dev/null
|
||||
free_gib=\$(df --output=avail -B 1073741824 / | tail -1 | tr -d ' ')
|
||||
stash_gb=\$(du -sb ${WORKSPACE} | awk '{printf "%.1f", \$1/1024/1024/1024}')
|
||||
echo "stash size: \${stash_gb}G (node free: \${free_gib}Gi)"
|
||||
if [ "\${free_gib}" -le 10 ] ; then
|
||||
echo "WARNING: only \${free_gib}Gi free on the node after building stash (\${stash_gb}G) — review jnlp's resourceLimitEphemeralStorage in .jenkins/k8s/jenkins-deployment.yaml"
|
||||
fi
|
||||
"""
|
||||
stash name: "${cell.arch}_${cell.jdk}"
|
||||
}
|
||||
|
||||
def organiseTestResultFiles(cell, cell_suffix) {
|
||||
sh label: "organise test result files...", script: """
|
||||
mkdir -p test/output/${cell.step}
|
||||
find test/output -type f -name "TEST*.xml" -execdir mkdir -p jdk_${cell.jdk}/${cell.arch} ';' -execdir mv {} jdk_${cell.jdk}/${cell.arch}/{} ';'
|
||||
find test/output -name cqlshlib.xml -execdir mv cqlshlib.xml ${cell.step}/cqlshlib${cell_suffix}.xml ';'
|
||||
find test/output -name nosetests.xml -execdir mv nosetests.xml ${cell.step}/nosetests${cell_suffix}.xml ';'
|
||||
"""
|
||||
}
|
||||
|
||||
def debugOomKiller() {
|
||||
// check if we had Linux OOM killer active within the test container which could kill forked JUnit JVM processes
|
||||
sh label: "checking for oom kills...", script: """
|
||||
# docker memory/oomkiller debug:
|
||||
cat /sys/fs/cgroup/docker/memory.events || true
|
||||
"""
|
||||
}
|
||||
|
||||
def compressTestResultFiles() {
|
||||
sh label: "compress test result files...", script: """
|
||||
{ set +x; } 2>/dev/null
|
||||
find test/output -type f -name "*.xml" -print0 | xargs -0 -r -n1 -P"\$(nproc)" xz -f
|
||||
echo "\$(find test/output -type f -name "*.xml.xz" | wc -l) test result files compressed"
|
||||
"""
|
||||
}
|
||||
|
||||
/////////////////////////////////////////
|
||||
|
|
@ -614,7 +655,7 @@ def generateTestReports() {
|
|||
def script_vars = "#!/bin/bash -x \n "
|
||||
if (isCanonical()) {
|
||||
// copyArtifacts takes >4hrs, hack with manual download
|
||||
sh """${script_vars}
|
||||
sh label: "manual download (instead of copyArtifacts)...", script: """${script_vars}
|
||||
( mkdir -p build/test
|
||||
wget -q ${BUILD_URL}/artifact/test/output/*zip*/output.zip
|
||||
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
|
||||
// TODO parallelised for loop
|
||||
// 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
|
||||
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}
|
||||
|
||||
# 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
|
||||
# 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
|
||||
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
|
||||
# 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.
|
||||
|
||||
### 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.
|
||||
|
||||
Comment these lines before running `heml upgrade …`
|
||||
Comment these lines before running `helm upgrade …`
|
||||
```
|
||||
# serviceType: LoadBalancer
|
||||
# ingress:
|
||||
# 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
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,11 @@
|
|||
persistence:
|
||||
enabled: true
|
||||
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"
|
||||
controller:
|
||||
# To get URL run `kubectl describe svc cassius-jenkins | grep 'LoadBalancer Ingress'`
|
||||
|
|
@ -31,7 +35,7 @@ controller:
|
|||
targetPort: 8080
|
||||
ingress:
|
||||
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>
|
||||
customJenkinsLabels:
|
||||
- controller
|
||||
|
|
@ -69,13 +73,14 @@ controller:
|
|||
- "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 putAt java.util.List java.util.List java.lang.Object"
|
||||
- "field hudson.plugins.git.GitSCMBackwardCompatibility branch"
|
||||
- "method org.jenkinsci.plugins.workflow.steps.FlowInterruptedException getCauses"
|
||||
JCasC:
|
||||
configScripts:
|
||||
welcome-message: |
|
||||
jenkins:
|
||||
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
|
||||
# see the CAUTION warning in .jenkins/Jenkinsfile
|
||||
# TODO: add new version each release branching
|
||||
|
|
@ -154,6 +159,23 @@ agent:
|
|||
node-selector:
|
||||
cassandra.jenkins.agent: true
|
||||
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:
|
||||
agent-dind-small: |
|
||||
- name: agent-dind-small
|
||||
|
|
@ -201,6 +223,9 @@ agent:
|
|||
resourceLimitCpu: 2
|
||||
resourceRequestMemory: 1G
|
||||
resourceLimitMemory: 1G
|
||||
# the workspace emptyDir
|
||||
resourceRequestEphemeralStorage: 10Gi
|
||||
resourceLimitEphemeralStorage: 20Gi
|
||||
ttyEnabled: 'true'
|
||||
workingDir: /home/jenkins/agent
|
||||
- name: dind
|
||||
|
|
@ -213,7 +238,7 @@ agent:
|
|||
key: "DOCKER_IPTABLES_LEGACY"
|
||||
value: "1"
|
||||
image: docker:dind
|
||||
args: "--default-address-pool base=192.168.96.0/20,size=24" # overwrite docker subnet in case of overlapping
|
||||
args: "--default-address-pool base=192.168.96.0/20,size=24" # overwrite docker subnet in case of overlapping
|
||||
livenessProbe:
|
||||
failureThreshold: '0'
|
||||
initialDelaySeconds: '0'
|
||||
|
|
@ -225,12 +250,13 @@ agent:
|
|||
resourceLimitCpu: 4
|
||||
resourceRequestMemory: 1G
|
||||
resourceLimitMemory: 2400M
|
||||
# docker's images and containers, in the docker-storage emptyDir
|
||||
resourceRequestEphemeralStorage: 40Gi
|
||||
resourceLimitEphemeralStorage: 60Gi
|
||||
ttyEnabled: 'true'
|
||||
workingDir: /home/jenkins/agent
|
||||
volumes:
|
||||
- emptyDirVolume:
|
||||
memory: 'false'
|
||||
mountPath: /var/lib/docker
|
||||
# /var/lib/docker is not here but in `yaml:` below, the only place it can carry a sizeLimit
|
||||
- emptyDirVolume:
|
||||
memory: 'false'
|
||||
mountPath: /certs
|
||||
|
|
@ -247,6 +273,18 @@ agent:
|
|||
values:
|
||||
- "true"
|
||||
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: |
|
||||
- name: agent-dind-medium
|
||||
label: agent-dind cassandra-medium cassandra-amd64-medium
|
||||
|
|
@ -291,6 +329,9 @@ agent:
|
|||
resourceLimitCpu: 3
|
||||
resourceRequestMemory: 1G
|
||||
resourceLimitMemory: 2400M
|
||||
# the workspace emptyDir
|
||||
resourceRequestEphemeralStorage: 10Gi
|
||||
resourceLimitEphemeralStorage: 20Gi
|
||||
ttyEnabled: 'true'
|
||||
workingDir: /home/jenkins/agent
|
||||
- name: dind
|
||||
|
|
@ -315,12 +356,13 @@ agent:
|
|||
resourceLimitCpu: 4
|
||||
resourceRequestMemory: 3400M
|
||||
resourceLimitMemory: 5G
|
||||
# docker's images and containers, in the docker-storage emptyDir
|
||||
resourceRequestEphemeralStorage: 40Gi
|
||||
resourceLimitEphemeralStorage: 60Gi
|
||||
ttyEnabled: 'true'
|
||||
workingDir: /home/jenkins/agent
|
||||
volumes:
|
||||
- emptyDirVolume:
|
||||
memory: 'false'
|
||||
mountPath: /var/lib/docker
|
||||
# /var/lib/docker is not here but in `yaml:` below, the only place it can carry a sizeLimit
|
||||
- emptyDirVolume:
|
||||
memory: 'false'
|
||||
mountPath: /certs
|
||||
|
|
@ -337,6 +379,18 @@ agent:
|
|||
values:
|
||||
- "true"
|
||||
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: |
|
||||
- name: agent-dind-large
|
||||
label: agent-dind cassandra-large cassandra-amd64-large cassandra-amd64-large-dedicated
|
||||
|
|
@ -381,6 +435,9 @@ agent:
|
|||
resourceLimitCpu: 3
|
||||
resourceRequestMemory: 1G
|
||||
resourceLimitMemory: 2G
|
||||
# the workspace emptyDir
|
||||
resourceRequestEphemeralStorage: 10Gi
|
||||
resourceLimitEphemeralStorage: 20Gi
|
||||
ttyEnabled: 'true'
|
||||
workingDir: /home/jenkins/agent
|
||||
- name: dind
|
||||
|
|
@ -405,12 +462,13 @@ agent:
|
|||
resourceLimitCpu: 7
|
||||
resourceRequestMemory: 16G
|
||||
resourceLimitMemory: 30G
|
||||
# docker's images and containers, in the docker-storage emptyDir
|
||||
resourceRequestEphemeralStorage: 40Gi
|
||||
resourceLimitEphemeralStorage: 60Gi
|
||||
ttyEnabled: 'true'
|
||||
workingDir: /home/jenkins/agent
|
||||
volumes:
|
||||
- emptyDirVolume:
|
||||
memory: 'false'
|
||||
mountPath: /var/lib/docker
|
||||
# /var/lib/docker is not here but in `yaml:` below, the only place it can carry a sizeLimit
|
||||
- emptyDirVolume:
|
||||
memory: 'false'
|
||||
mountPath: /certs
|
||||
|
|
@ -427,5 +485,17 @@ agent:
|
|||
values:
|
||||
- "true"
|
||||
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
|
||||
* 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)
|
||||
* Avoid using ObjectUtils.getFirstNonNull in Schema (CASSANDRA-21394)
|
||||
* Allow nodetool garbagecollect to take a user defined list of SSTables (CASSANDRA-16767)
|
||||
* Add a guardrail for misprepared statements (CASSANDRA-21139)
|
||||
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)
|
||||
* Expose immediately-executed tasks in the queries virtual table (CASSANDRA-21471)
|
||||
* 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)
|
||||
* Fix ReadCommand serializedSize() using incorrect epoch (CASSANDRA-21438)
|
||||
* 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)
|
||||
* Don't clear prepared statement cache on nodetool cms initialize (CASSANDRA-21234)
|
||||
* 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)
|
||||
* Fix failing select on system_views.settings for non-string keys (CASSANDRA-21348)
|
||||
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)
|
||||
* 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)
|
||||
|
|
@ -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)
|
||||
* Rework ZSTD dictionary compression logic to create a trainer per training (CASSANDRA-21209)
|
||||
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)
|
||||
Merged from 4.1:
|
||||
* 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
|
||||
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
|
||||
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:
|
||||
|
||||
|
|
|
|||
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}/" />
|
||||
</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 -->
|
||||
<target name="_createVersionPropFile" depends="_get-git-sha,set-cqlsh-version,_set-build-date">
|
||||
<taskdef name="propertyfile" classname="org.apache.tools.ant.taskdefs.optional.PropertyFile"/>
|
||||
|
|
@ -710,7 +768,7 @@
|
|||
</javac>
|
||||
</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">
|
||||
<echo message="${ant.project.name}: ${ant.file}"/>
|
||||
<!-- Order matters! -->
|
||||
|
|
@ -2173,7 +2231,7 @@
|
|||
</target>
|
||||
|
||||
<!-- 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 file="${eclipse.project.name}.iml"/>
|
||||
<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
|
||||
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>`
|
||||
|
||||
The directory location of the `cassandra.yaml file`. The default
|
||||
|
|
|
|||
|
|
@ -265,11 +265,11 @@ management, caching, and training behavior.
|
|||
|
||||
=== 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
|
||||
cluster-wide. Newly trained dictionaries will be picked up by all nodes
|
||||
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
|
||||
startup.
|
||||
|
||||
|
|
@ -278,7 +278,7 @@ startup.
|
|||
* `compression_dictionary_cache_size` (default: `10`): Maximum number of
|
||||
compression dictionaries to cache per table. Higher values reduce lookup
|
||||
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
|
||||
next access.
|
||||
|
||||
|
|
@ -289,10 +289,10 @@ Example configuration:
|
|||
[source,yaml]
|
||||
----
|
||||
# Dictionary refresh and caching
|
||||
compression_dictionary_refresh_interval: 3600
|
||||
compression_dictionary_refresh_initial_delay: 10
|
||||
compression_dictionary_refresh_interval: 3600s
|
||||
compression_dictionary_refresh_initial_delay: 10s
|
||||
compression_dictionary_cache_size: 10
|
||||
compression_dictionary_cache_expire: 3600
|
||||
compression_dictionary_cache_expire: 24h
|
||||
----
|
||||
|
||||
=== CQL training parameters:
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit c12dd6f8767dccd0c7cd3480f938845c9e24cb94
|
||||
Subproject commit e2352bc34edaf9e8662068ba163519c7ba2a50fb
|
||||
|
|
@ -2363,12 +2363,14 @@ vector_type returns [CQL3Type.Raw vt]
|
|||
username
|
||||
: IDENT
|
||||
| STRING_LITERAL
|
||||
| unreserved_keyword
|
||||
| QUOTED_NAME { addRecognitionError("Quoted strings are are not supported for user names and USER is deprecated, please use ROLE");}
|
||||
;
|
||||
|
||||
identity
|
||||
: IDENT
|
||||
| STRING_LITERAL
|
||||
| unreserved_keyword
|
||||
| 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 */
|
||||
|
||||
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
|
||||
// work with PasswordAuthenticator, so log a message if some other authenticator
|
||||
|
|
@ -82,7 +82,7 @@ public final class AuthConfig
|
|||
|
||||
// authorizer
|
||||
|
||||
IAuthorizer authorizer = authInstantiate(conf.authorizer, AllowAllAuthorizer.class);
|
||||
IAuthorizer authorizer = authInstantiate(conf.authorizer, IAuthorizer.class, AllowAllAuthorizer.class);
|
||||
|
||||
if (!authenticator.requireAuthentication() && authorizer.requireAuthorization())
|
||||
{
|
||||
|
|
@ -94,7 +94,7 @@ public final class AuthConfig
|
|||
|
||||
// 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))
|
||||
throw new ConfigurationException(authenticator.getClass().getName() + " requires " + CassandraRoleManager.class.getName(), false);
|
||||
|
|
@ -104,12 +104,15 @@ public final class AuthConfig
|
|||
// authenticator
|
||||
|
||||
IInternodeAuthenticator internodeAuthenticator = authInstantiate(conf.internode_authenticator,
|
||||
IInternodeAuthenticator.class,
|
||||
AllowAllInternodeAuthenticator.class);
|
||||
DatabaseDescriptor.setInternodeAuthenticator(internodeAuthenticator);
|
||||
|
||||
// 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())
|
||||
{
|
||||
|
|
@ -120,7 +123,9 @@ public final class AuthConfig
|
|||
|
||||
// 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())
|
||||
{
|
||||
|
|
@ -140,11 +145,11 @@ public final class AuthConfig
|
|||
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)
|
||||
{
|
||||
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
|
||||
|
|
|
|||
|
|
@ -101,7 +101,8 @@ public class MutualTlsAuthenticator implements IAuthenticator
|
|||
throw new ConfigurationException(message);
|
||||
}
|
||||
certificateValidator = ParameterizedClass.newInstance(new ParameterizedClass(certificateValidatorClassName),
|
||||
Arrays.asList("", AuthConfig.class.getPackage().getName()));
|
||||
Arrays.asList("", AuthConfig.class.getPackage().getName()),
|
||||
MutualTlsCertificateValidator.class);
|
||||
|
||||
Config config = DatabaseDescriptor.getRawConfig();
|
||||
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),
|
||||
Arrays.asList("", AuthConfig.class.getPackage().getName()));
|
||||
Arrays.asList("", AuthConfig.class.getPackage().getName()),
|
||||
MutualTlsCertificateValidator.class);
|
||||
Config config = DatabaseDescriptor.getRawConfig();
|
||||
|
||||
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.PaxosStatePurging;
|
||||
import org.apache.cassandra.config.DurationSpec.IntMillisecondsBound;
|
||||
import org.apache.cassandra.cql3.QueryOptions;
|
||||
import org.apache.cassandra.db.ConsistencyLevel;
|
||||
import org.apache.cassandra.db.commitlog.AbstractCommitLogSegmentManager;
|
||||
import org.apache.cassandra.db.commitlog.CommitLog;
|
||||
|
|
@ -273,6 +274,8 @@ public class DatabaseDescriptor
|
|||
|
||||
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
|
||||
* 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()
|
||||
{
|
||||
sstableFormats = null;
|
||||
defaultReadThresholds = null;
|
||||
clearMBean("org.apache.cassandra.db:type=DynamicEndpointSnitch");
|
||||
clearMBean("org.apache.cassandra.db:type=EndpointSnitchInfo");
|
||||
clearMBean("org.apache.cassandra.db:type=LocationInfo");
|
||||
|
|
@ -515,7 +519,7 @@ public class DatabaseDescriptor
|
|||
String loaderClass = CONFIG_LOADER.getString();
|
||||
ConfigurationLoader loader = loaderClass == null
|
||||
? new YamlConfigurationLoader()
|
||||
: FBUtilities.construct(loaderClass, "configuration loading");
|
||||
: FBUtilities.construct(loaderClass, "configuration loading", ConfigurationLoader.class);
|
||||
Config config = loader.loadConfig();
|
||||
|
||||
if (!hasLoggedConfig)
|
||||
|
|
@ -1655,7 +1659,8 @@ public class DatabaseDescriptor
|
|||
}
|
||||
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);
|
||||
}
|
||||
// there are about 5 checked exceptions that could be thrown here.
|
||||
|
|
@ -2032,7 +2037,7 @@ public class DatabaseDescriptor
|
|||
{
|
||||
if (!snitchClassName.contains("."))
|
||||
snitchClassName = "org.apache.cassandra.locator." + snitchClassName;
|
||||
IEndpointSnitch snitch = FBUtilities.construct(snitchClassName, "snitch");
|
||||
IEndpointSnitch snitch = FBUtilities.construct(snitchClassName, "snitch", IEndpointSnitch.class);
|
||||
return snitch;
|
||||
}
|
||||
|
||||
|
|
@ -2040,7 +2045,7 @@ public class DatabaseDescriptor
|
|||
{
|
||||
if (!className.contains("."))
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
@ -2048,7 +2053,7 @@ public class DatabaseDescriptor
|
|||
{
|
||||
if (!className.contains("."))
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
@ -2056,7 +2061,7 @@ public class DatabaseDescriptor
|
|||
{
|
||||
if (!className.contains("."))
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
@ -2064,7 +2069,7 @@ public class DatabaseDescriptor
|
|||
{
|
||||
if (!detectorClassName.contains("."))
|
||||
detectorClassName = "org.apache.cassandra.gms." + detectorClassName;
|
||||
IFailureDetector detector = FBUtilities.construct(detectorClassName, "failure detector");
|
||||
IFailureDetector detector = FBUtilities.construct(detectorClassName, "failure detector", IFailureDetector.class);
|
||||
return detector;
|
||||
}
|
||||
|
||||
|
|
@ -5570,6 +5575,14 @@ public class DatabaseDescriptor
|
|||
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()
|
||||
{
|
||||
return conf.read_thresholds_enabled;
|
||||
|
|
@ -5594,6 +5607,7 @@ public class DatabaseDescriptor
|
|||
{
|
||||
logger.info("updating coordinator_read_size_warn_threshold to {}", value);
|
||||
conf.coordinator_read_size_warn_threshold = value;
|
||||
defaultReadThresholds = null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
|
@ -5606,6 +5620,7 @@ public class DatabaseDescriptor
|
|||
{
|
||||
logger.info("updating coordinator_read_size_fail_threshold to {}", value);
|
||||
conf.coordinator_read_size_fail_threshold = value;
|
||||
defaultReadThresholds = null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
|
|
|||
|
|
@ -64,7 +64,17 @@ public class ParameterizedClass
|
|||
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)
|
||||
{
|
||||
return newInstance(parameterizedClass, searchPackages, null);
|
||||
}
|
||||
|
||||
static public <K> K newInstance(ParameterizedClass parameterizedClass, List<String> searchPackages, Class<K> expectedType)
|
||||
{
|
||||
Class<?> providerClass = null;
|
||||
if (searchPackages == null || searchPackages.isEmpty())
|
||||
|
|
@ -76,9 +86,12 @@ public class ParameterizedClass
|
|||
if (!searchPackage.isEmpty() && !searchPackage.endsWith("."))
|
||||
searchPackage = searchPackage + '.';
|
||||
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
|
||||
}
|
||||
|
|
@ -91,6 +104,13 @@ public class ParameterizedClass
|
|||
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
|
||||
{
|
||||
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 (!DatabaseDescriptor.isDaemonInitialized() || !DatabaseDescriptor.getReadThresholdsEnabled())
|
||||
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 abortThresholdBytes;
|
||||
|
|
|
|||
|
|
@ -96,12 +96,19 @@ public abstract class Selector
|
|||
SLICE_SELECTOR(ElementsSelector.SliceSelector.deserializer),
|
||||
VECTOR_SELECTOR(VectorSelector.deserializer);
|
||||
|
||||
private static final Kind[] VALUES = values();
|
||||
|
||||
private final SelectorDeserializer deserializer;
|
||||
|
||||
Kind(SelectorDeserializer 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
|
||||
{
|
||||
Kind kind = Kind.values()[in.readUnsignedByte()];
|
||||
Kind kind = Kind.fromOrdinal(in.readUnsignedByte());
|
||||
return kind.deserializer.deserialize(in, version, metadata);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -653,7 +653,7 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
|
|||
boolean forceMigrationChange = modeChange && explicitlySetMigrationFrom && next.transactionalMigrationFrom != newMigrateFrom;
|
||||
|
||||
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));
|
||||
|
||||
// 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
|
||||
{
|
||||
Kind kind = Kind.values()[in.readByte()];
|
||||
Kind kind = Kind.fromOrdinal(in.readByte());
|
||||
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);
|
||||
// @formatter:on
|
||||
|
||||
private static final Kind[] VALUES = values();
|
||||
|
||||
private final int comparison;
|
||||
|
||||
/**
|
||||
|
|
@ -101,6 +103,11 @@ public interface ClusteringPrefix<V> extends IMeasurableMemory, Clusterable<V>
|
|||
this.asByteComparable = asByteComparable;
|
||||
}
|
||||
|
||||
public static Kind fromOrdinal(int ordinal)
|
||||
{
|
||||
return VALUES[ordinal];
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the 2 provided kind.
|
||||
* <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
|
||||
{
|
||||
Kind kind = Kind.values()[in.readByte()];
|
||||
Kind kind = Kind.fromOrdinal(in.readByte());
|
||||
// We shouldn't serialize static clusterings
|
||||
assert kind != Kind.STATIC_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
|
||||
{
|
||||
Kind kind = Kind.values()[in.readByte()];
|
||||
Kind kind = Kind.fromOrdinal(in.readByte());
|
||||
// We shouldn't serialize static clusterings
|
||||
assert kind != Kind.STATIC_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);
|
||||
|
||||
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.deserializedSize = 0;
|
||||
|
||||
|
|
|
|||
|
|
@ -178,7 +178,8 @@ public abstract class DeletionTime implements Comparable<DeletionTime>, IMeasura
|
|||
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -198,6 +198,8 @@ public abstract class ReadCommand extends AbstractReadQuery
|
|||
SINGLE_PARTITION (SinglePartitionReadCommand.selectionDeserializer, SinglePartitionReadCommand.accordSelectionDeserializer),
|
||||
PARTITION_RANGE (PartitionRangeReadCommand.selectionDeserializer, ignore -> PartitionRangeReadCommand.selectionDeserializer);
|
||||
|
||||
private static final Kind[] VALUES = values();
|
||||
|
||||
private final SelectionDeserializer selectionDeserializer;
|
||||
private final Function<Seekable, SelectionDeserializer> accordSelectionDeserializer;
|
||||
|
||||
|
|
@ -206,6 +208,11 @@ public abstract class ReadCommand extends AbstractReadQuery
|
|||
this.selectionDeserializer = selectionDeserializer;
|
||||
this.accordSelectionDeserializer = accordSelectionDeserializer;
|
||||
}
|
||||
|
||||
public static Kind fromOrdinal(int ordinal)
|
||||
{
|
||||
return VALUES[ordinal];
|
||||
}
|
||||
}
|
||||
|
||||
protected ReadCommand(Epoch serializedAtEpoch,
|
||||
|
|
@ -1450,7 +1457,7 @@ public abstract class ReadCommand extends AbstractReadQuery
|
|||
|
||||
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();
|
||||
// Shouldn't happen or it's a user error (see comment above) but
|
||||
// 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
|
||||
{
|
||||
Kind kind = Kind.values()[in.readByte()];
|
||||
Kind kind = Kind.fromOrdinal(in.readByte());
|
||||
int flags = in.readByte();
|
||||
if (isDigest(flags) || isForThrift(flags) || acceptsTransient(flags))
|
||||
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();
|
||||
if (className != null)
|
||||
{
|
||||
return FBUtilities.construct(className, StorageHook.class.getSimpleName());
|
||||
return FBUtilities.construct(className, StorageHook.class.getSimpleName(), StorageHook.class);
|
||||
}
|
||||
|
||||
return new StorageHook()
|
||||
|
|
@ -89,4 +89,4 @@ public interface StorageHook
|
|||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,7 +65,14 @@ public abstract class AggregationSpecification
|
|||
*/
|
||||
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
|
||||
{
|
||||
Kind kind = Kind.values()[in.readUnsignedByte()];
|
||||
Kind kind = Kind.fromOrdinal(in.readUnsignedByte());
|
||||
switch (kind)
|
||||
{
|
||||
case AGGREGATE_EVERYTHING:
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ public abstract class AbstractClusteringIndexFilter implements ClusteringIndexFi
|
|||
|
||||
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();
|
||||
|
||||
return kind.deserializer.deserialize(in, version, metadata, reversed);
|
||||
|
|
|
|||
|
|
@ -46,12 +46,19 @@ public interface ClusteringIndexFilter
|
|||
SLICE (ClusteringIndexSliceFilter.deserializer),
|
||||
NAMES (ClusteringIndexNamesFilter.deserializer);
|
||||
|
||||
private static final Kind[] VALUES = values();
|
||||
|
||||
protected final InternalDeserializer deserializer;
|
||||
|
||||
private Kind(InternalDeserializer deserializer)
|
||||
{
|
||||
this.deserializer = deserializer;
|
||||
}
|
||||
|
||||
public static Kind fromOrdinal(int ordinal)
|
||||
{
|
||||
return VALUES[ordinal];
|
||||
}
|
||||
}
|
||||
|
||||
static interface InternalDeserializer
|
||||
|
|
|
|||
|
|
@ -44,7 +44,17 @@ public abstract class ColumnSubselection implements Comparable<ColumnSubselectio
|
|||
public static final Serializer serializer = new Serializer();
|
||||
|
||||
/* 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;
|
||||
|
||||
|
|
@ -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)
|
||||
{
|
||||
case SLICE:
|
||||
|
|
|
|||
|
|
@ -102,7 +102,14 @@ public abstract class DataLimits
|
|||
/** @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
|
||||
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)
|
||||
|
|
@ -1191,7 +1198,7 @@ public abstract class DataLimits
|
|||
|
||||
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)
|
||||
{
|
||||
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
|
||||
// (we could clean those on a major protocol update, but it's not worth
|
||||
// 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 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
|
||||
{
|
||||
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
|
||||
if (kind == Kind.CUSTOM)
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ public interface GuardrailsConfigProvider
|
|||
*/
|
||||
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
|
||||
{
|
||||
Class<? extends ValueGenerator> rawGeneratorClass =
|
||||
FBUtilities.classForNameWithoutInitialization(className, "generator", ValueGenerator.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<? extends ValueGenerator<VALUE>> generatorClass =
|
||||
FBUtilities.classForName(className, "generator");
|
||||
(Class<? extends ValueGenerator<VALUE>>) rawGeneratorClass;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
ValueGenerator<VALUE> generator = generatorClass.getConstructor(CustomGuardrailConfig.class)
|
||||
|
|
@ -165,4 +168,4 @@ public abstract class ValueGenerator<VALUE>
|
|||
className, message), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -127,8 +127,11 @@ public abstract class ValueValidator<VALUE>
|
|||
|
||||
try
|
||||
{
|
||||
Class<? extends ValueValidator> rawValidatorClass =
|
||||
FBUtilities.classForNameWithoutInitialization(className, "validator", ValueValidator.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<? extends ValueValidator<VALUE>> validatorClass =
|
||||
FBUtilities.classForName(className, "validator");
|
||||
(Class<? extends ValueValidator<VALUE>>) rawValidatorClass;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
ValueValidator<VALUE> validator = validatorClass.getConstructor(CustomGuardrailConfig.class)
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ public abstract class AbstractTimeUUIDType<T> extends TemporalType<T>
|
|||
{
|
||||
AbstractTimeUUIDType()
|
||||
{
|
||||
super(ComparisonType.CUSTOM);
|
||||
super(ComparisonType.CUSTOM, 16);
|
||||
} // singleton
|
||||
|
||||
@Override
|
||||
|
|
@ -193,12 +193,6 @@ public abstract class AbstractTimeUUIDType<T> extends TemporalType<T>
|
|||
return super.decomposeUntyped(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int valueLengthIfFixed()
|
||||
{
|
||||
return 16;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long toTimeInMillis(ByteBuffer value)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -90,11 +90,18 @@ public abstract class AbstractType<T> implements Comparator<ByteBuffer>, Assignm
|
|||
public final ComparisonType comparisonType;
|
||||
public final boolean isByteOrderComparable;
|
||||
public final ValueComparators comparatorSet;
|
||||
private final int valueLengthIfFixed;
|
||||
|
||||
protected AbstractType(ComparisonType comparisonType)
|
||||
{
|
||||
this(comparisonType, VARIABLE_LENGTH);
|
||||
}
|
||||
|
||||
protected AbstractType(ComparisonType comparisonType, int valueLengthIfFixed)
|
||||
{
|
||||
this.comparisonType = comparisonType;
|
||||
this.isByteOrderComparable = comparisonType == ComparisonType.BYTE_ORDER;
|
||||
this.valueLengthIfFixed = valueLengthIfFixed;
|
||||
reverseComparator = (o1, o2) -> AbstractType.this.compare(o2, o1);
|
||||
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.
|
||||
* 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)
|
||||
{
|
||||
return isValueCompatibleWith(previous)
|
||||
&& valueLengthIfFixed() == previous.valueLengthIfFixed()
|
||||
&& valueLengthIfFixed == previous.valueLengthIfFixed
|
||||
&& isMultiCell() == previous.isMultiCell();
|
||||
}
|
||||
|
||||
|
|
@ -498,7 +505,7 @@ public abstract class AbstractType<T> implements Comparator<ByteBuffer>, Assignm
|
|||
*/
|
||||
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()
|
||||
{
|
||||
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
|
||||
{
|
||||
assert !isNull(value, accessor) : "bytes should not be null for type " + this;
|
||||
int expectedValueLength = valueLengthIfFixed();
|
||||
int expectedValueLength = valueLengthIfFixed;
|
||||
if (expectedValueLength >= 0)
|
||||
{
|
||||
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
|
||||
{
|
||||
assert !valueHolder.isNull(i) : "bytes should not be null for type " + this;
|
||||
int expectedValueLength = valueLengthIfFixed();
|
||||
int expectedValueLength = valueLengthIfFixed;
|
||||
if (expectedValueLength >= 0)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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.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)
|
||||
{
|
||||
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
|
||||
: 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
|
||||
{
|
||||
int length = valueLengthIfFixed();
|
||||
int length = valueLengthIfFixed;
|
||||
|
||||
if (length >= 0)
|
||||
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
|
||||
{
|
||||
int length = valueLengthIfFixed();
|
||||
int length = valueLengthIfFixed;
|
||||
if (length >= 0)
|
||||
in.skipBytesFully(length);
|
||||
else
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ public class BooleanType extends AbstractType<Boolean>
|
|||
private static final ArgumentDeserializer ARGUMENT_DESERIALIZER = new DefaultArgumentDeserializer(instance);
|
||||
private static final ByteBuffer MASKED_VALUE = instance.decompose(false);
|
||||
|
||||
BooleanType() {super(ComparisonType.CUSTOM);} // singleton
|
||||
BooleanType() {super(ComparisonType.CUSTOM, 1);} // singleton
|
||||
|
||||
@Override
|
||||
public boolean allowsEmpty()
|
||||
|
|
@ -127,12 +127,6 @@ public class BooleanType extends AbstractType<Boolean>
|
|||
return ARGUMENT_DESERIALIZER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int valueLengthIfFixed()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
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 ByteBuffer MASKED_VALUE = instance.decompose(new Date(0));
|
||||
|
||||
DateType() {super(ComparisonType.BYTE_ORDER);} // singleton
|
||||
DateType() {super(ComparisonType.BYTE_ORDER, 8);} // singleton
|
||||
|
||||
public boolean isEmptyValueMeaningless()
|
||||
{
|
||||
|
|
@ -143,12 +143,6 @@ public class DateType extends AbstractType<Date>
|
|||
return ARGUMENT_DESERIALIZER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int valueLengthIfFixed()
|
||||
{
|
||||
return 8;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer getMaskedValue()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ public class DoubleType extends NumberType<Double>
|
|||
|
||||
private static final ByteBuffer MASKED_VALUE = instance.decompose(0d);
|
||||
|
||||
DoubleType() {super(ComparisonType.CUSTOM);} // singleton
|
||||
DoubleType() {super(ComparisonType.CUSTOM, 8);} // singleton
|
||||
|
||||
@Override
|
||||
public boolean allowsEmpty()
|
||||
|
|
@ -145,12 +145,6 @@ public class DoubleType extends NumberType<Double>
|
|||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public int valueLengthIfFixed()
|
||||
{
|
||||
return 8;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer add(Number left, Number right)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ public class EmptyType extends AbstractType<Void>
|
|||
|
||||
public static final EmptyType instance = new EmptyType();
|
||||
|
||||
private EmptyType() {super(ComparisonType.CUSTOM);} // singleton
|
||||
private EmptyType() {super(ComparisonType.CUSTOM, 0);} // singleton
|
||||
|
||||
@Override
|
||||
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();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int valueLengthIfFixed()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
|
||||
FloatType() {super(ComparisonType.CUSTOM);} // singleton
|
||||
FloatType() {super(ComparisonType.CUSTOM, 4);} // singleton
|
||||
|
||||
@Override
|
||||
public boolean allowsEmpty()
|
||||
|
|
@ -146,12 +146,6 @@ public class FloatType extends NumberType<Float>
|
|||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public int valueLengthIfFixed()
|
||||
{
|
||||
return 4;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer add(Number left, Number right)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ public class Int32Type extends NumberType<Integer>
|
|||
|
||||
Int32Type()
|
||||
{
|
||||
super(ComparisonType.CUSTOM);
|
||||
super(ComparisonType.CUSTOM, 4);
|
||||
} // singleton
|
||||
|
||||
@Override
|
||||
|
|
@ -152,12 +152,6 @@ public class Int32Type extends NumberType<Integer>
|
|||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public int valueLengthIfFixed()
|
||||
{
|
||||
return 4;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer add(Number left, Number right)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ public class LexicalUUIDType extends AbstractType<UUID>
|
|||
|
||||
LexicalUUIDType()
|
||||
{
|
||||
super(ComparisonType.CUSTOM);
|
||||
super(ComparisonType.CUSTOM, 16);
|
||||
} // singleton
|
||||
|
||||
@Override
|
||||
|
|
@ -148,12 +148,6 @@ public class LexicalUUIDType extends AbstractType<UUID>
|
|||
return ARGUMENT_DESERIALIZER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int valueLengthIfFixed()
|
||||
{
|
||||
return 16;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer getMaskedValue()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ public class LongType extends NumberType<Long>
|
|||
|
||||
private static final ByteBuffer MASKED_VALUE = instance.decompose(0L);
|
||||
|
||||
LongType() {super(ComparisonType.CUSTOM);} // singleton
|
||||
LongType() {super(ComparisonType.CUSTOM, 8);} // singleton
|
||||
|
||||
@Override
|
||||
public boolean allowsEmpty()
|
||||
|
|
@ -170,12 +170,6 @@ public class LongType extends NumberType<Long>
|
|||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public int valueLengthIfFixed()
|
||||
{
|
||||
return 8;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer add(Number left, Number right)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -38,6 +38,11 @@ public abstract class MultiElementType<T> extends AbstractType<T>
|
|||
super(comparisonType);
|
||||
}
|
||||
|
||||
protected MultiElementType(ComparisonType comparisonType, int valueLengthIfFixed)
|
||||
{
|
||||
super(comparisonType, valueLengthIfFixed);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -34,6 +34,11 @@ public abstract class NumberType<T extends Number> extends AbstractType<T>
|
|||
super(comparisonType);
|
||||
}
|
||||
|
||||
protected NumberType(ComparisonType comparisonType, int valueLengthIfFixed)
|
||||
{
|
||||
super(comparisonType, valueLengthIfFixed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this type support floating point numbers.
|
||||
* @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)
|
||||
{
|
||||
super(ComparisonType.CUSTOM);
|
||||
super(ComparisonType.CUSTOM, baseType.valueLengthIfFixed());
|
||||
this.baseType = baseType;
|
||||
}
|
||||
|
||||
|
|
@ -181,12 +181,6 @@ public class ReversedType<T> extends AbstractType<T>
|
|||
return getInstance(baseType.withUpdatedUserType(udt));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int valueLengthIfFixed()
|
||||
{
|
||||
return baseType.valueLengthIfFixed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReversed()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -37,6 +37,11 @@ public abstract class TemporalType<T> extends AbstractType<T>
|
|||
super(comparisonType);
|
||||
}
|
||||
|
||||
protected TemporalType(ComparisonType comparisonType, int valueLengthIfFixed)
|
||||
{
|
||||
super(comparisonType, valueLengthIfFixed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns 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 TimestampType() {super(ComparisonType.CUSTOM);} // singleton
|
||||
private TimestampType() {super(ComparisonType.CUSTOM, 8);} // singleton
|
||||
|
||||
@Override
|
||||
public boolean allowsEmpty()
|
||||
|
|
@ -166,12 +166,6 @@ public class TimestampType extends TemporalType<Date>
|
|||
return TimestampSerializer.instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int valueLengthIfFixed()
|
||||
{
|
||||
return 8;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void validateDuration(Duration duration)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -449,8 +449,7 @@ public class TypeParser
|
|||
|
||||
private static AbstractType<?> getAbstractType(String compareWith) throws ConfigurationException
|
||||
{
|
||||
String className = compareWith.contains(".") ? compareWith : "org.apache.cassandra.db.marshal." + compareWith;
|
||||
Class<? extends AbstractType<?>> typeClass = FBUtilities.<AbstractType<?>>classForName(className, "abstract-type");
|
||||
Class<? extends AbstractType<?>> typeClass = getAbstractTypeClass(compareWith);
|
||||
try
|
||||
{
|
||||
Field field = typeClass.getDeclaredField("instance");
|
||||
|
|
@ -465,8 +464,7 @@ public class TypeParser
|
|||
|
||||
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 = FBUtilities.<AbstractType<?>>classForName(className, "abstract-type");
|
||||
Class<? extends AbstractType<?>> typeClass = getAbstractTypeClass(compareWith);
|
||||
if (PseudoUtf8Type.class.isAssignableFrom(typeClass))
|
||||
{
|
||||
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
|
||||
{
|
||||
try
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ public class UUIDType extends AbstractType<UUID>
|
|||
|
||||
UUIDType()
|
||||
{
|
||||
super(ComparisonType.CUSTOM);
|
||||
super(ComparisonType.CUSTOM, 16);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -252,12 +252,6 @@ public class UUIDType extends AbstractType<UUID>
|
|||
return (uuid.get(6) & 0xf0) >> 4;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int valueLengthIfFixed()
|
||||
{
|
||||
return 16;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer getMaskedValue()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -82,20 +82,16 @@ public final class VectorType<T> extends MultiElementType<List<T>>
|
|||
public final AbstractType<T> elementType;
|
||||
public final int dimension;
|
||||
private final TypeSerializer<T> elementSerializer;
|
||||
private final int valueLengthIfFixed;
|
||||
private final VectorSerializer serializer;
|
||||
|
||||
private VectorType(AbstractType<T> elementType, int dimension)
|
||||
{
|
||||
super(ComparisonType.CUSTOM);
|
||||
super(ComparisonType.CUSTOM, valueLengthIfFixed(elementType, dimension));
|
||||
if (dimension <= 0)
|
||||
throw new InvalidRequestException(String.format("vectors may only have positive dimensions; given %d", dimension));
|
||||
this.elementType = elementType;
|
||||
this.dimension = dimension;
|
||||
this.elementSerializer = elementType.getSerializer();
|
||||
this.valueLengthIfFixed = elementType.isValueLengthFixed() ?
|
||||
elementType.valueLengthIfFixed() * dimension :
|
||||
super.valueLengthIfFixed();
|
||||
this.serializer = elementType.isValueLengthFixed() ?
|
||||
new FixedLengthSerializer() :
|
||||
new VariableLengthSerializer();
|
||||
|
|
@ -126,10 +122,10 @@ public final class VectorType<T> extends MultiElementType<List<T>>
|
|||
return getSerializer().compareCustom(left, accessorL, right, accessorR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int valueLengthIfFixed()
|
||||
private static int valueLengthIfFixed(AbstractType<?> elementType, int dimension)
|
||||
{
|
||||
return valueLengthIfFixed;
|
||||
int elementLength = elementType.valueLengthIfFixed();
|
||||
return elementLength >= 0 ? elementLength * dimension : elementLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -61,7 +61,18 @@ public abstract class AbstractCell<V> extends Cell<V>
|
|||
|
||||
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()
|
||||
|
|
|
|||
|
|
@ -31,17 +31,12 @@ import org.apache.cassandra.utils.memory.ByteBufferCloner;
|
|||
|
||||
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));
|
||||
|
||||
// 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 CellPath path;
|
||||
|
||||
// Please keep both int/long overloaded ctros public. Otherwise silent casts will mess timestamps when one is not
|
||||
// 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)
|
||||
{
|
||||
super(column);
|
||||
this.timestamp = timestamp;
|
||||
this.ttl = ttl;
|
||||
this.localDeletionTimeUnsignedInteger = localDeletionTimeUnsignedInteger;
|
||||
super(column, timestamp, ttl, localDeletionTimeUnsignedInteger, path);
|
||||
this.value = value;
|
||||
this.path = 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);
|
||||
}
|
||||
|
||||
public long timestamp()
|
||||
{
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public int ttl()
|
||||
{
|
||||
return ttl;
|
||||
}
|
||||
|
||||
public byte[] value()
|
||||
{
|
||||
return value;
|
||||
|
|
@ -91,10 +72,6 @@ public class ArrayCell extends AbstractCell<byte[]>
|
|||
return ByteArrayAccessor.instance;
|
||||
}
|
||||
|
||||
public CellPath path()
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int localDeletionTimeAsUnsignedInt()
|
||||
{
|
||||
return localDeletionTimeUnsignedInteger;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -172,7 +172,7 @@ public class BTreeRow extends AbstractRow
|
|||
|
||||
private static long minDeletionTime(Cell<?> cell)
|
||||
{
|
||||
return cell.isTombstone() ? Long.MIN_VALUE : cell.localDeletionTime();
|
||||
return cell.minDeletionTime();
|
||||
}
|
||||
|
||||
private static long minDeletionTime(LivenessInfo info)
|
||||
|
|
@ -439,6 +439,11 @@ public class BTreeRow extends AbstractRow
|
|||
return nowInSec >= minLocalDeletionTime;
|
||||
}
|
||||
|
||||
public long minLocalDeletionTime()
|
||||
{
|
||||
return minLocalDeletionTime;
|
||||
}
|
||||
|
||||
public boolean hasInvalidDeletions()
|
||||
{
|
||||
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;
|
||||
|
||||
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));
|
||||
|
||||
// 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 CellPath path;
|
||||
|
||||
// Please keep both int/long overloaded ctros public. Otherwise silent casts will mess timestamps when one is not
|
||||
// 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)
|
||||
{
|
||||
super(column);
|
||||
super(column, timestamp, ttl, localDeletionTimeUnsignedInteger, path);
|
||||
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);
|
||||
this.timestamp = timestamp;
|
||||
this.ttl = ttl;
|
||||
this.localDeletionTimeUnsignedInteger = localDeletionTimeUnsignedInteger;
|
||||
this.value = value;
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public long timestamp()
|
||||
{
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public int ttl()
|
||||
{
|
||||
return ttl;
|
||||
}
|
||||
|
||||
public ByteBuffer value()
|
||||
{
|
||||
return value;
|
||||
|
|
@ -112,10 +93,6 @@ public class BufferCell extends AbstractCell<ByteBuffer>
|
|||
return ByteBufferAccessor.instance;
|
||||
}
|
||||
|
||||
public CellPath path()
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int localDeletionTimeAsUnsignedInt()
|
||||
{
|
||||
return localDeletionTimeUnsignedInteger;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -150,6 +150,8 @@ public abstract class Cell<V> extends ColumnData
|
|||
return deletionTimeUnsignedIntegerToLong(localDeletionTimeAsUnsignedInt());
|
||||
}
|
||||
|
||||
public abstract long minDeletionTime();
|
||||
|
||||
/**
|
||||
* 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.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
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.utils.BiLongAccumulator;
|
||||
import org.apache.cassandra.utils.BulkIterator;
|
||||
import org.apache.cassandra.utils.ComplexCellMergeIterator;
|
||||
import org.apache.cassandra.utils.LongAccumulator;
|
||||
import org.apache.cassandra.utils.MergeIterator;
|
||||
import org.apache.cassandra.utils.ObjectSizes;
|
||||
import org.apache.cassandra.utils.RowMergeIterator;
|
||||
import org.apache.cassandra.utils.SearchIterator;
|
||||
import org.apache.cassandra.utils.btree.BTree;
|
||||
import org.apache.cassandra.utils.btree.UpdateFunction;
|
||||
|
|
@ -221,6 +221,15 @@ public interface Row extends Unfiltered, Iterable<ColumnData>, IMeasurableMemory
|
|||
*/
|
||||
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.
|
||||
*
|
||||
|
|
@ -762,6 +771,11 @@ public interface Row extends Unfiltered, Iterable<ColumnData>, IMeasurableMemory
|
|||
|
||||
LivenessInfo rowInfo = LivenessInfo.EMPTY;
|
||||
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)
|
||||
{
|
||||
if (row == null)
|
||||
|
|
@ -771,6 +785,11 @@ public interface Row extends Unfiltered, Iterable<ColumnData>, IMeasurableMemory
|
|||
rowInfo = row.primaryKeyLivenessInfo();
|
||||
if (row.deletion().supersedes(rowDeletion))
|
||||
rowDeletion = row.deletion();
|
||||
|
||||
minDeletionTime = Math.min(minDeletionTime, row.minLocalDeletionTime());
|
||||
|
||||
columnDataIterators.add(row.iterator());
|
||||
columnsCountEstimation = Math.max(columnsCountEstimation, row.columnCount());
|
||||
}
|
||||
|
||||
if (rowDeletion.isShadowedBy(rowInfo))
|
||||
|
|
@ -784,25 +803,12 @@ public interface Row extends Unfiltered, Iterable<ColumnData>, IMeasurableMemory
|
|||
if (activeDeletion.deletes(rowInfo))
|
||||
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
|
||||
if (dataBuffer.length < columnsCountEstimation)
|
||||
dataBuffer = new ColumnData[columnsCountEstimation];
|
||||
|
||||
columnDataReducer.setActiveDeletion(activeDeletion);
|
||||
Iterator<ColumnData> merged = MergeIterator.get(columnDataIterators, ColumnData.comparator, columnDataReducer);
|
||||
Iterator<ColumnData> merged = RowMergeIterator.get(columnDataIterators, ColumnData.comparator, columnDataReducer);
|
||||
while (merged.hasNext())
|
||||
{
|
||||
ColumnData data = merged.next();
|
||||
|
|
@ -819,8 +825,12 @@ public interface Row extends Unfiltered, Iterable<ColumnData>, IMeasurableMemory
|
|||
|
||||
try (BulkIterator<ColumnData> it = BulkIterator.of(dataBuffer))
|
||||
{
|
||||
return BTreeRow.create(clustering, rowInfo, rowDeletion,
|
||||
BTree.build(it, dataBufferSize, UpdateFunction.noOp()));
|
||||
Object[] tree = 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;
|
||||
}
|
||||
|
||||
private static class ColumnDataReducer extends MergeIterator.Reducer<ColumnData, ColumnData>
|
||||
private static class ColumnDataReducer extends RowMergeIterator.Reducer<ColumnData, ColumnData>
|
||||
{
|
||||
private ColumnMetadata column;
|
||||
private final List<ColumnData> versions;
|
||||
private final ColumnData[] versions;
|
||||
private int versionsSize;
|
||||
|
||||
private DeletionTime activeDeletion;
|
||||
|
||||
|
|
@ -854,7 +865,7 @@ public interface Row extends Unfiltered, Iterable<ColumnData>, IMeasurableMemory
|
|||
|
||||
public ColumnDataReducer(int size, boolean hasComplex)
|
||||
{
|
||||
this.versions = new ArrayList<>(size);
|
||||
this.versions = new ColumnData[size];
|
||||
this.complexBuilder = hasComplex ? ComplexColumnData.builder() : null;
|
||||
this.complexCells = hasComplex ? new ArrayList<>(size) : null;
|
||||
this.cellReducer = new CellReducer();
|
||||
|
|
@ -870,20 +881,24 @@ public interface Row extends Unfiltered, Iterable<ColumnData>, IMeasurableMemory
|
|||
if (useColumnMetadata(data.column()))
|
||||
column = data.column();
|
||||
|
||||
versions.add(data);
|
||||
versions[versionsSize++] = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines it the {@code ColumnMetadata} is the one that should be used.
|
||||
* @param dataColumn the {@code ColumnMetadata} to use.
|
||||
* @return {@code true} if the {@code ColumnMetadata} is the one that should be used, {@code false} otherwise.
|
||||
* Determines whether {@code dataColumn} should replace the currently selected column metadata,
|
||||
* i.e. whether no column has been selected yet or {@code dataColumn} is a newer version.
|
||||
* @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)
|
||||
{
|
||||
if (column == null)
|
||||
ColumnMetadata currentColumn = column;
|
||||
if (currentColumn == null)
|
||||
return true;
|
||||
if (currentColumn == dataColumn)
|
||||
return false;
|
||||
|
||||
return ColumnMetadataVersionComparator.INSTANCE.compare(column, dataColumn) < 0;
|
||||
return ColumnMetadataVersionComparator.INSTANCE.compare(currentColumn, dataColumn) < 0;
|
||||
}
|
||||
|
||||
protected ColumnData getReduced()
|
||||
|
|
@ -891,9 +906,9 @@ public interface Row extends Unfiltered, Iterable<ColumnData>, IMeasurableMemory
|
|||
if (column.isSimple())
|
||||
{
|
||||
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))
|
||||
merged = merged == null ? cell : Cells.reconcile(merged, cell);
|
||||
}
|
||||
|
|
@ -904,9 +919,9 @@ public interface Row extends Unfiltered, Iterable<ColumnData>, IMeasurableMemory
|
|||
complexBuilder.newColumn(column);
|
||||
complexCells.clear();
|
||||
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;
|
||||
if (cd.complexDeletion().supersedes(complexDeletion))
|
||||
complexDeletion = cd.complexDeletion();
|
||||
|
|
@ -923,7 +938,7 @@ public interface Row extends Unfiltered, Iterable<ColumnData>, IMeasurableMemory
|
|||
cellReducer.setActiveDeletion(activeDeletion);
|
||||
}
|
||||
|
||||
Iterator<Cell<?>> cells = MergeIterator.get(complexCells, Cell.comparator, cellReducer);
|
||||
Iterator<Cell<?>> cells = ComplexCellMergeIterator.get(complexCells, Cell.comparator, cellReducer);
|
||||
while (cells.hasNext())
|
||||
{
|
||||
Cell<?> merged = cells.next();
|
||||
|
|
@ -937,11 +952,12 @@ public interface Row extends Unfiltered, Iterable<ColumnData>, IMeasurableMemory
|
|||
protected void onKeyChange()
|
||||
{
|
||||
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 Cell<?> merged;
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ import org.apache.cassandra.schema.TableMetadata;
|
|||
import org.apache.cassandra.serializers.MarshalException;
|
||||
import org.apache.cassandra.utils.FBUtilities;
|
||||
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.
|
||||
|
|
@ -415,7 +415,7 @@ public abstract class UnfilteredRowIterators
|
|||
reversed,
|
||||
EncodingStats.merge(iterators, UnfilteredRowIterator::stats));
|
||||
|
||||
this.mergeIterator = MergeIterator.get(iterators,
|
||||
this.mergeIterator = UnfilteredMergeIterator.get(iterators,
|
||||
reversed ? metadata.comparator.reversed() : metadata.comparator,
|
||||
new MergeReducer(iterators.size(), reversed, listener));
|
||||
this.listener = listener;
|
||||
|
|
@ -540,7 +540,7 @@ public abstract class UnfilteredRowIterators
|
|||
listener.close();
|
||||
}
|
||||
|
||||
private class MergeReducer extends MergeIterator.Reducer<Unfiltered, Unfiltered>
|
||||
private class MergeReducer extends UnfilteredMergeIterator.Reducer<Unfiltered, Unfiltered>
|
||||
{
|
||||
private final MergeListener listener;
|
||||
|
||||
|
|
|
|||
|
|
@ -128,6 +128,7 @@ public final class DiagnosticEventPersistence
|
|||
LastEventIdBroadcaster.instance().setLastEventId(event.getClass().getName(), store.getLastEventId());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Class<DiagnosticEvent> getEventClass(String eventClazz) throws ClassNotFoundException, InvalidClassException
|
||||
{
|
||||
// get class by eventClazz argument name
|
||||
|
|
@ -135,12 +136,12 @@ public final class DiagnosticEventPersistence
|
|||
if (!eventClazz.startsWith("org.apache.cassandra."))
|
||||
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)))
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -921,6 +921,11 @@ public class SecondaryIndexManager implements IndexRegistry, INotificationConsum
|
|||
return indexes.get(indexName);
|
||||
}
|
||||
|
||||
static Class<? extends Index> loadIndexClass(String className)
|
||||
{
|
||||
return FBUtilities.classForNameWithoutInitialization(className, "Index", Index.class);
|
||||
}
|
||||
|
||||
private Index createInstance(IndexMetadata indexDef)
|
||||
{
|
||||
Index newIndex;
|
||||
|
|
@ -933,7 +938,7 @@ public class SecondaryIndexManager implements IndexRegistry, INotificationConsum
|
|||
|
||||
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);
|
||||
newIndex = ctor.newInstance(baseCfs, indexDef);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ package org.apache.cassandra.index.sai.disk.io;
|
|||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.annotation.concurrent.NotThreadSafe;
|
||||
|
||||
import org.apache.lucene.store.DataInput;
|
||||
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}
|
||||
* 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
|
||||
* optimised to read multibyte objects faster.
|
||||
* optimized to read multibyte objects faster.
|
||||
*/
|
||||
@NotThreadSafe
|
||||
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,
|
||||
* 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 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());
|
||||
this.input = input;
|
||||
this.doOnClose = doOnClose;
|
||||
this.offset = offset;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
RandomAccessReader reader = handle.createReader();
|
||||
return new IndexInputReader(reader, () -> {});
|
||||
return new IndexInputReader(reader, reader::close, 0L, reader.length());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -80,37 +105,42 @@ public class IndexInputReader extends IndexInput
|
|||
@Override
|
||||
public void close()
|
||||
{
|
||||
try
|
||||
{
|
||||
input.close();
|
||||
}
|
||||
finally
|
||||
{
|
||||
doOnClose.run();
|
||||
}
|
||||
doOnClose.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getFilePointer()
|
||||
{
|
||||
return input.getFilePointer();
|
||||
return input.getFilePointer() - offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
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
|
||||
public long length()
|
||||
{
|
||||
return input.length();
|
||||
return length;
|
||||
}
|
||||
|
||||
@Override
|
||||
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.UncheckedIOException;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import com.codahale.metrics.Gauge;
|
||||
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.slf4j.Logger;
|
||||
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.OnDiskFormat;
|
||||
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.utils.IndexIdentifier;
|
||||
import org.apache.cassandra.index.sai.utils.IndexTermType;
|
||||
|
|
@ -96,6 +100,18 @@ public class V1OnDiskFormat implements OnDiskFormat
|
|||
IndexComponent.TERMS_DATA,
|
||||
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.
|
||||
* <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))
|
||||
{
|
||||
if (!isEmptyIndex && isNotBuildCompletionMarker(indexComponent))
|
||||
if (isNotBuildCompletionMarker(indexComponent))
|
||||
{
|
||||
validateIndexComponent(indexDescriptor, indexIdentifier, indexComponent, checksum);
|
||||
// META was validated up-front in CHECKSUM mode; don't validate it twice.
|
||||
if (checksum && indexComponent == IndexComponent.META)
|
||||
continue;
|
||||
|
||||
if (checksum && SEGMENTED_COMPONENTS.contains(indexComponent))
|
||||
{
|
||||
assert segments != null : "No segment metadata available!";
|
||||
validateSegmentedIndexComponent(indexDescriptor, indexIdentifier, indexComponent, segments, indexTermType.isVector());
|
||||
}
|
||||
else
|
||||
validateIndexComponent(indexDescriptor, indexIdentifier, indexComponent, checksum);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void validateSegmentedIndexComponent(IndexDescriptor indexDescriptor,
|
||||
IndexIdentifier indexIdentifier,
|
||||
IndexComponent indexComponent,
|
||||
List<SegmentMetadata> segments,
|
||||
boolean payloadOnlyMetadata)
|
||||
{
|
||||
try (IndexInput input = indexDescriptor.openPerIndexInput(indexComponent, indexIdentifier))
|
||||
{
|
||||
long fileLength = input.length();
|
||||
long frameStart = 0;
|
||||
|
||||
for (SegmentMetadata segment : segments)
|
||||
{
|
||||
SegmentMetadata.ComponentMetadata cm = segment.componentMetadatas.get(indexComponent);
|
||||
|
||||
// Non-vector writers record offsets as the codec-framed segment starts (before
|
||||
// the header) and length as the full framed length (through the footer). The vector
|
||||
// writer (OnHeapGraph#writeData) instead records the offset as the payload start (after
|
||||
// the header) and length as just the payload length, because vector readers seek
|
||||
// directly at the payload. Segments are written contiguously in append mode, so we can
|
||||
// recover the vector-path frame extent by walking segment ends and adding the trailing
|
||||
// 16-byte codec footer.
|
||||
long frameEnd = payloadOnlyMetadata ? cm.offset + cm.length + CodecUtil.footerLength() : cm.offset + cm.length;
|
||||
|
||||
if (frameEnd > fileLength || frameEnd < frameStart)
|
||||
throw new CorruptIndexException(String.format("Segment frame [%d, %d) is inconsistent with component file length %d",
|
||||
frameStart, frameEnd, fileLength),
|
||||
indexComponent.name + '@' + frameStart);
|
||||
|
||||
IndexInput slice = input.slice(indexComponent.name + '@' + frameStart, frameStart, frameEnd - frameStart);
|
||||
SAICodecUtils.validateChecksum(slice);
|
||||
frameStart = frameEnd;
|
||||
}
|
||||
|
||||
if (frameStart != fileLength)
|
||||
throw new CorruptIndexException(String.format("Component file length %d does not match combined frame length of all segments %d",
|
||||
fileLength, frameStart),
|
||||
indexComponent.name);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.warn(indexDescriptor.logMessage("Segmented checksum validation failed for index component {} on SSTable {}"),
|
||||
indexComponent, indexDescriptor.sstableDescriptor);
|
||||
rethrowIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void validateIndexComponent(IndexDescriptor indexDescriptor,
|
||||
IndexIdentifier indexContext,
|
||||
IndexComponent indexComponent,
|
||||
|
|
@ -245,9 +336,7 @@ public class V1OnDiskFormat implements OnDiskFormat
|
|||
catch (Exception e)
|
||||
{
|
||||
logger.warn(indexDescriptor.logMessage("{} failed for index component {} on SSTable {}"),
|
||||
checksum ? "Checksum validation" : "Validation",
|
||||
indexComponent,
|
||||
indexDescriptor.sstableDescriptor);
|
||||
checksum ? "Checksum validation" : "Validation", indexComponent, indexDescriptor.sstableDescriptor);
|
||||
rethrowIOException(e);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -103,6 +103,12 @@ public class CellWithSource<T> extends Cell<T>
|
|||
return cell.localDeletionTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long minDeletionTime()
|
||||
{
|
||||
return cell.minDeletionTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTombstone()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -213,6 +213,12 @@ public class RowWithSource implements Row
|
|||
return row.hasDeletion(nowInSec);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long minLocalDeletionTime()
|
||||
{
|
||||
return row.minLocalDeletionTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
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.schema.ColumnMetadata;
|
||||
import org.apache.cassandra.schema.IndexMetadata;
|
||||
import org.apache.cassandra.utils.FBUtilities;
|
||||
|
||||
public class IndexMode
|
||||
{
|
||||
|
|
@ -60,10 +61,10 @@ public class IndexMode
|
|||
|
||||
public final Mode mode;
|
||||
public final boolean isAnalyzed, isLiteral;
|
||||
public final Class analyzerClass;
|
||||
public final Class<? extends AbstractAnalyzer> analyzerClass;
|
||||
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.isLiteral = isLiteral;
|
||||
|
|
@ -81,7 +82,7 @@ public class IndexMode
|
|||
if (isAnalyzed)
|
||||
{
|
||||
if (analyzerClass != null)
|
||||
analyzer = (AbstractAnalyzer) analyzerClass.newInstance();
|
||||
analyzer = analyzerClass.newInstance();
|
||||
else if (TOKENIZABLE_TYPES.contains(validator))
|
||||
analyzer = new StandardAnalyzer();
|
||||
}
|
||||
|
|
@ -99,21 +100,14 @@ public class IndexMode
|
|||
// validate that a valid analyzer class was provided if specified
|
||||
if (indexOptions.containsKey(INDEX_ANALYZER_CLASS_OPTION))
|
||||
{
|
||||
Class<?> analyzerClass;
|
||||
try
|
||||
{
|
||||
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)));
|
||||
}
|
||||
Class<? extends AbstractAnalyzer> analyzerClass = FBUtilities.classForNameWithoutInitialization(indexOptions.get(INDEX_ANALYZER_CLASS_OPTION),
|
||||
"analyzer",
|
||||
AbstractAnalyzer.class);
|
||||
|
||||
AbstractAnalyzer analyzer;
|
||||
try
|
||||
{
|
||||
analyzer = (AbstractAnalyzer) analyzerClass.newInstance();
|
||||
analyzer = analyzerClass.newInstance();
|
||||
analyzer.validate(indexOptions, cd);
|
||||
}
|
||||
catch (InstantiationException | IllegalAccessException e)
|
||||
|
|
@ -148,25 +142,30 @@ public class IndexMode
|
|||
}
|
||||
|
||||
boolean isAnalyzed = false;
|
||||
Class analyzerClass = null;
|
||||
try
|
||||
Class<? extends AbstractAnalyzer> analyzerClass = null;
|
||||
if (indexOptions.get(INDEX_ANALYZER_CLASS_OPTION) != null)
|
||||
{
|
||||
if (indexOptions.get(INDEX_ANALYZER_CLASS_OPTION) != null)
|
||||
try
|
||||
{
|
||||
analyzerClass = Class.forName(indexOptions.get(INDEX_ANALYZER_CLASS_OPTION));
|
||||
analyzerClass = FBUtilities.classForNameWithoutInitialization(indexOptions.get(INDEX_ANALYZER_CLASS_OPTION),
|
||||
"analyzer",
|
||||
AbstractAnalyzer.class);
|
||||
isAnalyzed = indexOptions.get(INDEX_ANALYZED_OPTION) == null
|
||||
? true : Boolean.parseBoolean(indexOptions.get(INDEX_ANALYZED_OPTION));
|
||||
? true : Boolean.parseBoolean(indexOptions.get(INDEX_ANALYZED_OPTION));
|
||||
}
|
||||
else if (indexOptions.get(INDEX_ANALYZED_OPTION) != null)
|
||||
catch (ConfigurationException e)
|
||||
{
|
||||
isAnalyzed = Boolean.parseBoolean(indexOptions.get(INDEX_ANALYZED_OPTION));
|
||||
if (!(e.getCause() instanceof ClassNotFoundException))
|
||||
throw e;
|
||||
|
||||
// Should not happen as we already validated we could instantiate an instance in validateAnalyzer().
|
||||
logger.error("Failed to find specified analyzer class [{}]. Falling back to default analyzer",
|
||||
indexOptions.get(INDEX_ANALYZER_CLASS_OPTION));
|
||||
}
|
||||
}
|
||||
catch (ClassNotFoundException e)
|
||||
else if (indexOptions.get(INDEX_ANALYZED_OPTION) != null)
|
||||
{
|
||||
// should not happen as we already validated we could instantiate an instance in validateAnalyzer()
|
||||
logger.error("Failed to find specified analyzer class [{}]. Falling back to default analyzer",
|
||||
indexOptions.get(INDEX_ANALYZER_CLASS_OPTION));
|
||||
isAnalyzed = Boolean.parseBoolean(indexOptions.get(INDEX_ANALYZED_OPTION));
|
||||
}
|
||||
|
||||
boolean isLiteral = false;
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ public class ClusteringDescriptor extends ResizableByteBuffer
|
|||
|
||||
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)
|
||||
readUnfilteredClustering(dataReader, clusteringTypes, this.clusteringColumnsBound, this);
|
||||
else
|
||||
|
|
@ -103,7 +103,7 @@ public class ClusteringDescriptor extends ResizableByteBuffer
|
|||
}
|
||||
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -513,7 +513,7 @@ public class SSTableCursorWriter implements AutoCloseable
|
|||
public void writeRangeTombstone(UnfilteredDescriptor rangeTombstone, boolean updateClusteringMetadata) throws IOException
|
||||
{
|
||||
int tombstoneKind = rangeTombstone.clusteringKindEncoded();
|
||||
ClusteringPrefix.Kind kind = ClusteringPrefix.Kind.values()[tombstoneKind];
|
||||
ClusteringPrefix.Kind kind = ClusteringPrefix.Kind.fromOrdinal(tombstoneKind);
|
||||
long unfilteredStartPosition = getPosition();
|
||||
/** See: {@link org.apache.cassandra.db.rows.UnfilteredSerializer#serialize */
|
||||
dataWriter.writeByte((byte)IS_MARKER);
|
||||
|
|
|
|||
|
|
@ -340,11 +340,11 @@ public abstract class AbstractReplicationStrategy
|
|||
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");
|
||||
|
||||
Class<AbstractReplicationStrategy> strategyClass = FBUtilities.classForName(className, "replication strategy");
|
||||
if (!AbstractReplicationStrategy.class.isAssignableFrom(strategyClass))
|
||||
{
|
||||
throw new ConfigurationException(String.format("Specified replication strategy class (%s) is not derived from AbstractReplicationStrategy", className));
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<AbstractReplicationStrategy> strategyClass =
|
||||
(Class<AbstractReplicationStrategy>) FBUtilities.classForNameWithoutInitialization(className,
|
||||
"replication strategy",
|
||||
AbstractReplicationStrategy.class);
|
||||
return strategyClass;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ import java.util.List;
|
|||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
|
@ -41,6 +43,7 @@ import org.apache.cassandra.concurrent.ScheduledExecutors;
|
|||
import org.apache.cassandra.concurrent.Shutdownable;
|
||||
|
||||
import io.netty.util.concurrent.FastThreadLocal;
|
||||
import io.netty.util.concurrent.FastThreadLocalThread;
|
||||
|
||||
import static com.google.common.collect.ImmutableList.of;
|
||||
import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory;
|
||||
|
|
@ -64,10 +67,8 @@ public class ThreadLocalMetrics
|
|||
|
||||
static final AtomicInteger idGenerator = new AtomicInteger();
|
||||
|
||||
private static final Object freeMetricIdSetGuard = new Object();
|
||||
|
||||
@VisibleForTesting
|
||||
static final BitSet freeMetricIdSet = new BitSet();
|
||||
static final FreeMetricIdSetTracker freeMetricIdSetTracker = new FreeMetricIdSetTracker();
|
||||
|
||||
private static final List<ThreadLocalMetrics> allThreadLocalMetrics = new CopyOnWriteArrayList<>();
|
||||
|
||||
|
|
@ -101,7 +102,13 @@ public class ThreadLocalMetrics
|
|||
{
|
||||
ThreadLocalMetrics result = new ThreadLocalMetrics();
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
@ -317,13 +324,7 @@ public class ThreadLocalMetrics
|
|||
|
||||
static int allocateMetricId()
|
||||
{
|
||||
int metricId;
|
||||
synchronized (freeMetricIdSetGuard)
|
||||
{
|
||||
metricId = freeMetricIdSet.nextSetBit(0);
|
||||
if (metricId >= 0)
|
||||
freeMetricIdSet.clear(metricId);
|
||||
}
|
||||
int metricId = freeMetricIdSetTracker.getFreeMetricId();
|
||||
if (metricId < 0)
|
||||
metricId = idGenerator.getAndIncrement();
|
||||
|
||||
|
|
@ -374,26 +375,92 @@ public class ThreadLocalMetrics
|
|||
lock.unlock();
|
||||
}
|
||||
|
||||
// there's no an obvious happens-before relation between currentCounterValues[metricId] = 0 write we just did
|
||||
// and an initial read of the entry by a thread which updates the reused metric
|
||||
// as a workaround we introduce a delay in recyling to provide the write visibility in practice
|
||||
// even if it is not formally guaranteed by the JMM
|
||||
ScheduledExecutors.scheduledTasks.schedule(() -> {
|
||||
synchronized (freeMetricIdSetGuard)
|
||||
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
|
||||
// and an initial read of the entry by a thread which updates the reused metric
|
||||
// as a workaround we introduce a delay in recyling to provide the write visibility in practice
|
||||
// even if it is not formally guaranteed by the JMM
|
||||
delayedToFreeMetricIdSet.set(metricId);
|
||||
scheduleCleanupTask();
|
||||
}
|
||||
|
||||
// must be called while holding this monitor (from a synchronized method)
|
||||
@VisibleForTesting
|
||||
protected void scheduleCleanupTask()
|
||||
{
|
||||
try
|
||||
{
|
||||
freeMetricIdSet.set(metricId);
|
||||
if (cleanupTask == null)
|
||||
cleanupTask = ScheduledExecutors.scheduledTasks.schedule(this::triggerRecycling, 5, TimeUnit.SECONDS);
|
||||
}
|
||||
}, 5, TimeUnit.SECONDS);
|
||||
catch (RejectedExecutionException e)
|
||||
{
|
||||
// ignore theoretically possible rejections during a shutdown
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized int getFreeMetricSetCardinality()
|
||||
{
|
||||
return freeMetricIdSet.cardinality();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized String toString()
|
||||
{
|
||||
return "FreeMetricIdSetTracker{" +
|
||||
"freeMetricIdSet=" + freeMetricIdSet +
|
||||
", tickDelayedToFreeMetricIdSet=" + tickDelayedToFreeMetricIdSet +
|
||||
", tockDelayedToFreeMetricIdSet=" + tockDelayedToFreeMetricIdSet +
|
||||
", delayedToFreeMetricIdSet=" + (delayedToFreeMetricIdSet == tickDelayedToFreeMetricIdSet ? "tick" : "tock") +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static int getAllocatedMetricsCount()
|
||||
{
|
||||
int freeCount;
|
||||
synchronized (freeMetricIdSetGuard)
|
||||
{
|
||||
freeCount = freeMetricIdSet.cardinality();
|
||||
}
|
||||
int freeCount = freeMetricIdSetTracker.getFreeMetricSetCardinality();
|
||||
return idGenerator.get() - freeCount;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@ import org.slf4j.Logger;
|
|||
import org.slf4j.LoggerFactory;
|
||||
|
||||
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.Frame;
|
||||
import org.apache.cassandra.net.FrameDecoder.FrameProcessor;
|
||||
|
|
@ -314,7 +313,7 @@ public abstract class AbstractMessageHandler extends ChannelInboundHandlerAdapte
|
|||
decoder.reactivate();
|
||||
|
||||
if (decoder.isActive())
|
||||
ClientMetrics.instance.unpauseConnection();
|
||||
onConnectionUnpaused();
|
||||
}
|
||||
}
|
||||
catch (Throwable t)
|
||||
|
|
@ -323,6 +322,10 @@ public abstract class AbstractMessageHandler extends ChannelInboundHandlerAdapte
|
|||
}
|
||||
}
|
||||
|
||||
protected void onConnectionUnpaused()
|
||||
{
|
||||
}
|
||||
|
||||
protected abstract void fatalExceptionCaught(Throwable t);
|
||||
|
||||
// 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(".") ?
|
||||
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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -311,15 +311,7 @@ public final class CompactionParams
|
|||
String className = name.contains(".")
|
||||
? name
|
||||
: "org.apache.cassandra.db.compaction." + name;
|
||||
Class<AbstractCompactionStrategy> strategyClass = FBUtilities.classForName(className, "compaction strategy");
|
||||
|
||||
if (!AbstractCompactionStrategy.class.isAssignableFrom(strategyClass))
|
||||
{
|
||||
throw new ConfigurationException(format("Compaction strategy class %s is not derived from AbstractReplicationStrategy",
|
||||
className));
|
||||
}
|
||||
|
||||
return strategyClass;
|
||||
return FBUtilities.classForNameWithoutInitialization(className, "compaction strategy", AbstractCompactionStrategy.class);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ import org.apache.cassandra.io.compress.ZstdDictionaryCompressor;
|
|||
import org.apache.cassandra.io.util.DataInputPlus;
|
||||
import org.apache.cassandra.io.util.DataOutputPlus;
|
||||
import org.apache.cassandra.net.MessagingService;
|
||||
import org.apache.cassandra.utils.FBUtilities;
|
||||
|
||||
import static java.lang.String.format;
|
||||
|
||||
|
|
@ -302,7 +303,7 @@ public final class CompressionParams
|
|||
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())
|
||||
return null;
|
||||
|
|
@ -310,15 +311,17 @@ public final class CompressionParams
|
|||
className = className.contains(".") ? className : "org.apache.cassandra.io.compress." + className;
|
||||
try
|
||||
{
|
||||
return Class.forName(className);
|
||||
return FBUtilities.classForNameWithoutInitialization(className, "compression", ICompressor.class);
|
||||
}
|
||||
catch (Exception e)
|
||||
catch (ConfigurationException e)
|
||||
{
|
||||
throw new ConfigurationException("Could not create Compression for type " + className, e);
|
||||
if (e.getCause() instanceof ClassNotFoundException || e.getCause() instanceof NoClassDefFoundError)
|
||||
throw new ConfigurationException("Could not create Compression for type " + className, e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private static ICompressor createCompressor(Class<?> compressorClass, Map<String, String> compressionOptions) throws ConfigurationException
|
||||
private static ICompressor createCompressor(Class<? extends ICompressor> compressorClass, Map<String, String> compressionOptions) throws ConfigurationException
|
||||
{
|
||||
if (compressorClass == null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -148,9 +148,7 @@ public final class IndexMetadata
|
|||
// Get the fully qualified class name:
|
||||
String className = getIndexClassName();
|
||||
|
||||
Class<Index> indexerClass = FBUtilities.classForName(className, "custom indexer");
|
||||
if (!Index.class.isAssignableFrom(indexerClass))
|
||||
throw new ConfigurationException(String.format("Specified Indexer class (%s) does not implement the Indexer interface", className));
|
||||
Class<? extends Index> indexerClass = FBUtilities.classForNameWithoutInitialization(className, "custom indexer", Index.class);
|
||||
validateCustomIndexOptions(table, indexerClass, options);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -228,19 +228,25 @@ public final class MemtableParams
|
|||
try
|
||||
{
|
||||
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
|
||||
? new HashMap<>(options.parameters)
|
||||
: new HashMap<>();
|
||||
try
|
||||
{
|
||||
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);
|
||||
}
|
||||
catch (NoSuchMethodException e)
|
||||
{
|
||||
// continue with FACTORY field
|
||||
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);
|
||||
}
|
||||
if (!parametersCopy.isEmpty())
|
||||
|
|
|
|||
|
|
@ -286,7 +286,7 @@ public final class ReplicationParams
|
|||
Map<String, String> options = new HashMap<>(size);
|
||||
for (int i = 0; i < size; i++)
|
||||
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)
|
||||
|
|
@ -322,7 +322,7 @@ public final class ReplicationParams
|
|||
Map<String, String> options = new HashMap<>(size);
|
||||
for (int i=0; i<size; i++)
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ public abstract class AbstractCryptoProvider
|
|||
return;
|
||||
}
|
||||
|
||||
FBUtilities.classForName(getProviderClassAsString(), "crypto provider");
|
||||
FBUtilities.classForNameWithoutInitialization(getProviderClassAsString(), "crypto provider", Provider.class);
|
||||
|
||||
String providerName = getProviderName();
|
||||
int providerPosition = getProviderPosition(providerName);
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ import org.slf4j.LoggerFactory;
|
|||
|
||||
import org.apache.cassandra.concurrent.ImmediateExecutor;
|
||||
import org.apache.cassandra.config.TransparentDataEncryptionOptions;
|
||||
import org.apache.cassandra.utils.FBUtilities;
|
||||
|
||||
import io.netty.util.concurrent.FastThreadLocal;
|
||||
|
||||
|
|
@ -71,9 +72,10 @@ public class CipherFactory
|
|||
try
|
||||
{
|
||||
secureRandom = SecureRandom.getInstance("SHA1PRNG");
|
||||
Class<KeyProvider> keyProviderClass = (Class<KeyProvider>)Class.forName(options.key_provider.class_name);
|
||||
Constructor ctor = keyProviderClass.getConstructor(TransparentDataEncryptionOptions.class);
|
||||
keyProvider = (KeyProvider)ctor.newInstance(options);
|
||||
Class<? extends KeyProvider> keyProviderClass =
|
||||
FBUtilities.classForNameWithoutInitialization(options.key_provider.class_name, "key provider", KeyProvider.class);
|
||||
Constructor<? extends KeyProvider> ctor = keyProviderClass.getConstructor(TransparentDataEncryptionOptions.class);
|
||||
keyProvider = ctor.newInstance(options);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -147,9 +147,11 @@ public class CacheService implements CacheServiceMBean
|
|||
? DatabaseDescriptor.getRowCacheClassName() : "org.apache.cassandra.cache.NopCacheProvider";
|
||||
try
|
||||
{
|
||||
Class<CacheProvider<RowCacheKey, IRowCacheEntry>> cacheProviderClass =
|
||||
(Class<CacheProvider<RowCacheKey, IRowCacheEntry>>) Class.forName(cacheProviderClassName);
|
||||
cacheProvider = cacheProviderClass.newInstance();
|
||||
Class<? extends CacheProvider> cacheProviderClass =
|
||||
FBUtilities.classForNameWithoutInitialization(cacheProviderClassName, "row cache provider", CacheProvider.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
CacheProvider<RowCacheKey, IRowCacheEntry> typedCacheProvider = cacheProviderClass.newInstance();
|
||||
cacheProvider = typedCacheProvider;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ public class ClientState
|
|||
{
|
||||
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)",
|
||||
customHandlerClass, CUSTOM_QUERY_HANDLER_CLASS.getKey());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ public class DiskErrorsHandlerService
|
|||
String fsErrorHandlerClass = CassandraRelevantProperties.CUSTOM_DISK_ERROR_HANDLER.getString();
|
||||
DiskErrorsHandler fsErrorHandler = fsErrorHandlerClass == null
|
||||
? new DefaultDiskErrorsHandler()
|
||||
: FBUtilities.construct(fsErrorHandlerClass, "disk error handler");
|
||||
: FBUtilities.construct(fsErrorHandlerClass, "disk error handler", DiskErrorsHandler.class);
|
||||
DiskErrorsHandlerService.set(fsErrorHandler);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3403,6 +3403,11 @@ public class StorageService extends NotificationBroadcasterSupport implements IE
|
|||
*/
|
||||
@Deprecated(since = "4.0")
|
||||
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);
|
||||
List<InetAddress> inetList = new ArrayList<>(replicas.size());
|
||||
|
|
@ -3410,11 +3415,16 @@ public class StorageService extends NotificationBroadcasterSupport implements IE
|
|||
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);
|
||||
}
|
||||
|
||||
public List<String> getNaturalEndpointsWithPort(String keyspaceName, String cf, String key)
|
||||
{
|
||||
return getNaturalReplicasWithPort(keyspaceName, cf, key);
|
||||
}
|
||||
|
||||
/** @deprecated See CASSANDRA-7544 */
|
||||
@Deprecated(since = "4.0")
|
||||
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 -
|
||||
* the endpoint responsible for this key
|
||||
* @deprecated See CASSANDRA-7544
|
||||
* @link getNaturalReplicas
|
||||
* @link getNaturalReplicasWithPort
|
||||
*/
|
||||
@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(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
|
||||
|
|
|
|||
|
|
@ -463,7 +463,9 @@ public class AccordService implements IAccordService, Shutdownable
|
|||
{
|
||||
Invariants.require(localId != null, "static localId must be set before instantiating AccordService");
|
||||
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);
|
||||
AccordTimeService time = new AccordTimeService();
|
||||
this.scheduler = new AccordScheduler();
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ import java.util.Map;
|
|||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
|
@ -98,8 +97,6 @@ public class CoordinatedTransfer
|
|||
|
||||
final Map<InetAddressAndPort, NodeStreamingMetadata> nodeStreamingContext;
|
||||
SingleTransferResult streamResult = SingleTransferResult.Init();
|
||||
// If importTxnEpochMismatch is true, all replicas will deterministically not import the SSTables in the pending directory
|
||||
volatile boolean importTxnEpochMismatch = false;
|
||||
|
||||
public CoordinatedTransfer(UUID importID, TableMetadata tableMetadata, Map<InetAddressAndPort, NodeStreamingMetadata> nodeStreamingContext, long streamingEpoch, TokenRange allSSTableRanges)
|
||||
{
|
||||
|
|
@ -120,21 +117,13 @@ public class CoordinatedTransfer
|
|||
logger.debug("{} Executing Accord bulk transfer {}", logPrefix(), this);
|
||||
LocalTransfers.instance().save(this);
|
||||
stream();
|
||||
PendingLocalTransfer pendingLocalTransfer = LocalTransfers.instance().local.get(streamResult.planId);
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
pendingLocalTransfer.registerLatch(latch);
|
||||
|
||||
try
|
||||
{
|
||||
performImportTxn();
|
||||
latch.await();
|
||||
if (importTxnEpochMismatch)
|
||||
{
|
||||
LocalTransfers.instance().scheduleCoordinatedTransferCleanup(this);
|
||||
throw new RuntimeException("SSTable import failed because of a concurrent topology change; please retry the operation");
|
||||
}
|
||||
|
||||
}
|
||||
catch (ReadTimeoutException | InterruptedException e)
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new RuntimeException("SSTable import failed locally; however the operation may still be applied by the recovery coordinator", e);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -84,27 +84,7 @@ public class PendingLocalTransfer
|
|||
if (activated)
|
||||
return;
|
||||
|
||||
CoordinatedTransfer coordinatedTransfer = LocalTransfers.instance.coordinating.get(metadata.getImportID());
|
||||
boolean isCoordinator = coordinatedTransfer != null;
|
||||
|
||||
if (metadata.getStreamingEpoch() != executeAtEpoch)
|
||||
{
|
||||
logger.info("{} Failing activation of pending SSTables because streaming epoch {} != importTxn executeAt epoch {}",
|
||||
logPrefix(), metadata.getStreamingEpoch(), executeAtEpoch);
|
||||
|
||||
if (isCoordinator)
|
||||
{
|
||||
Invariants.require(latch != null);
|
||||
latch.countDown();
|
||||
coordinatedTransfer.importTxnEpochMismatch = true;
|
||||
}
|
||||
|
||||
LocalTransfers.instance().schedulePendingLocalTransferCleanup(planId);
|
||||
|
||||
activated = true;
|
||||
return;
|
||||
}
|
||||
|
||||
Invariants.require(metadata.getStreamingEpoch() == executeAtEpoch);
|
||||
long startedActivation = currentTimeMillis();
|
||||
logger.info("{} Activating transfer {}, {} ms since pending", logPrefix(), this, startedActivation - createdAt);
|
||||
ColumnFamilyStore cfs = ColumnFamilyStore.getIfExists(tableId);
|
||||
|
|
|
|||
|
|
@ -535,6 +535,14 @@ public class TxnRead extends AbstractKeySorted<TxnNamedRead> implements Read
|
|||
return importMetadata != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getImportStreamingEpoch()
|
||||
{
|
||||
if (importMetadata != null)
|
||||
return importMetadata.streamingEpoch;
|
||||
return -1L;
|
||||
}
|
||||
|
||||
public static final ParameterisedVersionedSerializer<TxnRead, TableMetadatasAndKeys, Version> serializer = new ParameterisedVersionedSerializer<>()
|
||||
{
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ public interface StreamHook
|
|||
String className = STREAM_HOOK.getString();
|
||||
if (className != null)
|
||||
{
|
||||
return FBUtilities.construct(className, StreamHook.class.getSimpleName());
|
||||
return FBUtilities.construct(className, StreamHook.class.getSimpleName(), StreamHook.class);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ public class ExtensionKey<V, K extends ExtensionValue<V>> extends MetadataKey
|
|||
|
||||
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<?, ?>>
|
||||
|
|
@ -58,7 +58,9 @@ public class ExtensionKey<V, K extends ExtensionValue<V>> extends MetadataKey
|
|||
{
|
||||
String id = 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
|
||||
|
|
@ -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))
|
||||
{
|
||||
logger.info("Resuming decommission @ {} (current epoch = {}): {}", inProgress.latestModification, metadata.epoch, inProgress.status());
|
||||
StorageService.instance.clearTransientMode();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1200,14 +1200,14 @@ public class NodeProbe implements AutoCloseable
|
|||
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)
|
||||
|
|
|
|||
|
|
@ -17,62 +17,10 @@
|
|||
*/
|
||||
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 = "getendpoints", description = "Print the end points that owns the key")
|
||||
public class GetEndpoints extends AbstractCommand
|
||||
@Deprecated(since = "7.0") // this is alias to getreplicas
|
||||
@Command(name = "getendpoints", description = "Print the end points that owns the key, deprecated, use getreplicas instead")
|
||||
public class GetEndpoints extends GetReplicas
|
||||
{
|
||||
@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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue