Implementation of Transactional Cluster Metadata as described in CEP-21

An overview of the core components can be found in the included
TransactionalClusterMetadata.md

patch by Alex Petrov, Marcus Eriksson and Sam Tunnicliffe; reviewed by
Alex Petrov, Marcus Eriksson and Sam Tunnicliffe for CASSANDRA-18330

Co-authored-by: Marcus Eriksson <marcuse@apache.org>
Co-authored-by: Alex Petrov <oleksandr.petrov@gmail.com>
Co-authored-by: Sam Tunnicliffe <samt@apache.org>
This commit is contained in:
Sam Tunnicliffe 2023-11-23 18:39:11 +00:00
parent 360128b3eb
commit ae0842372f
934 changed files with 66115 additions and 21599 deletions

View File

@ -59,7 +59,7 @@
<remoterepo id="resolver-central" url="${artifact.remoteRepository.central}"/>
<remoterepo id="resolver-apache" url="${artifact.remoteRepository.apache}"/>
<!-- Snapshots are not allowed, but for feature branches they may be needed, so uncomment the below to allow snapshots to work -->
<!-- <remoterepo id="resolver-apache-snapshot" url="https://repository.apache.org/content/repositories/snapshots" releases="false" snapshots="true" updates="always" checksums="fail" /> -->
<remoterepo id="resolver-apache-snapshot" url="https://repository.apache.org/content/repositories/snapshots" releases="false" snapshots="true" updates="always" checksums="fail" />
</resolver:remoterepos>
<macrodef name="resolve">
@ -267,6 +267,10 @@
<delete file="${build.lib}/netty-tcnative-boringssl-static-2.0.61.Final-windows-x86_64.jar" failonerror="false"/>
<delete file="${build.dir.lib}/jars/netty-tcnative-boringssl-static-2.0.61.Final-windows-x86_64.jar" failonerror="false"/>
<copy todir="${test.lib}/jars" quiet="false">
<file file="${build.lib}/harry-core-0.0.2-CASSANDRA-18768.jar"/>
</copy>
</target>
<target name="_resolver-dist-lib_get_files">

View File

@ -70,6 +70,10 @@
<groupId>com.puppycrawl.tools</groupId>
<artifactId>checkstyle</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava-testlib</artifactId>
</dependency>
<dependency>
<groupId>org.quicktheories</groupId>
<artifactId>quicktheories</artifactId>

View File

@ -330,6 +330,11 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava-testlib</artifactId>
<version>27.0-jre</version>
</dependency>
<dependency>
<groupId>com.google.jimfs</groupId>
<artifactId>jimfs</artifactId>
@ -511,8 +516,9 @@
<dependency>
<groupId>org.apache.cassandra</groupId>
<artifactId>harry-core</artifactId>
<version>0.0.1</version>
<scope>test</scope>
<version>harry-core-0.0.2-CASSANDRA-18768</version>
<scope>system</scope>
<systemPath>${test.lib}/harry-core-0.0.2-CASSANDRA-18768.jar</systemPath>
</dependency>
<dependency>
<groupId>org.reflections</groupId>

File diff suppressed because it is too large Load Diff

1
.gitignore vendored
View File

@ -17,6 +17,7 @@ pylib/src/
pylib/cqlshlib/serverversion.py
!lib/cassandra-driver-internal-only-*.zip
!lib/puresasl-*.zip
!lib/harry-0.0.2-internal-20221121.14211-2.jar
# C* debs
build-stamp

View File

@ -1,4 +1,5 @@
5.1
* Transactional Cluster Metadata [CEP-21] (CASSANDRA-18330)
* Add ELAPSED command to cqlsh (CASSANDRA-18861)
* Add the ability to disable bulk loading of SSTables (CASSANDRA-18781)
* Clean up obsolete functions and simplify cql_version handling in cqlsh (CASSANDRA-18787)

View File

@ -71,11 +71,70 @@ using the provided 'sstableupgrade' tool.
New features
------------
[The following is a placeholder, to be revised asap]
- CEP-21 Transactional Cluster Metadata introduces a distributed log for linearizing modifications to cluster
metadata. In the first instance, this encompasses cluster membership, token ownership and schema metadata. See
https://cwiki.apache.org/confluence/display/CASSANDRA/CEP-21%3A+Transactional+Cluster+Metadata for more detail on
the motivation and design, and see "Upgrading" below for specific instructions on migrating clusters to this system.
More updates and documentation to follow.
- New Guardrails added:
- Whether bulk loading of SSTables is allowed.
Upgrading
---------
[The following is a placeholder, to be revised asap]
- Upgrading to 5.1 is currently only supported from 4.0, 4.1 or 5.0. Clusters running earlier releases must first
upgrade to at least the latest 4.0 release. When a cluster is upgraded to 5.1 there is an additional step for
operators to perform. The upgrade is not considered complete until:
1. All UP nodes are running 5.1
2. The Cluster Metadata Service (CMS) has been enabled
The first step is just a regular upgrade, there are no changes to external APIs, SSTable formats to consider.
Step 2 requires an operator to run a new nodetool subcommand, intializecms, on one node in the cluster.
- > nodetool initializecms
Doing so creates the CMS with the local node as its only member. The initializecms command cannot be executed until
step 1 is complete and all nodes are running 5.1 as migrating metadata management over to the new TCM system
requires all nodes to be in agreement over the current state of the cluster. Essentially this means agreement on
schema and topology. Once the upgrade has started, but before initializecms is run, metadata-changing operations are
not permitted and if attempted from an upgraded node will be rejected.
Prohibited operations include:
- schema changes
- node replacement
- bootstrap
- decommission
- move
- assasinate
For the time being there is no mechanism in place to prevent these operations being executed by a node still running
a previous version, though this is planned before release. Any automation which can trigger these operations should
be disabled for the cluster prior to starting the upgrade.
Should any of the prohibited operations be executed (i.e. on a node that is still running a pre-5.1 version) before
the CMS migration, nodes which are DOWN or which have been upgraded will not process the metadata changes. However,
nodes still UP and running the old version will. This will eventually cause the migration to fail, as the cluster
will not be in agreement.
- > nodetool initializecms
Got mismatching cluster metadatas from [/x.x.x.x:7000] aborting migration
See 'nodetool help' or 'nodetool help <command>'.
If the initializecms command fails, it will indicate which nodes current metadata does not agree with the node
where the command was executed. To mitigate this situation, bring any mismatching nodes DOWN and rerun the
initializecms command with the additional —ignore flag.
- nodetool intializecms -ignore x.x.x.x
Once the command has run successfully the ignored nodes can be restarted but any metadata changes that they
accepted/and or applied whilst the cluster was in mixed mode will be lost. We plan to improve this before beta, but
in the meantime operators should ensure no schema or membership/ownership changes are performed during the upgrade.
Although the restrictions on metadata-changing operations will be removed as soon as the initial CMS migration is
complete, at that point the CMS will only contain a single member, which is not suitable for real clusters. To
modify the membership of the CMS a second nodetool subcommand, reconfigurecms, should be run. This command allows
the number of required number of CMS members to be specified for each datacenter. Consensus for metadata operations
by the CMS is obtained via Paxos, operating at SERIAL/QUORUM consistency, so the minimum safe size for the CMS is 3
nodes. In multi-DC deployments, the CMS members may be distributed across DCs to ensure resilience in the case of a
DC outage. It is not recommended to make every node a member of the CMS. Redundancy is the primary concern here, not
locality or latency, so between 3 and 7 nodes per DC depending on the size of the cluster should be the goal.
Deploying to a fresh cluster is more straightforward. As the cluster comes up, at least one node will automatically
nominate itself as the first CMS node. A simple election mechanism ensures that only one node will be chosen if
multiple peers attempt to nominate themselves. As soon as the election is complete, metadata modifications are
supported. Typically, this takes only a few seconds and is likely to complete before all nodes are fully UP. Nodes
which come up during or after an election will learn of the elected first CMS node and direct metadata updates to
it. It is important to remember that at the completion of the election, the CMS still only comprises a single
member. Just as in the upgrade case, operators should add further members as soon as possible.
Deprecation
-----------

View File

@ -403,7 +403,7 @@
<target name="realclean" depends="clean" description="Remove the entire build directory and all downloaded artifacts">
<delete>
<fileset dir="${build.lib}" excludes="cassandra-driver-internal-only-*,puresasl-internal-only-*"/>
<fileset dir="${build.lib}" excludes="cassandra-driver-internal-only-*,puresasl-internal-only-*,harry-*"/>
</delete>
<delete dir="${build.dir}" />
<delete dir="${doc.dir}/build" />

96
ci/harry_simulation.sh Executable file
View File

@ -0,0 +1,96 @@
#!/bin/sh
# 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.
set -x
set -o errexit
set -o nounset
set -o pipefail
rm -f build/test/lib/jars/guava-18.0.jar
current_dir=$(dirname "$(readlink -f "$0")")
common=(-Dstorage-config=$current_dir/../test/conf
-Djava.awt.headless=true
-ea
-XX:SoftRefLRUPolicyMSPerMB=0
-XX:HeapDumpPath=build/test
-Dcassandra.test.driver.connection_timeout_ms=10000
-Dcassandra.test.driver.read_timeout_ms=24000
-Dcassandra.memtable_row_overhead_computation_step=100
-Dcassandra.test.use_prepared=true
-Dcassandra.test.sstableformatdevelopment=true
-Djava.security.egd=file:/dev/urandom
-Dcassandra.testtag=.jdk11
-Dstorage-config=$current_dir/../test/conf
-Djava.awt.headless=true
-Dcassandra.keepBriefBrief=true
-Dcassandra.allow_simplestrategy=true
-Dcassandra.strict.runtime.checks=true
-Dcassandra.reads.thresholds.coordinator.defensive_checks_enabled=true
-Dcassandra.test.flush_local_schema_changes=false
-Dcassandra.test.messagingService.nonGracefulShutdown=true
-Dcassandra.use_nix_recursive_delete=true
-Dcie-cassandra.disable_schema_drop_log=true
-Dcassandra.ring_delay_ms=10000
-Dcassandra.tolerate_sstable_size=true
-Dcassandra.skip_sync=true
-Dcassandra.debugrefcount=false
-Dcassandra.test.simulator.determinismcheck=strict
-Dcassandra.test.simulator.print_asm=none
-Dlog4j2.disableJmx=true
-Dlog4j2.disable.jmx=true
-Dlog4j.shutdownHookEnabled=false
-Dcassandra.test.logConfigPath=$current_dir/../test/conf/log4j2-dtest-simulator.xml
-Dcassandra.test.logConfigProperty=log4j.configurationFile
-Dlog4j2.configurationFile=$current_dir/../test/conf/log4j2-dtest-simulator.xml
-javaagent:$current_dir/../lib/jamm-0.3.2.jar
-javaagent:$current_dir/../build/test/lib/jars/simulator-asm.jar
-Xbootclasspath/a:$current_dir/../build/test/lib/jars/simulator-bootstrap.jar
-XX:ActiveProcessorCount=4
-XX:-TieredCompilation
-XX:-BackgroundCompilation
-XX:CICompilerCount=1
-XX:Tier4CompileThreshold=1000
-XX:ReservedCodeCacheSize=256M
-Xmx16G
-Xmx4G
-Xss384k
--add-exports java.base/jdk.internal.misc=ALL-UNNAMED
--add-exports java.base/jdk.internal.ref=ALL-UNNAMED
--add-exports java.base/sun.nio.ch=ALL-UNNAMED
--add-exports java.management.rmi/com.sun.jmx.remote.internal.rmi=ALL-UNNAMED
--add-exports java.rmi/sun.rmi.registry=ALL-UNNAMED
--add-exports java.rmi/sun.rmi.server=ALL-UNNAMED
--add-exports java.sql/java.sql=ALL-UNNAMED
--add-exports java.rmi/sun.rmi.registry=ALL-UNNAMED
--add-opens java.base/java.lang.module=ALL-UNNAMED
--add-opens java.base/java.net=ALL-UNNAMED
--add-opens java.base/jdk.internal.loader=ALL-UNNAMED
--add-opens java.base/jdk.internal.ref=ALL-UNNAMED
--add-opens java.base/jdk.internal.reflect=ALL-UNNAMED
--add-opens java.base/jdk.internal.math=ALL-UNNAMED
--add-opens java.base/jdk.internal.module=ALL-UNNAMED
--add-opens java.base/jdk.internal.util.jar=ALL-UNNAMED
--add-opens jdk.management/com.sun.management.internal=ALL-UNNAMED
--add-opens jdk.management.jfr/jdk.management.jfr=ALL-UNNAMED
--add-opens java.desktop/com.sun.beans.introspect=ALL-UNNAMED
-classpath $current_dir/../build/classes/main/:$current_dir/../build/test/classes/:$current_dir/../build/test/lib/jars/*:$current_dir/../build/lib/jars/*
)
java "${common[@]}" org.apache.cassandra.simulator.test.HarrySimulatorTest

View File

@ -1201,16 +1201,16 @@ range_request_timeout: 10000ms
# How long the coordinator should wait for writes to complete.
# Lowest acceptable value is 10 ms.
# Min unit: ms
write_request_timeout: 2000ms
write_request_timeout: 10000ms
# How long the coordinator should wait for counter writes to complete.
# Lowest acceptable value is 10 ms.
# Min unit: ms
counter_write_request_timeout: 5000ms
counter_write_request_timeout: 1000ms
# How long a coordinator should continue to retry a CAS operation
# that contends with other proposals for the same row.
# Lowest acceptable value is 10 ms.
# Min unit: ms
cas_contention_timeout: 1000ms
cas_contention_timeout: 5000ms
# How long the coordinator should wait for truncates to complete
# (This can be much longer, because unless auto_snapshot is disabled
# we need to flush first so we can snapshot before removing the data.)
@ -2088,12 +2088,6 @@ drop_compact_storage_enabled: false
# enabled: false
# ownership_token: "sometoken" # (overriden by "CassandraOwnershipToken" system property)
# ownership_filename: ".cassandra_fs_ownership" # (overriden by "cassandra.fs_ownership_filename")
# Prevents a node from starting if snitch's data center differs from previous data center.
# check_dc:
# enabled: true # (overriden by cassandra.ignore_dc system property)
# Prevents a node from starting if snitch's rack differs from previous rack.
# check_rack:
# enabled: true # (overriden by cassandra.ignore_rack system property)
# Enable this property to fail startup if the node is down for longer than gc_grace_seconds, to potentially
# prevent data resurrection on tables with deletes. By default, this will run against all keyspaces and tables
# except the ones specified on excluded_keyspaces and excluded_tables.
@ -2129,4 +2123,4 @@ drop_compact_storage_enabled: false
# - Do a rolling restart with all nodes starting with NONE. This sheds the extra cost of checking nodes versions and ensures
# a stable cluster. If a node from a previous version was started by accident we won't any longer toggle behaviors as when UPGRADING.
#
storage_compatibility_mode: CASSANDRA_4
storage_compatibility_mode: NONE

95
conf/harry-example.yaml Normal file
View File

@ -0,0 +1,95 @@
# 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.
seed: 1596731732524
# Default schema provider generates random schema
schema_provider:
fixed:
keyspace: harry
table: test_table
partition_keys:
pk1: bigint
pk2: ascii
clustering_keys:
ck1: ascii
ck2: bigint
regular_columns:
v1: ascii
v2: bigint
v3: ascii
v4: bigint
static_keys:
s1: ascii
s2: bigint
s3: ascii
s4: bigint
# Clock is a component responsible for mapping _logical_ timestamps to _real-time_ ones.
#
# When reproducing test failures, and for validation purposes, a snapshot of such clock can
# be taken to map a real-time timestamp from the value retrieved from the database in order
# to map it back to the logical timestamp of the operation that wrote this value.
clock:
offset:
offset: 1000
drop_schema: false
create_schema: true
truncate_table: true
# Partition descriptor selector controls how partitions is selected based on the current logical
# timestamp. Default implementation is a sliding window of partition descriptors that will visit
# one partition after the other in the window `slide_after_repeats` times. After that will
# retire one partition descriptor, and pick one instead of it.
partition_descriptor_selector:
default:
window_size: 10
slide_after_repeats: 100
# Clustering descriptor selector controls how clusterings are picked within the partition:
# how many rows there can be in a partition, how many rows will be visited for a logical timestamp,
# how many operations there will be in batch, what kind of operations there will and how often
# each kind of operation is going to occur.
clustering_descriptor_selector:
default:
modifications_per_lts:
type: "constant"
constant: 2
rows_per_modification:
type: "constant"
constant: 2
operation_kind_weights:
DELETE_RANGE: 0
DELETE_SLICE: 0
DELETE_ROW: 0
DELETE_COLUMN: 0
DELETE_PARTITION: 0
DELETE_COLUMN_WITH_STATICS: 0
INSERT_WITH_STATICS: 50
INSERT: 50
UPDATE_WITH_STATICS: 50
UPDATE: 50
column_mask_bitsets: null
max_partition_size: 1000
metric_reporter:
no_op: {}
data_tracker:
locking:
max_seen_lts: -1
max_complete_lts: -1

Binary file not shown.

View File

@ -34,8 +34,8 @@ class UnexpectedTableStructure(UserWarning):
SYSTEM_KEYSPACES = ('system', 'system_schema', 'system_traces', 'system_auth', 'system_distributed', 'system_views',
'system_virtual_schema')
NONALTERBALE_KEYSPACES = ('system', 'system_schema', 'system_views', 'system_virtual_schema')
'system_virtual_schema', 'system_cluster_metadata')
NONALTERBALE_KEYSPACES = ('system', 'system_schema', 'system_views', 'system_virtual_schema', 'system_cluster_metadata')
class Cql3ParsingRuleSet(CqlParsingRuleSet):

View File

@ -589,7 +589,7 @@ class TestCqlshCompletion(CqlshCompletionCase):
def test_complete_in_drop_type(self):
self.trycompletions('DROP TYPE ', choices=['IF', 'system_views.',
'tags', 'system_traces.', 'system_distributed.',
'tags', 'system_traces.', 'system_distributed.', 'system_cluster_metadata.',
'phone_number', 'quote_udt', 'band_info_type', 'address', 'system.', 'system_schema.',
'system_auth.', 'system_virtual_schema.', self.cqlsh.keyspace + '.'
])
@ -897,7 +897,7 @@ class TestCqlshCompletion(CqlshCompletionCase):
self.trycompletions('US', immediate='E ')
self.trycompletions('USE ', choices=[self.cqlsh.keyspace, 'system', 'system_auth',
'system_distributed', 'system_schema', 'system_traces', 'system_views',
'system_virtual_schema' ])
'system_virtual_schema', 'system_cluster_metadata' ])
def test_complete_in_create_index(self):
self.trycompletions('CREATE I', immediate='NDEX ')
@ -994,6 +994,7 @@ class TestCqlshCompletion(CqlshCompletionCase):
'system_traces.', 'songs', 'system_views.',
'system_virtual_schema.',
'system_schema.', 'system_distributed.',
'system_cluster_metadata.',
self.cqlsh.keyspace + '.'])
self.trycompletions('ALTER TABLE IF EXISTS new_table ADD ', choices=['<new_column_name>', 'IF'])
self.trycompletions('ALTER TABLE IF EXISTS new_table ADD IF NOT EXISTS ', choices=['<new_column_name>'])
@ -1021,7 +1022,7 @@ class TestCqlshCompletion(CqlshCompletionCase):
self.trycompletions('ALTER TYPE ', choices=['IF', 'system_views.',
'tags', 'system_traces.', 'system_distributed.',
'phone_number', 'quote_udt', 'band_info_type', 'address', 'system.', 'system_schema.',
'system_auth.', 'system_virtual_schema.', self.cqlsh.keyspace + '.'
'system_auth.', 'system_virtual_schema.', 'system_cluster_metadata.', self.cqlsh.keyspace + '.'
])
self.trycompletions('ALTER TYPE IF EXISTS new_type ADD ', choices=['<new_field_name>', 'IF'])
self.trycompletions('ALTER TYPE IF EXISTS new_type ADD IF NOT EXISTS ', choices=['<new_field_name>'])

View File

@ -41,7 +41,7 @@ public final class AuthKeyspace
{
}
private static final int DEFAULT_RF = CassandraRelevantProperties.SYSTEM_AUTH_DEFAULT_RF.getInt();
public static final int DEFAULT_RF = CassandraRelevantProperties.SYSTEM_AUTH_DEFAULT_RF.getInt();
/**
* Generation is used as a timestamp for automatic table creation on startup.
@ -68,79 +68,85 @@ public final class AuthKeyspace
public static final long SUPERUSER_SETUP_DELAY = SUPERUSER_SETUP_DELAY_MS.getLong();
private static final TableMetadata Roles =
parse(ROLES,
"role definitions",
"CREATE TABLE %s ("
public static String ROLES_CQL = "CREATE TABLE IF NOT EXISTS %s ("
+ "role text,"
+ "is_superuser boolean,"
+ "can_login boolean,"
+ "salted_hash text,"
+ "member_of set<text>,"
+ "PRIMARY KEY(role))");
+ "PRIMARY KEY(role))";
private static final TableMetadata Roles =
parse(ROLES,
"role definitions",
ROLES_CQL);
public static String IDENTITY_TO_ROLES_CQL = "CREATE TABLE IF NOT EXISTS %s ("
+ "identity text," // opaque identity string for use by role authenticators
+ "role text,"
+ "PRIMARY KEY(identity))";
private static final TableMetadata IdentityToRoles =
parse(IDENTITY_TO_ROLES,
"mtls authorized identities lookup table",
"CREATE TABLE %s ("
+ "identity text," // opaque identity string for use by role authenticators
+ "role text,"
+ "PRIMARY KEY(identity))"
IDENTITY_TO_ROLES_CQL
);
public static String ROLE_MEMBERS_CQL = "CREATE TABLE IF NOT EXISTS %s ("
+ "role text,"
+ "member text,"
+ "PRIMARY KEY(role, member))";
private static final TableMetadata RoleMembers =
parse(ROLE_MEMBERS,
"role memberships lookup table",
"CREATE TABLE %s ("
+ "role text,"
+ "member text,"
+ "PRIMARY KEY(role, member))");
ROLE_MEMBERS_CQL);
private static final TableMetadata RolePermissions =
parse(ROLE_PERMISSIONS,
"permissions granted to db roles",
"CREATE TABLE %s ("
public static String ROLE_PERMISSIONS_CQL = "CREATE TABLE IF NOT EXISTS %s ("
+ "role text,"
+ "resource text,"
+ "permissions set<text>,"
+ "PRIMARY KEY(role, resource))");
+ "PRIMARY KEY(role, resource))";
private static final TableMetadata RolePermissions =
parse(ROLE_PERMISSIONS,
"permissions granted to db roles",
ROLE_PERMISSIONS_CQL);
public static String RESOURCE_ROLE_INDEX_CQL = "CREATE TABLE IF NOT EXISTS %s ("
+ "resource text,"
+ "role text,"
+ "PRIMARY KEY(resource, role))";
private static final TableMetadata ResourceRoleIndex =
parse(RESOURCE_ROLE_INDEX,
"index of db roles with permissions granted on a resource",
"CREATE TABLE %s ("
+ "resource text,"
+ "role text,"
+ "PRIMARY KEY(resource, role))");
RESOURCE_ROLE_INDEX_CQL);
public static String NETWORK_PERMISSIONS_CQL = "CREATE TABLE IF NOT EXISTS %s ("
+ "role text, "
+ "dcs frozen<set<text>>, "
+ "PRIMARY KEY(role))";
private static final TableMetadata NetworkPermissions =
parse(NETWORK_PERMISSIONS,
"user network permissions",
"CREATE TABLE %s ("
+ "role text, "
+ "dcs frozen<set<text>>, "
+ "PRIMARY KEY(role))");
NETWORK_PERMISSIONS_CQL);
public static String CIDR_PERMISSIONS_CQL = "CREATE TABLE %s ("
+ "role text, "
+ "cidr_groups frozen<set<text>>, "
+ "PRIMARY KEY(role))";
public static final String CIDR_PERMISSIONS_TBL_ROLE_COL_NAME = "role";
public static final String CIDR_PERMISSIONS_TBL_CIDR_GROUPS_COL_NAME = "cidr_groups";
private static final TableMetadata CIDRPermissions =
parse(CIDR_PERMISSIONS,
"user cidr permissions",
"CREATE TABLE %s ("
+ CIDR_PERMISSIONS_TBL_ROLE_COL_NAME + " text, "
+ CIDR_PERMISSIONS_TBL_CIDR_GROUPS_COL_NAME + " frozen<set<text>>, "
+ "PRIMARY KEY(" + CIDR_PERMISSIONS_TBL_ROLE_COL_NAME + "))"
CIDR_PERMISSIONS_CQL
);
public static final String CIDR_GROUPS_TBL_CIDR_GROUP_COL_NAME = "cidr_group";
public static final String CIDR_GROUPS_TBL_CIDRS_COL_NAME = "cidrs";
public static String CIDR_GROUPS_CQL = "CREATE TABLE %s ("
+ "cidr_group text, "
+ "cidrs frozen<set<tuple<inet, smallint>>>, "
+ "PRIMARY KEY(cidr_group))";
private static final TableMetadata CIDRGroups =
parse(CIDR_GROUPS,
"cidr groups to cidrs mapping",
"CREATE TABLE %s ("
+ CIDR_GROUPS_TBL_CIDR_GROUP_COL_NAME + " text, "
+ CIDR_GROUPS_TBL_CIDRS_COL_NAME + " frozen<set<tuple<inet, smallint>>>, "
+ "PRIMARY KEY(" + CIDR_GROUPS_TBL_CIDR_GROUP_COL_NAME + "))"
CIDR_GROUPS_CQL
);
private static TableMetadata parse(String name, String description, String cql)
@ -158,7 +164,6 @@ public final class AuthKeyspace
KeyspaceParams.simple(Math.max(DEFAULT_RF, DatabaseDescriptor.getDefaultKeyspaceRF())),
Tables.of(Roles, RoleMembers, RolePermissions,
ResourceRoleIndex, NetworkPermissions,
CIDRPermissions, CIDRGroups,
IdentityToRoles));
CIDRPermissions, CIDRGroups, IdentityToRoles));
}
}

View File

@ -58,7 +58,7 @@ public class CIDRGroupsMappingLoader
UntypedResultSet rows = cidrGroupsMappingManager.getCidrGroupsTableEntries();
for (UntypedResultSet.Row row : rows)
{
String cidrGroupName = row.getString(AuthKeyspace.CIDR_GROUPS_TBL_CIDR_GROUP_COL_NAME);
String cidrGroupName = row.getString("cidr_group");
Set<Pair<InetAddress, Short>> cidrs = cidrGroupsMappingManager.retrieveCidrsFromRow(row);
for (Pair<InetAddress, Short> cidr : cidrs)

View File

@ -70,18 +70,15 @@ public class CIDRGroupsMappingManager implements CIDRGroupsMappingManagerMBean
if (!MBeanWrapper.instance.isRegistered(MBEAN_NAME))
MBeanWrapper.instance.registerMBean(this, MBEAN_NAME);
String getCidrGroupsQuery = String.format("SELECT %s FROM %s.%s",
AuthKeyspace.CIDR_GROUPS_TBL_CIDR_GROUP_COL_NAME,
String getCidrGroupsQuery = String.format("SELECT cidr_group FROM %s.%s",
SchemaConstants.AUTH_KEYSPACE_NAME,
AuthKeyspace.CIDR_GROUPS);
getCidrGroupsStatement = (SelectStatement) QueryProcessor.getStatement(getCidrGroupsQuery,
ClientState.forInternalCalls());
String getCidrsForCidrGroupQuery = String.format("SELECT %s FROM %s.%s where %s = ?",
AuthKeyspace.CIDR_GROUPS_TBL_CIDRS_COL_NAME,
String getCidrsForCidrGroupQuery = String.format("SELECT cidrs FROM %s.%s where cidr_group = ?",
SchemaConstants.AUTH_KEYSPACE_NAME,
AuthKeyspace.CIDR_GROUPS,
AuthKeyspace.CIDR_GROUPS_TBL_CIDR_GROUP_COL_NAME);
AuthKeyspace.CIDR_GROUPS);
getCidrsForCidrGroupStatement = (SelectStatement) QueryProcessor.getStatement(getCidrsForCidrGroupQuery,
ClientState.forInternalCalls());
}
@ -125,11 +122,11 @@ public class CIDRGroupsMappingManager implements CIDRGroupsMappingManagerMBean
for (UntypedResultSet.Row row : result)
{
if (!row.has(AuthKeyspace.CIDR_GROUPS_TBL_CIDR_GROUP_COL_NAME))
if (!row.has("cidr_group"))
throw new IllegalStateException("Invalid row " + row + " in table: " +
SchemaConstants.AUTH_KEYSPACE_NAME + '.' + AuthKeyspace.CIDR_GROUPS);
availableCidrGroups.add(row.getString(AuthKeyspace.CIDR_GROUPS_TBL_CIDR_GROUP_COL_NAME));
availableCidrGroups.add(row.getString("cidr_group"));
}
return availableCidrGroups;
@ -137,14 +134,13 @@ public class CIDRGroupsMappingManager implements CIDRGroupsMappingManagerMBean
public Set<Pair<InetAddress, Short>> retrieveCidrsFromRow(UntypedResultSet.Row row)
{
if (!row.has(AuthKeyspace.CIDR_GROUPS_TBL_CIDRS_COL_NAME))
throw new RuntimeException("Invalid row, doesn't have column " +
AuthKeyspace.CIDR_GROUPS_TBL_CIDRS_COL_NAME);
if (!row.has("cidrs"))
throw new RuntimeException("Invalid row, doesn't have column cidrs");
Set<Pair<InetAddress, Short>> cidrs = new HashSet<>();
TupleType tupleType = new TupleType(Arrays.asList(InetAddressType.instance, ShortType.instance));
Set<ByteBuffer> cidrAsTuples = row.getFrozenSet(AuthKeyspace.CIDR_GROUPS_TBL_CIDRS_COL_NAME, tupleType);
Set<ByteBuffer> cidrAsTuples = row.getFrozenSet("cidrs", tupleType);
for (ByteBuffer cidrAsTuple : cidrAsTuples)
{
ByteBuffer[] splits = tupleType.split(ByteBufferAccessor.instance, cidrAsTuple);
@ -182,12 +178,10 @@ public class CIDRGroupsMappingManager implements CIDRGroupsMappingManagerMBean
validCidrs.add(CIDR.getInstance(cidr));
}
String query = String.format("UPDATE %s.%s SET %s = %s WHERE %s = '%s'",
String query = String.format("UPDATE %s.%s SET cidrs = %s WHERE cidr_group = '%s'",
SchemaConstants.AUTH_KEYSPACE_NAME,
AuthKeyspace.CIDR_GROUPS,
AuthKeyspace.CIDR_GROUPS_TBL_CIDRS_COL_NAME,
getCidrTuplesSetString(validCidrs),
AuthKeyspace.CIDR_GROUPS_TBL_CIDR_GROUP_COL_NAME,
cidrGroupName);
process(query, CassandraAuthorizer.authWriteConsistencyLevel());

View File

@ -64,11 +64,9 @@ public class CIDRPermissionsManager implements CIDRPermissionsManagerMBean, Auth
if (!MBeanWrapper.instance.isRegistered(MBEAN_NAME))
MBeanWrapper.instance.registerMBean(this, MBEAN_NAME);
String getCidrPermissionsOfUserQuery = String.format("SELECT %s FROM %s.%s WHERE %s = ?",
AuthKeyspace.CIDR_PERMISSIONS_TBL_CIDR_GROUPS_COL_NAME,
String getCidrPermissionsOfUserQuery = String.format("SELECT cidr_groups FROM %s.%s WHERE role = ?",
SchemaConstants.AUTH_KEYSPACE_NAME,
AuthKeyspace.CIDR_PERMISSIONS,
AuthKeyspace.CIDR_PERMISSIONS_TBL_ROLE_COL_NAME);
AuthKeyspace.CIDR_PERMISSIONS);
getCidrPermissionsOfUserStatement = (SelectStatement) QueryProcessor.getStatement(getCidrPermissionsOfUserQuery,
ClientState.forInternalCalls());
}
@ -92,9 +90,9 @@ public class CIDRPermissionsManager implements CIDRPermissionsManagerMBean, Auth
ResultMessage.Rows rows = select(getCidrPermissionsOfUserStatement, options);
UntypedResultSet result = UntypedResultSet.create(rows.result);
if (!result.isEmpty() && result.one().has(AuthKeyspace.CIDR_PERMISSIONS_TBL_CIDR_GROUPS_COL_NAME))
if (!result.isEmpty() && result.one().has("cidr_groups"))
{
return result.one().getFrozenSet(AuthKeyspace.CIDR_PERMISSIONS_TBL_CIDR_GROUPS_COL_NAME, UTF8Type.instance);
return result.one().getFrozenSet("cidr_groups", UTF8Type.instance);
}
return Collections.emptySet();
@ -145,12 +143,10 @@ public class CIDRPermissionsManager implements CIDRPermissionsManagerMBean, Auth
*/
public void setCidrGroupsForRole(RoleResource role, CIDRPermissions cidrPermissions)
{
String query = String.format("UPDATE %s.%s SET %s = %s WHERE %s = '%s'",
String query = String.format("UPDATE %s.%s SET cidr_groups = %s WHERE role = '%s'",
SchemaConstants.AUTH_KEYSPACE_NAME,
AuthKeyspace.CIDR_PERMISSIONS,
AuthKeyspace.CIDR_PERMISSIONS_TBL_CIDR_GROUPS_COL_NAME,
getCidrPermissionsSetString(cidrPermissions),
AuthKeyspace.CIDR_PERMISSIONS_TBL_ROLE_COL_NAME,
role.getRoleName());
process(query, CassandraAuthorizer.authWriteConsistencyLevel());
@ -191,18 +187,16 @@ public class CIDRPermissionsManager implements CIDRPermissionsManagerMBean, Auth
logger.info("Pre-warming CIDR permissions cache from cidr_permissions table");
Map<RoleResource, CIDRPermissions> entries = new HashMap<>();
UntypedResultSet rows = process(String.format("SELECT %s, %s FROM %s.%s",
AuthKeyspace.CIDR_PERMISSIONS_TBL_ROLE_COL_NAME,
AuthKeyspace.CIDR_PERMISSIONS_TBL_CIDR_GROUPS_COL_NAME,
UntypedResultSet rows = process(String.format("SELECT role, cidr_groups FROM %s.%s",
SchemaConstants.AUTH_KEYSPACE_NAME,
AuthKeyspace.CIDR_PERMISSIONS),
CassandraAuthorizer.authReadConsistencyLevel());
for (UntypedResultSet.Row row : rows)
{
RoleResource role = RoleResource.role(row.getString(AuthKeyspace.CIDR_PERMISSIONS_TBL_ROLE_COL_NAME));
RoleResource role = RoleResource.role(row.getString("role"));
CIDRPermissions.Builder builder = new CIDRPermissions.Builder();
Set<String> cidrGroups = row.getFrozenSet(AuthKeyspace.CIDR_PERMISSIONS_TBL_CIDR_GROUPS_COL_NAME,
Set<String> cidrGroups = row.getFrozenSet("cidr_groups",
UTF8Type.instance);
for (String cidrGroup : cidrGroups)
builder.add(cidrGroup);

View File

@ -36,18 +36,19 @@ import org.slf4j.LoggerFactory;
import org.apache.cassandra.concurrent.ScheduledExecutors;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.schema.SchemaConstants;
import org.apache.cassandra.cql3.*;
import org.apache.cassandra.cql3.statements.SelectStatement;
import org.apache.cassandra.db.ConsistencyLevel;
import org.apache.cassandra.db.marshal.UTF8Type;
import org.apache.cassandra.exceptions.*;
import org.apache.cassandra.schema.SchemaConstants;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.service.StorageProxy;
import org.apache.cassandra.service.StorageService;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.transport.messages.ResultMessage;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.NoSpamLogger;
import org.apache.cassandra.utils.FBUtilities;
import org.mindrot.jbcrypt.BCrypt;
import static org.apache.cassandra.config.CassandraRelevantProperties.AUTH_BCRYPT_GENSALT_LOG2_ROUNDS;
@ -144,15 +145,22 @@ public class CassandraRoleManager implements IRoleManager
}
@Override
public void setup()
public void setup(boolean asyncRoleSetup)
{
loadRoleStatement();
loadIdentityStatement();
if (asyncRoleSetup)
{
scheduleSetupTask(() -> {
setupDefaultRole();
return null;
});
}
else
{
setupDefaultRole();
}
}
@Override
public String roleForIdentity(String identity)
@ -417,7 +425,7 @@ public class CassandraRoleManager implements IRoleManager
*/
private static void setupDefaultRole()
{
if (StorageService.instance.getTokenMetadata().sortedTokens().isEmpty())
if (ClusterMetadata.current().tokenMap.tokens().isEmpty())
throw new IllegalStateException("CassandraRoleManager skipped default role setup: no known tokens in ring");
try
@ -461,7 +469,7 @@ public class CassandraRoleManager implements IRoleManager
{
// The delay is to give the node a chance to see its peers before attempting the operation
ScheduledExecutors.optionalTasks.scheduleSelfRecurring(() -> {
if (!StorageProxy.isSafeToPerformRead())
if (!StorageProxy.hasJoined())
{
logger.trace("Setup task may not run due to it not being safe to perform reads... rescheduling");
scheduleSetupTask(setupTask);
@ -487,7 +495,7 @@ public class CassandraRoleManager implements IRoleManager
}
catch (RequestValidationException e)
{
throw new AssertionError(e); // not supposed to happen
throw new AssertionError(e + " " + FBUtilities.getJustLocalAddress()); // not supposed to happen
}
}

View File

@ -28,6 +28,7 @@ import com.google.common.collect.Sets;
import org.apache.cassandra.dht.Datacenters;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.tcm.ClusterMetadata;
public abstract class DCPermissions
{
@ -93,7 +94,7 @@ public abstract class DCPermissions
public void validate()
{
Set<String> unknownDcs = Sets.difference(subset, Datacenters.getValidDatacenters());
Set<String> unknownDcs = Sets.difference(subset, Datacenters.getValidDatacenters(ClusterMetadata.current()));
if (!unknownDcs.isEmpty())
{
throw new InvalidRequestException(String.format("Invalid value(s) for DATACENTERS '%s'," +

View File

@ -22,6 +22,8 @@ import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import com.google.common.annotations.VisibleForTesting;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.exceptions.RequestExecutionException;
import org.apache.cassandra.exceptions.RequestValidationException;
@ -226,7 +228,16 @@ public interface IRoleManager extends AuthCache.BulkLoader<RoleResource, Set<Rol
*
* For example, use this method to create any required keyspaces/column families.
*/
void setup();
default void setup()
{
setup(true);
}
/**
* Like the method above, but allows to disable async role setup, making it synchronous.
*/
@VisibleForTesting
void setup(boolean asyncRoleSetup);
/**
* Each valid identity is associated with a role in the identity_to_role table, this method returns role

View File

@ -36,6 +36,8 @@ import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Iterables;
import com.google.common.util.concurrent.RateLimiter;
import org.apache.cassandra.concurrent.ScheduledExecutorPlus;
import org.apache.cassandra.schema.KeyspaceMetadata;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.utils.TimeUUID;
import org.apache.cassandra.utils.concurrent.Future;
import org.slf4j.Logger;
@ -203,7 +205,7 @@ public class BatchlogManager implements BatchlogManagerMBean
// rate limit is in bytes per second. Uses Double.MAX_VALUE if disabled (set to 0 in cassandra.yaml).
// max rate is scaled by the number of nodes in the cluster (same as for HHOM - see CASSANDRA-5272).
int endpointsCount = StorageService.instance.getTokenMetadata().getSizeOfAllEndpoints();
int endpointsCount = ClusterMetadata.current().directory.allJoinedEndpoints().size();
if (endpointsCount <= 0)
{
logger.trace("Replay cancelled as there are no peers in the ring.");
@ -234,7 +236,7 @@ public class BatchlogManager implements BatchlogManagerMBean
*/
public void setRate(final int throttleInKB)
{
int endpointsCount = StorageService.instance.getTokenMetadata().getSizeOfAllEndpoints();
int endpointsCount = ClusterMetadata.current().directory.allAddresses().size();
if (endpointsCount > 0)
{
int endpointThrottleInKiB = throttleInKB / endpointsCount;
@ -471,29 +473,27 @@ public class BatchlogManager implements BatchlogManagerMBean
Set<UUID> hintedNodes)
{
String ks = mutation.getKeyspaceName();
Keyspace keyspace = Keyspace.open(ks);
Token tk = mutation.key().getToken();
ClusterMetadata metadata = ClusterMetadata.current();
KeyspaceMetadata keyspaceMetadata = metadata.schema.getKeyspaceMetadata(ks);
// TODO: this logic could do with revisiting at some point, as it is unclear what its rationale is
// we perform a local write, ignoring errors and inline in this thread (potentially slowing replay down)
// effectively bumping CL for locally owned writes and also potentially stalling log replay if an error occurs
// once we decide how it should work, it can also probably be simplified, and avoid constructing a ReplicaPlan directly
ReplicaLayout.ForTokenWrite liveAndDown = ReplicaLayout.forTokenWriteLiveAndDown(keyspace, tk);
Replicas.temporaryAssertFull(liveAndDown.all()); // TODO in CASSANDRA-14549
ReplicaLayout.ForTokenWrite allReplias = ReplicaLayout.forTokenWriteLiveAndDown(metadata, keyspaceMetadata, tk);
ReplicaPlan.ForWrite replicaPlan = forReplayMutation(metadata, Keyspace.open(ks), tk);
Replica selfReplica = liveAndDown.all().selfIfPresent();
Replica selfReplica = allReplias.all().selfIfPresent();
if (selfReplica != null)
mutation.apply();
ReplicaLayout.ForTokenWrite liveRemoteOnly = liveAndDown.filter(
r -> FailureDetector.isReplicaAlive.test(r) && r != selfReplica);
for (Replica replica : liveAndDown.all())
for (Replica replica : allReplias.all())
{
if (replica == selfReplica || liveRemoteOnly.all().contains(replica))
if (replica == selfReplica || replicaPlan.liveAndDown().contains(replica))
continue;
UUID hostId = StorageService.instance.getHostIdForEndpoint(replica.endpoint());
UUID hostId = metadata.directory.peerId(replica.endpoint()).toUUID();
if (null != hostId)
{
HintsService.instance.write(hostId, Hint.create(mutation, writtenAt));
@ -501,15 +501,26 @@ public class BatchlogManager implements BatchlogManagerMBean
}
}
ReplicaPlan.ForWrite replicaPlan = new ReplicaPlan.ForWrite(keyspace, liveAndDown.replicationStrategy(),
ConsistencyLevel.ONE, liveRemoteOnly.pending(), liveRemoteOnly.all(), liveRemoteOnly.all(), liveRemoteOnly.all());
ReplayWriteResponseHandler<Mutation> handler = new ReplayWriteResponseHandler<>(replicaPlan, mutation, nanoTime());
Message<Mutation> message = Message.outWithFlag(MUTATION_REQ, mutation, MessageFlag.CALL_BACK_ON_FAILURE);
for (Replica replica : liveRemoteOnly.all())
for (Replica replica : replicaPlan.liveAndDown())
MessagingService.instance().sendWriteWithCallback(message, replica, handler);
return handler;
}
public static ReplicaPlan.ForWrite forReplayMutation(ClusterMetadata metadata, Keyspace keyspace, Token token)
{
ReplicaLayout.ForTokenWrite liveAndDown = ReplicaLayout.forTokenWriteLiveAndDown(metadata, keyspace.getMetadata(), token);
Replicas.temporaryAssertFull(liveAndDown.all()); // TODO in CASSANDRA-14549
Replica selfReplica = liveAndDown.all().selfIfPresent();
ReplicaLayout.ForTokenWrite liveRemoteOnly = liveAndDown.filter(r -> FailureDetector.isReplicaAlive.test(r) && r != selfReplica);
return new ReplicaPlan.ForWrite(keyspace, liveAndDown.replicationStrategy(),
ConsistencyLevel.ONE, liveRemoteOnly.pending(), liveRemoteOnly.all(), liveRemoteOnly.all(), liveRemoteOnly.all(),
(cm) -> forReplayMutation(cm, keyspace, token),
metadata.epoch);
}
private static int gcgs(Collection<Mutation> mutations)
{
int gcgs = Integer.MAX_VALUE;

View File

@ -60,6 +60,7 @@ import org.apache.cassandra.schema.SchemaConstants;
import org.apache.cassandra.schema.TableId;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.CacheService;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.utils.JVMStabilityInspector;
import org.apache.cassandra.utils.Pair;
import org.apache.cassandra.utils.concurrent.Future;
@ -219,12 +220,13 @@ public class AutoSavingCache<K extends CacheKey, V> extends InstrumentingCache<K
in = streamFactory.getInputStream(dataPath, crcPath);
//Check the schema has not changed since CFs are looked up by name which is ambiguous
UUID schemaVersion = new UUID(in.readLong(), in.readLong());
if (!schemaVersion.equals(Schema.instance.getVersion()))
UUID expected = new UUID(in.readLong(), in.readLong());
UUID actual = ClusterMetadata.current().schema.getVersion();
if (!expected.equals(actual))
throw new RuntimeException("Cache schema version "
+ schemaVersion
+ expected
+ " does not match current schema version "
+ Schema.instance.getVersion());
+ actual);
ArrayDeque<Future<Pair<K, V>>> futures = new ArrayDeque<>();
long loadByNanos = start + TimeUnit.SECONDS.toNanos(DatabaseDescriptor.getCacheLoadTimeout());

View File

@ -17,12 +17,14 @@
*/
package org.apache.cassandra.concurrent;
import java.util.Arrays;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
import com.google.common.annotations.VisibleForTesting;
import io.netty.util.concurrent.FastThreadLocalThread;
import org.apache.cassandra.config.CassandraRelevantProperties;
import org.apache.cassandra.utils.JVMStabilityInspector;
/**
@ -33,6 +35,8 @@ import org.apache.cassandra.utils.JVMStabilityInspector;
public class NamedThreadFactory implements ThreadFactory
{
public static final Boolean PRESERVE_THREAD_CREATION_STACKTRACE = CassandraRelevantProperties.TEST_PRESERVE_THREAD_CREATION_STACKTRACE.getBoolean();
private static final AtomicInteger anonymousCounter = new AtomicInteger();
private static volatile String globalPrefix;
@ -159,11 +163,43 @@ public class NamedThreadFactory implements ThreadFactory
public static Thread createThread(ThreadGroup threadGroup, Runnable runnable, String name, boolean daemon)
{
String prefix = globalPrefix;
Thread thread = new FastThreadLocalThread(threadGroup, runnable, prefix != null ? prefix + name : name);
Thread thread;
String threadName = prefix != null ? prefix + name : name;
if (PRESERVE_THREAD_CREATION_STACKTRACE)
thread = new InspectableFastThreadLocalThread(threadGroup, runnable, threadName);
else
thread = new FastThreadLocalThread(threadGroup, runnable, threadName);
thread.setDaemon(daemon);
return thread;
}
public static class InspectableFastThreadLocalThread extends FastThreadLocalThread
{
public StackTraceElement[] creationTrace;
private void setStack()
{
creationTrace = Thread.currentThread().getStackTrace();
creationTrace = Arrays.copyOfRange(creationTrace, 2, creationTrace.length);
}
public InspectableFastThreadLocalThread() { super(); setStack(); }
public InspectableFastThreadLocalThread(Runnable target) { super(target); setStack(); }
public InspectableFastThreadLocalThread(ThreadGroup group, Runnable target) { super(group, target); setStack(); }
public InspectableFastThreadLocalThread(String name) { super(name); setStack(); }
public InspectableFastThreadLocalThread(ThreadGroup group, String name) { super(group, name); setStack(); }
public InspectableFastThreadLocalThread(Runnable target, String name) { super(target, name); setStack(); }
public InspectableFastThreadLocalThread(ThreadGroup group, Runnable target, String name) { super(group, target, name); setStack(); }
public InspectableFastThreadLocalThread(ThreadGroup group, Runnable target, String name, long stackSize) { super(group, target, name, stackSize); setStack(); }
}
public static <T extends Thread> T setupThread(T thread, int priority, ClassLoader contextClassLoader, Thread.UncaughtExceptionHandler uncaughtExceptionHandler)
{
thread.setPriority(priority);

View File

@ -55,6 +55,8 @@ public enum Stage
INTERNAL_RESPONSE (false, "InternalResponseStage", "internal", FBUtilities::getAvailableProcessors, null, Stage::multiThreadedStage),
IMMEDIATE (false, "ImmediateStage", "internal", () -> 0, null, Stage::immediateExecutor),
PAXOS_REPAIR (false, "PaxosRepairStage", "internal", FBUtilities::getAvailableProcessors, null, Stage::multiThreadedStage),
INTERNAL_METADATA (false, "InternalMetadataStage", "internal", FBUtilities::getAvailableProcessors, null, Stage::multiThreadedStage),
FETCH_LOG (false, "MetadataFetchLogStage", "internal", () -> 1, null, Stage::singleThreadedStage)
;
public final String jmxName;

View File

@ -353,7 +353,7 @@ public enum CassandraRelevantProperties
MEMTABLE_OVERHEAD_SIZE("cassandra.memtable.row_overhead_size", "-1"),
MEMTABLE_SHARD_COUNT("cassandra.memtable.shard.count"),
MEMTABLE_TRIE_SIZE_LIMIT("cassandra.trie_size_limit_mb"),
MIGRATION_DELAY("cassandra.migration_delay_ms", "60000"),
METRICS_REPORTER_CONFIG_FILE("cassandra.metricsReporterConfigFile"),
/** Defines the maximum number of unique timed out queries that will be reported in the logs. Use a negative number to remove any limit. */
MONITORING_MAX_OPERATIONS("cassandra.monitoring_max_operations", "50"),
/** Defines the interval for reporting any operations that have timed out. */
@ -468,7 +468,6 @@ public enum CassandraRelevantProperties
*/
SAI_VECTOR_SEARCH_ORDER_CHUNK_SIZE("cassandra.sai.vector_search.order_chunk_size", "100000"),
SCHEMA_PULL_INTERVAL_MS("cassandra.schema_pull_interval_ms", "60000"),
SCHEMA_UPDATE_HANDLER_FACTORY_CLASS("cassandra.schema.update_handler_factory.class"),
SEARCH_CONCURRENCY_FACTOR("cassandra.search_concurrency_factor", "1"),
@ -482,6 +481,7 @@ public enum CassandraRelevantProperties
SET_SEP_THREAD_NAME("cassandra.set_sep_thread_name", "true"),
SHUTDOWN_ANNOUNCE_DELAY_IN_MS("cassandra.shutdown_announce_in_ms", "2000"),
SIZE_RECORDER_INTERVAL("cassandra.size_recorder_interval", "300"),
SKIP_GC_INSPECTOR("cassandra.skip_gc_inspector", "false"),
SKIP_PAXOS_REPAIR_ON_TOPOLOGY_CHANGE("cassandra.skip_paxos_repair_on_topology_change"),
/** If necessary for operational purposes, permit certain keyspaces to be ignored for paxos topology repairs. */
SKIP_PAXOS_REPAIR_ON_TOPOLOGY_CHANGE_KEYSPACES("cassandra.skip_paxos_repair_on_topology_change_keyspaces"),
@ -518,6 +518,30 @@ public enum CassandraRelevantProperties
SYSTEM_AUTH_DEFAULT_RF("cassandra.system_auth.default_rf", "1"),
SYSTEM_DISTRIBUTED_DEFAULT_RF("cassandra.system_distributed.default_rf", "3"),
SYSTEM_TRACES_DEFAULT_RF("cassandra.system_traces.default_rf", "2"),
// transactional cluster metadata relevant properties
// TODO: not a fan of being forced to prefix these to satisfy the alphabetic ordering constraint
// but it makes sense to group logically related properties together
TCM_ALLOW_TRANSFORMATIONS_DURING_UPGRADES("cassandra.allow_transformations_during_upgrades", "false"),
/**
* for obtaining acknowlegement from peers to make progress in multi-step operations
*/
TCM_PROGRESS_BARRIER_BACKOFF_MILLIS("cassandra.progress_barrier_backoff_ms", "1000"),
TCM_PROGRESS_BARRIER_TIMEOUT_MILLIS("cassandra.progress_barrier_timeout_ms", "3600000"),
/**
* size of in-memory index of max epoch -> sealed period
*/
TCM_RECENTLY_SEALED_PERIOD_INDEX_SIZE("cassandra.recently_sealed_period_index_size", "10"),
/**
* should replica groups in data placements be sorted to ensure the primary replica is first in the list
*/
TCM_SORT_REPLICA_GROUPS("cassandra.sorted_replica_groups_enabled", "true"),
TCM_UNSAFE_BOOT_WITH_CLUSTERMETADATA("cassandra.unsafe_boot_with_clustermetadata", null),
TCM_USE_ATOMIC_LONG_PROCESSOR("cassandra.test.use_atomic_long_processor", "false"),
TCM_USE_NO_OP_REPLICATOR("cassandra.test.use_no_op_replicator", "false"),
TEST_BBFAILHELPER_ENABLED("test.bbfailhelper.enabled"),
TEST_BLOB_SHARED_SEED("cassandra.test.blob.shared.seed"),
TEST_BYTEMAN_TRANSFORMATIONS_DEBUG("cassandra.test.byteman.transformations.debug"),
@ -543,13 +567,16 @@ public enum CassandraRelevantProperties
TEST_IGNORE_SIGAR("cassandra.test.ignore_sigar"),
TEST_INVALID_LEGACY_SSTABLE_ROOT("invalid-legacy-sstable-root"),
TEST_JVM_DTEST_DISABLE_SSL("cassandra.test.disable_ssl"),
TEST_JVM_SHUTDOWN_MESSAGING_GRACEFULLY("cassandra.test.messagingService.gracefulShutdown", "false"),
TEST_LEGACY_SSTABLE_ROOT("legacy-sstable-root"),
TEST_ORG_CAFFINITAS_OHC_SEGMENTCOUNT("org.caffinitas.ohc.segmentCount"),
TEST_PRESERVE_THREAD_CREATION_STACKTRACE("cassandra.test.preserve_thread_creation_stacktrace", "false"),
TEST_RANDOM_SEED("cassandra.test.random.seed"),
TEST_READ_ITERATION_DELAY_MS("cassandra.test.read_iteration_delay_ms", "0"),
TEST_REUSE_PREPARED("cassandra.test.reuse_prepared", "true"),
TEST_ROW_CACHE_SIZE("cassandra.test.row_cache_size"),
TEST_SERIALIZATION_WRITES("cassandra.test-serialization-writes"),
TEST_SIGAR_NATIVE_LOGGING("sigar.nativeLogging", "true"),
TEST_SIMULATOR_DEBUG("cassandra.test.simulator.debug"),
TEST_SIMULATOR_DETERMINISM_CHECK("cassandra.test.simulator.determinismcheck", "none"),
TEST_SIMULATOR_LIVENESS_CHECK("cassandra.test.simulator.livenesscheck", "true"),
@ -590,7 +617,9 @@ public enum CassandraRelevantProperties
USE_NIX_RECURSIVE_DELETE("cassandra.use_nix_recursive_delete"),
/** Gossiper compute expiration timeout. Default value 3 days. */
VERY_LONG_TIME_MS("cassandra.very_long_time_ms", "259200000"),
WAIT_FOR_TRACING_EVENTS_TIMEOUT_SECS("cassandra.wait_for_tracing_events_timeout_secs", "0");
WAIT_FOR_TRACING_EVENTS_TIMEOUT_SECS("cassandra.wait_for_tracing_events_timeout_secs", "0"),
;
static
{

View File

@ -171,6 +171,15 @@ public class Config
public volatile DurationSpec.LongMillisecondsBound stream_transfer_task_timeout = new DurationSpec.LongMillisecondsBound("12h");
public volatile DurationSpec.LongMillisecondsBound cms_await_timeout = new DurationSpec.LongMillisecondsBound("120000ms");
public volatile int cms_default_max_retries = 10;
public volatile DurationSpec.IntMillisecondsBound cms_default_retry_backoff = new DurationSpec.IntMillisecondsBound("50ms");
/**
* How often we should snapshot the cluster metadata.
*/
public volatile int metadata_snapshot_frequency = 100;
public volatile double phi_convict_threshold = 8.0;
public int concurrent_reads = 32;
@ -1243,5 +1252,21 @@ public class Config
public double severity_during_decommission = 0;
public StorageCompatibilityMode storage_compatibility_mode = StorageCompatibilityMode.CASSANDRA_4;
// TODO Revisit MessagingService::current_version
public StorageCompatibilityMode storage_compatibility_mode = StorageCompatibilityMode.NONE;
/**
* For the purposes of progress barrier we only support ALL, EACH_QUORUM, QUORUM, LOCAL_QUORUM, ANY, and ONE.
*
* We will still try all consistency levels above the lowest acceptable, and only fall back to it if we can not
* collect enough nodes.
*/
public volatile ConsistencyLevel progress_barrier_min_consistency_level = ConsistencyLevel.EACH_QUORUM;
public volatile boolean log_out_of_token_range_requests = true;
public volatile boolean reject_out_of_token_range_requests = true;
public volatile ConsistencyLevel progress_barrier_default_consistency_level = ConsistencyLevel.EACH_QUORUM;
public volatile DurationSpec.LongMillisecondsBound progress_barrier_timeout = new DurationSpec.LongMillisecondsBound("3600000ms");
public volatile DurationSpec.LongMillisecondsBound progress_barrier_backoff = new DurationSpec.LongMillisecondsBound("1000ms");
public boolean unsafe_tcm_mode = false;
}

View File

@ -29,10 +29,12 @@ import java.net.UnknownHostException;
import java.nio.file.FileStore;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@ -84,6 +86,7 @@ import org.apache.cassandra.dht.IPartitioner;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.fql.FullQueryLoggerOptions;
import org.apache.cassandra.gms.IFailureDetector;
import org.apache.cassandra.gms.VersionedValue;
import org.apache.cassandra.io.FSWriteError;
import org.apache.cassandra.io.sstable.format.SSTableFormat;
import org.apache.cassandra.io.sstable.format.big.BigFormat;
@ -104,6 +107,7 @@ import org.apache.cassandra.security.EncryptionContext;
import org.apache.cassandra.security.JREProvider;
import org.apache.cassandra.security.SSLFactory;
import org.apache.cassandra.service.CacheService.CacheType;
import org.apache.cassandra.service.StorageService;
import org.apache.cassandra.service.paxos.Paxos;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.StorageCompatibilityMode;
@ -135,6 +139,7 @@ import static org.apache.cassandra.config.CassandraRelevantProperties.UNSAFE_SYS
import static org.apache.cassandra.config.DataRateSpec.DataRateUnit.BYTES_PER_SECOND;
import static org.apache.cassandra.config.DataRateSpec.DataRateUnit.MEBIBYTES_PER_SECOND;
import static org.apache.cassandra.config.DataStorageSpec.DataStorageUnit.MEBIBYTES;
import static org.apache.cassandra.db.ConsistencyLevel.*;
import static org.apache.cassandra.io.util.FileUtils.ONE_GIB;
import static org.apache.cassandra.io.util.FileUtils.ONE_MIB;
import static org.apache.cassandra.utils.Clock.Global.logInitializationOutcome;
@ -968,6 +973,20 @@ public class DatabaseDescriptor
throw new ConfigurationException(String.format("Invalid configuration. Heap dump is enabled but cannot create heap dump output path: %s.", conf.heap_dump_path != null ? conf.heap_dump_path : "null"));
conf.sai_options.validate();
List<ConsistencyLevel> progressBarrierCLsArr = Arrays.asList(ALL, EACH_QUORUM, LOCAL_QUORUM, QUORUM, ONE, NODE_LOCAL);
Set<ConsistencyLevel> progressBarrierCls = new HashSet<>(progressBarrierCLsArr);
if (!progressBarrierCls.contains(conf.progress_barrier_min_consistency_level))
{
throw new ConfigurationException(String.format("Invalid value for progress_barrier_min_consistency_level %s. Allowed values: %s",
conf.progress_barrier_min_consistency_level, progressBarrierCLsArr));
}
if (!progressBarrierCls.contains(conf.progress_barrier_default_consistency_level))
{
throw new ConfigurationException(String.format("Invalid value for.progress_barrier_default_consistency_level %s. Allowed values: %s",
conf.progress_barrier_default_consistency_level, progressBarrierCLsArr));
}
}
@VisibleForTesting
@ -1518,7 +1537,9 @@ public class DatabaseDescriptor
private static long tryGetSpace(String dir, PathUtils.IOToLongFunction<FileStore> getSpace)
{
return PathUtils.tryGetSpace(new File(dir).toPath(), getSpace, e -> { throw new ConfigurationException("Unable check disk space in '" + dir + "'. Perhaps the Cassandra user does not have the necessary permissions"); });
return PathUtils.tryGetSpace(new File(dir).toPath(), getSpace, e -> {
throw new ConfigurationException("Unable check disk space in '" + dir + "'. Perhaps the Cassandra user does not have the necessary permissions");
});
}
public static IEndpointSnitch createEndpointSnitch(boolean dynamic, String snitchClassName) throws ConfigurationException
@ -1546,6 +1567,7 @@ public class DatabaseDescriptor
{
DatabaseDescriptor.cryptoProvider = cryptoProvider;
}
public static IAuthenticator getAuthenticator()
{
return authenticator;
@ -1872,6 +1894,13 @@ public class DatabaseDescriptor
/* For tests ONLY, don't use otherwise or all hell will break loose. Tests should restore value at the end. */
public static IPartitioner setPartitionerUnsafe(IPartitioner newPartitioner)
{
IPartitioner old = setOnlyPartitionerUnsafe(newPartitioner);
StorageService.instance.valueFactory = new VersionedValue.VersionedValueFactory(partitioner);
return old;
}
public static IPartitioner setOnlyPartitionerUnsafe(IPartitioner newPartitioner)
{
IPartitioner old = partitioner;
partitioner = newPartitioner;
@ -1882,6 +1911,7 @@ public class DatabaseDescriptor
{
return snitch;
}
public static void setEndpointSnitch(IEndpointSnitch eps)
{
snitch = eps;
@ -2021,7 +2051,8 @@ public class DatabaseDescriptor
try
{
return UUID.fromString(REPLACE_NODE.getString());
} catch (NullPointerException e)
}
catch (NullPointerException e)
{
return null;
}
@ -2634,6 +2665,7 @@ public class DatabaseDescriptor
* Update commitlog_segment_size in the tests.
* {@link CommitLogSegmentManagerCDC} uses the CommitLogSegmentSize to estimate the file size on allocation.
* It is important to keep the value unchanged for the estimation to be correct.
*
* @param sizeMebibytes
*/
@VisibleForTesting /* Only for testing */
@ -2729,7 +2761,7 @@ public class DatabaseDescriptor
* refer to it as native address although some places still call it RPC address. It's not thrift RPC anymore
* so native is more appropriate. The address alone is not enough to uniquely identify this instance because
* multiple instances might use the same interface with different ports.
*
* <p>
* May be null, please use {@link FBUtilities#getBroadcastNativeAddressAndPort()} instead.
*/
public static InetAddress getBroadcastRpcAddress()
@ -2872,7 +2904,7 @@ public class DatabaseDescriptor
/**
* If this value is set to <= 0 it will move auth requests to the standard request pool regardless of the current
* size of the {@link org.apache.cassandra.transport.Dispatcher#authExecutor}'s active size.
*
* <p>
* see {@link org.apache.cassandra.transport.Dispatcher#dispatch} for executor selection
*/
public static void setNativeTransportMaxAuthThreads(int threads)
@ -3211,6 +3243,7 @@ public class DatabaseDescriptor
{
conf.auto_snapshot = autoSnapshot;
}
@VisibleForTesting
public static boolean getAutoSnapshot()
{
@ -3322,6 +3355,7 @@ public class DatabaseDescriptor
{
return conf.dynamic_snitch_update_interval.toMilliseconds();
}
public static void setDynamicUpdateInterval(int dynamicUpdateInterval)
{
conf.dynamic_snitch_update_interval = new DurationSpec.IntMillisecondsBound(dynamicUpdateInterval);
@ -3331,6 +3365,7 @@ public class DatabaseDescriptor
{
return conf.dynamic_snitch_reset_interval.toMilliseconds();
}
public static void setDynamicResetInterval(int dynamicResetInterval)
{
conf.dynamic_snitch_reset_interval = new DurationSpec.IntMillisecondsBound(dynamicResetInterval);
@ -3513,7 +3548,9 @@ public class DatabaseDescriptor
conf.key_cache_migrate_during_compaction = migrateCacheEntry;
}
/** This method can return negative number for disabled */
/**
* This method can return negative number for disabled
*/
public static int getSSTablePreemptiveOpenIntervalInMiB()
{
if (conf.sstable_preemptive_open_interval == null)
@ -3521,7 +3558,9 @@ public class DatabaseDescriptor
return conf.sstable_preemptive_open_interval.toMebibytes();
}
/** Negative number for disabled */
/**
* Negative number for disabled
*/
public static void setSSTablePreemptiveOpenIntervalInMiB(int mib)
{
if (mib < 0)
@ -3809,8 +3848,10 @@ public class DatabaseDescriptor
{
switch (datamodel)
{
case "64": return true;
case "32": return false;
case "64":
return true;
case "32":
return false;
}
}
String arch = OS_ARCH.getString();
@ -4572,7 +4613,10 @@ public class DatabaseDescriptor
conf.row_index_read_size_fail_threshold = value;
}
public static int getDefaultKeyspaceRF() { return conf.default_keyspace_rf; }
public static int getDefaultKeyspaceRF()
{
return conf.default_keyspace_rf;
}
public static void setDefaultKeyspaceRF(int value) throws IllegalArgumentException
{
@ -4665,11 +4709,13 @@ public class DatabaseDescriptor
}
}
public static DurationSpec.IntSecondsBound getStreamingSlowEventsLogTimeout() {
public static DurationSpec.IntSecondsBound getStreamingSlowEventsLogTimeout()
{
return conf.streaming_slow_events_log_timeout;
}
public static void setStreamingSlowEventsLogTimeout(String value) {
public static void setStreamingSlowEventsLogTimeout(String value)
{
DurationSpec.IntSecondsBound next = new DurationSpec.IntSecondsBound(value);
if (!conf.streaming_slow_events_log_timeout.equals(next))
{
@ -4785,6 +4831,7 @@ public class DatabaseDescriptor
* both the more evolved cassandra.yaml approach but also the -XX param to override it on a one-off basis so you don't
* have to change the full config of a node or a cluster in order to get a heap dump from a single node that's
* misbehaving.
*
* @return the absolute path of the -XX param if provided, else the heap_dump_path in cassandra.yaml
*/
public static Path getHeapDumpPath()
@ -4911,4 +4958,94 @@ public class DatabaseDescriptor
{
return conf == null ? new RepairRetrySpec() : conf.repair.retries;
}
public static int getCmsDefaultRetryMaxTries()
{
return conf.cms_default_max_retries;
}
public static void setCmsDefaultRetryMaxTries(int value)
{
conf.cms_default_max_retries = value;
}
public static DurationSpec getDefaultRetryBackoff()
{
return conf.cms_default_retry_backoff;
}
public static DurationSpec getCmsAwaitTimeout()
{
return conf.cms_await_timeout;
}
public static int getMetadataSnapshotFrequency()
{
return conf.metadata_snapshot_frequency;
}
public static void setMetadataSnapshotFrequency(int frequency)
{
conf.metadata_snapshot_frequency = frequency;
}
public static ConsistencyLevel getProgressBarrierMinConsistencyLevel()
{
return conf.progress_barrier_min_consistency_level;
}
public static void setProgressBarrierMinConsistencyLevel(ConsistencyLevel newLevel)
{
conf.progress_barrier_min_consistency_level = newLevel;
}
public static boolean getLogOutOfTokenRangeRequests()
{
return conf.log_out_of_token_range_requests;
}
public static void setLogOutOfTokenRangeRequests(boolean enabled)
{
conf.log_out_of_token_range_requests = enabled;
}
public static boolean getRejectOutOfTokenRangeRequests()
{
return conf.reject_out_of_token_range_requests;
}
public static void setRejectOutOfTokenRangeRequests(boolean enabled)
{
conf.reject_out_of_token_range_requests = enabled;
}
public static ConsistencyLevel getProgressBarrierDefaultConsistencyLevel()
{
return conf.progress_barrier_default_consistency_level;
}
public static long getProgressBarrierTimeout(TimeUnit unit)
{
return conf.progress_barrier_timeout.to(unit);
}
public static long getProgressBarrierBackoff(TimeUnit unit)
{
return conf.progress_barrier_backoff.to(unit);
}
public static void setProgressBarrierTimeout(long timeOutInMillis)
{
conf.progress_barrier_timeout = new DurationSpec.LongMillisecondsBound(timeOutInMillis);
}
public static void setProgressBarrierBackoff(long timeOutInMillis)
{
conf.progress_barrier_backoff = new DurationSpec.LongMillisecondsBound(timeOutInMillis);
}
public static boolean getUnsafeTCMMode()
{
return conf.unsafe_tcm_mode;
}
}

View File

@ -18,28 +18,63 @@
package org.apache.cassandra.cql3;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Predicate;
import com.google.common.collect.*;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import com.google.common.primitives.Ints;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.antlr.runtime.*;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.antlr.runtime.RecognitionException;
import org.apache.cassandra.concurrent.ImmediateExecutor;
import org.apache.cassandra.concurrent.ScheduledExecutors;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.cql3.functions.Function;
import org.apache.cassandra.cql3.functions.FunctionName;
import org.apache.cassandra.cql3.functions.UDAggregate;
import org.apache.cassandra.cql3.functions.UDFunction;
import org.apache.cassandra.cql3.selection.ResultSetBuilder;
import org.apache.cassandra.cql3.statements.BatchStatement;
import org.apache.cassandra.cql3.statements.ModificationStatement;
import org.apache.cassandra.cql3.statements.QualifiedStatement;
import org.apache.cassandra.cql3.statements.SelectStatement;
import org.apache.cassandra.cql3.statements.schema.AlterSchemaStatement;
import org.apache.cassandra.db.ConsistencyLevel;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.ReadCommand;
import org.apache.cassandra.db.ReadQuery;
import org.apache.cassandra.db.ReadResponse;
import org.apache.cassandra.db.SinglePartitionReadQuery;
import org.apache.cassandra.db.SystemKeyspace;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.partitions.PartitionIterator;
import org.apache.cassandra.db.partitions.PartitionIterators;
import org.apache.cassandra.db.partitions.UnfilteredPartitionIterators;
import org.apache.cassandra.db.rows.Row;
import org.apache.cassandra.db.rows.RowIterator;
import org.apache.cassandra.exceptions.CassandraException;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.exceptions.IsBootstrappingException;
import org.apache.cassandra.exceptions.RequestExecutionException;
import org.apache.cassandra.exceptions.RequestValidationException;
import org.apache.cassandra.exceptions.SyntaxException;
import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.metrics.CQLMetrics;
import org.apache.cassandra.metrics.ClientRequestMetrics;
import org.apache.cassandra.metrics.ClientRequestsMetricsHolder;
import org.apache.cassandra.net.Message;
@ -49,27 +84,20 @@ import org.apache.cassandra.schema.Schema;
import org.apache.cassandra.schema.SchemaChangeListener;
import org.apache.cassandra.schema.SchemaConstants;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.cql3.functions.UDAggregate;
import org.apache.cassandra.cql3.functions.UDFunction;
import org.apache.cassandra.cql3.functions.Function;
import org.apache.cassandra.cql3.functions.FunctionName;
import org.apache.cassandra.cql3.selection.ResultSetBuilder;
import org.apache.cassandra.cql3.statements.*;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.rows.Row;
import org.apache.cassandra.db.rows.RowIterator;
import org.apache.cassandra.db.partitions.PartitionIterator;
import org.apache.cassandra.db.partitions.PartitionIterators;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.exceptions.*;
import org.apache.cassandra.gms.Gossiper;
import org.apache.cassandra.metrics.CQLMetrics;
import org.apache.cassandra.service.*;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.service.QueryState;
import org.apache.cassandra.service.StorageService;
import org.apache.cassandra.service.pager.QueryPager;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.tracing.Tracing;
import org.apache.cassandra.transport.ProtocolVersion;
import org.apache.cassandra.transport.messages.ResultMessage;
import org.apache.cassandra.utils.*;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.CassandraVersion;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.JVMStabilityInspector;
import org.apache.cassandra.utils.MD5Digest;
import org.apache.cassandra.utils.ObjectSizes;
import org.apache.cassandra.utils.concurrent.Future;
import org.apache.cassandra.utils.concurrent.FutureCombiner;
@ -206,7 +234,6 @@ public class QueryProcessor implements QueryHandler
private QueryProcessor()
{
Schema.instance.registerListener(new StatementInvalidatingListener());
}
@VisibleForTesting
@ -428,11 +455,16 @@ public class QueryProcessor implements QueryHandler
qualifiedStatement.setKeyspace(clientState);
keyspace = qualifiedStatement.keyspace();
}
// Note: if 2 threads prepare the same query, we'll live so don't bother synchronizing
CQLStatement statement = raw.prepare(clientState);
statement.validate(clientState);
// Set CQL string for AlterSchemaStatement as this is used to serialize the transformation
// in the cluster metadata log
if (statement instanceof AlterSchemaStatement)
((AlterSchemaStatement)statement).setCql(query);
if (isInternal)
return new Prepared(statement, "", fullyQualified, keyspace);
else
@ -641,7 +673,7 @@ public class QueryProcessor implements QueryHandler
synchronized (this)
{
CassandraVersion minVersion = Gossiper.instance.getMinVersion(DatabaseDescriptor.getWriteRpcTimeout(TimeUnit.MILLISECONDS), TimeUnit.MILLISECONDS);
CassandraVersion minVersion = ClusterMetadata.current().directory.clusterMinVersion.cassandraVersion;
if (minVersion != null && minVersion.compareTo(NEW_PREPARED_STATEMENT_BEHAVIOUR_SINCE_40, true) >= 0)
{
logger.info("Fully upgraded to at least {}", minVersion);
@ -854,7 +886,13 @@ public class QueryProcessor implements QueryHandler
((QualifiedStatement) statement).setKeyspace(clientState);
Tracing.trace("Preparing statement");
return statement.prepare(clientState);
CQLStatement prepared = statement.prepare(clientState);
// Set CQL string for AlterSchemaStatement as this is used to serialize the transformation
// in the cluster metadata log
if (prepared instanceof AlterSchemaStatement)
((AlterSchemaStatement) prepared).setCql(queryStr);
return prepared;
}
public static <T extends CQLStatement.Raw> T parseStatement(String queryStr, Class<T> klass, String type) throws SyntaxException
@ -917,6 +955,11 @@ public class QueryProcessor implements QueryHandler
preparedStatements.asMap().clear();
}
public static void registerStatementInvalidatingListener()
{
Schema.instance.registerListener(new StatementInvalidatingListener());
}
private static class StatementInvalidatingListener implements SchemaChangeListener
{
private static void removeInvalidPreparedStatements(String ksName, String cfName)
@ -1019,7 +1062,7 @@ public class QueryProcessor implements QueryHandler
{
// in case there are other overloads, we have to remove all overloads since argument type
// matching may change (due to type casting)
if (Schema.instance.getKeyspaceMetadata(ksName).userFunctions.get(new FunctionName(ksName, functionName)).size() > 1)
if (!Schema.instance.getKeyspaceMetadata(ksName).userFunctions.get(new FunctionName(ksName, functionName)).isEmpty())
removeInvalidPreparedStatementsForFunction(ksName, functionName);
}

View File

@ -17,6 +17,7 @@
*/
package org.apache.cassandra.cql3.functions;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.*;
@ -30,13 +31,20 @@ import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.marshal.UserType;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.tcm.serialization.Version;
import org.apache.cassandra.schema.CQLTypeParser;
import org.apache.cassandra.schema.Difference;
import org.apache.cassandra.schema.UserFunctions;
import org.apache.cassandra.schema.Types;
import org.apache.cassandra.tracing.Tracing;
import org.apache.cassandra.transport.ProtocolVersion;
import org.apache.cassandra.utils.ByteBufferUtil;
import static com.google.common.collect.Iterables.any;
import static com.google.common.collect.Iterables.transform;
import static org.apache.cassandra.db.TypeSizes.sizeof;
import static org.apache.cassandra.utils.Clock.Global.nanoTime;
/**
@ -44,6 +52,8 @@ import static org.apache.cassandra.utils.Clock.Global.nanoTime;
*/
public class UDAggregate extends UserFunction implements AggregateFunction
{
public static final Serializer serializer = new Serializer();
protected static final Logger logger = LoggerFactory.getLogger(UDAggregate.class);
private final UDFDataType stateType;
@ -375,4 +385,69 @@ public class UDAggregate extends UserFunction implements AggregateFunction
return builder.append(";")
.toString();
}
// Not quite a MetadataSerializer, or even a UDTAwareMetadataSerializer, as it needs the collection of UDFs during deserialization.
public static class Serializer
{
public void serialize(UDAggregate t, DataOutputPlus out, Version version) throws IOException
{
out.writeUTF(t.name().keyspace);
out.writeUTF(t.name().name);
out.writeInt(t.argumentsList().size());
for (String arg : t.argumentsList())
out.writeUTF(arg);
out.writeUTF(t.returnType().asCQL3Type().toString());
out.writeUTF(t.stateFunction.name().name);
out.writeUTF(t.stateType.toAbstractType().asCQL3Type().toString());
out.writeBoolean(t.finalFunction() != null);
if (t.finalFunction() != null)
out.writeUTF(t.finalFunction().name().name);
out.writeBoolean(t.initialCondition() != null);
if (t.initialCondition() != null)
ByteBufferUtil.writeWithShortLength(t.initialCondition(), out);
}
public UDAggregate deserialize(DataInputPlus in, Types types, Collection<UDFunction> functions, Version version) throws IOException
{
String ks = in.readUTF();
String name = in.readUTF();
FunctionName fn = new FunctionName(ks, name);
int argCount = in.readInt();
List<AbstractType<?>> argList = new ArrayList<>(argCount);
for (int i = 0; i < argCount; i++)
argList.add(CQLTypeParser.parse(ks, in.readUTF(), types).udfType());
AbstractType<?> returnType = CQLTypeParser.parse(ks, in.readUTF(), types).udfType();
FunctionName stateFunction = new FunctionName(ks, in.readUTF());
AbstractType<?> stateType = CQLTypeParser.parse(ks, in.readUTF(), types).udfType();
boolean hasFinalFunction = in.readBoolean();
FunctionName finalFunction = null;
if (hasFinalFunction)
finalFunction = new FunctionName(ks, in.readUTF());
boolean hasInitialCondition = in.readBoolean();
ByteBuffer initCond = null;
if (hasInitialCondition)
initCond = ByteBufferUtil.readWithShortLength(in);
return UDAggregate.create(functions, fn, argList, returnType, stateFunction, finalFunction, stateType, initCond);
}
public long serializedSize(UDAggregate t, Version version)
{
long size = sizeof(t.name().keyspace) + sizeof(t.name().name);
size += sizeof(t.argumentsList().size());
for (String arg : t.argumentsList())
size += sizeof(arg);
size += sizeof(t.returnType().asCQL3Type().toString());
size += sizeof(t.stateFunction.name().name);
size += sizeof(t.stateType.toAbstractType().asCQL3Type().toString());
size += sizeof(t.finalFunction() != null);
if (t.finalFunction() != null)
size += sizeof(t.finalFunction().name().name);
size += sizeof(t.initialCondition() != null);
if (t.initialCondition() != null)
size += ByteBufferUtil.serializedSizeWithShortLength(t.initialCondition());
return size;
}
}
}

View File

@ -17,11 +17,13 @@
*/
package org.apache.cassandra.cql3.functions;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadMXBean;
import java.net.InetAddress;
import java.net.URL;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
@ -34,6 +36,7 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
import com.google.common.base.Objects;
import com.google.common.collect.Lists;
@ -50,6 +53,10 @@ import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.marshal.UserType;
import org.apache.cassandra.exceptions.FunctionExecutionException;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.tcm.serialization.UDTAwareMetadataSerializer;
import org.apache.cassandra.tcm.serialization.Version;
import org.apache.cassandra.schema.*;
import org.apache.cassandra.service.ClientWarn;
import org.apache.cassandra.tracing.Tracing;
@ -59,6 +66,8 @@ import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException;
import static com.google.common.collect.Iterables.any;
import static com.google.common.collect.Iterables.transform;
import static org.apache.cassandra.db.TypeSizes.*;
import static org.apache.cassandra.schema.SchemaKeyspace.bbToString;
import static org.apache.cassandra.utils.Clock.Global.nanoTime;
/**
@ -66,6 +75,8 @@ import static org.apache.cassandra.utils.Clock.Global.nanoTime;
*/
public abstract class UDFunction extends UserFunction implements ScalarFunction
{
public static final Serializer serializer = new Serializer();
protected static final Logger logger = LoggerFactory.getLogger(UDFunction.class);
static final ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
@ -732,4 +743,66 @@ public abstract class UDFunction extends UserFunction implements ScalarFunction
return super.loadClass(name);
}
}
public static class Serializer implements UDTAwareMetadataSerializer<UDFunction>
{
public void serialize(UDFunction t, DataOutputPlus out, Version version) throws IOException
{
out.writeUTF(t.name().keyspace);
out.writeUTF(t.name().name);
out.writeUTF(t.body());
out.writeUTF(t.language());
out.writeUTF(t.returnType().asCQL3Type().toString());
out.writeBoolean(t.isCalledOnNullInput());
List<String> arguments = t.argNames().stream().map(c -> bbToString(c.bytes)).collect(Collectors.toList());
out.writeInt(arguments.size());
for (String argument : arguments)
out.writeUTF(argument);
out.writeInt(t.argumentsList().size());
for (String type : t.argumentsList())
out.writeUTF(type);
}
public UDFunction deserialize(DataInputPlus in, Types types, Version version) throws IOException
{
String keyspace = in.readUTF();
String name = in.readUTF();
FunctionName fn = new FunctionName(keyspace, name);
String body = in.readUTF();
String language = in.readUTF();
AbstractType<?> returnType = CQLTypeParser.parse(keyspace, in.readUTF(), types).udfType();
boolean isCalledOnNullInput = in.readBoolean();
int argumentCount = in.readInt();
List<ColumnIdentifier> arguments = new ArrayList<>(argumentCount);
for (int i = 0; i < argumentCount; i++)
arguments.add(new ColumnIdentifier(in.readUTF(), true));
int argumentTypeCount = in.readInt();
List<AbstractType<?>> argTypes = new ArrayList<>(argumentTypeCount);
for (int i = 0; i < argumentTypeCount; i++)
argTypes.add(CQLTypeParser.parse(keyspace, in.readUTF(), types).udfType());
return UDFunction.create(fn, arguments, argTypes, returnType, isCalledOnNullInput, language, body);
}
public long serializedSize(UDFunction t, Version version)
{
long size = sizeof(t.name().keyspace);
size += sizeof(t.name().name);
size += sizeof(t.body());
size += sizeof(t.language());
size += sizeof(t.returnType().asCQL3Type().toString());
size += sizeof(t.isCalledOnNullInput());
List<String> arguments = t.argNames().stream().map(c -> bbToString(c.bytes)).collect(Collectors.toList());
size += sizeof(arguments.size());
for (String argument : arguments)
size += sizeof(argument);
size += sizeof(t.argumentsList().size());
for (String type : t.argumentsList())
size += sizeof(type);
return size;
}
}
}

View File

@ -18,6 +18,7 @@
package org.apache.cassandra.cql3.functions.masking;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
@ -40,10 +41,19 @@ import org.apache.cassandra.cql3.functions.Function;
import org.apache.cassandra.cql3.functions.FunctionName;
import org.apache.cassandra.cql3.functions.FunctionResolver;
import org.apache.cassandra.cql3.functions.ScalarFunction;
import org.apache.cassandra.db.TypeSizes;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.marshal.ReversedType;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.schema.CQLTypeParser;
import org.apache.cassandra.schema.Types;
import org.apache.cassandra.schema.UserFunctions;
import org.apache.cassandra.tcm.serialization.Version;
import org.apache.cassandra.transport.ProtocolVersion;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.vint.VIntCoding;
import static java.lang.String.format;
import static org.apache.cassandra.cql3.statements.RequestValidations.invalidRequest;
@ -67,6 +77,7 @@ import static org.apache.cassandra.cql3.statements.RequestValidations.invalidReq
*/
public class ColumnMask
{
public static Serializer serializer = new Serializer();
public static final String DISABLED_ERROR_MESSAGE = "Cannot mask columns because dynamic data masking is not " +
"enabled. You can enable it with the " +
"dynamic_data_masking_enabled property on cassandra.yaml";
@ -268,4 +279,72 @@ public class ColumnMask
return format("%s(%s)", name, StringUtils.join(rawPartialArguments, ", "));
}
}
public static class Serializer
{
public void serialize(ColumnMask columnMask, DataOutputPlus out, Version version) throws IOException
{
out.writeUTF(columnMask.function.name().keyspace);
out.writeUTF(columnMask.function.name().name);
List<AbstractType<?>> argTypes = columnMask.partialArgumentTypes();
int numArgs = argTypes.size();
out.writeUnsignedVInt32(numArgs);
for (int i = 0; i < numArgs; i++)
{
out.writeUTF(argTypes.get(i).asCQL3Type().toString());
ByteBuffer value = columnMask.partialArgumentValues[i];
out.writeBoolean(value != null);
if (value != null)
ByteBufferUtil.writeWithVIntLength(value, out);
}
}
public ColumnMask deserialize(DataInputPlus in, String keyspace, AbstractType<?> columnType, Types types, UserFunctions functions, Version version) throws IOException
{
FunctionName functionName = new FunctionName(in.readUTF(), in.readUTF());
int numArgs = in.readUnsignedVInt32();
List<AbstractType<?>> argTypes = new ArrayList<>(numArgs + 1);
argTypes.set(0, columnType);
ByteBuffer[] partialArgValues = new ByteBuffer[numArgs];
for (int i = 0; i < numArgs; i++)
{
AbstractType<?> argType = CQLTypeParser.parse(keyspace, in.readUTF(), types);
argTypes.set(i + 1, argType);
boolean valuePresent = in.readBoolean();
partialArgValues[i] = valuePresent ? null : ByteBufferUtil.readWithVIntLength(in);
}
Function function = FunctionResolver.get(keyspace, functionName, argTypes, null, null, null, functions);
if (function == null)
{
throw new AssertionError(format("Unable to find masking function %s(%s)", functionName, argTypes));
}
else if (!(function instanceof ScalarFunction))
{
throw new AssertionError(format("Function %s is not a scalar masking function", function));
}
return new ColumnMask((ScalarFunction) function, partialArgValues);
}
public long serializedSize(ColumnMask columnMask, Version version)
{
List<AbstractType<?>> argTypes = columnMask.partialArgumentTypes();
int numArgs = argTypes.size();
long size = TypeSizes.sizeof(columnMask.function.name().keyspace) +
TypeSizes.sizeof(columnMask.function.name().name) +
VIntCoding.computeUnsignedVIntSize(numArgs);
for (int i = 0; i < numArgs; i++)
{
size += TypeSizes.sizeof(argTypes.get(i).asCQL3Type().toString());
size += TypeSizes.BOOL_SIZE;
ByteBuffer value = columnMask.partialArgumentValues[i];
if (value != null)
size += ByteBufferUtil.serializedSizeWithVIntLength(value);
}
return size;
}
}
}

View File

@ -177,8 +177,9 @@ public final class StatementRestrictions
{
this(type, table, allowFiltering);
final IndexRegistry indexRegistry = type.allowUseOfSecondaryIndices() ? IndexRegistry.obtain(table) : null;
final IndexRegistry indexRegistry = type.allowUseOfSecondaryIndices() && allowUseOfSecondaryIndices
? IndexRegistry.obtain(table)
: null;
/*
* WHERE clause. For a given entity, rules are:
* - EQ relation conflicts with anything else (including a 2nd EQ)

View File

@ -132,13 +132,9 @@ public abstract class DescribeStatement<T> extends CQLStatement.Raw implements C
@Override
public ResultMessage executeLocally(QueryState state, QueryOptions options)
{
DistributedSchema schema = Schema.instance.getDistributedSchemaBlocking();
Keyspaces keyspaces = Keyspaces.builder()
.add(schema.getKeyspaces())
.add(Schema.instance.getLocalKeyspaces())
.add(VirtualKeyspaceRegistry.instance.virtualKeyspacesMetadata())
.build();
Keyspaces keyspaces = Schema.instance.distributedAndLocalKeyspaces();
UUID schemaVersion = Schema.instance.getVersion();
keyspaces = keyspaces.with(VirtualKeyspaceRegistry.instance.virtualKeyspacesMetadata());
PagingState pagingState = options.getPagingState();
@ -156,7 +152,7 @@ public abstract class DescribeStatement<T> extends CQLStatement.Raw implements C
// (vint bytes) serialized schema hash (currently the result of Keyspaces.hashCode())
//
long offset = getOffset(pagingState, schema.getVersion());
long offset = getOffset(pagingState, schemaVersion);
int pageSize = options.getPageSize();
Stream<? extends T> stream = describe(state.getClientState(), keyspaces);
@ -173,7 +169,7 @@ public abstract class DescribeStatement<T> extends CQLStatement.Raw implements C
ResultSet result = new ResultSet(resultMetadata, rows);
if (pageSize > 0 && rows.size() == pageSize)
result.metadata.setHasMorePages(getPagingState(offset + pageSize, schema.getVersion()));
result.metadata.setHasMorePages(getPagingState(offset + pageSize, schemaVersion));
return new ResultMessage.Rows(result);
}

View File

@ -82,7 +82,7 @@ public class PropertyDefinitions
return properties.containsKey(name);
}
protected String getString(String name) throws SyntaxException
public String getString(String name) throws SyntaxException
{
Object val = properties.get(name);
if (val == null)

View File

@ -18,21 +18,19 @@
package org.apache.cassandra.cql3.statements.schema;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.audit.AuditLogContext;
import org.apache.cassandra.audit.AuditLogEntryType;
import org.apache.cassandra.auth.Permission;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.cql3.CQLStatement;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.Keyspace;
import org.apache.cassandra.db.guardrails.Guardrails;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.gms.Gossiper;
import org.apache.cassandra.locator.AbstractReplicationStrategy;
import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.locator.LocalStrategy;
@ -42,7 +40,11 @@ import org.apache.cassandra.schema.KeyspaceMetadata;
import org.apache.cassandra.schema.KeyspaceMetadata.KeyspaceDiff;
import org.apache.cassandra.schema.Keyspaces;
import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff;
import org.apache.cassandra.schema.SchemaConstants;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.tcm.membership.NodeId;
import org.apache.cassandra.transport.Event.SchemaChange;
import org.apache.cassandra.transport.Event.SchemaChange.Change;
import org.apache.cassandra.utils.FBUtilities;
@ -52,9 +54,10 @@ import static org.apache.cassandra.config.CassandraRelevantProperties.ALLOW_UNSA
public final class AlterKeyspaceStatement extends AlterSchemaStatement
{
private static final Logger logger = LoggerFactory.getLogger(AlterKeyspaceStatement.class);
private static final boolean allow_alter_rf_during_range_movement = ALLOW_ALTER_RF_DURING_RANGE_MOVEMENT.getBoolean();
private static final boolean allow_unsafe_transient_changes = ALLOW_UNSAFE_TRANSIENT_CHANGES.getBoolean();
private final HashSet<String> clientWarnings = new HashSet<>();
private final KeyspaceAttributes attrs;
private final boolean ifExists;
@ -66,10 +69,11 @@ public final class AlterKeyspaceStatement extends AlterSchemaStatement
this.ifExists = ifExists;
}
public Keyspaces apply(Keyspaces schema)
public Keyspaces apply(ClusterMetadata metadata)
{
attrs.validate();
Keyspaces schema = metadata.schema.getKeyspaces();
KeyspaceMetadata keyspace = schema.getNullable(keyspaceName);
if (null == keyspace)
{
@ -83,17 +87,29 @@ public final class AlterKeyspaceStatement extends AlterSchemaStatement
if (attrs.getReplicationStrategyClass() != null && attrs.getReplicationStrategyClass().equals(SimpleStrategy.class.getSimpleName()))
Guardrails.simpleStrategyEnabled.ensureEnabled(state);
if (keyspace.params.replication.isMeta() && !keyspace.name.equals(SchemaConstants.METADATA_KEYSPACE_NAME))
throw ire("Can not alter a keyspace to use MetaReplicationStrategy");
if (newKeyspace.params.replication.klass.equals(LocalStrategy.class))
throw ire("Unable to use given strategy class: LocalStrategy is reserved for internal use.");
newKeyspace.params.validate(keyspaceName, state);
newKeyspace.params.validate(keyspaceName, state, metadata);
newKeyspace.replicationStrategy.validate(metadata);
validateNoRangeMovements();
validateTransientReplication(keyspace.createReplicationStrategy(), newKeyspace.createReplicationStrategy());
validateTransientReplication(keyspace, newKeyspace);
Keyspaces res = schema.withAddedOrUpdated(newKeyspace);
// Because we used to not properly validate unrecognized options, we only log a warning if we find one.
try
{
newKeyspace.replicationStrategy.validateExpectedOptions(metadata);
}
catch (ConfigurationException e)
{
logger.warn("Ignoring {}", e.getMessage());
}
return res;
return schema.withAddedOrUpdated(newKeyspace);
}
SchemaChange schemaChangeEvent(KeyspacesDiff diff)
@ -109,13 +125,14 @@ public final class AlterKeyspaceStatement extends AlterSchemaStatement
@Override
Set<String> clientWarnings(KeyspacesDiff diff)
{
HashSet<String> clientWarnings = new HashSet<>();
if (diff.isEmpty())
return clientWarnings;
KeyspaceDiff keyspaceDiff = diff.altered.get(0);
AbstractReplicationStrategy before = keyspaceDiff.before.createReplicationStrategy();
AbstractReplicationStrategy after = keyspaceDiff.after.createReplicationStrategy();
AbstractReplicationStrategy before = keyspaceDiff.before.replicationStrategy;
AbstractReplicationStrategy after = keyspaceDiff.after.replicationStrategy;
if (before.getReplicationFactor().fullReplicas < after.getReplicationFactor().fullReplicas)
clientWarnings.add("When increasing replication factor you need to run a full (-full) repair to distribute the data.");
@ -128,29 +145,36 @@ public final class AlterKeyspaceStatement extends AlterSchemaStatement
if (allow_alter_rf_during_range_movement)
return;
Stream<InetAddressAndPort> unreachableNotAdministrativelyInactive =
Gossiper.instance.getUnreachableMembers().stream().filter(endpoint -> !FBUtilities.getBroadcastAddressAndPort().equals(endpoint) &&
!Gossiper.instance.isAdministrativelyInactiveState(endpoint));
Stream<InetAddressAndPort> endpoints = Stream.concat(Gossiper.instance.getLiveMembers().stream(),
unreachableNotAdministrativelyInactive);
List<InetAddressAndPort> notNormalEndpoints = endpoints.filter(endpoint -> !FBUtilities.getBroadcastAddressAndPort().equals(endpoint) &&
!Gossiper.instance.getEndpointStateForEndpoint(endpoint).isNormalState())
.collect(Collectors.toList());
ClusterMetadata metadata = ClusterMetadata.current();
NodeId nodeId = metadata.directory.peerId(FBUtilities.getBroadcastAddressAndPort());
Set<InetAddressAndPort> notNormalEndpoints = metadata.directory.states.entrySet().stream().filter(e -> !e.getKey().equals(nodeId)).filter(e -> {
switch (e.getValue())
{
case BOOTSTRAPPING:
case LEAVING:
case MOVING:
return true;
default:
return false;
}
}).map(e -> metadata.directory.endpoint(e.getKey())).collect(Collectors.toSet());
if (!notNormalEndpoints.isEmpty())
{
throw new ConfigurationException("Cannot alter RF while some endpoints are not in normal state (no range movements): " + notNormalEndpoints);
}
}
private void validateTransientReplication(AbstractReplicationStrategy oldStrategy, AbstractReplicationStrategy newStrategy)
private void validateTransientReplication(KeyspaceMetadata current, KeyspaceMetadata proposed)
{
//If there is no read traffic there are some extra alterations you can safely make, but this is so atypical
//that a good default is to not allow unsafe changes
if (allow_unsafe_transient_changes)
return;
ReplicationFactor oldRF = oldStrategy.getReplicationFactor();
ReplicationFactor newRF = newStrategy.getReplicationFactor();
ReplicationFactor oldRF = current.replicationStrategy.getReplicationFactor();
ReplicationFactor newRF = proposed.replicationStrategy.getReplicationFactor();
int oldTrans = oldRF.transientReplicas();
int oldFull = oldRF.fullReplicas;
@ -162,20 +186,14 @@ public final class AlterKeyspaceStatement extends AlterSchemaStatement
if (DatabaseDescriptor.getNumTokens() > 1)
throw new ConfigurationException(String.format("Transient replication is not supported with vnodes yet"));
Keyspace ks = Keyspace.open(keyspaceName);
for (ColumnFamilyStore cfs : ks.getColumnFamilyStores())
{
if (cfs.viewManager.hasViews())
{
throw new ConfigurationException("Cannot use transient replication on keyspaces using materialized views");
}
if (cfs.indexManager.hasIndexes())
{
if (!current.views.isEmpty())
throw new ConfigurationException("Cannot use transient replication on keyspaces using materialized views");
for (TableMetadata table : current.tables)
if (!table.indexes.isEmpty())
throw new ConfigurationException("Cannot use transient replication on keyspaces using secondary indexes");
}
}
}
//This is true right now because the transition from transient -> full lacks the pending state
//necessary for correctness. What would happen if we allowed this is that we would attempt

View File

@ -34,6 +34,7 @@ import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.service.ClientWarn;
import org.apache.cassandra.service.QueryState;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.transport.Event.SchemaChange;
import org.apache.cassandra.transport.messages.ResultMessage;
@ -41,12 +42,46 @@ abstract public class AlterSchemaStatement implements CQLStatement.SingleKeyspac
{
protected final String keyspaceName; // name of the keyspace affected by the statement
protected ClientState state;
// TODO: not sure if this is going to stay the same, or will be replaced by more efficient serialization/sanitation means
// or just `toString` for every statement
private String cql;
protected AlterSchemaStatement(String keyspaceName)
{
this.keyspaceName = keyspaceName;
}
public void setCql(String cql)
{
this.cql = cql;
}
@Override
public String cql()
{
assert cql != null;
return cql;
}
@Override
public void enterExecution()
{
ClientWarn.instance.pauseCapture();
ClientState localState = state;
if (localState != null)
localState.pauseGuardrails();
}
@Override
public void exitExecution()
{
ClientWarn.instance.resumeCapture();
ClientState localState = state;
if (localState != null)
localState.resumeGuardrails();
}
// TODO: validation should be performed during application
public void validate(ClientState state)
{
// validation is performed while executing the statement, in apply()
@ -57,7 +92,7 @@ abstract public class AlterSchemaStatement implements CQLStatement.SingleKeyspac
public ResultMessage execute(QueryState state, QueryOptions options, long queryStartNanoTime)
{
return execute(state, false);
return execute(state);
}
@Override
@ -68,7 +103,7 @@ abstract public class AlterSchemaStatement implements CQLStatement.SingleKeyspac
public ResultMessage executeLocally(QueryState state, QueryOptions options)
{
return execute(state, true);
return execute(state);
}
/**
@ -100,7 +135,7 @@ abstract public class AlterSchemaStatement implements CQLStatement.SingleKeyspac
return ImmutableSet.of();
}
public ResultMessage execute(QueryState state, boolean locally)
public ResultMessage execute(QueryState state)
{
if (SchemaConstants.isLocalSystemKeyspace(keyspaceName))
throw ire("System keyspace '%s' is not user-modifiable", keyspaceName);
@ -110,12 +145,24 @@ abstract public class AlterSchemaStatement implements CQLStatement.SingleKeyspac
throw ire("Virtual keyspace '%s' is not user-modifiable", keyspaceName);
validateKeyspaceName();
// Perform a 'dry-run' attempt to apply the transformation locally before submitting to the CMS. This can save a
// round trip to the CMS for things syntax errors, but also fail fast for things like configuration errors.
// Such failures may be dependent on the specific node's config (for things like guardrails/memtable
// config/etc), but executing a schema change which has already been committed by the CMS should always succeed
// or else the node cannot make progress on any subsequent metadata changes. For this reason, validation errors
// during execution are trapped and the node will fall back to safe default config wherever possible. Attempting
// to apply the SchemaTransformation at this point will catch any such error which occurs locally before
// submission to the CMS, but it can't guarantee that the statement can be applied as-is on every node in the
// cluster, as config can be heterogenous falling back to safe defaults may occur on some nodes.
ClusterMetadata metadata = ClusterMetadata.current();
apply(metadata);
SchemaTransformationResult result = Schema.instance.transform(this, locally);
ClusterMetadata result = Schema.instance.submit(this);
clientWarnings(result.diff).forEach(ClientWarn.instance::warn);
KeyspacesDiff diff = Keyspaces.diff(metadata.schema.getKeyspaces(), result.schema.getKeyspaces());
clientWarnings(diff).forEach(ClientWarn.instance::warn);
if (result.diff.isEmpty())
if (diff.isEmpty())
return new ResultMessage.Void();
/*
@ -127,9 +174,9 @@ abstract public class AlterSchemaStatement implements CQLStatement.SingleKeyspac
*/
AuthenticatedUser user = state.getClientState().getUser();
if (null != user && !user.isAnonymous())
createdResources(result.diff).forEach(r -> grantPermissionsOnResource(r, user));
createdResources(diff).forEach(r -> grantPermissionsOnResource(r, user));
return new ResultMessage.SchemaChange(schemaChangeEvent(result.diff));
return new ResultMessage.SchemaChange(schemaChangeEvent(diff));
}
private void validateKeyspaceName()
@ -170,4 +217,12 @@ abstract public class AlterSchemaStatement implements CQLStatement.SingleKeyspac
{
return new InvalidRequestException(String.format(format, args));
}
public String toString()
{
return "AlterSchemaStatement{" +
"keyspaceName='" + keyspaceName + '\'' +
", cql='" + cql() + '\'' +
'}';
}
}

View File

@ -24,14 +24,15 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -44,38 +45,38 @@ import org.apache.cassandra.cql3.CQLStatement;
import org.apache.cassandra.cql3.ColumnIdentifier;
import org.apache.cassandra.cql3.QualifiedName;
import org.apache.cassandra.cql3.functions.masking.ColumnMask;
import org.apache.cassandra.db.Keyspace;
import org.apache.cassandra.db.guardrails.Guardrails;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.gms.ApplicationState;
import org.apache.cassandra.gms.Gossiper;
import org.apache.cassandra.index.TargetParser;
import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.net.MessagingService;
import org.apache.cassandra.schema.ColumnMetadata;
import org.apache.cassandra.schema.IndexMetadata;
import org.apache.cassandra.schema.KeyspaceMetadata;
import org.apache.cassandra.schema.Keyspaces;
import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff;
import org.apache.cassandra.schema.MemtableParams;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.schema.TableParams;
import org.apache.cassandra.schema.ViewMetadata;
import org.apache.cassandra.schema.Views;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.service.StorageService;
import org.apache.cassandra.service.reads.repair.ReadRepairStrategy;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.tcm.Epoch;
import org.apache.cassandra.tcm.membership.Directory;
import org.apache.cassandra.transport.Event.SchemaChange;
import org.apache.cassandra.transport.Event.SchemaChange.Change;
import org.apache.cassandra.transport.Event.SchemaChange.Target;
import org.apache.cassandra.utils.CassandraVersion;
import org.apache.cassandra.utils.NoSpamLogger;
import static java.lang.String.format;
import static java.lang.String.join;
import org.apache.cassandra.utils.Pair;
import static com.google.common.collect.Iterables.isEmpty;
import static com.google.common.collect.Iterables.transform;
import static java.lang.String.format;
import static java.lang.String.join;
import static org.apache.cassandra.schema.TableMetadata.Flag;
public abstract class AlterTableStatement extends AlterSchemaStatement
@ -100,8 +101,9 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
this.state = state;
}
public Keyspaces apply(Keyspaces schema)
public Keyspaces apply(ClusterMetadata metadata)
{
Keyspaces schema = metadata.schema.getKeyspaces();
KeyspaceMetadata keyspace = schema.getNullable(keyspaceName);
TableMetadata table = null == keyspace
@ -118,7 +120,7 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
if (table.isView())
throw ire("Cannot use ALTER TABLE on a materialized view; use ALTER MATERIALIZED VIEW instead");
return schema.withAddedOrUpdated(apply(keyspace, table));
return schema.withAddedOrUpdated(apply(metadata.nextEpoch(), keyspace, table));
}
SchemaChange schemaChangeEvent(KeyspacesDiff diff)
@ -142,7 +144,7 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
return format("%s (%s, %s)", getClass().getSimpleName(), keyspaceName, tableName);
}
abstract KeyspaceMetadata apply(KeyspaceMetadata keyspace, TableMetadata table);
abstract KeyspaceMetadata apply(Epoch epoch, KeyspaceMetadata keyspace, TableMetadata table);
/**
* {@code ALTER TABLE [IF EXISTS] <table> ALTER <column> TYPE <newtype>;}
@ -156,7 +158,7 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
super(keyspaceName, tableName, ifTableExists);
}
public KeyspaceMetadata apply(KeyspaceMetadata keyspace, TableMetadata table)
public KeyspaceMetadata apply(Epoch epoch, KeyspaceMetadata keyspace, TableMetadata table)
{
throw ire("Altering column types is no longer supported");
}
@ -196,7 +198,7 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
}
@Override
public KeyspaceMetadata apply(KeyspaceMetadata keyspace, TableMetadata table)
public KeyspaceMetadata apply(Epoch epoch, KeyspaceMetadata keyspace, TableMetadata table)
{
ColumnMetadata column = table.getColumn(columnName);
@ -214,7 +216,7 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
if (Objects.equals(oldMask, newMask))
return keyspace;
TableMetadata.Builder tableBuilder = table.unbuild();
TableMetadata.Builder tableBuilder = table.unbuild().epoch(epoch);
tableBuilder.alterColumnMask(columnName, newMask);
TableMetadata newTable = tableBuilder.build();
newTable.validate();
@ -274,10 +276,10 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
newColumns.forEach(c -> c.type.validate(state, "Column " + c.name));
}
public KeyspaceMetadata apply(KeyspaceMetadata keyspace, TableMetadata table)
public KeyspaceMetadata apply(Epoch epoch, KeyspaceMetadata keyspace, TableMetadata table)
{
Guardrails.alterTableEnabled.ensureEnabled("ALTER TABLE changing columns", state);
TableMetadata.Builder tableBuilder = table.unbuild();
TableMetadata.Builder tableBuilder = table.unbuild().epoch(epoch);
Views.Builder viewsBuilder = keyspace.views.unbuild();
newColumns.forEach(c -> addColumn(keyspace, table, c, ifColumnNotExists, tableBuilder, viewsBuilder));
@ -360,6 +362,38 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
}
}
private static void validateIndexesForColumnModification(TableMetadata table,
ColumnIdentifier colId,
boolean isRename)
{
ColumnMetadata column = table.getColumn(colId);
Set<String> dependentIndexes = new HashSet<>();
for (IndexMetadata index : table.indexes)
{
Optional<Pair<ColumnMetadata, IndexTarget.Type>> target = TargetParser.tryParse(table, index);
if (target.isEmpty())
{
// The target column(s) of this index is not trivially discernible from its metadata.
// This implies an external custom index implementation and without instantiating the
// index itself we cannot be sure that the column metadata is safe to modify.
dependentIndexes.add(index.name);
}
else if (target.get().left.equals(column))
{
// The index metadata declares an explicit dependency on the column being modified, so
// the mutation must be rejected.
dependentIndexes.add(index.name);
}
}
if (!dependentIndexes.isEmpty())
{
throw ire("Cannot %s column %s because it has dependent secondary indexes (%s)",
isRename ? "rename" : "drop",
colId,
join(", ", dependentIndexes));
}
}
/**
* {@code ALTER TABLE [IF EXISTS] <table> DROP [IF EXISTS] <column>}
* {@code ALTER TABLE [IF EXISTS] <table> DROP [IF EXISTS] ( <column>, <column1>, ... <columnn>)}
@ -379,7 +413,7 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
this.timestamp = timestamp;
}
public KeyspaceMetadata apply(KeyspaceMetadata keyspace, TableMetadata table)
public KeyspaceMetadata apply(Epoch epoch, KeyspaceMetadata keyspace, TableMetadata table)
{
Guardrails.alterTableEnabled.ensureEnabled("ALTER TABLE changing columns", state);
TableMetadata.Builder builder = table.unbuild();
@ -407,14 +441,8 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
if (currentColumn.type.isUDT() && currentColumn.type.isMultiCell())
throw ire("Cannot drop non-frozen column %s of user type %s", column, currentColumn.type.asCQL3Type());
// TODO: some day try and find a way to not rely on Keyspace/IndexManager/Index to find dependent indexes
Set<IndexMetadata> dependentIndexes = Keyspace.openAndGetStore(table).indexManager.getDependentIndexes(currentColumn);
if (!dependentIndexes.isEmpty())
{
throw ire("Cannot drop column %s because it has dependent secondary indexes (%s)",
currentColumn,
join(", ", transform(dependentIndexes, i -> i.name)));
}
if (!table.indexes.isEmpty())
AlterTableStatement.validateIndexesForColumnModification(table, column, false);
if (!isEmpty(keyspace.views.forTable(table.id)))
throw ire("Cannot drop column %s on base table %s with materialized views", currentColumn, table.name);
@ -447,10 +475,10 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
this.ifColumnsExists = ifColumnsExists;
}
public KeyspaceMetadata apply(KeyspaceMetadata keyspace, TableMetadata table)
public KeyspaceMetadata apply(Epoch epoch, KeyspaceMetadata keyspace, TableMetadata table)
{
Guardrails.alterTableEnabled.ensureEnabled("ALTER TABLE changing columns", state);
TableMetadata.Builder tableBuilder = table.unbuild();
TableMetadata.Builder tableBuilder = table.unbuild().epoch(epoch);
Views.Builder viewsBuilder = keyspace.views.unbuild();
renamedColumns.forEach((o, n) -> renameColumn(keyspace, table, o, n, ifColumnsExists, tableBuilder, viewsBuilder));
@ -485,14 +513,8 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
table);
}
// TODO: some day try and find a way to not rely on Keyspace/IndexManager/Index to find dependent indexes
Set<IndexMetadata> dependentIndexes = Keyspace.openAndGetStore(table).indexManager.getDependentIndexes(column);
if (!dependentIndexes.isEmpty())
{
throw ire("Can't rename column %s because it has dependent secondary indexes (%s)",
oldName,
join(", ", transform(dependentIndexes, i -> i.name)));
}
if (!table.indexes.isEmpty())
AlterTableStatement.validateIndexesForColumnModification(table, oldName, true);
for (ViewMetadata view : keyspace.views.forTable(table.id))
{
@ -523,13 +545,15 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
public void validate(ClientState state)
{
super.validate(state);
// If a memtable configuration is specified, validate it against config
if (attrs.hasOption(TableParams.Option.MEMTABLE))
MemtableParams.get(attrs.getString(TableParams.Option.MEMTABLE.toString()));
Guardrails.tableProperties.guard(attrs.updatedProperties(), attrs::removeProperty, state);
validateDefaultTimeToLive(attrs.asNewTableParams());
}
public KeyspaceMetadata apply(KeyspaceMetadata keyspace, TableMetadata table)
public KeyspaceMetadata apply(Epoch epoch, KeyspaceMetadata keyspace, TableMetadata table)
{
attrs.validate();
@ -547,7 +571,7 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
"before being replayed.");
}
if (keyspace.createReplicationStrategy().hasTransientReplicas()
if (keyspace.replicationStrategy.hasTransientReplicas()
&& params.readRepair != ReadRepairStrategy.NONE)
{
throw ire("read_repair must be set to 'NONE' for transiently replicated keyspaces");
@ -573,7 +597,7 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
super(keyspaceName, tableName, ifTableExists);
}
public KeyspaceMetadata apply(KeyspaceMetadata keyspace, TableMetadata table)
public KeyspaceMetadata apply(Epoch epoch, KeyspaceMetadata keyspace, TableMetadata table)
{
if (!DatabaseDescriptor.enableDropCompactStorage())
throw new InvalidRequestException("DROP COMPACT STORAGE is disabled. Enable in cassandra.yaml to use.");
@ -587,7 +611,7 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
? ImmutableSet.of(Flag.COMPOUND, Flag.COUNTER)
: ImmutableSet.of(Flag.COMPOUND);
return keyspace.withSwapped(keyspace.tables.withSwapped(table.withSwapped(flags)));
return keyspace.withSwapped(keyspace.tables.withSwapped(table.unbuild().flags(flags).build()));
}
/**
@ -608,22 +632,26 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
Set<InetAddressAndPort> preC15897nodes = new HashSet<>();
Set<InetAddressAndPort> with2xSStables = new HashSet<>();
Splitter onComma = Splitter.on(',').omitEmptyStrings().trimResults();
for (InetAddressAndPort node : StorageService.instance.getTokenMetadata().getAllEndpoints())
Directory directory = ClusterMetadata.current().directory;
for (InetAddressAndPort node : directory.allAddresses())
{
if (MessagingService.instance().versions.knows(node) &&
MessagingService.instance().versions.getRaw(node) < MessagingService.VERSION_40)
CassandraVersion version = directory.version(directory.peerId(node)).cassandraVersion;
if (version.compareTo(CassandraVersion.CASSANDRA_4_0) < 0)
{
// if the cluster contains any pre-4.0 nodes (which really shouldn't be the case), reject this
// operation as we can't be certain all peers can support it.
before4.add(node);
continue;
}
String sstableVersionsString = Gossiper.instance.getApplicationState(node, ApplicationState.SSTABLE_VERSIONS);
if (sstableVersionsString == null)
else
{
preC15897nodes.add(node);
// any peer on a version greater than 4.0.0 must include CASSANDRA-15897, so just check that
// its min sstable version. Note: this app state may be empty/unset if the full StorageService
// initialisation hasn't been done, i.e. in tests.
String sstableVersionsString = Gossiper.instance.getApplicationState(node, ApplicationState.SSTABLE_VERSIONS);
if (Strings.isNullOrEmpty(sstableVersionsString))
continue;
}
try
{
boolean has2xSStables = onComma.splitToList(sstableVersionsString)
@ -644,15 +672,12 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
"`--force-compact-storage-on` option.", node, sstableVersionsString, node);
}
}
}
if (!before4.isEmpty())
throw new InvalidRequestException(format("Cannot DROP COMPACT STORAGE as some nodes in the cluster (%s) " +
"are not on 4.0+ yet. Please upgrade those nodes and run " +
"`upgradesstables` before retrying.", before4));
if (!preC15897nodes.isEmpty())
throw new InvalidRequestException(format("Cannot guarantee that DROP COMPACT STORAGE is safe as some nodes " +
"in the cluster (%s) do not have https://issues.apache.org/jira/browse/CASSANDRA-15897. " +
"Please upgrade those nodes and retry.", preC15897nodes));
if (!with2xSStables.isEmpty())
throw new InvalidRequestException(format("Cannot DROP COMPACT STORAGE as some nodes in the cluster (%s) " +
"has some non-upgraded 2.x sstables. Please run `upgradesstables` " +

View File

@ -34,6 +34,7 @@ import org.apache.cassandra.schema.KeyspaceMetadata;
import org.apache.cassandra.schema.Keyspaces;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.transport.Event.SchemaChange;
import org.apache.cassandra.transport.Event.SchemaChange.Change;
import org.apache.cassandra.transport.Event.SchemaChange.Target;
@ -69,8 +70,10 @@ public abstract class AlterTypeStatement extends AlterSchemaStatement
return new SchemaChange(Change.UPDATED, Target.TYPE, keyspaceName, typeName);
}
public Keyspaces apply(Keyspaces schema)
@Override
public Keyspaces apply(ClusterMetadata metadata)
{
Keyspaces schema = metadata.schema.getKeyspaces();
KeyspaceMetadata keyspace = schema.getNullable(keyspaceName);
UserType type = null == keyspace

View File

@ -26,6 +26,7 @@ import org.apache.cassandra.db.guardrails.Guardrails;
import org.apache.cassandra.schema.*;
import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.transport.Event.SchemaChange;
import org.apache.cassandra.transport.Event.SchemaChange.Change;
import org.apache.cassandra.transport.Event.SchemaChange.Target;
@ -54,8 +55,10 @@ public final class AlterViewStatement extends AlterSchemaStatement
this.state = state;
}
public Keyspaces apply(Keyspaces schema)
@Override
public Keyspaces apply(ClusterMetadata metadata)
{
Keyspaces schema = metadata.schema.getKeyspaces();
KeyspaceMetadata keyspace = schema.getNullable(keyspaceName);
ViewMetadata view = null == keyspace

View File

@ -44,6 +44,7 @@ import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff;
import org.apache.cassandra.schema.Schema;
import org.apache.cassandra.serializers.MarshalException;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.transport.Event.SchemaChange;
import org.apache.cassandra.transport.Event.SchemaChange.Change;
import org.apache.cassandra.transport.Event.SchemaChange.Target;
@ -89,7 +90,8 @@ public final class CreateAggregateStatement extends AlterSchemaStatement
this.ifNotExists = ifNotExists;
}
public Keyspaces apply(Keyspaces schema)
@Override
public Keyspaces apply(ClusterMetadata metadata)
{
if (ifNotExists && orReplace)
throw ire("Cannot use both 'OR REPLACE' and 'IF NOT EXISTS' directives");
@ -105,6 +107,7 @@ public final class CreateAggregateStatement extends AlterSchemaStatement
if (!rawStateType.isImplicitlyFrozen() && rawStateType.isFrozen())
throw ire("State type '%s' cannot be frozen; remove frozen<> modifier from '%s'", rawStateType, rawStateType);
Keyspaces schema = metadata.schema.getKeyspaces();
KeyspaceMetadata keyspace = schema.getNullable(keyspaceName);
if (null == keyspace)
throw ire("Keyspace '%s' doesn't exist", keyspaceName);

View File

@ -40,6 +40,7 @@ import org.apache.cassandra.schema.Keyspaces;
import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff;
import org.apache.cassandra.schema.Schema;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.transport.Event.SchemaChange;
import org.apache.cassandra.transport.Event.SchemaChange.Change;
import org.apache.cassandra.transport.Event.SchemaChange.Target;
@ -82,7 +83,7 @@ public final class CreateFunctionStatement extends AlterSchemaStatement
}
// TODO: replace affected aggregates !!
public Keyspaces apply(Keyspaces schema)
public Keyspaces apply(ClusterMetadata metadata)
{
if (ifNotExists && orReplace)
throw ire("Cannot use both 'OR REPLACE' and 'IF NOT EXISTS' directives");
@ -103,6 +104,7 @@ public final class CreateFunctionStatement extends AlterSchemaStatement
if (!rawReturnType.isImplicitlyFrozen() && rawReturnType.isFrozen())
throw ire("Return type '%s' cannot be frozen; remove frozen<> modifier from '%s'", rawReturnType, rawReturnType);
Keyspaces schema = metadata.schema.getKeyspaces();
KeyspaceMetadata keyspace = schema.getNullable(keyspaceName);
if (null == keyspace)
throw ire("Keyspace '%s' doesn't exist", keyspaceName);

View File

@ -31,7 +31,6 @@ import org.apache.cassandra.cql3.CQLStatement;
import org.apache.cassandra.cql3.ColumnIdentifier;
import org.apache.cassandra.cql3.QualifiedName;
import org.apache.cassandra.cql3.statements.schema.IndexTarget.Type;
import org.apache.cassandra.db.Keyspace;
import org.apache.cassandra.db.guardrails.Guardrails;
import org.apache.cassandra.db.marshal.MapType;
import org.apache.cassandra.exceptions.InvalidRequestException;
@ -40,6 +39,7 @@ import org.apache.cassandra.index.sasi.SASIIndex;
import org.apache.cassandra.schema.*;
import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.transport.Event.SchemaChange;
import org.apache.cassandra.transport.Event.SchemaChange.Change;
import org.apache.cassandra.transport.Event.SchemaChange.Target;
@ -112,7 +112,8 @@ public final class CreateIndexStatement extends AlterSchemaStatement
this.state = state;
}
public Keyspaces apply(Keyspaces schema)
@Override
public Keyspaces apply(ClusterMetadata metadata)
{
attrs.validate();
@ -121,6 +122,7 @@ public final class CreateIndexStatement extends AlterSchemaStatement
if (attrs.isCustom && attrs.customClass.equals(SASIIndex.class.getName()) && !DatabaseDescriptor.getSASIIndexesEnabled())
throw new InvalidRequestException(SASI_INDEX_DISABLED);
Keyspaces schema = metadata.schema.getKeyspaces();
KeyspaceMetadata keyspace = schema.getNullable(keyspaceName);
if (null == keyspace)
throw ire(KEYSPACE_DOES_NOT_EXIST, keyspaceName);
@ -143,7 +145,7 @@ public final class CreateIndexStatement extends AlterSchemaStatement
if (table.isView())
throw ire(MATERIALIZED_VIEWS_NOT_SUPPORTED);
if (Keyspace.open(table.keyspace).getReplicationStrategy().hasTransientReplicas())
if (keyspace.replicationStrategy.hasTransientReplicas())
throw new InvalidRequestException(TRANSIENTLY_REPLICATED_KEYSPACE_NOT_SUPPORTED);
// guardrails to limit number of secondary indexes per table.

View File

@ -17,7 +17,6 @@
*/
package org.apache.cassandra.cql3.statements.schema;
import java.util.HashSet;
import java.util.Set;
import com.google.common.collect.ImmutableSet;
@ -36,12 +35,11 @@ import org.apache.cassandra.db.guardrails.Guardrails;
import org.apache.cassandra.exceptions.AlreadyExistsException;
import org.apache.cassandra.locator.LocalStrategy;
import org.apache.cassandra.locator.SimpleStrategy;
import org.apache.cassandra.schema.KeyspaceMetadata;
import org.apache.cassandra.schema.*;
import org.apache.cassandra.schema.KeyspaceParams.Option;
import org.apache.cassandra.schema.Keyspaces;
import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff;
import org.apache.cassandra.schema.Schema;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.transport.Event.SchemaChange;
import org.apache.cassandra.transport.Event.SchemaChange.Change;
@ -51,7 +49,6 @@ public final class CreateKeyspaceStatement extends AlterSchemaStatement
private final KeyspaceAttributes attrs;
private final boolean ifNotExists;
private final HashSet<String> clientWarnings = new HashSet<>();
public CreateKeyspaceStatement(String keyspaceName, KeyspaceAttributes attrs, boolean ifNotExists)
{
@ -60,7 +57,7 @@ public final class CreateKeyspaceStatement extends AlterSchemaStatement
this.ifNotExists = ifNotExists;
}
public Keyspaces apply(Keyspaces schema)
public Keyspaces apply(ClusterMetadata metadata)
{
attrs.validate();
@ -70,6 +67,7 @@ public final class CreateKeyspaceStatement extends AlterSchemaStatement
if (attrs.getReplicationStrategyClass() != null && attrs.getReplicationStrategyClass().equals(SimpleStrategy.class.getSimpleName()))
Guardrails.simpleStrategyEnabled.ensureEnabled("SimpleStrategy", state);
Keyspaces schema = metadata.schema.getKeyspaces();
if (schema.containsKeyspace(keyspaceName))
{
if (ifNotExists)
@ -83,7 +81,11 @@ public final class CreateKeyspaceStatement extends AlterSchemaStatement
if (keyspace.params.replication.klass.equals(LocalStrategy.class))
throw ire("Unable to use given strategy class: LocalStrategy is reserved for internal use.");
keyspace.params.validate(keyspaceName, state);
if (keyspace.params.replication.isMeta())
throw ire("Can not create a keyspace with MetaReplicationStrategy");
keyspace.params.validate(keyspaceName, state, metadata);
keyspace.replicationStrategy.validateExpectedOptions(metadata);
return schema.withAddedOrUpdated(keyspace);
}

View File

@ -34,15 +34,16 @@ import org.apache.cassandra.audit.AuditLogEntryType;
import org.apache.cassandra.auth.DataResource;
import org.apache.cassandra.auth.IResource;
import org.apache.cassandra.auth.Permission;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.cql3.*;
import org.apache.cassandra.cql3.functions.masking.ColumnMask;
import org.apache.cassandra.db.Keyspace;
import org.apache.cassandra.db.guardrails.Guardrails;
import org.apache.cassandra.db.marshal.*;
import org.apache.cassandra.exceptions.AlreadyExistsException;
import org.apache.cassandra.schema.*;
import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.service.reads.repair.ReadRepairStrategy;
import org.apache.cassandra.transport.Event.SchemaChange;
import org.apache.cassandra.transport.Event.SchemaChange.Change;
@ -94,8 +95,9 @@ public final class CreateTableStatement extends AlterSchemaStatement
this.useCompactStorage = useCompactStorage;
}
public Keyspaces apply(Keyspaces schema)
public Keyspaces apply(ClusterMetadata metadata)
{
Keyspaces schema = metadata.schema.getKeyspaces();
KeyspaceMetadata keyspace = schema.getNullable(keyspaceName);
if (null == keyspace)
throw ire("Keyspace '%s' doesn't exist", keyspaceName);
@ -108,10 +110,13 @@ public final class CreateTableStatement extends AlterSchemaStatement
throw new AlreadyExistsException(keyspaceName, tableName);
}
TableMetadata table = builder(keyspace.types).build();
TableMetadata.Builder builder = builder(keyspace.types).epoch(metadata.nextEpoch());
if (!builder.hasId() && !DatabaseDescriptor.useDeterministicTableID())
builder.id(TableId.get(metadata));
TableMetadata table = builder.build();
table.validate();
if (keyspace.createReplicationStrategy().hasTransientReplicas()
if (keyspace.replicationStrategy.hasTransientReplicas()
&& table.params.readRepair != ReadRepairStrategy.NONE)
{
throw ire("read_repair must be set to 'NONE' for transiently replicated keyspaces");
@ -128,6 +133,10 @@ public final class CreateTableStatement extends AlterSchemaStatement
{
super.validate(state);
// If a memtable configuration is specified, validate it against config
if (attrs.hasOption(TableParams.Option.MEMTABLE))
MemtableParams.get(attrs.getString(TableParams.Option.MEMTABLE.toString()));
// Guardrail on table properties
Guardrails.tableProperties.guard(attrs.updatedProperties(), attrs::removeProperty, state);
@ -139,8 +148,7 @@ public final class CreateTableStatement extends AlterSchemaStatement
{
int totalUserTables = Schema.instance.getUserKeyspaces()
.stream()
.map(Keyspace::open)
.mapToInt(keyspace -> keyspace.getColumnFamilyStores().size())
.mapToInt(ksm -> ksm.tables.size())
.sum();
Guardrails.tables.guard(totalUserTables + 1, tableName, false, state);
}

View File

@ -24,6 +24,7 @@ import org.apache.cassandra.cql3.QualifiedName;
import org.apache.cassandra.schema.*;
import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.triggers.TriggerExecutor;
import org.apache.cassandra.transport.Event.SchemaChange;
import org.apache.cassandra.transport.Event.SchemaChange.Change;
@ -45,8 +46,10 @@ public final class CreateTriggerStatement extends AlterSchemaStatement
this.ifNotExists = ifNotExists;
}
public Keyspaces apply(Keyspaces schema)
@Override
public Keyspaces apply(ClusterMetadata metadata)
{
Keyspaces schema = metadata.schema.getKeyspaces();
KeyspaceMetadata keyspace = schema.getNullable(keyspaceName);
if (null == keyspace)
throw ire("Keyspace '%s' doesn't exist", keyspaceName);

View File

@ -34,6 +34,7 @@ import org.apache.cassandra.schema.Keyspaces;
import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff;
import org.apache.cassandra.schema.Types;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.transport.Event.SchemaChange;
import org.apache.cassandra.transport.Event.SchemaChange.Change;
import org.apache.cassandra.transport.Event.SchemaChange.Target;
@ -75,8 +76,9 @@ public final class CreateTypeStatement extends AlterSchemaStatement
}
}
public Keyspaces apply(Keyspaces schema)
public Keyspaces apply(ClusterMetadata metadata)
{
Keyspaces schema = metadata.schema.getKeyspaces();
KeyspaceMetadata keyspace = schema.getNullable(keyspaceName);
if (null == keyspace)
throw ire("Keyspace '%s' doesn't exist", keyspaceName);

View File

@ -41,6 +41,7 @@ import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.schema.*;
import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.transport.Event.SchemaChange;
import org.apache.cassandra.transport.Event.SchemaChange.Change;
import org.apache.cassandra.transport.Event.SchemaChange.Target;
@ -110,7 +111,8 @@ public final class CreateViewStatement extends AlterSchemaStatement
this.state = state;
}
public Keyspaces apply(Keyspaces schema)
@Override
public Keyspaces apply(ClusterMetadata metadata)
{
if (!DatabaseDescriptor.getMaterializedViewsEnabled())
throw ire("Materialized views are disabled. Enable in cassandra.yaml to use.");
@ -119,11 +121,12 @@ public final class CreateViewStatement extends AlterSchemaStatement
* Basic dependency validations
*/
Keyspaces schema = metadata.schema.getKeyspaces();
KeyspaceMetadata keyspace = schema.getNullable(keyspaceName);
if (null == keyspace)
throw ire("Keyspace '%s' doesn't exist", keyspaceName);
if (keyspace.createReplicationStrategy().hasTransientReplicas())
if (keyspace.replicationStrategy.hasTransientReplicas())
throw new InvalidRequestException("Materialized views are not supported on transiently replicated keyspaces");
TableMetadata table = keyspace.tables.getNullable(tableName);
@ -324,6 +327,8 @@ public final class CreateViewStatement extends AlterSchemaStatement
if (attrs.hasProperty(TableAttributes.ID))
builder.id(attrs.getId());
else if (!builder.hasId() && !DatabaseDescriptor.useDeterministicTableID())
builder.id(TableId.get(metadata));
builder.params(attrs.asNewTableParams())
.kind(TableMetadata.Kind.VIEW);

View File

@ -35,6 +35,7 @@ import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.schema.*;
import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.transport.Event.SchemaChange;
import org.apache.cassandra.transport.Event.SchemaChange.Change;
@ -64,13 +65,14 @@ public final class DropAggregateStatement extends AlterSchemaStatement
this.ifExists = ifExists;
}
public Keyspaces apply(Keyspaces schema)
public Keyspaces apply(ClusterMetadata metadata)
{
String name =
argumentsSpeficied
? format("%s.%s(%s)", keyspaceName, aggregateName, join(", ", transform(arguments, CQL3Type.Raw::toString)))
: format("%s.%s", keyspaceName, aggregateName);
Keyspaces schema = metadata.schema.getKeyspaces();
KeyspaceMetadata keyspace = schema.getNullable(keyspaceName);
if (null == keyspace)
{

View File

@ -35,6 +35,7 @@ import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.schema.*;
import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.transport.Event.SchemaChange;
import org.apache.cassandra.transport.Event.SchemaChange.Change;
@ -65,13 +66,15 @@ public final class DropFunctionStatement extends AlterSchemaStatement
this.ifExists = ifExists;
}
public Keyspaces apply(Keyspaces schema)
@Override
public Keyspaces apply(ClusterMetadata metadata)
{
String name =
argumentsSpeficied
? format("%s.%s(%s)", keyspaceName, functionName, join(", ", transform(arguments, CQL3Type.Raw::toString)))
: format("%s.%s", keyspaceName, functionName);
Keyspaces schema = metadata.schema.getKeyspaces();
KeyspaceMetadata keyspace = schema.getNullable(keyspaceName);
if (null == keyspace)
{

View File

@ -26,6 +26,7 @@ import org.apache.cassandra.schema.*;
import org.apache.cassandra.schema.KeyspaceMetadata.KeyspaceDiff;
import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.transport.Event.SchemaChange;
import org.apache.cassandra.transport.Event.SchemaChange.Change;
import org.apache.cassandra.transport.Event.SchemaChange.Target;
@ -42,8 +43,10 @@ public final class DropIndexStatement extends AlterSchemaStatement
this.ifExists = ifExists;
}
public Keyspaces apply(Keyspaces schema)
@Override
public Keyspaces apply(ClusterMetadata metadata)
{
Keyspaces schema = metadata.schema.getKeyspaces();
KeyspaceMetadata keyspace = schema.getNullable(keyspaceName);
TableMetadata table = null == keyspace

View File

@ -25,6 +25,7 @@ import org.apache.cassandra.db.guardrails.Guardrails;
import org.apache.cassandra.schema.Keyspaces;
import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.transport.Event.SchemaChange;
import org.apache.cassandra.transport.Event.SchemaChange.Change;
@ -38,10 +39,12 @@ public final class DropKeyspaceStatement extends AlterSchemaStatement
this.ifExists = ifExists;
}
public Keyspaces apply(Keyspaces schema)
@Override
public Keyspaces apply(ClusterMetadata metadata)
{
Guardrails.dropKeyspaceEnabled.ensureEnabled(state);
Keyspaces schema = metadata.schema.getKeyspaces();
if (schema.containsKeyspace(keyspaceName))
return schema.without(keyspaceName);

View File

@ -26,6 +26,7 @@ import org.apache.cassandra.db.guardrails.Guardrails;
import org.apache.cassandra.schema.*;
import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.transport.Event.SchemaChange;
import org.apache.cassandra.transport.Event.SchemaChange.Change;
import org.apache.cassandra.transport.Event.SchemaChange.Target;
@ -47,10 +48,11 @@ public final class DropTableStatement extends AlterSchemaStatement
this.ifExists = ifExists;
}
public Keyspaces apply(Keyspaces schema)
public Keyspaces apply(ClusterMetadata metadata)
{
Guardrails.dropTruncateTableEnabled.ensureEnabled(state);
Keyspaces schema = metadata.schema.getKeyspaces();
KeyspaceMetadata keyspace = schema.getNullable(keyspaceName);
TableMetadata table = null == keyspace

View File

@ -24,6 +24,7 @@ import org.apache.cassandra.cql3.QualifiedName;
import org.apache.cassandra.schema.*;
import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.transport.Event.SchemaChange;
import org.apache.cassandra.transport.Event.SchemaChange.Change;
import org.apache.cassandra.transport.Event.SchemaChange.Target;
@ -42,8 +43,10 @@ public final class DropTriggerStatement extends AlterSchemaStatement
this.ifExists = ifExists;
}
public Keyspaces apply(Keyspaces schema)
@Override
public Keyspaces apply(ClusterMetadata metadata)
{
Keyspaces schema = metadata.schema.getKeyspaces();
KeyspaceMetadata keyspace = schema.getNullable(keyspaceName);
TableMetadata table = null == keyspace

View File

@ -31,6 +31,7 @@ import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff;
import org.apache.cassandra.schema.Keyspaces;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.transport.Event.SchemaChange.Change;
import org.apache.cassandra.transport.Event.SchemaChange.Target;
import org.apache.cassandra.transport.Event.SchemaChange;
@ -55,10 +56,12 @@ public final class DropTypeStatement extends AlterSchemaStatement
}
// TODO: expand types into tuples in all dropped columns of all tables
public Keyspaces apply(Keyspaces schema)
@Override
public Keyspaces apply(ClusterMetadata metadata)
{
ByteBuffer name = bytes(typeName);
Keyspaces schema = metadata.schema.getKeyspaces();
KeyspaceMetadata keyspace = schema.getNullable(keyspaceName);
UserType type = null == keyspace

View File

@ -25,6 +25,7 @@ import org.apache.cassandra.cql3.QualifiedName;
import org.apache.cassandra.schema.*;
import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.transport.Event.SchemaChange;
import org.apache.cassandra.transport.Event.SchemaChange.Change;
import org.apache.cassandra.transport.Event.SchemaChange.Target;
@ -41,8 +42,10 @@ public final class DropViewStatement extends AlterSchemaStatement
this.ifExists = ifExists;
}
public Keyspaces apply(Keyspaces schema)
@Override
public Keyspaces apply(ClusterMetadata metadata)
{
Keyspaces schema = metadata.schema.getKeyspaces();
KeyspaceMetadata keyspace = schema.getNullable(keyspaceName);
ViewMetadata view = null == keyspace

View File

@ -115,8 +115,8 @@ public final class TableAttributes extends PropertyDefinitions
if (hasOption(COMPRESSION))
builder.compression(CompressionParams.fromMap(getMap(COMPRESSION)));
if (hasOption(MEMTABLE))
builder.memtable(MemtableParams.get(getString(MEMTABLE)));
if (hasOption(Option.MEMTABLE))
builder.memtable(MemtableParams.getWithFallback(getString(Option.MEMTABLE)));
if (hasOption(DEFAULT_TIME_TO_LIVE))
builder.defaultTimeToLive(getInt(DEFAULT_TIME_TO_LIVE));

View File

@ -0,0 +1,185 @@
/*
* 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;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.db.partitions.PartitionUpdate;
import org.apache.cassandra.exceptions.CoordinatorBehindException;
import org.apache.cassandra.exceptions.InvalidRoutingException;
import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.metrics.TCMMetrics;
import org.apache.cassandra.net.IVerbHandler;
import org.apache.cassandra.net.Message;
import org.apache.cassandra.schema.SchemaConstants;
import org.apache.cassandra.service.StorageService;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.tcm.ClusterMetadataService;
import org.apache.cassandra.tcm.Epoch;
import org.apache.cassandra.tcm.ownership.VersionedEndpoints;
import org.apache.cassandra.utils.NoSpamLogger;
public abstract class AbstractMutationVerbHandler<T extends IMutation> implements IVerbHandler<T>
{
private static final Logger logger = LoggerFactory.getLogger(AbstractMutationVerbHandler.class);
private static final String logMessageTemplate = "Received mutation from {} for token {} outside valid range for keyspace {}";
public void doVerb(Message<T> message) throws IOException
{
processMessage(message, message.respondTo());
}
protected void processMessage(Message<T> message, InetAddressAndPort respondTo)
{
if (message.epoch().isAfter(Epoch.EMPTY))
{
ClusterMetadata metadata = ClusterMetadata.current();
metadata = checkTokenOwnership(metadata, message);
metadata = checkSchemaVersion(metadata, message);
}
applyMutation(message, respondTo);
}
abstract void applyMutation(Message<T> message, InetAddressAndPort respondToAddress);
private ClusterMetadata checkTokenOwnership(ClusterMetadata metadata, Message<T> message)
{
String keyspace = message.payload.getKeyspaceName();
DecoratedKey key = message.payload.key();
VersionedEndpoints.ForToken forToken = writePlacements(metadata, keyspace, key);
if (message.epoch().isAfter(metadata.epoch))
{
// If replica detects that coordinator has made an out-of-range request, it has to catch up blockingly,
// since coordinator's routing may be more recent.
if (!forToken.get().containsSelf())
{
metadata = ClusterMetadataService.instance().fetchLogFromPeerOrCMS(metadata, message.from(), message.epoch());
forToken = writePlacements(metadata, keyspace, key);
}
// Otherwise, coordinator and the replica agree about the placement of the givent token, so catch-up can be async
else
{
ClusterMetadataService.instance().fetchLogFromPeerOrCMSAsync(metadata, message.from(), message.epoch());
}
}
if (!forToken.get().containsSelf())
{
StorageService.instance.incOutOfRangeOperationCount();
Keyspace.open(message.payload.getKeyspaceName()).metric.outOfRangeTokenWrites.inc();
NoSpamLogger.log(logger, NoSpamLogger.Level.WARN, 1, TimeUnit.SECONDS, logMessageTemplate, message.from(), key.getToken(), message.payload.getKeyspaceName());
throw InvalidRoutingException.forWrite(message.from(), key.getToken(), metadata.epoch, message.payload);
}
if (forToken.lastModified().isAfter(message.epoch()))
{
TCMMetrics.instance.coordinatorBehindPlacements.mark();
throw new CoordinatorBehindException(String.format("Routing is correct, but coordinator needs to catch-up at least to epoch %s to maintain consistency. Current coordinator epoch is %s",
forToken.lastModified(), message.epoch()));
}
return metadata;
}
private ClusterMetadata checkSchemaVersion(ClusterMetadata metadata, Message<T> message)
{
if (SchemaConstants.isSystemKeyspace(message.payload.getKeyspaceName()) || message.epoch().is(metadata.epoch))
return metadata;
String keyspace = message.payload.getKeyspaceName();
Keyspace ks = metadata.schema.getKeyspace(keyspace);
if (ks != null)
{
if (message.epoch().isAfter(metadata.epoch))
{
// coordinator is ahead - check each partition update if the schema is ahead of the schema we have for the table
for (PartitionUpdate pu : message.payload.getPartitionUpdates())
{
Epoch remoteSchemaEpoch = pu.serializedAtEpoch;
if (remoteSchemaEpoch != null && remoteSchemaEpoch.isAfter(metadata.epoch))
{
// the partition update was serialized after the epoch we currently know, catch up and
// make sure we've seen the epoch it has seen, otherwise fail request.
metadata = ClusterMetadataService.instance().fetchLogFromPeerOrCMS(metadata, message.from(), message.epoch());
if (pu.serializedAtEpoch.isAfter(metadata.epoch))
throw new IllegalStateException(String.format("Coordinator %s is still ahead after fetching log, our epoch = %s, their epoch = %s",
message.from(),
metadata.epoch, message.epoch()));
}
}
}
else if (message.epoch().isBefore(metadata.schema.lastModified()))
{
// coordinator might not have seen the latest schema change - check each modified table individually
for (PartitionUpdate pu : message.payload.getPartitionUpdates())
{
// coordinator could be behind, check local tables
ColumnFamilyStore cfs = ks.getColumnFamilyStore(pu.metadata().id);
if (cfs != null)
{
Epoch remoteSchemaEpoch = pu.serializedAtEpoch;
if (remoteSchemaEpoch != null && remoteSchemaEpoch.isBefore(cfs.metadata().epoch))
{
TCMMetrics.instance.coordinatorBehindSchema.mark();
throw new CoordinatorBehindException(String.format("Coordinator %s is behind, our epoch = %s, their epoch = %s",
message.from(),
metadata.epoch, message.epoch()));
}
}
else
{
TCMMetrics.instance.coordinatorBehindSchema.mark();
throw new CoordinatorBehindException(String.format("Schema mismatch, coordinator %s is behind, we're missing table %s.%s, our epoch = %s, their epoch = %s",
message.from(),
pu.metadata().keyspace,
pu.metadata().name,
metadata.epoch, message.epoch()));
}
}
}
}
else
{
if (message.epoch().isBefore(metadata.schema.lastModified()))
{
TCMMetrics.instance.coordinatorBehindSchema.mark();
throw new CoordinatorBehindException(String.format("Schema mismatch, coordinator %s is behind, we're missing keyspace %s, our epoch = %s, their epoch = %s",
message.from(),
keyspace,
metadata.epoch, message.epoch()));
}
else
{
metadata = ClusterMetadataService.instance().fetchLogFromPeerOrCMS(metadata, message.from(), message.epoch());
}
}
return metadata;
}
private static VersionedEndpoints.ForToken writePlacements(ClusterMetadata metadata, String keyspace, DecoratedKey key)
{
return metadata.placements.get(metadata.schema.getKeyspace(keyspace).getMetadata().params.replication).writes.forToken(key.getToken());
}
}

View File

@ -90,14 +90,14 @@ import org.apache.cassandra.db.compaction.CompactionStrategyManager;
import org.apache.cassandra.db.compaction.OperationType;
import org.apache.cassandra.db.filter.ClusteringIndexFilter;
import org.apache.cassandra.db.filter.DataLimits;
import org.apache.cassandra.db.memtable.Flushing;
import org.apache.cassandra.db.memtable.Memtable;
import org.apache.cassandra.db.memtable.ShardBoundaries;
import org.apache.cassandra.db.lifecycle.LifecycleNewTracker;
import org.apache.cassandra.db.lifecycle.LifecycleTransaction;
import org.apache.cassandra.db.lifecycle.SSTableSet;
import org.apache.cassandra.db.lifecycle.Tracker;
import org.apache.cassandra.db.lifecycle.View;
import org.apache.cassandra.db.memtable.Flushing;
import org.apache.cassandra.db.memtable.Memtable;
import org.apache.cassandra.db.memtable.ShardBoundaries;
import org.apache.cassandra.db.partitions.CachedPartition;
import org.apache.cassandra.db.partitions.PartitionUpdate;
import org.apache.cassandra.db.repair.CassandraTableRepairManager;
@ -161,6 +161,8 @@ import org.apache.cassandra.service.snapshot.SnapshotLoader;
import org.apache.cassandra.service.snapshot.SnapshotManifest;
import org.apache.cassandra.service.snapshot.TableSnapshot;
import org.apache.cassandra.streaming.TableStreamManager;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.tcm.Epoch;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.DefaultValue;
import org.apache.cassandra.utils.ExecutorUtils;
@ -261,7 +263,8 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner
static final String TOKEN_DELIMITER = ":";
/** Special values used when the local ranges are not changed with ring changes (e.g. local tables). */
public static final int RING_VERSION_IRRELEVANT = -1;
// TODO - make this Epoch.EMPTY
public static final Epoch RING_VERSION_IRRELEVANT = Epoch.create(-1);
static
{
@ -375,26 +378,31 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner
}
public void reload()
{
reload(metadata());
}
public void reload(TableMetadata tableMetadata)
{
// metadata object has been mutated directly. make all the members jibe with new settings.
// only update these runtime-modifiable settings if they have not been modified.
if (!minCompactionThreshold.isModified())
for (ColumnFamilyStore cfs : concatWithIndexes())
cfs.minCompactionThreshold = new DefaultValue(metadata().params.compaction.minCompactionThreshold());
cfs.minCompactionThreshold = new DefaultValue<>(tableMetadata.params.compaction.minCompactionThreshold());
if (!maxCompactionThreshold.isModified())
for (ColumnFamilyStore cfs : concatWithIndexes())
cfs.maxCompactionThreshold = new DefaultValue(metadata().params.compaction.maxCompactionThreshold());
cfs.maxCompactionThreshold = new DefaultValue<>(tableMetadata.params.compaction.maxCompactionThreshold());
if (!crcCheckChance.isModified())
for (ColumnFamilyStore cfs : concatWithIndexes())
cfs.crcCheckChance = new DefaultValue(metadata().params.crcCheckChance);
cfs.crcCheckChance = new DefaultValue<>(tableMetadata.params.crcCheckChance);
compactionStrategyManager.maybeReloadParamsFromSchema(metadata().params.compaction);
compactionStrategyManager.maybeReloadParamsFromSchema(tableMetadata.params.compaction);
indexManager.reload();
indexManager.reload(tableMetadata);
memtableFactory = metadata().params.memtable.factory();
switchMemtableOrNotify(FlushReason.SCHEMA_CHANGE, Memtable::metadataUpdated);
memtableFactory = tableMetadata.params.memtable.factory();
switchMemtableOrNotify(FlushReason.SCHEMA_CHANGE, tableMetadata, Memtable::metadataUpdated);
}
public static Runnable getBackgroundCompactionTaskSubmitter()
@ -453,7 +461,9 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner
{
CompressionParams params = CompressionParams.fromMap(opts);
params.validate();
metadata.setLocalOverrides(metadata().unbuild().compression(params).build());
TableMetadata orig = metadata();
metadata.setLocalOverrides(orig.unbuild().compression(params).epoch(orig.epoch).build());
}
catch (ConfigurationException e)
{
@ -470,27 +480,26 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner
public ColumnFamilyStore(Keyspace keyspace,
String columnFamilyName,
Supplier<? extends SSTableId> sstableIdGenerator,
TableMetadataRef metadata,
TableMetadata initMetadata,
Directories directories,
boolean loadSSTables,
boolean registerBookeeping,
boolean offline)
boolean registerBookeeping)
{
assert directories != null;
assert metadata != null : "null metadata for " + keyspace + ':' + columnFamilyName;
assert initMetadata != null : "null metadata for " + keyspace + ':' + columnFamilyName;
this.keyspace = keyspace;
this.metadata = metadata;
this.metadata = initMetadata.ref;
this.directories = directories;
name = columnFamilyName;
minCompactionThreshold = new DefaultValue<>(metadata.get().params.compaction.minCompactionThreshold());
maxCompactionThreshold = new DefaultValue<>(metadata.get().params.compaction.maxCompactionThreshold());
crcCheckChance = new DefaultValue<>(metadata.get().params.crcCheckChance);
viewManager = keyspace.viewManager.forTable(metadata.id);
minCompactionThreshold = new DefaultValue<>(initMetadata.params.compaction.minCompactionThreshold());
maxCompactionThreshold = new DefaultValue<>(initMetadata.params.compaction.maxCompactionThreshold());
crcCheckChance = new DefaultValue<>(initMetadata.params.crcCheckChance);
viewManager = keyspace.viewManager.forTable(initMetadata);
this.sstableIdGenerator = sstableIdGenerator;
sampleReadLatencyMicros = DatabaseDescriptor.getReadRpcTimeout(TimeUnit.MICROSECONDS) / 2;
additionalWriteLatencyMicros = DatabaseDescriptor.getWriteRpcTimeout(TimeUnit.MICROSECONDS) / 2;
memtableFactory = metadata.get().params.memtable.factory();
memtableFactory = initMetadata.params.memtable.factory();
logger.info("Initializing {}.{}", getKeyspaceName(), name);
@ -514,7 +523,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner
{
Directories.SSTableLister sstableFiles = directories.sstableLister(Directories.OnTxnErr.IGNORE).skipTemporary(true);
sstables = SSTableReader.openAll(this, sstableFiles.list().entrySet(), metadata);
data.addInitialSSTablesWithoutUpdatingSize(sstables);
data.addInitialSSTablesWithoutUpdatingSize(sstables, this);
}
// compaction strategy should be created after the CFS has been prepared
@ -528,7 +537,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner
// create the private ColumnFamilyStores for the secondary column indexes
indexManager = new SecondaryIndexManager(this);
for (IndexMetadata info : metadata.get().indexes)
for (IndexMetadata info : initMetadata.indexes)
{
indexManager.addIndex(info, true);
}
@ -563,7 +572,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner
if (DatabaseDescriptor.isClientOrToolInitialized() || SchemaConstants.isSystemKeyspace(getKeyspaceName()))
topPartitions = null;
else
topPartitions = new TopPartitionTracker(metadata());
topPartitions = new TopPartitionTracker(initMetadata);
}
public static String getTableMBeanName(String ks, String name, boolean isIndex)
@ -741,32 +750,31 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner
}
public static ColumnFamilyStore createColumnFamilyStore(Keyspace keyspace, TableMetadataRef metadata, boolean loadSSTables)
public static ColumnFamilyStore createColumnFamilyStore(Keyspace keyspace, TableMetadata metadata, boolean loadSSTables)
{
return createColumnFamilyStore(keyspace, metadata.name, metadata, loadSSTables);
}
public static ColumnFamilyStore createColumnFamilyStore(Keyspace keyspace,
String columnFamily,
TableMetadataRef metadata,
TableMetadata metadata,
boolean loadSSTables)
{
Directories directories = new Directories(metadata.get());
return createColumnFamilyStore(keyspace, columnFamily, metadata, directories, loadSSTables, true, false);
Directories directories = new Directories(metadata);
return createColumnFamilyStore(keyspace, columnFamily, metadata, directories, loadSSTables, true);
}
/** This is only directly used by offline tools */
public static synchronized ColumnFamilyStore createColumnFamilyStore(Keyspace keyspace,
String columnFamily,
TableMetadataRef metadata,
TableMetadata metadata,
Directories directories,
boolean loadSSTables,
boolean registerBookkeeping,
boolean offline)
boolean registerBookkeeping)
{
return new ColumnFamilyStore(keyspace, columnFamily,
directories.getUIDGenerator(SSTableIdFactory.instance.defaultBuilder()),
metadata, directories, loadSSTables, registerBookkeeping, offline);
metadata, directories, loadSSTables, registerBookkeeping);
}
/**
@ -963,10 +971,10 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner
* Checks with the memtable if it should be switched for the given reason, and if not, calls the specified
* notification method.
*/
private void switchMemtableOrNotify(FlushReason reason, Consumer<Memtable> elseNotify)
private void switchMemtableOrNotify(FlushReason reason, TableMetadata metadata, Consumer<Memtable> elseNotify)
{
Memtable currentMemtable = data.getView().getCurrentMemtable();
if (currentMemtable.shouldSwitch(reason))
if (currentMemtable.shouldSwitch(reason, metadata))
switchMemtableIfCurrent(currentMemtable, reason);
else
elseNotify.accept(currentMemtable);
@ -1476,9 +1484,9 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner
public static class VersionedLocalRanges extends ArrayList<Splitter.WeightedRange>
{
public final long ringVersion;
public final Epoch ringVersion;
public VersionedLocalRanges(long ringVersion, int initialSize)
public VersionedLocalRanges(Epoch ringVersion, int initialSize)
{
super(initialSize);
this.ringVersion = ringVersion;
@ -1487,16 +1495,16 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner
public VersionedLocalRanges localRangesWeighted()
{
ClusterMetadata metadata = ClusterMetadata.current();
if (!SchemaConstants.isLocalSystemKeyspace(getKeyspaceName())
&& getPartitioner() == StorageService.instance.getTokenMetadata().partitioner)
&& getPartitioner() == metadata.partitioner)
{
DiskBoundaryManager.VersionedRangesAtEndpoint versionedLocalRanges = DiskBoundaryManager.getVersionedLocalRanges(this);
Set<Range<Token>> localRanges = versionedLocalRanges.rangesAtEndpoint.ranges();
long ringVersion = versionedLocalRanges.ringVersion;
Epoch epoch = versionedLocalRanges.epoch;
if (!localRanges.isEmpty())
{
VersionedLocalRanges weightedRanges = new VersionedLocalRanges(ringVersion, localRanges.size());
VersionedLocalRanges weightedRanges = new VersionedLocalRanges(epoch, localRanges.size());
for (Range<Token> r : localRanges)
{
// WeightedRange supports only unwrapped ranges as it relies
@ -1509,7 +1517,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner
}
else
{
return fullWeightedRange(ringVersion, getPartitioner());
return fullWeightedRange(epoch, getPartitioner());
}
}
else
@ -1527,11 +1535,13 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner
return ShardBoundaries.NONE;
ShardBoundaries shardBoundaries = cachedShardBoundaries;
ClusterMetadata metadata = ClusterMetadata.currentNullable();
if (metadata == null)
return ShardBoundaries.NONE;
if (shardBoundaries == null ||
shardBoundaries.shardCount() != shardCount ||
(shardBoundaries.ringVersion != RING_VERSION_IRRELEVANT &&
shardBoundaries.ringVersion != StorageService.instance.getTokenMetadata().getRingVersion()))
(!shardBoundaries.epoch.equals(Epoch.EMPTY) && !shardBoundaries.epoch.equals(metadata.epoch)))
{
VersionedLocalRanges weightedRanges = localRangesWeighted();
@ -1545,9 +1555,9 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner
}
@VisibleForTesting
public static VersionedLocalRanges fullWeightedRange(long ringVersion, IPartitioner partitioner)
public static VersionedLocalRanges fullWeightedRange(Epoch epoch, IPartitioner partitioner)
{
VersionedLocalRanges ranges = new VersionedLocalRanges(ringVersion, 1);
VersionedLocalRanges ranges = new VersionedLocalRanges(epoch, 1);
ranges.add(new Splitter.WeightedRange(1.0, new Range<>(partitioner.getMinimumToken(), partitioner.getMinimumToken())));
return ranges;
}
@ -3341,14 +3351,19 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner
public DiskBoundaries getDiskBoundaries()
{
return diskBoundaryManager.getDiskBoundaries(this);
return diskBoundaryManager.getDiskBoundaries(this, metadata.get());
}
public DiskBoundaries getDiskBoundaries(TableMetadata initialMetadata)
{
return diskBoundaryManager.getDiskBoundaries(this, initialMetadata);
}
public void invalidateLocalRanges()
{
diskBoundaryManager.invalidate();
switchMemtableOrNotify(FlushReason.OWNED_RANGES_CHANGE, Memtable::localRangesUpdated);
switchMemtableOrNotify(FlushReason.OWNED_RANGES_CHANGE, metadata(), Memtable::localRangesUpdated);
}
@Override

View File

@ -21,20 +21,20 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.net.IVerbHandler;
import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.net.Message;
import org.apache.cassandra.net.MessagingService;
import org.apache.cassandra.service.StorageProxy;
import static org.apache.cassandra.utils.Clock.Global.nanoTime;
public class CounterMutationVerbHandler implements IVerbHandler<CounterMutation>
public class CounterMutationVerbHandler extends AbstractMutationVerbHandler<CounterMutation>
{
public static final CounterMutationVerbHandler instance = new CounterMutationVerbHandler();
private static final Logger logger = LoggerFactory.getLogger(CounterMutationVerbHandler.class);
public void doVerb(final Message<CounterMutation> message)
protected void applyMutation(final Message<CounterMutation> message, InetAddressAndPort respondToAddress)
{
long queryStartNanoTime = nanoTime();
final CounterMutation cm = message.payload;
@ -50,7 +50,7 @@ public class CounterMutationVerbHandler implements IVerbHandler<CounterMutation>
// it's own in that case.
StorageProxy.applyCounterMutationOnLeader(cm,
localDataCenter,
() -> MessagingService.instance().send(message.emptyResponse(), message.from()),
() -> MessagingService.instance().send(message.emptyResponse(), respondToAddress),
queryStartNanoTime);
}
}

View File

@ -27,28 +27,32 @@ import com.google.common.collect.ImmutableList;
import org.apache.cassandra.io.sstable.Descriptor;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.service.StorageService;
import org.apache.cassandra.tcm.Epoch;
public class DiskBoundaries
{
public final List<Directories.DataDirectory> directories;
public final ImmutableList<PartitionPosition> positions;
final long ringVersion;
final Epoch epoch;
final int directoriesVersion;
private final ColumnFamilyStore cfs;
private volatile boolean isInvalid = false;
public DiskBoundaries(ColumnFamilyStore cfs, Directories.DataDirectory[] directories, int diskVersion)
{
this(cfs, directories, null, -1, diskVersion);
this(cfs, directories, null, Epoch.EMPTY, diskVersion);
}
@VisibleForTesting
public DiskBoundaries(ColumnFamilyStore cfs, Directories.DataDirectory[] directories, List<PartitionPosition> positions, long ringVersion, int diskVersion)
public DiskBoundaries(ColumnFamilyStore cfs,
Directories.DataDirectory[] directories,
List<PartitionPosition> positions,
Epoch epoch,
int diskVersion)
{
this.directories = directories == null ? null : ImmutableList.copyOf(directories);
this.positions = positions == null ? null : ImmutableList.copyOf(positions);
this.ringVersion = ringVersion;
this.epoch = epoch;
this.directoriesVersion = diskVersion;
this.cfs = cfs;
}
@ -60,7 +64,7 @@ public class DiskBoundaries
DiskBoundaries that = (DiskBoundaries) o;
if (ringVersion != that.ringVersion) return false;
if (!epoch.equals(that.epoch)) return false;
if (directoriesVersion != that.directoriesVersion) return false;
if (!directories.equals(that.directories)) return false;
return positions != null ? positions.equals(that.positions) : that.positions == null;
@ -70,7 +74,7 @@ public class DiskBoundaries
{
int result = directories != null ? directories.hashCode() : 0;
result = 31 * result + (positions != null ? positions.hashCode() : 0);
result = 31 * result + (int) (ringVersion ^ (ringVersion >>> 32));
result = 31 * result + epoch.hashCode();
result = 31 * result + directoriesVersion;
return result;
}
@ -80,7 +84,7 @@ public class DiskBoundaries
return "DiskBoundaries{" +
"directories=" + directories +
", positions=" + positions +
", ringVersion=" + ringVersion +
", epoch=" + epoch +
", directoriesVersion=" + directoriesVersion +
'}';
}
@ -93,8 +97,7 @@ public class DiskBoundaries
if (isInvalid)
return true;
int currentDiskVersion = DisallowedDirectories.getDirectoriesVersion();
long currentRingVersion = StorageService.instance.getTokenMetadata().getRingVersion();
return currentDiskVersion != directoriesVersion || (ringVersion != -1 && currentRingVersion != ringVersion);
return currentDiskVersion != directoriesVersion;
}
public void invalidate()

View File

@ -31,9 +31,12 @@ import org.apache.cassandra.dht.Range;
import org.apache.cassandra.dht.Splitter;
import org.apache.cassandra.dht.Token;
import org.apache.cassandra.locator.RangesAtEndpoint;
import org.apache.cassandra.locator.TokenMetadata;
import org.apache.cassandra.service.PendingRangeCalculatorService;
import org.apache.cassandra.tcm.ClusterMetadataService;
import org.apache.cassandra.tcm.Epoch;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.StorageService;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.tcm.ownership.DataPlacement;
import org.apache.cassandra.utils.FBUtilities;
public class DiskBoundaryManager
@ -43,18 +46,24 @@ public class DiskBoundaryManager
public DiskBoundaries getDiskBoundaries(ColumnFamilyStore cfs)
{
if (!cfs.getPartitioner().splitter().isPresent())
return getDiskBoundaries(cfs, cfs.metadata());
}
public DiskBoundaries getDiskBoundaries(ColumnFamilyStore cfs, TableMetadata metadata)
{
if (!metadata.partitioner.splitter().isPresent())
return new DiskBoundaries(cfs, cfs.getDirectories().getWriteableLocations(), DisallowedDirectories.getDirectoriesVersion());
if (diskBoundaries == null || diskBoundaries.isOutOfDate())
{
synchronized (this)
{
if (diskBoundaries == null || diskBoundaries.isOutOfDate())
{
logger.debug("Refreshing disk boundary cache for {}.{}", cfs.getKeyspaceName(), cfs.getTableName());
logger.trace("Refreshing disk boundary cache for {}.{}", cfs.getKeyspaceName(), cfs.getTableName());
DiskBoundaries oldBoundaries = diskBoundaries;
diskBoundaries = getDiskBoundaryValue(cfs);
logger.debug("Updating boundaries from {} to {} for {}.{}", oldBoundaries, diskBoundaries, cfs.getKeyspaceName(), cfs.getTableName());
diskBoundaries = getDiskBoundaryValue(cfs, metadata.partitioner);
logger.trace("Updating boundaries from {} to {} for {}.{}", oldBoundaries, diskBoundaries, cfs.getKeyspaceName(), cfs.getTableName());
}
}
}
@ -70,12 +79,12 @@ public class DiskBoundaryManager
static class VersionedRangesAtEndpoint
{
public final RangesAtEndpoint rangesAtEndpoint;
public final long ringVersion;
public final Epoch epoch;
VersionedRangesAtEndpoint(RangesAtEndpoint rangesAtEndpoint, long ringVersion)
VersionedRangesAtEndpoint(RangesAtEndpoint rangesAtEndpoint, Epoch epoch)
{
this.rangesAtEndpoint = rangesAtEndpoint;
this.ringVersion = ringVersion;
this.epoch = epoch;
}
}
@ -83,26 +92,35 @@ public class DiskBoundaryManager
{
RangesAtEndpoint localRanges;
long ringVersion;
TokenMetadata tmd;
Epoch epoch;
ClusterMetadata metadata;
do
{
tmd = StorageService.instance.getTokenMetadata();
ringVersion = tmd.getRingVersion();
localRanges = getLocalRanges(cfs, tmd);
logger.debug("Got local ranges {} (ringVersion = {})", localRanges, ringVersion);
metadata = ClusterMetadata.current();
epoch = metadata.epoch;
localRanges = getLocalRanges(cfs, metadata);
logger.debug("Got local ranges {} (epoch = {})", localRanges, epoch);
}
while (ringVersion != tmd.getRingVersion()); // if ringVersion is different here it means that
while (!metadata.epoch.equals(ClusterMetadata.current().epoch)); // if epoch is different here it means that
// it might have changed before we calculated localRanges - recalculate
return new VersionedRangesAtEndpoint(localRanges, ringVersion);
return new VersionedRangesAtEndpoint(localRanges, epoch);
}
private static DiskBoundaries getDiskBoundaryValue(ColumnFamilyStore cfs)
private static DiskBoundaries getDiskBoundaryValue(ColumnFamilyStore cfs, IPartitioner partitioner)
{
VersionedRangesAtEndpoint rangesAtEndpoint = getVersionedLocalRanges(cfs);
RangesAtEndpoint localRanges = rangesAtEndpoint.rangesAtEndpoint;
long ringVersion = rangesAtEndpoint.ringVersion;
if (ClusterMetadataService.instance() == null)
return new DiskBoundaries(cfs, cfs.getDirectories().getWriteableLocations(), null, Epoch.EMPTY, DisallowedDirectories.getDirectoriesVersion());
RangesAtEndpoint localRanges;
ClusterMetadata metadata;
do
{
metadata = ClusterMetadata.current();
localRanges = getLocalRanges(cfs, metadata);
logger.debug("Got local ranges {} (epoch = {})", localRanges, metadata.epoch);
}
while (metadata.epoch != ClusterMetadata.current().epoch);
int directoriesVersion;
Directories.DataDirectory[] dirs;
@ -114,29 +132,31 @@ public class DiskBoundaryManager
while (directoriesVersion != DisallowedDirectories.getDirectoriesVersion()); // if directoriesVersion has changed we need to recalculate
if (localRanges == null || localRanges.isEmpty())
return new DiskBoundaries(cfs, dirs, null, ringVersion, directoriesVersion);
return new DiskBoundaries(cfs, dirs, null, metadata.epoch, directoriesVersion);
List<PartitionPosition> positions = getDiskBoundaries(localRanges, cfs.getPartitioner(), dirs);
List<PartitionPosition> positions = getDiskBoundaries(localRanges, partitioner, dirs);
return new DiskBoundaries(cfs, dirs, positions, ringVersion, directoriesVersion);
return new DiskBoundaries(cfs, dirs, positions, metadata.epoch, directoriesVersion);
}
private static RangesAtEndpoint getLocalRanges(ColumnFamilyStore cfs, TokenMetadata tmd)
private static RangesAtEndpoint getLocalRanges(ColumnFamilyStore cfs, ClusterMetadata metadata)
{
RangesAtEndpoint localRanges;
DataPlacement placement;
if (StorageService.instance.isBootstrapMode()
&& !StorageService.isReplacingSameAddress()) // When replacing same address, the node marks itself as UN locally
{
PendingRangeCalculatorService.instance.blockUntilFinished();
localRanges = tmd.getPendingRanges(cfs.getKeyspaceName(), FBUtilities.getBroadcastAddressAndPort());
placement = metadata.placements.get(cfs.keyspace.getMetadata().params.replication);
}
else
{
// Reason we use use the future settled TMD is that if we decommission a node, we want to stream
// Reason we use use the future settled metadata is that if we decommission a node, we want to stream
// from that node to the correct location on disk, if we didn't, we would put new files in the wrong places.
// We do this to minimize the amount of data we need to move in rebalancedisks once everything settled
localRanges = cfs.keyspace.getReplicationStrategy().getAddressReplicas(tmd.cloneAfterAllSettled(), FBUtilities.getBroadcastAddressAndPort());
placement = metadata.writePlacementAllSettled(cfs.keyspace.getMetadata());
}
localRanges = placement.writes.byEndpoint().get(FBUtilities.getBroadcastAddressAndPort());
return localRanges;
}

View File

@ -56,13 +56,11 @@ import org.apache.cassandra.locator.AbstractReplicationStrategy;
import org.apache.cassandra.metrics.KeyspaceMetrics;
import org.apache.cassandra.repair.KeyspaceRepairManager;
import org.apache.cassandra.schema.KeyspaceMetadata;
import org.apache.cassandra.schema.ReplicationParams;
import org.apache.cassandra.schema.Schema;
import org.apache.cassandra.schema.SchemaConstants;
import org.apache.cassandra.schema.SchemaProvider;
import org.apache.cassandra.schema.TableId;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.schema.TableMetadataRef;
import org.apache.cassandra.service.snapshot.TableSnapshot;
import org.apache.cassandra.tracing.Tracing;
import org.apache.cassandra.utils.ByteBufferUtil;
@ -91,6 +89,7 @@ public class Keyspace
private static int TEST_FAIL_MV_LOCKS_COUNT = CassandraRelevantProperties.TEST_FAIL_MV_LOCKS_COUNT.getInt();
public final KeyspaceMetrics metric;
public final KeyspaceMetadataRef metadataRef;
// It is possible to call Keyspace.open without a running daemon, so it makes sense to ensure
// proper directories here as well as in CassandraDaemon.
@ -100,8 +99,6 @@ public class Keyspace
DatabaseDescriptor.createAllDirectories();
}
private volatile KeyspaceMetadata metadata;
//OpOrder is defined globally since we need to order writes across
//Keyspaces in the case of Views (batchlog of view mutations)
public static final OpOrder writeOrder = new OpOrder();
@ -109,12 +106,11 @@ public class Keyspace
/* ColumnFamilyStore per column family */
private final ConcurrentMap<TableId, ColumnFamilyStore> columnFamilyStores = new ConcurrentHashMap<>();
private volatile AbstractReplicationStrategy replicationStrategy;
public final ViewManager viewManager;
private final KeyspaceWriteHandler writeHandler;
private volatile ReplicationParams replicationParams;
private final KeyspaceRepairManager repairManager;
private final SchemaProvider schema;
private final String name;
private static volatile boolean initialized = false;
@ -148,23 +144,15 @@ public class Keyspace
public static Keyspace open(String keyspaceName)
{
assert initialized || SchemaConstants.isLocalSystemKeyspace(keyspaceName) : "Initialized: " + initialized;
return open(keyspaceName, Schema.instance, true);
Keyspace ks = Schema.instance.getKeyspaceInstance(keyspaceName);
assert ks != null : "Unknown keyspace " + keyspaceName;
return ks;
}
// to only be used by org.apache.cassandra.tools.Standalone* classes
public static Keyspace openWithoutSSTables(String keyspaceName)
{
return open(keyspaceName, Schema.instance, false);
}
public static Keyspace open(String keyspaceName, SchemaProvider schema, boolean loadSSTables)
{
return schema.maybeAddKeyspaceInstance(keyspaceName, () -> new Keyspace(keyspaceName, schema, loadSSTables));
}
public static ColumnFamilyStore openAndGetStore(TableMetadataRef tableRef)
{
return open(tableRef.keyspace).getColumnFamilyStore(tableRef.id);
return Schema.instance.getKeyspaceInstance(keyspaceName);
}
public static ColumnFamilyStore openAndGetStore(TableMetadata table)
@ -188,15 +176,9 @@ public class Keyspace
}
}
public void setMetadata(KeyspaceMetadata metadata)
{
this.metadata = metadata;
createReplicationStrategy(metadata);
}
public KeyspaceMetadata getMetadata()
{
return metadata;
return metadataRef.get();
}
public Collection<ColumnFamilyStore> getColumnFamilyStores()
@ -216,7 +198,7 @@ public class Keyspace
{
ColumnFamilyStore cfs = columnFamilyStores.get(id);
if (cfs == null)
throw new IllegalArgumentException("Unknown CF " + id);
throw new IllegalArgumentException(String.format("Unknown CF %s %s", id, columnFamilyStores));
return cfs;
}
@ -317,38 +299,53 @@ public class Keyspace
return getColumnFamilyStores().stream().flatMap(cfs -> cfs.listSnapshots().values().stream());
}
public static Keyspace forSchema(String keyspaceName, SchemaProvider schema)
{
return new Keyspace(keyspaceName, schema, true);
}
private Keyspace(String keyspaceName, SchemaProvider schema, boolean loadSSTables)
{
this(schema, schema.getKeyspaceMetadata(keyspaceName), loadSSTables);
}
public Keyspace(SchemaProvider schema, KeyspaceMetadata metadata, boolean loadSSTables)
{
this.schema = schema;
metadata = schema.getKeyspaceMetadata(keyspaceName);
assert metadata != null : "Unknown keyspace " + keyspaceName;
this.name = metadata.name;
assert metadata != null : "Unknown keyspace " + metadata.name;
if (metadata.isVirtual())
throw new IllegalStateException("Cannot initialize Keyspace with virtual metadata " + keyspaceName);
createReplicationStrategy(metadata);
throw new IllegalStateException("Cannot initialize Keyspace with virtual metadata " + metadata.name);
this.metric = new KeyspaceMetrics(this);
this.viewManager = new ViewManager(this);
this.metadataRef = new KeyspaceMetadataRef(metadata, schema);
for (TableMetadata cfm : metadata.tablesAndViews())
{
logger.trace("Initializing {}.{}", getName(), cfm.name);
initCf(schema.getTableMetadataRef(cfm.id), loadSSTables);
initCf(cfm, loadSSTables);
}
this.viewManager.reload(false);
this.viewManager.reload(metadata);
this.metadataRef.unsetInitial();
this.repairManager = new CassandraKeyspaceRepairManager(this);
this.writeHandler = new CassandraKeyspaceWriteHandler(this);
}
private Keyspace(KeyspaceMetadata metadata)
public Keyspace(KeyspaceMetadata metadata)
{
this.schema = Schema.instance;
this.metadata = metadata;
createReplicationStrategy(metadata);
this.name = metadata.name;
this.metric = new KeyspaceMetrics(this);
this.viewManager = new ViewManager(this);
this.repairManager = new CassandraKeyspaceRepairManager(this);
this.writeHandler = new CassandraKeyspaceWriteHandler(this);
this.metadataRef = new KeyspaceMetadataRef(metadata, schema);
}
public KeyspaceRepairManager getRepairManager()
@ -361,18 +358,6 @@ public class Keyspace
return new Keyspace(metadata);
}
private void createReplicationStrategy(KeyspaceMetadata ksm)
{
logger.info("Creating replication strategy " + ksm.name + " params " + ksm.params);
replicationStrategy = ksm.createReplicationStrategy();
if (!ksm.params.replication.equals(replicationParams))
{
logger.debug("New replication settings for keyspace {} - invalidating disk boundary caches", ksm.name);
columnFamilyStores.values().forEach(ColumnFamilyStore::invalidateLocalRanges);
}
replicationParams = ksm.params.replication;
}
// best invoked on the compaction manager.
public void dropCf(TableId tableId, boolean dropData)
{
@ -433,7 +418,7 @@ public class Keyspace
/**
* adds a cf to internal structures, ends up creating disk files).
*/
public void initCf(TableMetadataRef metadata, boolean loadSSTables)
public void initCf(TableMetadata metadata, boolean loadSSTables)
{
ColumnFamilyStore cfs = columnFamilyStores.get(metadata.id);
@ -442,7 +427,8 @@ public class Keyspace
// CFS being created for the first time, either on server startup or new CF being added.
// We don't worry about races here; startup is safe, and adding multiple idential CFs
// simultaneously is a "don't do that" scenario.
ColumnFamilyStore oldCfs = columnFamilyStores.putIfAbsent(metadata.id, ColumnFamilyStore.createColumnFamilyStore(this, metadata, loadSSTables));
ColumnFamilyStore oldCfs = columnFamilyStores.putIfAbsent(metadata.id,
ColumnFamilyStore.createColumnFamilyStore(this, metadata, loadSSTables));
// CFS mbean instantiation will error out before we hit this, but in case that changes...
if (oldCfs != null)
throw new IllegalStateException("added multiple mappings for cf id " + metadata.id);
@ -452,7 +438,7 @@ public class Keyspace
// re-initializing an existing CF. This will happen if you cleared the schema
// on this node and it's getting repopulated from the rest of the cluster.
assert cfs.name.equals(metadata.name);
cfs.reload();
cfs.reload(metadata);
}
}
@ -514,7 +500,7 @@ public class Keyspace
boolean isDeferrable,
Promise<?> future)
{
if (TEST_FAIL_WRITES && metadata.name.equals(TEST_FAIL_WRITES_KS))
if (TEST_FAIL_WRITES && getMetadata().name.equals(TEST_FAIL_WRITES_KS))
throw new RuntimeException("Testing write failures");
Lock[] locks = null;
@ -626,7 +612,7 @@ public class Keyspace
try
{
Tracing.trace("Creating materialized view mutations from base table replica");
viewManager.forTable(upd.metadata().id).pushViewReplicaUpdates(upd, makeDurable, baseComplete);
viewManager.forTable(upd.metadata()).pushViewReplicaUpdates(upd, makeDurable, baseComplete);
}
catch (Throwable t)
{
@ -661,7 +647,7 @@ public class Keyspace
public AbstractReplicationStrategy getReplicationStrategy()
{
return replicationStrategy;
return getMetadata().replicationStrategy;
}
public List<Future<?>> flush(ColumnFamilyStore.FlushReason reason)
@ -749,6 +735,11 @@ public class Keyspace
return Schema.instance.getKeyspaces().stream().map(Schema.instance::getKeyspaceInstance).filter(Objects::nonNull);
}
public static Iterable<Keyspace> nonSystem()
{
return Iterables.transform(Schema.instance.distributedKeyspaces().names(), Keyspace::open);
}
public static Iterable<Keyspace> nonLocalStrategy()
{
return Iterables.transform(Schema.instance.distributedKeyspaces().names(), Keyspace::open);
@ -767,6 +758,37 @@ public class Keyspace
public String getName()
{
return metadata.name;
return name;
}
private static class KeyspaceMetadataRef
{
// We need "initial" keyspace metadata for initCF to run, due to circular dependency
// between keyspace keyspace -> column family -> keyspace metadata. There are some
// calls within initCF that try accessing keyspace metadata, which requires the metadata
// of initializing keyspace to already be visible via ClusterMetadata#schema.
private KeyspaceMetadata initial;
private final String name;
private final SchemaProvider provider;
public KeyspaceMetadataRef(KeyspaceMetadata initial, SchemaProvider provider)
{
this.initial = initial;
this.name = initial.name;
this.provider = provider;
}
public KeyspaceMetadata get()
{
if (initial != null)
return initial;
return provider.getKeyspaceMetadata(name);
}
public void unsetInitial()
{
this.initial = null;
}
}
}

View File

@ -24,7 +24,7 @@ import org.apache.cassandra.tracing.Tracing;
import static org.apache.cassandra.db.commitlog.CommitLogSegment.ENTRY_OVERHEAD_SIZE;
public class MutationVerbHandler implements IVerbHandler<Mutation>
public class MutationVerbHandler extends AbstractMutationVerbHandler<Mutation>
{
public static final MutationVerbHandler instance = new MutationVerbHandler();
@ -51,7 +51,7 @@ public class MutationVerbHandler implements IVerbHandler<Mutation>
InetAddressAndPort respondToAddress = message.respondTo();
try
{
message.payload.applyFuture().addCallback(o -> respond(message, respondToAddress), wto -> failed());
processMessage(message, respondToAddress);
}
catch (WriteTimeoutException wto)
{
@ -59,6 +59,11 @@ public class MutationVerbHandler implements IVerbHandler<Mutation>
}
}
protected void applyMutation(Message<Mutation> message, InetAddressAndPort respondToAddress)
{
message.payload.applyFuture().addCallback(o -> respond(message, respondToAddress), wto -> failed());
}
private static void forwardToLocalNodes(Message<Mutation> originalMessage, ForwardingInfo forwardTo)
{
Message.Builder<Mutation> builder =

View File

@ -53,6 +53,7 @@ import org.apache.cassandra.net.Verb;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.service.StorageProxy;
import org.apache.cassandra.tcm.Epoch;
import org.apache.cassandra.tracing.Tracing;
/**
@ -65,7 +66,9 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
protected final DataRange dataRange;
protected final Slices requestedSlices;
private PartitionRangeReadCommand(boolean isDigest,
@VisibleForTesting
protected PartitionRangeReadCommand(Epoch serializedAtEpoch,
boolean isDigest,
int digestVersion,
boolean acceptsTransient,
TableMetadata metadata,
@ -77,13 +80,13 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
Index.QueryPlan indexQueryPlan,
boolean trackWarnings)
{
super(Kind.PARTITION_RANGE, isDigest, digestVersion, acceptsTransient, metadata, nowInSec, columnFilter, rowFilter, limits, indexQueryPlan, trackWarnings);
super(serializedAtEpoch, Kind.PARTITION_RANGE, isDigest, digestVersion, acceptsTransient, metadata, nowInSec, columnFilter, rowFilter, limits, indexQueryPlan, trackWarnings);
this.dataRange = dataRange;
this.requestedSlices = dataRange.clusteringIndexFilter.getSlices(metadata());
}
private static PartitionRangeReadCommand create(boolean isDigest,
private static PartitionRangeReadCommand create(Epoch serializedAtEpoch,
boolean isDigest,
int digestVersion,
boolean acceptsTransient,
TableMetadata metadata,
@ -109,7 +112,8 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
indexQueryPlan,
trackWarnings);
}
return new PartitionRangeReadCommand(isDigest,
return new PartitionRangeReadCommand(serializedAtEpoch,
isDigest,
digestVersion,
acceptsTransient,
metadata,
@ -129,7 +133,8 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
DataLimits limits,
DataRange dataRange)
{
return create(false,
return create(metadata.epoch,
false,
0,
false,
metadata,
@ -152,7 +157,8 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
*/
public static PartitionRangeReadCommand allDataRead(TableMetadata metadata, long nowInSec)
{
return create(false,
return create(metadata.epoch,
false,
0,
false,
metadata,
@ -202,7 +208,8 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
// DataLimits.CQLGroupByLimits.GroupByAwareCounter assumes that if GroupingState.hasClustering(), then we're in
// the middle of a group, but we can't make that assumption if we query and range "in advance" of where we are
// on the ring.
return create(isDigestQuery(),
return create(serializedAtEpoch(),
isDigestQuery(),
digestVersion(),
acceptsTransient(),
metadata(),
@ -217,7 +224,8 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
public PartitionRangeReadCommand copy()
{
return create(isDigestQuery(),
return create(serializedAtEpoch(),
isDigestQuery(),
digestVersion(),
acceptsTransient(),
metadata(),
@ -233,7 +241,8 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
@Override
protected PartitionRangeReadCommand copyAsDigestQuery()
{
return create(true,
return create(serializedAtEpoch(),
true,
digestVersion(),
false,
metadata(),
@ -249,7 +258,8 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
@Override
protected PartitionRangeReadCommand copyAsTransientQuery()
{
return create(false,
return create(serializedAtEpoch(),
false,
0,
true,
metadata(),
@ -265,7 +275,8 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
@Override
public PartitionRangeReadCommand withUpdatedLimit(DataLimits newLimits)
{
return create(isDigestQuery(),
return create(serializedAtEpoch(),
isDigestQuery(),
digestVersion(),
acceptsTransient(),
metadata(),
@ -281,7 +292,8 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
@Override
public PartitionRangeReadCommand withUpdatedLimitsAndDataRange(DataLimits newLimits, DataRange newDataRange)
{
return create(isDigestQuery(),
return create(serializedAtEpoch(),
isDigestQuery(),
digestVersion(),
acceptsTransient(),
metadata(),
@ -515,6 +527,7 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
{
public ReadCommand deserialize(DataInputPlus in,
int version,
Epoch serializedAtEpoch,
boolean isDigest,
int digestVersion,
boolean acceptsTransient,
@ -527,7 +540,7 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
throws IOException
{
DataRange range = DataRange.serializer.deserialize(in, version, metadata);
return PartitionRangeReadCommand.create(isDigest, digestVersion, acceptsTransient, metadata, nowInSec, columnFilter, rowFilter, limits, range, indexQueryPlan, false);
return PartitionRangeReadCommand.create(serializedAtEpoch, isDigest, digestVersion, acceptsTransient, metadata, nowInSec, columnFilter, rowFilter, limits, range, indexQueryPlan, false);
}
}
@ -545,7 +558,7 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
Index.QueryPlan indexQueryPlan,
boolean trackWarnings)
{
super(isDigest, digestVersion, acceptsTransient, metadata, nowInSec, columnFilter, rowFilter, limits, dataRange, indexQueryPlan, trackWarnings);
super(metadata.epoch, isDigest, digestVersion, acceptsTransient, metadata, nowInSec, columnFilter, rowFilter, limits, dataRange, indexQueryPlan, trackWarnings);
}
@Override

View File

@ -38,7 +38,10 @@ import org.slf4j.LoggerFactory;
import io.netty.util.concurrent.FastThreadLocal;
import org.apache.cassandra.config.*;
import org.apache.cassandra.db.filter.*;
import org.apache.cassandra.exceptions.CoordinatorBehindException;
import org.apache.cassandra.exceptions.QueryCancelledException;
import org.apache.cassandra.exceptions.UnknownTableException;
import org.apache.cassandra.metrics.TCMMetrics;
import org.apache.cassandra.net.MessageFlag;
import org.apache.cassandra.net.MessagingService;
import org.apache.cassandra.net.ParamType;
@ -67,9 +70,12 @@ import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.schema.SchemaProvider;
import org.apache.cassandra.service.ActiveRepairService;
import org.apache.cassandra.service.ClientWarn;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.tcm.Epoch;
import org.apache.cassandra.tracing.Tracing;
import org.apache.cassandra.utils.CassandraUInt;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.NoSpamLogger;
import org.apache.cassandra.utils.ObjectSizes;
import org.apache.cassandra.utils.TimeUUID;
@ -100,6 +106,7 @@ public abstract class ReadCommand extends AbstractReadQuery
private final boolean isDigestQuery;
private final boolean acceptsTransient;
private final Epoch serializedAtEpoch;
// if a digest query, the version for which the digest is expected. Ignored if not a digest.
private int digestVersion;
@ -112,6 +119,7 @@ public abstract class ReadCommand extends AbstractReadQuery
{
public abstract ReadCommand deserialize(DataInputPlus in,
int version,
Epoch serializedAtEpoch,
boolean isDigest,
int digestVersion,
boolean acceptsTransient,
@ -136,7 +144,8 @@ public abstract class ReadCommand extends AbstractReadQuery
}
}
protected ReadCommand(Kind kind,
protected ReadCommand(Epoch serializedAtEpoch,
Kind kind,
boolean isDigestQuery,
int digestVersion,
boolean acceptsTransient,
@ -158,6 +167,7 @@ public abstract class ReadCommand extends AbstractReadQuery
this.acceptsTransient = acceptsTransient;
this.indexQueryPlan = indexQueryPlan;
this.trackWarnings = trackWarnings;
this.serializedAtEpoch = serializedAtEpoch;
}
public static ReadCommand getCommand()
@ -197,6 +207,15 @@ public abstract class ReadCommand extends AbstractReadQuery
return isDigestQuery;
}
/**
* the schema version on the table when serializing this read command
* @return
*/
public Epoch serializedAtEpoch()
{
return serializedAtEpoch;
}
/**
* If the query is a digest one, the requested digest version.
*
@ -1011,6 +1030,11 @@ public abstract class ReadCommand extends AbstractReadQuery
@VisibleForTesting
public static class Serializer implements IVersionedSerializer<ReadCommand>
{
private static final NoSpamLogger noSpamLogger = NoSpamLogger.getLogger(logger, 10L, TimeUnit.SECONDS);
private static final NoSpamLogger.NoSpamLogStatement schemaMismatchStmt =
noSpamLogger.getStatement("Schema epoch mismatch during read command deserialization. " +
"TableId: {}, remote epoch: {}, local epoch: {}", 10L, TimeUnit.SECONDS);
private final SchemaProvider schema;
public Serializer()
@ -1075,6 +1099,8 @@ public abstract class ReadCommand extends AbstractReadQuery
if (command.isDigestQuery())
out.writeUnsignedVInt32(command.digestVersion());
command.metadata().id.serialize(out);
if (version >= MessagingService.VERSION_50)
Epoch.serializer.serialize(command.serializedAtEpoch, out);
out.writeInt(version >= MessagingService.VERSION_50 ? CassandraUInt.fromLong(command.nowInSec()) : (int) command.nowInSec());
ColumnFilter.serializer.serialize(command.columnFilter(), out, version);
RowFilter.serializer.serialize(command.rowFilter(), out, version);
@ -1103,23 +1129,42 @@ public abstract class ReadCommand extends AbstractReadQuery
+ "upgrading to 4.0");
boolean hasIndex = hasIndex(flags);
int digestVersion = isDigest ? in.readUnsignedVInt32() : 0;
TableMetadata metadata = schema.getExistingTableMetadata(TableId.deserialize(in));
long nowInSec = version >= MessagingService.VERSION_50 ? CassandraUInt.toLong(in.readInt()) : in.readInt();
ColumnFilter columnFilter = ColumnFilter.serializer.deserialize(in, version, metadata);
RowFilter rowFilter = RowFilter.serializer.deserialize(in, version, metadata);
DataLimits limits = DataLimits.serializer.deserialize(in, version, metadata);
int digestVersion = isDigest ? (int)in.readUnsignedVInt() : 0;
TableId tableId = TableId.deserialize(in);
Epoch schemaVersion = null;
if (version >= MessagingService.VERSION_50)
schemaVersion = Epoch.serializer.deserialize(in);
TableMetadata tableMetadata;
try
{
tableMetadata = schema.getExistingTableMetadata(tableId);
}
catch (UnknownTableException e)
{
ClusterMetadata metadata = ClusterMetadata.current();
Epoch localCurrentEpoch = metadata.epoch;
if (schemaVersion != null && localCurrentEpoch.isAfter(schemaVersion))
{
TCMMetrics.instance.coordinatorBehindSchema.mark();
throw new CoordinatorBehindException(e.getMessage());
}
throw e;
}
long nowInSec = version >= MessagingService.VERSION_50 ? CassandraUInt.toLong(in.readInt()) : in.readInt();
ColumnFilter columnFilter = ColumnFilter.serializer.deserialize(in, version, tableMetadata);
RowFilter rowFilter = RowFilter.serializer.deserialize(in, version, tableMetadata);
DataLimits limits = DataLimits.serializer.deserialize(in, version, tableMetadata);
Index.QueryPlan indexQueryPlan = null;
if (hasIndex)
{
IndexMetadata index = deserializeIndexMetadata(in, version, metadata);
Index.Group indexGroup = Keyspace.openAndGetStore(metadata).indexManager.getIndexGroup(index);
IndexMetadata index = deserializeIndexMetadata(in, version, tableMetadata);
Index.Group indexGroup = Keyspace.openAndGetStore(tableMetadata).indexManager.getIndexGroup(index);
if (indexGroup != null)
indexQueryPlan = indexGroup.queryPlanFor(rowFilter);
}
return kind.selectionDeserializer.deserialize(in, version, isDigest, digestVersion, acceptsTransient, metadata, nowInSec, columnFilter, rowFilter, limits, indexQueryPlan);
return kind.selectionDeserializer.deserialize(in, version, schemaVersion, isDigest, digestVersion, acceptsTransient, tableMetadata, nowInSec, columnFilter, rowFilter, limits, indexQueryPlan);
}
private IndexMetadata deserializeIndexMetadata(DataInputPlus in, int version, TableMetadata metadata) throws IOException
@ -1144,6 +1189,7 @@ public abstract class ReadCommand extends AbstractReadQuery
return 2 // kind + flags
+ (command.isDigestQuery() ? TypeSizes.sizeofUnsignedVInt(command.digestVersion()) : 0)
+ command.metadata().id.serializedSize()
+ (version >= MessagingService.VERSION_50 ? Epoch.serializer.serializedSize(command.metadata().epoch) : 0)
+ TypeSizes.INT_SIZE // command.nowInSec() is serialized as uint
+ ColumnFilter.serializer.serializedSize(command.columnFilter(), version)
+ RowFilter.serializer.serializedSize(command.rowFilter(), version)

View File

@ -22,15 +22,24 @@ import org.slf4j.LoggerFactory;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.db.partitions.UnfilteredPartitionIterator;
import org.apache.cassandra.exceptions.CoordinatorBehindException;
import org.apache.cassandra.exceptions.InvalidRoutingException;
import org.apache.cassandra.exceptions.QueryCancelledException;
import org.apache.cassandra.dht.AbstractBounds;
import org.apache.cassandra.dht.Token;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.exceptions.QueryCancelledException;
import org.apache.cassandra.locator.Replica;
import org.apache.cassandra.metrics.TCMMetrics;
import org.apache.cassandra.schema.SchemaConstants;
import org.apache.cassandra.service.StorageService;
import org.apache.cassandra.tcm.ClusterMetadataService;
import org.apache.cassandra.net.IVerbHandler;
import org.apache.cassandra.net.Message;
import org.apache.cassandra.net.MessagingService;
import org.apache.cassandra.service.StorageService;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.tcm.Epoch;
import org.apache.cassandra.tracing.Tracing;
import org.apache.cassandra.utils.FBUtilities;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
@ -42,16 +51,17 @@ public class ReadCommandVerbHandler implements IVerbHandler<ReadCommand>
public void doVerb(Message<ReadCommand> message)
{
if (StorageService.instance.isBootstrapMode())
if (message.epoch().isAfter(Epoch.EMPTY))
{
throw new RuntimeException("Cannot service reads while bootstrapping!");
ClusterMetadata metadata = ClusterMetadata.current();
metadata = checkTokenOwnership(metadata, message);
metadata = checkSchemaVersion(metadata, message);
}
ReadCommand command = message.payload;
validateTransientStatus(message);
MessageParams.reset();
long timeout = message.expiresAtNanos() - message.createdAtNanos();
ReadCommand command = message.payload;
command.setMonitoringTime(message.createdAtNanos(), message.isCrossNode(), timeout, DatabaseDescriptor.getSlowQueryTimeout(NANOSECONDS));
if (message.trackWarnings())
@ -103,40 +113,110 @@ public class ReadCommandVerbHandler implements IVerbHandler<ReadCommand>
}
}
private void validateTransientStatus(Message<ReadCommand> message)
private ClusterMetadata checkSchemaVersion(ClusterMetadata metadata, Message<ReadCommand> message)
{
ReadCommand readCommand = message.payload;
if (SchemaConstants.isSystemKeyspace(readCommand.metadata().keyspace) ||
readCommand.serializedAtEpoch() == null) // don't try to catch up with pre-5.0 nodes
return metadata;
Keyspace ks = metadata.schema.getKeyspace(readCommand.metadata().keyspace);
ColumnFamilyStore cfs = ks != null ? ks.getColumnFamilyStore(readCommand.metadata().id) : null;
Epoch localComparisonEpoch = metadata.epoch;
if (cfs != null)
localComparisonEpoch = cfs.metadata().epoch;
if (localComparisonEpoch.isBefore(readCommand.serializedAtEpoch()))
metadata = ClusterMetadataService.instance().fetchLogFromPeerOrCMS(metadata, message.from(), message.epoch());
else if (localComparisonEpoch.isAfter(readCommand.serializedAtEpoch()))
{
TCMMetrics.instance.coordinatorBehindSchema.mark();
throw new CoordinatorBehindException(String.format("Coordinator schema for %s.%s with epoch %s is behind our schema %s",
message.payload.metadata().keyspace,
message.payload.metadata().name,
readCommand.serializedAtEpoch(),
localComparisonEpoch));
}
ks = metadata.schema.getKeyspace(readCommand.metadata().keyspace);
if (ks == null || ks.getColumnFamilyStore(readCommand.metadata().id) == null)
throw new IllegalStateException("Unknown table " + readCommand.metadata().id +" after fetching remote log entries");
return metadata;
}
private ClusterMetadata checkTokenOwnership(ClusterMetadata metadata, Message<ReadCommand> message)
{
ReadCommand command = message.payload;
if (command.metadata().isVirtual())
return;
Token token;
return metadata;
if (command.isTopK())
return metadata;
if (command instanceof SinglePartitionReadCommand)
token = ((SinglePartitionReadCommand) command).partitionKey().getToken();
else
token = ((PartitionRangeReadCommand) command).dataRange().keyRange().right.getToken();
Replica replica = Keyspace.open(command.metadata().keyspace)
.getReplicationStrategy()
.getLocalReplicaFor(token);
if (replica == null)
{
if (command.isTopK())
return;
logger.warn("Received a read request from {} for a range that is not owned by the current replica {}.",
message.from(),
command);
return;
Token token = ((SinglePartitionReadCommand) command).partitionKey().getToken();
Replica localReplica = getLocalReplica(metadata, token, command.metadata().keyspace);
if (localReplica == null)
{
metadata = ClusterMetadataService.instance().fetchLogFromPeerOrCMS(metadata, message.from(), message.epoch());
localReplica = getLocalReplica(metadata, token, command.metadata().keyspace);
}
if (localReplica == null)
{
StorageService.instance.incOutOfRangeOperationCount();
Keyspace.open(command.metadata().keyspace).metric.outOfRangeTokenReads.inc();
throw InvalidRoutingException.forTokenRead(message.from(), token, metadata.epoch, message.payload);
}
if (!command.acceptsTransient() && replica.isTransient())
if (!command.acceptsTransient() && localReplica.isTransient())
{
MessagingService.instance().metrics.recordDroppedMessage(message, message.elapsedSinceCreated(NANOSECONDS), NANOSECONDS);
throw new InvalidRequestException(String.format("Attempted to serve %s data request from %s node in %s",
command.acceptsTransient() ? "transient" : "full",
replica.isTransient() ? "transient" : "full",
localReplica.isTransient() ? "transient" : "full",
this));
}
}
else
{
AbstractBounds<PartitionPosition> range = ((PartitionRangeReadCommand) command).dataRange().keyRange();
// TODO: preexisting issue: for the range queries or queries that span multiple replicas, we can only make requests where the right token is owned, but not the left one
Replica maxTokenLocalReplica = getLocalReplica(metadata, range.right.getToken(), command.metadata().keyspace);
if (maxTokenLocalReplica == null)
{
metadata = ClusterMetadataService.instance().fetchLogFromPeerOrCMS(metadata, message.from(), message.epoch());
maxTokenLocalReplica = getLocalReplica(metadata, range.right.getToken(), command.metadata().keyspace);
}
if (maxTokenLocalReplica == null)
{
StorageService.instance.incOutOfRangeOperationCount();
Keyspace.open(command.metadata().keyspace).metric.outOfRangeTokenReads.inc();
throw InvalidRoutingException.forRangeRead(message.from(), range, metadata.epoch, message.payload);
}
// TODO: preexisting issue: we should change the whole range for transient-ness, not just the right token
if (command.acceptsTransient() != maxTokenLocalReplica.isTransient())
{
MessagingService.instance().metrics.recordDroppedMessage(message, message.elapsedSinceCreated(NANOSECONDS), NANOSECONDS);
throw new InvalidRequestException(String.format("Attempted to serve %s data request from %s node in %s",
command.acceptsTransient() ? "transient" : "full",
maxTokenLocalReplica.isTransient() ? "transient" : "full",
this));
}
}
return metadata;
}
private static Replica getLocalReplica(ClusterMetadata metadata, Token token, String keyspace)
{
return metadata.placements
.get(metadata.schema.getKeyspaces().getNullable(keyspace).params.replication)
.reads
.forToken(token)
.get()
.lookup(FBUtilities.getBroadcastAddressAndPort());
}
}

View File

@ -17,17 +17,17 @@
*/
package org.apache.cassandra.db;
import org.apache.cassandra.net.IVerbHandler;
import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.net.Message;
import org.apache.cassandra.net.MessagingService;
public class ReadRepairVerbHandler implements IVerbHandler<Mutation>
public class ReadRepairVerbHandler extends AbstractMutationVerbHandler<Mutation>
{
public static final ReadRepairVerbHandler instance = new ReadRepairVerbHandler();
public void doVerb(Message<Mutation> message)
void applyMutation(Message<Mutation> message, InetAddressAndPort respondToAddress)
{
message.payload.apply();
MessagingService.instance().send(message.emptyResponse(), message.from());
MessagingService.instance().send(message.emptyResponse(), respondToAddress);
}
}

View File

@ -34,6 +34,8 @@ import org.apache.cassandra.net.MessagingService;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.utils.ByteBufferUtil;
import static org.apache.cassandra.db.RepairedDataInfo.NO_OP_REPAIRED_DATA_INFO;
public abstract class ReadResponse
{
// Serializer for single partition read response
@ -48,6 +50,11 @@ public abstract class ReadResponse
return new LocalDataResponse(data, command, rdi);
}
public static ReadResponse createDataResponse(UnfilteredPartitionIterator data, ReadCommand command)
{
return new LocalDataResponse(data, command, NO_OP_REPAIRED_DATA_INFO);
}
public static ReadResponse createSimpleDataResponse(UnfilteredPartitionIterator data, ColumnFilter selection)
{
return new LocalDataResponse(data, selection);

View File

@ -76,6 +76,7 @@ import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.CacheService;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.service.StorageProxy;
import org.apache.cassandra.tcm.Epoch;
import org.apache.cassandra.tracing.Tracing;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.btree.BTreeSet;
@ -91,7 +92,8 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
protected final ClusteringIndexFilter clusteringIndexFilter;
@VisibleForTesting
protected SinglePartitionReadCommand(boolean isDigest,
protected SinglePartitionReadCommand(Epoch serializedAtEpoch,
boolean isDigest,
int digestVersion,
boolean acceptsTransient,
TableMetadata metadata,
@ -104,13 +106,14 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
Index.QueryPlan indexQueryPlan,
boolean trackWarnings)
{
super(Kind.SINGLE_PARTITION, isDigest, digestVersion, acceptsTransient, metadata, nowInSec, columnFilter, rowFilter, limits, indexQueryPlan, trackWarnings);
super(serializedAtEpoch, Kind.SINGLE_PARTITION, isDigest, digestVersion, acceptsTransient, metadata, nowInSec, columnFilter, rowFilter, limits, indexQueryPlan, trackWarnings);
assert partitionKey.getPartitioner() == metadata.partitioner;
this.partitionKey = partitionKey;
this.clusteringIndexFilter = clusteringIndexFilter;
}
private static SinglePartitionReadCommand create(boolean isDigest,
private static SinglePartitionReadCommand create(Epoch serializedAtEpoch,
boolean isDigest,
int digestVersion,
boolean acceptsTransient,
TableMetadata metadata,
@ -138,7 +141,8 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
indexQueryPlan,
trackWarnings);
}
return new SinglePartitionReadCommand(isDigest,
return new SinglePartitionReadCommand(serializedAtEpoch,
isDigest,
digestVersion,
acceptsTransient,
metadata,
@ -175,7 +179,8 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
ClusteringIndexFilter clusteringIndexFilter,
Index.QueryPlan indexQueryPlan)
{
return create(false,
return create(metadata.epoch,
false,
0,
false,
metadata,
@ -352,7 +357,8 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
public SinglePartitionReadCommand copy()
{
return create(isDigestQuery(),
return create(serializedAtEpoch(),
isDigestQuery(),
digestVersion(),
acceptsTransient(),
metadata(),
@ -369,7 +375,8 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
@Override
protected SinglePartitionReadCommand copyAsDigestQuery()
{
return create(true,
return create(serializedAtEpoch(),
true,
digestVersion(),
acceptsTransient(),
metadata(),
@ -386,7 +393,8 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
@Override
protected SinglePartitionReadCommand copyAsTransientQuery()
{
return create(false,
return create(serializedAtEpoch(),
false,
0,
true,
metadata(),
@ -403,7 +411,8 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
@Override
public SinglePartitionReadCommand withUpdatedLimit(DataLimits newLimits)
{
return create(isDigestQuery(),
return create(serializedAtEpoch(),
isDigestQuery(),
digestVersion(),
acceptsTransient(),
metadata(),
@ -1302,6 +1311,7 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
{
public ReadCommand deserialize(DataInputPlus in,
int version,
Epoch serializedAtEpoch,
boolean isDigest,
int digestVersion,
boolean acceptsTransient,
@ -1315,7 +1325,7 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
{
DecoratedKey key = metadata.partitioner.decorateKey(metadata.partitionKeyType.readBuffer(in, DatabaseDescriptor.getMaxValueSize()));
ClusteringIndexFilter filter = ClusteringIndexFilter.serializer.deserialize(in, version, metadata);
return SinglePartitionReadCommand.create(isDigest, digestVersion, acceptsTransient, metadata, nowInSec, columnFilter, rowFilter, limits, key, filter, indexQueryPlan, false);
return SinglePartitionReadCommand.create(serializedAtEpoch, isDigest, digestVersion, acceptsTransient, metadata, nowInSec, columnFilter, rowFilter, limits, key, filter, indexQueryPlan, false);
}
}
@ -1362,7 +1372,7 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
Index.QueryPlan indexQueryPlan,
boolean trackWarnings)
{
super(isDigest, digestVersion, acceptsTransient, metadata, nowInSec, columnFilter, rowFilter, limits, partitionKey, clusteringIndexFilter, indexQueryPlan, trackWarnings);
super(metadata.epoch, isDigest, digestVersion, acceptsTransient, metadata, nowInSec, columnFilter, rowFilter, limits, partitionKey, clusteringIndexFilter, indexQueryPlan, trackWarnings);
}
@Override

View File

@ -19,7 +19,10 @@ package org.apache.cassandra.db;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Lists;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -29,15 +32,17 @@ import org.apache.cassandra.db.lifecycle.View;
import org.apache.cassandra.dht.Range;
import org.apache.cassandra.dht.Token;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.locator.TokenMetadata;
import org.apache.cassandra.schema.Schema;
import org.apache.cassandra.schema.SchemaChangeListener;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.StorageService;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.tcm.membership.NodeId;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.Pair;
import org.apache.cassandra.utils.concurrent.Refs;
import static org.apache.cassandra.tcm.compatibility.TokenRingUtils.getAllRanges;
import static org.apache.cassandra.utils.Clock.Global.nanoTime;
/**
@ -62,8 +67,7 @@ public class SizeEstimatesRecorder implements SchemaChangeListener, Runnable
public void run()
{
TokenMetadata metadata = StorageService.instance.getTokenMetadata().cloneOnlyTokenMap();
if (!metadata.isMember(FBUtilities.getBroadcastAddressAndPort()))
if (!ClusterMetadata.current().directory.allAddresses().contains(FBUtilities.getBroadcastAddressAndPort()))
{
logger.debug("Node is not part of the ring; not recording size estimates");
return;
@ -73,6 +77,9 @@ public class SizeEstimatesRecorder implements SchemaChangeListener, Runnable
for (Keyspace keyspace : Keyspace.nonLocalStrategy())
{
if (keyspace.getMetadata().params.replication.isMeta())
continue;
// In tools the call to describe_splits_ex() used to be coupled with the call to describe_local_ring() so
// most access was for the local primary range; after creating the size_estimates table this was changed
// to be the primary range.
@ -88,7 +95,7 @@ public class SizeEstimatesRecorder implements SchemaChangeListener, Runnable
// range. If we publish multiple ranges downstream integrations may start to see duplicate data.
// See CASSANDRA-15637
Collection<Range<Token>> primaryRanges = StorageService.instance.getPrimaryRanges(keyspace.getName());
Collection<Range<Token>> localPrimaryRanges = StorageService.instance.getLocalPrimaryRange();
Collection<Range<Token>> localPrimaryRanges = getLocalPrimaryRange();
boolean rangesAreEqual = primaryRanges.equals(localPrimaryRanges);
for (ColumnFamilyStore table : keyspace.getColumnFamilyStores())
{
@ -116,6 +123,34 @@ public class SizeEstimatesRecorder implements SchemaChangeListener, Runnable
}
}
@VisibleForTesting
public static Collection<Range<Token>> getLocalPrimaryRange()
{
ClusterMetadata metadata = ClusterMetadata.current();
NodeId localNodeId = metadata.myNodeId();
return getLocalPrimaryRange(metadata, localNodeId);
}
@VisibleForTesting
public static Collection<Range<Token>> getLocalPrimaryRange(ClusterMetadata metadata, NodeId nodeId)
{
String dc = metadata.directory.location(nodeId).datacenter;
Set<Token> tokens = new HashSet<>(metadata.tokenMap.tokens(nodeId));
// filter tokens to the single DC
List<Token> filteredTokens = Lists.newArrayList();
for (Token token : metadata.tokenMap.tokens())
{
NodeId owner = metadata.tokenMap.owner(token);
if (dc.equals(metadata.directory.location(owner).datacenter))
filteredTokens.add(token);
}
return getAllRanges(filteredTokens).stream()
.filter(t -> tokens.contains(t.right))
.collect(Collectors.toList());
}
@SuppressWarnings("resource")
private static Map<Range<Token>, Pair<Long, Long>> computeSizeEstimates(ColumnFamilyStore table, Collection<Range<Token>> ranges)
{
// for each local primary range, estimate (crudely) mean partition size and partitions count.

View File

@ -36,7 +36,6 @@ import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import javax.management.openmbean.OpenDataException;
@ -59,7 +58,6 @@ import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.cql3.QueryProcessor;
import org.apache.cassandra.cql3.UntypedResultSet;
import org.apache.cassandra.cql3.statements.schema.CreateTableStatement;
import org.apache.cassandra.db.commitlog.CommitLog;
import org.apache.cassandra.db.commitlog.CommitLogPosition;
import org.apache.cassandra.db.compaction.CompactionHistoryTabularData;
import org.apache.cassandra.db.marshal.ByteBufferAccessor;
@ -77,6 +75,9 @@ import org.apache.cassandra.dht.LocalPartitioner;
import org.apache.cassandra.dht.Range;
import org.apache.cassandra.dht.Token;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.gms.EndpointState;
import org.apache.cassandra.gms.HeartBeatState;
import org.apache.cassandra.gms.VersionedValue;
import org.apache.cassandra.io.sstable.SSTableId;
import org.apache.cassandra.io.sstable.SequenceBasedSSTableId;
import org.apache.cassandra.io.util.DataInputBuffer;
@ -110,6 +111,10 @@ import org.apache.cassandra.service.paxos.PaxosState;
import org.apache.cassandra.service.paxos.uncommitted.PaxosRows;
import org.apache.cassandra.service.paxos.uncommitted.PaxosUncommittedIndex;
import org.apache.cassandra.streaming.StreamOperation;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.tcm.Epoch;
import org.apache.cassandra.tcm.Sealed;
import org.apache.cassandra.tcm.membership.NodeState;
import org.apache.cassandra.transport.ProtocolVersion;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.CassandraVersion;
@ -127,6 +132,14 @@ import static org.apache.cassandra.config.DatabaseDescriptor.paxosStatePurging;
import static org.apache.cassandra.cql3.QueryProcessor.executeInternal;
import static org.apache.cassandra.cql3.QueryProcessor.executeInternalWithNowInSec;
import static org.apache.cassandra.cql3.QueryProcessor.executeOnceInternal;
import static org.apache.cassandra.gms.ApplicationState.DC;
import static org.apache.cassandra.gms.ApplicationState.HOST_ID;
import static org.apache.cassandra.gms.ApplicationState.INTERNAL_ADDRESS_AND_PORT;
import static org.apache.cassandra.gms.ApplicationState.NATIVE_ADDRESS_AND_PORT;
import static org.apache.cassandra.gms.ApplicationState.RACK;
import static org.apache.cassandra.gms.ApplicationState.RELEASE_VERSION;
import static org.apache.cassandra.gms.ApplicationState.STATUS_WITH_PORT;
import static org.apache.cassandra.gms.ApplicationState.TOKENS;
import static org.apache.cassandra.service.paxos.Commit.latest;
import static org.apache.cassandra.utils.CassandraVersion.NULL_VERSION;
import static org.apache.cassandra.utils.CassandraVersion.UNREADABLE_VERSION;
@ -163,6 +176,10 @@ public final class SystemKeyspace
public static final String PREPARED_STATEMENTS = "prepared_statements";
public static final String REPAIRS = "repairs";
public static final String TOP_PARTITIONS = "top_partitions";
public static final String METADATA_LOG = "local_metadata_log";
public static final String SNAPSHOT_TABLE_NAME = "metadata_snapshots";
public static final String SEALED_PERIODS_TABLE_NAME = "metadata_sealed_periods";
public static final String LAST_SEALED_PERIOD_TABLE_NAME = "metadata_last_sealed_period";
/**
* By default the system keyspace tables should be stored in a single data directory to allow the server
@ -195,13 +212,15 @@ public final class SystemKeyspace
COMPACTION_HISTORY, SSTABLE_ACTIVITY_V2, TABLE_ESTIMATES, TABLE_ESTIMATES_TYPE_PRIMARY,
TABLE_ESTIMATES_TYPE_LOCAL_PRIMARY, AVAILABLE_RANGES_V2, TRANSFERRED_RANGES_V2, VIEW_BUILDS_IN_PROGRESS,
BUILT_VIEWS, PREPARED_STATEMENTS, REPAIRS, TOP_PARTITIONS, LEGACY_PEERS, LEGACY_PEER_EVENTS,
LEGACY_TRANSFERRED_RANGES, LEGACY_AVAILABLE_RANGES, LEGACY_SIZE_ESTIMATES, LEGACY_SSTABLE_ACTIVITY);
LEGACY_TRANSFERRED_RANGES, LEGACY_AVAILABLE_RANGES, LEGACY_SIZE_ESTIMATES, LEGACY_SSTABLE_ACTIVITY,
METADATA_LOG, SNAPSHOT_TABLE_NAME, SEALED_PERIODS_TABLE_NAME, LAST_SEALED_PERIOD_TABLE_NAME);
public static final Set<String> TABLE_NAMES = ImmutableSet.of(
BATCHES, PAXOS, PAXOS_REPAIR_HISTORY, BUILT_INDEXES, LOCAL, PEERS_V2, PEER_EVENTS_V2,
COMPACTION_HISTORY, SSTABLE_ACTIVITY_V2, TABLE_ESTIMATES, AVAILABLE_RANGES_V2, TRANSFERRED_RANGES_V2, VIEW_BUILDS_IN_PROGRESS,
BUILT_VIEWS, PREPARED_STATEMENTS, REPAIRS, TOP_PARTITIONS, LEGACY_PEERS, LEGACY_PEER_EVENTS,
LEGACY_TRANSFERRED_RANGES, LEGACY_AVAILABLE_RANGES, LEGACY_SIZE_ESTIMATES, LEGACY_SSTABLE_ACTIVITY);
LEGACY_TRANSFERRED_RANGES, LEGACY_AVAILABLE_RANGES, LEGACY_SIZE_ESTIMATES, LEGACY_SSTABLE_ACTIVITY,
METADATA_LOG, SNAPSHOT_TABLE_NAME, SEALED_PERIODS_TABLE_NAME, LAST_SEALED_PERIOD_TABLE_NAME);
public static final TableMetadata Batches =
parse(BATCHES,
@ -466,7 +485,45 @@ public final class SystemKeyspace
+ "cfids set<uuid>, "
+ "PRIMARY KEY (parent_id))").build();
/** @deprecated See CASSANDRA-7544 */
public static final TableMetadata LocalMetadataLog =
parse(METADATA_LOG,
"Local Metadata Log",
"CREATE TABLE %s ("
+ "period bigint,"
+ "current_epoch bigint static,"
+ "epoch bigint,"
+ "entry_id bigint,"
+ "transformation blob,"
+ "kind text,"
+ "PRIMARY KEY (period, epoch))")
.compaction(CompactionParams.twcs(ImmutableMap.of("compaction_window_unit","DAYS",
"compaction_window_size","1")))
.build();
public static final TableMetadata Snapshots = parse(SNAPSHOT_TABLE_NAME,
"ClusterMetadata snapshots",
"CREATE TABLE IF NOT EXISTS %s (" +
"epoch bigint PRIMARY KEY," +
"period bigint," +
"snapshot blob)")
.build();
public static final TableMetadata SealedPeriods = parse(SEALED_PERIODS_TABLE_NAME,
"ClusterMetadata sealed periods",
"CREATE TABLE IF NOT EXISTS %s (" +
"max_epoch bigint PRIMARY KEY," +
"period bigint)")
.partitioner(new LocalPartitioner(LongType.instance))
.build();
public static final TableMetadata LastSealedPeriod = parse(LAST_SEALED_PERIOD_TABLE_NAME,
"ClusterMetadata last sealed period",
"CREATE TABLE IF NOT EXISTS %s (" +
"key text PRIMARY KEY," +
"epoch bigint," +
"period bigint)")
.build();
@Deprecated(since = "4.0")
private static final TableMetadata LegacyPeers =
parse(LEGACY_PEERS,
@ -557,7 +614,11 @@ public final class SystemKeyspace
BuiltViews,
PreparedStatements,
Repairs,
TopPartitions);
TopPartitions,
LocalMetadataLog,
LastSealedPeriod,
SealedPeriods,
Snapshots);
}
private static volatile Map<TableId, Pair<CommitLogPosition, Long>> truncationRecords;
@ -567,16 +628,31 @@ public final class SystemKeyspace
NEEDS_BOOTSTRAP,
COMPLETED,
IN_PROGRESS,
DECOMMISSIONED
DECOMMISSIONED;
public static BootstrapState fromNodeState(NodeState nodeState)
{
if (nodeState == null) // todo, handle this properly
return DECOMMISSIONED;
switch (nodeState)
{
case REGISTERED:
return NEEDS_BOOTSTRAP;
case BOOTSTRAPPING:
case BOOT_REPLACING:
return IN_PROGRESS;
case JOINED:
case LEAVING:
case MOVING:
return COMPLETED;
case LEFT:
default:
return DECOMMISSIONED;
}
}
}
public static void persistLocalMetadata()
{
persistLocalMetadata(UUID::randomUUID);
}
@VisibleForTesting
public static void persistLocalMetadata(Supplier<UUID> nodeIdSupplier)
{
String req = "INSERT INTO system.%s (" +
"key," +
@ -610,13 +686,6 @@ public final class SystemKeyspace
DatabaseDescriptor.getStoragePort(),
FBUtilities.getJustLocalAddress(),
DatabaseDescriptor.getStoragePort());
// We should store host ID as soon as possible in the system.local table and flush that table to disk so that
// we can be sure that those changes are stored in sstable and not in the commit log (see CASSANDRA-18153).
// It is very unlikely that when upgrading the host id is not flushed to disk, but if that's the case, we limit
// this change only to the new installations or the user should just flush system.local table.
if (!CommitLog.instance.hasFilesToReplay())
SystemKeyspace.getOrInitializeLocalHostId(nodeIdSupplier);
}
public static void updateCompactionHistory(TimeUUID taskId,
@ -831,7 +900,10 @@ public final class SystemKeyspace
public static synchronized void updateTokens(InetAddressAndPort ep, Collection<Token> tokens)
{
if (ep.equals(FBUtilities.getBroadcastAddressAndPort()))
{
updateLocalTokens(tokens);
return;
}
String req = "INSERT INTO system.%s (peer, tokens) VALUES (?, ?)";
executeInternal(String.format(req, LEGACY_PEERS), ep.getAddress(), tokensAsSet(tokens));
@ -895,11 +967,11 @@ public final class SystemKeyspace
executeInternal(format(req, LOCAL, LOCAL), version);
}
private static Set<String> tokensAsSet(Collection<Token> tokens)
public static Set<String> tokensAsSet(Collection<Token> tokens)
{
if (tokens.isEmpty())
return Collections.emptySet();
Token.TokenFactory factory = StorageService.instance.getTokenFactory();
Token.TokenFactory factory = ClusterMetadata.current().partitioner.getTokenFactory();
Set<String> s = new HashSet<>(tokens.size());
for (Token tk : tokens)
s.add(factory.toString(tk));
@ -908,7 +980,7 @@ public final class SystemKeyspace
private static Collection<Token> deserializeTokens(Collection<String> tokensStrings)
{
Token.TokenFactory factory = StorageService.instance.getTokenFactory();
Token.TokenFactory factory = ClusterMetadata.current().partitioner.getTokenFactory();
List<Token> tokens = new ArrayList<>(tokensStrings.size());
for (String tk : tokensStrings)
tokens.add(factory.fromString(tk));
@ -928,9 +1000,10 @@ public final class SystemKeyspace
}
/**
*
* This method is used to update the System Keyspace with the new tokens for this node
*/
public static synchronized void updateTokens(Collection<Token> tokens)
public static synchronized void updateLocalTokens(Collection<Token> tokens)
{
assert !tokens.isEmpty() : "removeEndpoint should be used instead";
@ -1240,27 +1313,6 @@ public final class SystemKeyspace
return null;
}
/**
* Read the host ID from the system keyspace, creating (and storing) one if
* none exists.
*/
public static synchronized UUID getOrInitializeLocalHostId()
{
return getOrInitializeLocalHostId(UUID::randomUUID);
}
private static synchronized UUID getOrInitializeLocalHostId(Supplier<UUID> nodeIdSupplier)
{
UUID hostId = getLocalHostId();
if (hostId != null)
return hostId;
// ID not found, generate a new one, persist, and then return it.
hostId = nodeIdSupplier.get();
logger.warn("No host ID found, created {} (Note: This should happen exactly once per node).", hostId);
return setLocalHostId(hostId);
}
/**
* Sets the local host ID explicitly. Should only be called outside of SystemTable when replacing a node.
*/
@ -1316,6 +1368,20 @@ public final class SystemKeyspace
return null;
}
public static Set<String> allKnownDatacenters()
{
Set<String> dcs = new HashSet<>();
dcs.add(getDatacenter());
String req = "SELECT data_center FROM system.%s";
UntypedResultSet result = executeInternal(format(req, PEERS_V2));
if (result != null)
{
for (UntypedResultSet.Row row : result)
dcs.add(row.getString("data_center"));
}
return dcs;
}
/**
* Load the current paxos state for the table and key
*/
@ -1937,4 +2003,122 @@ public final class SystemKeyspace
return TopPartitionTracker.StoredTopPartitions.EMPTY;
}
}
public static void storeSnapshot(Epoch epoch, long period, ByteBuffer snapshot)
{
logger.info("Storing snapshot of cluster metadata at epoch {} (period {})", epoch, period);
String query = String.format("INSERT INTO %s.%s (epoch, period, snapshot) VALUES (?, ?, ?)", SchemaConstants.SYSTEM_KEYSPACE_NAME, SNAPSHOT_TABLE_NAME);
executeInternal(query, epoch.getEpoch(), period, snapshot);
}
public static ByteBuffer getSnapshot(Epoch epoch)
{
logger.info("Getting snapshot of epoch = {}", epoch);
String query = String.format("SELECT SNAPSHOT FROM %s.%s WHERE epoch = ?", SchemaConstants.SYSTEM_KEYSPACE_NAME, SNAPSHOT_TABLE_NAME);
UntypedResultSet res = executeInternal(query, epoch.getEpoch());
if (res == null || res.isEmpty())
return null;
return res.one().getBytes("snapshot").duplicate();
}
public static Sealed findSealedPeriodForEpochScan(Epoch search)
{
String query = String.format("SELECT max_epoch, period FROM %s.%s WHERE max_epoch >= ? LIMIT 1 ALLOW FILTERING", SchemaConstants.SYSTEM_KEYSPACE_NAME, SEALED_PERIODS_TABLE_NAME);
UntypedResultSet res = executeInternal(query, search.getEpoch());
if (res != null && !res.isEmpty())
{
long period = res.one().getLong("period");
long epoch = res.one().getLong("max_epoch");
return new Sealed(period, epoch);
}
// nothing found for this epoch, is the table empty or is the search epoch > the maximum
query = String.format("SELECT max_epoch, period FROM %s.%s LIMIT 1", SchemaConstants.SYSTEM_KEYSPACE_NAME, SEALED_PERIODS_TABLE_NAME);
res = executeInternal(query);
// table is empty, so any scan for the epoch will have to begin at Period.EMPTY
if (res == null || res.isEmpty())
return Sealed.EMPTY;
// the index table has some data, but is the search target greater than the max epoch in last sealed period?
// This query is relatively costly, so we do it last. Retain the min period/epoch that we did find in the
// previous query just in case we need them
// TODO add a nodetool command to rebuild the local sealed periods table
long lowestPeriod = res.one().getLong("period");
long lowestMaxEpoch = res.one().getLong("max_epoch");
logger.info("Scanning sealed periods by epoch table, this may be an expensive operation and the index table {} should be rebuilt", SEALED_PERIODS_TABLE_NAME);
query = String.format("SELECT max(max_epoch) AS max_epoch FROM %s.%s LIMIT 1 ALLOW FILTERING;", SchemaConstants.SYSTEM_KEYSPACE_NAME, SEALED_PERIODS_TABLE_NAME);
res = executeInternal(query);
// should never happen because the previous query returned the min, but just in case the table has been
// truncated since then, return the min Sealed.
if (res == null || res.isEmpty())
return new Sealed(lowestPeriod, lowestMaxEpoch);
// use the max epoch to look up the sealed period
long maxEpoch = res.one().getLong("max_epoch");
query = String.format("SELECT period FROM %s.%s WHERE max_epoch = ?", SchemaConstants.SYSTEM_KEYSPACE_NAME, SEALED_PERIODS_TABLE_NAME);
res = executeInternal(query, maxEpoch);
if (res == null || res.isEmpty())
return new Sealed(lowestPeriod, lowestMaxEpoch);
// this is the last recorded sealed period *before* the target epoch, so any scan should start at the
// *next* period, so we bump both period and epoch by 1
long maxPeriod = res.one().getLong("period");
return new Sealed(maxPeriod + 1, maxEpoch + 1);
}
public static Sealed getLastSealedPeriod()
{
String query = String.format("SELECT epoch, period FROM %s.%s WHERE key = 'latest'", SchemaConstants.SYSTEM_KEYSPACE_NAME, LAST_SEALED_PERIOD_TABLE_NAME);
UntypedResultSet res = executeInternal(query);
if (res == null || res.isEmpty())
return Sealed.EMPTY;
long epoch = res.one().getLong("epoch");
long period = res.one().getLong("period");
return new Sealed(period, Epoch.create(epoch));
}
public static void sealPeriod(long period, Epoch epoch)
{
String query = String.format("INSERT INTO %s.%s (max_epoch, period) VALUES (?,?)", SchemaConstants.SYSTEM_KEYSPACE_NAME, SEALED_PERIODS_TABLE_NAME);
executeInternal(query, epoch.getEpoch(), period);
query = String.format("UPDATE %s.%s SET period = ?, epoch = ? WHERE key = 'latest'", SchemaConstants.SYSTEM_KEYSPACE_NAME, LAST_SEALED_PERIOD_TABLE_NAME);
executeInternal(query, period, epoch.getEpoch());
}
public static Map<InetAddressAndPort, EndpointState> peerEndpointStates()
{
Map<InetAddressAndPort, EndpointState> epstates = new HashMap<>();
VersionedValue.VersionedValueFactory vf = StorageService.instance.valueFactory;
String query = String.format("select * from %s.%s", SchemaConstants.SYSTEM_KEYSPACE_NAME, PEERS_V2);
UntypedResultSet res = executeInternal(query);
for (UntypedResultSet.Row row : res)
{
EndpointState epstate = new EndpointState(new HeartBeatState(0, 0));
InetAddressAndPort endpoint = InetAddressAndPort.getByAddressOverrideDefaults(row.getInetAddress("peer"), row.getInt("peer_port"));
epstate.addApplicationState(DC, vf.datacenter(row.getString("data_center")));
epstate.addApplicationState(RACK, vf.rack(row.getString("rack")));
epstate.addApplicationState(RELEASE_VERSION, vf.releaseVersion(row.getString("release_version")));
epstate.addApplicationState(HOST_ID, vf.hostId(row.getUUID("host_id")));
Collection<Token> tokens = deserializeTokens(row.getSet("tokens", UTF8Type.instance));
epstate.addApplicationState(STATUS_WITH_PORT, vf.normal(tokens));
epstate.addApplicationState(TOKENS, vf.tokens(tokens));
if (row.has("preferred_ip"))
{
epstate.addApplicationState(INTERNAL_ADDRESS_AND_PORT,
vf.internalAddressAndPort(InetAddressAndPort.getByAddressOverrideDefaults(row.getInetAddress("preferred_ip"),
row.getInt("preferred_port"))));
}
if (row.has("native_ip"))
{
epstate.addApplicationState(NATIVE_ADDRESS_AND_PORT,
vf.nativeaddressAndPort(InetAddressAndPort.getByAddressOverrideDefaults(row.getInetAddress("native_ip"),
row.getInt("native_port"))));
}
epstates.put(endpoint, epstate);
}
return epstates;
}
}

View File

@ -320,10 +320,11 @@ public abstract class AbstractCommitLogSegmentManager
void forceRecycleAll(Collection<TableId> droppedTables)
{
List<CommitLogSegment> segmentsToRecycle = new ArrayList<>(activeSegments);
CommitLogSegment last = segmentsToRecycle.get(segmentsToRecycle.size() - 1);
CommitLogSegment last = segmentsToRecycle.isEmpty() ? null : segmentsToRecycle.get(segmentsToRecycle.size() - 1);
advanceAllocatingFrom(last);
// wait for the commit log modifications
if (last != null)
last.waitForModifications();
// make sure the writes have materialized inside of the memtables by waiting for all outstanding writes
@ -350,7 +351,7 @@ public abstract class AbstractCommitLogSegmentManager
}
CommitLogSegment first;
if ((first = activeSegments.peek()) != null && first.id <= last.id)
if ((first = activeSegments.peek()) != null && last != null && first.id <= last.id)
logger.error("Failed to force-recycle all segments; at least one segment is still in use with dirty CFs.");
}
catch (Throwable t)

View File

@ -96,6 +96,7 @@ import org.apache.cassandra.io.sstable.metadata.MetadataCollector;
import org.apache.cassandra.io.sstable.metadata.StatsMetadata;
import org.apache.cassandra.io.util.File;
import org.apache.cassandra.io.util.FileUtils;
import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.locator.RangesAtEndpoint;
import org.apache.cassandra.metrics.CompactionMetrics;
import org.apache.cassandra.metrics.TableMetrics;
@ -113,10 +114,13 @@ import org.apache.cassandra.utils.OutputHandler;
import org.apache.cassandra.utils.Throwables;
import org.apache.cassandra.utils.TimeUUID;
import org.apache.cassandra.utils.WrappedRunnable;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.tcm.ownership.DataPlacement;
import org.apache.cassandra.utils.concurrent.Future;
import org.apache.cassandra.utils.concurrent.ImmediateFuture;
import org.apache.cassandra.utils.concurrent.Refs;
import static java.util.Collections.singleton;
import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory;
import static org.apache.cassandra.concurrent.FutureTask.callable;
@ -617,13 +621,26 @@ public class CompactionManager implements CompactionManagerMBean, ICompactionMan
assert !cfStore.isIndex();
Keyspace keyspace = cfStore.keyspace;
// if local ranges is empty, it means no data should remain
final RangesAtEndpoint replicas = StorageService.instance.getLocalReplicas(keyspace.getName());
final Set<Range<Token>> allRanges = replicas.ranges();
final Set<Range<Token>> transientRanges = replicas.onlyTransient().ranges();
final Set<Range<Token>> fullRanges = replicas.onlyFull().ranges();
if (!StorageService.instance.isJoined())
{
logger.info("Cleanup cannot run before a node has joined the ring");
return AllSSTableOpStatus.ABORTED;
}
if (cfStore.keyspace.getMetadata().params.replication.isMeta())
return AllSSTableOpStatus.SUCCESSFUL; // todo - we probably want to be able to cleanup MetaStrategy keyspaces
final boolean hasIndexes = cfStore.indexManager.hasIndexes();
// if local ranges is empty, it means no data should remain
// we only consider write placements during cleanup as range movements always ensure
// overlap between new replicas accepting reads and old replicas accepting writes
ClusterMetadata cm = ClusterMetadata.current();
DataPlacement placement = cm.placements.get(keyspace.getMetadata().params.replication);
InetAddressAndPort local = FBUtilities.getBroadcastAddressAndPort();
RangesAtEndpoint localWrites = placement.writes.byEndpoint().get(local);
final Set<Range<Token>> allRanges = new HashSet<>(localWrites.ranges());
final Set<Range<Token>> transientRanges = new HashSet<>(localWrites.onlyTransient().ranges());
final Set<Range<Token>> fullRanges = new HashSet<>(localWrites.onlyFull().ranges());
return parallelAllSSTableOperation(cfStore, new OneSSTableOperation()
{
@Override
@ -665,7 +682,7 @@ public class CompactionManager implements CompactionManagerMBean, ICompactionMan
public void execute(LifecycleTransaction txn) throws IOException
{
CleanupStrategy cleanupStrategy = CleanupStrategy.get(cfStore, allRanges, transientRanges, txn.onlyOne().isRepaired(), FBUtilities.nowInSeconds());
doCleanupOne(cfStore, txn, cleanupStrategy, replicas.ranges(), hasIndexes);
doCleanupOne(cfStore, txn, cleanupStrategy, allRanges, hasIndexes);
}
}, jobs, OperationType.CLEANUP);
}
@ -1291,7 +1308,7 @@ public class CompactionManager implements CompactionManagerMBean, ICompactionMan
/* Used in tests. */
public void disableAutoCompaction()
{
for (String ksname : Schema.instance.distributedKeyspaces().names())
for (String ksname : Schema.instance.getKeyspaces())
{
for (ColumnFamilyStore cfs : Keyspace.open(ksname).getColumnFamilyStores())
cfs.disableAutoCompaction();

View File

@ -70,6 +70,7 @@ import org.apache.cassandra.io.sstable.metadata.StatsMetadata;
import org.apache.cassandra.io.util.File;
import org.apache.cassandra.notifications.INotification;
import org.apache.cassandra.notifications.INotificationConsumer;
import org.apache.cassandra.notifications.InitialSSTableAddedNotification;
import org.apache.cassandra.notifications.SSTableAddedNotification;
import org.apache.cassandra.notifications.SSTableDeletingNotification;
import org.apache.cassandra.notifications.SSTableListChangedNotification;
@ -890,6 +891,11 @@ public class CompactionStrategyManager implements INotificationConsumer
SSTableAddedNotification flushedNotification = (SSTableAddedNotification) notification;
handleFlushNotification(flushedNotification.added);
}
else if (notification instanceof InitialSSTableAddedNotification)
{
InitialSSTableAddedNotification flushedNotification = (InitialSSTableAddedNotification) notification;
handleFlushNotification(flushedNotification.added);
}
else if (notification instanceof SSTableListChangedNotification)
{
SSTableListChangedNotification listChangedNotification = (SSTableListChangedNotification) notification;

View File

@ -55,8 +55,8 @@ public class ShardManagerNoDisks implements ShardManager
public boolean isOutOfDate(long ringVersion)
{
return ringVersion != localRanges.ringVersion &&
localRanges.ringVersion != ColumnFamilyStore.RING_VERSION_IRRELEVANT;
return ringVersion != localRanges.ringVersion.getEpoch() &&
!localRanges.ringVersion.is(ColumnFamilyStore.RING_VERSION_IRRELEVANT);
}
@Override

View File

@ -52,7 +52,7 @@ import org.apache.cassandra.io.sstable.Descriptor;
import org.apache.cassandra.io.sstable.SSTableMultiWriter;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.StorageService;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.utils.Clock;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.Overlaps;
@ -295,13 +295,14 @@ public class UnifiedCompactionStrategy extends AbstractCompactionStrategy
private void maybeUpdateShardManager()
{
if (shardManager != null && !shardManager.isOutOfDate(StorageService.instance.getTokenMetadata().getRingVersion()))
// TODO - modify ShardManager::isOutOfDate to take an Epoch
if (shardManager != null && !shardManager.isOutOfDate(ClusterMetadata.current().epoch.getEpoch()))
return; // the disk boundaries (and thus the local ranges too) have not changed since the last time we calculated
synchronized (this)
{
// Recheck after entering critical section, another thread may have beaten us to it.
while (shardManager == null || shardManager.isOutOfDate(StorageService.instance.getTokenMetadata().getRingVersion()))
while (shardManager == null || shardManager.isOutOfDate(ClusterMetadata.current().epoch.getEpoch()))
shardManager = ShardManager.create(cfs);
// Note: this can just as well be done without the synchronization (races would be benign, just doing some
// redundant work). For the current usages of this blocking is fine and expected to perform no worse.

View File

@ -65,6 +65,7 @@ public abstract class RowFilter implements Iterable<RowFilter.Expression>
private static final Logger logger = LoggerFactory.getLogger(RowFilter.class);
public static final Serializer serializer = new Serializer();
public static final RowFilter NONE = new CQLFilter(Collections.emptyList());
protected final List<Expression> expressions;

View File

@ -95,7 +95,7 @@ public abstract class Guardrail
*/
public boolean enabled(@Nullable ClientState state)
{
return DatabaseDescriptor.isDaemonInitialized() && (state == null || state.isOrdinaryUser());
return DatabaseDescriptor.isDaemonInitialized() && (state == null || (state.isOrdinaryUser() && state.applyGuardrails()));
}
protected void warn(String message)

View File

@ -230,9 +230,15 @@ public class Tracker
addSSTablesInternal(sstables, true, false, true);
}
public void addInitialSSTablesWithoutUpdatingSize(Iterable<SSTableReader> sstables)
public void addInitialSSTablesWithoutUpdatingSize(Iterable<SSTableReader> sstables, ColumnFamilyStore cfs)
{
addSSTablesInternal(sstables, true, false, false);
if (!isDummy())
{
for (SSTableReader reader : sstables)
reader.setupOnline();
}
apply(updateLiveSet(emptySet(), sstables));
notifyAdded(sstables, true);
}
public void updateInitialSSTableSize(Iterable<SSTableReader> sstables)

View File

@ -32,6 +32,7 @@ import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.db.ClusteringComparator;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.commitlog.CommitLogPosition;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.schema.TableMetadataRef;
import org.apache.cassandra.utils.Clock;
import org.apache.cassandra.utils.FBUtilities;
@ -128,13 +129,13 @@ public abstract class AbstractAllocatorMemtable extends AbstractMemtableWithComm
}
@Override
public boolean shouldSwitch(ColumnFamilyStore.FlushReason reason)
public boolean shouldSwitch(ColumnFamilyStore.FlushReason reason, TableMetadata latest)
{
switch (reason)
{
case SCHEMA_CHANGE:
return initialComparator != metadata().comparator // If the CF comparator has changed, because our partitions reference the old one
|| !initialFactory.equals(metadata().params.memtable.factory()); // If a different type of memtable is requested
return initialComparator != latest.comparator // If the CF comparator has changed, because our partitions reference the old one
|| !initialFactory.equals(latest.params.memtable.factory()); // If a different type of memtable is requested
case OWNED_RANGES_CHANGE:
return false; // by default we don't use the local ranges, thus this has no effect
default:

View File

@ -399,10 +399,19 @@ public interface Memtable extends Comparable<Memtable>, UnfilteredSource
* - SNAPSHOT will be followed by performSnapshot().
* - STREAMING/REPAIR will be followed by creating a FlushSet for the streamed/repaired ranges. This data will be
* used to create sstables, which will be streamed and then deleted.
* The table metadata is supplied explicitly as this might not be the same as the current published metadata for
* the table. When applying a schema change, the ColumnFamilyStore instance is reloaded using the new table metadata
* before the Schema registry is updated. The memtable needs to examine the new metadata in order to determine
* whether the changes warrant a switch.
* This will not be called to perform truncation or drop (in that case the memtable is unconditionally dropped),
* but a flush may nevertheless be requested in that case to prepare a snapshot.
*/
boolean shouldSwitch(ColumnFamilyStore.FlushReason reason);
boolean shouldSwitch(ColumnFamilyStore.FlushReason reason, TableMetadata latest);
default boolean shouldSwitch(ColumnFamilyStore.FlushReason reason)
{
return shouldSwitch(reason, metadata());
}
/**
* Called when the table's metadata is updated. The memtable's metadata reference now points to the new version.

View File

@ -25,6 +25,7 @@ import com.google.common.annotations.VisibleForTesting;
import org.apache.cassandra.db.Keyspace;
import org.apache.cassandra.db.PartitionPosition;
import org.apache.cassandra.dht.Token;
import org.apache.cassandra.tcm.Epoch;
/**
* Holds boundaries (tokens) used to map a particular token (so partition key) to a shard id.
@ -43,21 +44,21 @@ public class ShardBoundaries
// - there is only 1 shard configured
// - the default partitioner doesn't support splitting
// - the keyspace is local system keyspace
public static final ShardBoundaries NONE = new ShardBoundaries(EMPTY_TOKEN_ARRAY, -1);
public static final ShardBoundaries NONE = new ShardBoundaries(EMPTY_TOKEN_ARRAY, Epoch.EMPTY);
private final Token[] boundaries;
public final long ringVersion;
public final Epoch epoch;
@VisibleForTesting
public ShardBoundaries(Token[] boundaries, long ringVersion)
public ShardBoundaries(Token[] boundaries, Epoch epoch)
{
this.boundaries = boundaries;
this.ringVersion = ringVersion;
this.epoch = epoch;
}
public ShardBoundaries(List<Token> boundaries, long ringVersion)
public ShardBoundaries(List<Token> boundaries, Epoch epoch)
{
this(boundaries.toArray(EMPTY_TOKEN_ARRAY), ringVersion);
this(boundaries.toArray(EMPTY_TOKEN_ARRAY), epoch);
}
/**

View File

@ -35,11 +35,17 @@ import org.slf4j.LoggerFactory;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.filter.ColumnFilter;
import org.apache.cassandra.db.rows.*;
import org.apache.cassandra.exceptions.CoordinatorBehindException;
import org.apache.cassandra.exceptions.UnknownTableException;
import org.apache.cassandra.index.IndexRegistry;
import org.apache.cassandra.io.util.DataInputBuffer;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputBuffer;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.metrics.TCMMetrics;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.tcm.Epoch;
import org.apache.cassandra.net.MessagingService;
import org.apache.cassandra.schema.ColumnMetadata;
import org.apache.cassandra.schema.Schema;
import org.apache.cassandra.schema.TableId;
@ -73,10 +79,12 @@ public class PartitionUpdate extends AbstractBTreePartition
private final BTreePartitionData holder;
private final DeletionInfo deletionInfo;
private final TableMetadata metadata;
public final Epoch serializedAtEpoch;
private final boolean canHaveShadowedData;
private PartitionUpdate(TableMetadata metadata,
Epoch serializedAtEpoch,
DecoratedKey key,
BTreePartitionData holder,
MutableDeletionInfo deletionInfo,
@ -87,6 +95,7 @@ public class PartitionUpdate extends AbstractBTreePartition
this.holder = holder;
this.deletionInfo = deletionInfo;
this.canHaveShadowedData = canHaveShadowedData;
this.serializedAtEpoch = serializedAtEpoch;
}
/**
@ -101,7 +110,7 @@ public class PartitionUpdate extends AbstractBTreePartition
{
MutableDeletionInfo deletionInfo = MutableDeletionInfo.live();
BTreePartitionData holder = new BTreePartitionData(RegularAndStaticColumns.NONE, BTree.empty(), deletionInfo, Rows.EMPTY_STATIC_ROW, EncodingStats.NO_STATS);
return new PartitionUpdate(metadata, key, holder, deletionInfo, false);
return new PartitionUpdate(metadata, metadata.epoch, key, holder, deletionInfo, false);
}
/**
@ -118,7 +127,7 @@ public class PartitionUpdate extends AbstractBTreePartition
{
MutableDeletionInfo deletionInfo = new MutableDeletionInfo(timestamp, nowInSec);
BTreePartitionData holder = new BTreePartitionData(RegularAndStaticColumns.NONE, BTree.empty(), deletionInfo, Rows.EMPTY_STATIC_ROW, EncodingStats.NO_STATS);
return new PartitionUpdate(metadata, key, holder, deletionInfo, false);
return new PartitionUpdate(metadata, metadata.epoch, key, holder, deletionInfo, false);
}
/**
@ -144,7 +153,7 @@ public class PartitionUpdate extends AbstractBTreePartition
staticRow == null ? Rows.EMPTY_STATIC_ROW : staticRow,
EncodingStats.NO_STATS
);
return new PartitionUpdate(metadata, key, holder, deletionInfo, false);
return new PartitionUpdate(metadata, metadata.epoch, key, holder, deletionInfo, false);
}
/**
@ -191,7 +200,7 @@ public class PartitionUpdate extends AbstractBTreePartition
iterator = UnfilteredRowIterators.withOnlyQueriedData(iterator, filter);
BTreePartitionData holder = build(iterator, 16);
MutableDeletionInfo deletionInfo = (MutableDeletionInfo) holder.deletionInfo;
return new PartitionUpdate(iterator.metadata(), iterator.partitionKey(), holder, deletionInfo, false);
return new PartitionUpdate(iterator.metadata(), iterator.metadata().epoch, iterator.partitionKey(), holder, deletionInfo, false);
}
/**
@ -210,7 +219,7 @@ public class PartitionUpdate extends AbstractBTreePartition
iterator = RowIterators.withOnlyQueriedData(iterator, filter);
MutableDeletionInfo deletionInfo = MutableDeletionInfo.live();
BTreePartitionData holder = build(iterator, deletionInfo, true);
return new PartitionUpdate(iterator.metadata(), iterator.partitionKey(), holder, deletionInfo, false);
return new PartitionUpdate(iterator.metadata(), iterator.metadata().epoch, iterator.partitionKey(), holder, deletionInfo, false);
}
@ -223,7 +232,7 @@ public class PartitionUpdate extends AbstractBTreePartition
columnSet.add(column.column());
RegularAndStaticColumns columns = RegularAndStaticColumns.builder().addAll(columnSet).build();
return new PartitionUpdate(this.metadata, this.partitionKey, this.holder.withColumns(columns), this.deletionInfo.mutableCopy(), false);
return new PartitionUpdate(this.metadata, this.metadata.epoch, this.partitionKey, this.holder.withColumns(columns), this.deletionInfo.mutableCopy(), false);
}
@ -539,7 +548,14 @@ public class PartitionUpdate extends AbstractBTreePartition
*/
public static SimpleBuilder simpleBuilder(TableMetadata metadata, Object... partitionKeyValues)
{
return new SimpleBuilders.PartitionUpdateBuilder(metadata, partitionKeyValues);
// Here we dereference the current version of the supplied TableMetadata. The reason for this is that in some
// places we still reference static TableMetadata instances.
// For instance, TraceKeyspace contains Sessions & Events static members which are created at startup when the
// current epoch is Epoch.EMPTY. These are used to construct mutations when tracing is enabled and when the
// mutations are serialised and sent between replica & coordinator the epoch comparisons in PartitionUpdate
// deserializer trigger an IncompatibleSchemaException.
// TODO ultimately remove the use of static TableMetadata instances in System/Tracing/Auth keyspaces.
return new SimpleBuilders.PartitionUpdateBuilder(metadata.ref.get(), partitionKeyValues);
}
public void validateIndexedColumns()
@ -554,7 +570,7 @@ public class PartitionUpdate extends AbstractBTreePartition
MutableDeletionInfo deletionInfo,
boolean canHaveShadowedData)
{
return new PartitionUpdate(metadata, key, holder, deletionInfo, canHaveShadowedData);
return new PartitionUpdate(metadata, metadata.epoch, key, holder, deletionInfo, canHaveShadowedData);
}
/**
@ -713,24 +729,45 @@ public class PartitionUpdate extends AbstractBTreePartition
assert !iter.isReverseOrder();
update.metadata.id.serialize(out);
if (version >= MessagingService.VERSION_50)
Epoch.serializer.serialize(update.metadata.epoch != null ? update.metadata.epoch : Epoch.EMPTY, out);
UnfilteredRowIteratorSerializer.serializer.serialize(iter, null, out, version, update.rowCount());
}
}
public PartitionUpdate deserialize(DataInputPlus in, int version, DeserializationHelper.Flag flag) throws IOException
{
TableMetadata metadata = Schema.instance.getExistingTableMetadata(TableId.deserialize(in));
UnfilteredRowIteratorSerializer.Header header = UnfilteredRowIteratorSerializer.serializer.deserializeHeader(metadata, null, in, version, flag);
TableId tableId = TableId.deserialize(in);
Epoch remoteVersion = null;
if (version >= MessagingService.VERSION_50)
remoteVersion = Epoch.serializer.deserialize(in);
TableMetadata tableMetadata;
try
{
tableMetadata = Schema.instance.getExistingTableMetadata(tableId);
}
catch (UnknownTableException e)
{
ClusterMetadata metadata = ClusterMetadata.current();
Epoch localCurrentEpoch = metadata.epoch;
if (remoteVersion != null && localCurrentEpoch.isAfter(remoteVersion))
{
TCMMetrics.instance.coordinatorBehindSchema.mark();
throw new CoordinatorBehindException(e.getMessage(), e);
}
throw e;
}
UnfilteredRowIteratorSerializer.Header header = UnfilteredRowIteratorSerializer.serializer.deserializeHeader(tableMetadata, null, in, version, flag);
if (header.isEmpty)
return emptyUpdate(metadata, header.key);
return emptyUpdate(tableMetadata, header.key);
assert !header.isReversed;
assert header.rowEstimate >= 0;
MutableDeletionInfo.Builder deletionBuilder = MutableDeletionInfo.builder(header.partitionDeletion, metadata.comparator, false);
MutableDeletionInfo.Builder deletionBuilder = MutableDeletionInfo.builder(header.partitionDeletion, tableMetadata.comparator, false);
Object[] rows;
try (BTree.FastBuilder<Row> builder = BTree.fastBuilder();
UnfilteredRowIterator partition = UnfilteredRowIteratorSerializer.serializer.deserialize(in, version, metadata, flag, header))
UnfilteredRowIterator partition = UnfilteredRowIteratorSerializer.serializer.deserialize(in, version, tableMetadata, flag, header))
{
while (partition.hasNext())
{
@ -744,19 +781,27 @@ public class PartitionUpdate extends AbstractBTreePartition
}
MutableDeletionInfo deletionInfo = deletionBuilder.build();
return new PartitionUpdate(metadata,
return new PartitionUpdate(tableMetadata,
remoteVersion,
header.key,
new BTreePartitionData(header.sHeader.columns(), rows, deletionInfo, header.staticRow, header.sHeader.stats()),
deletionInfo,
false);
}
public static boolean isEmpty(ByteBuffer in, DeserializationHelper.Flag flag, DecoratedKey key) throws IOException
public static boolean isEmpty(ByteBuffer in, DeserializationHelper.Flag flag, DecoratedKey key, int version) throws IOException
{
int position = in.position();
position += 16; // CFMetaData.serializer.deserialize(in, version);
if (position >= in.limit())
throw new EOFException();
if (version >= MessagingService.VERSION_50)
{
long epoch = VIntCoding.getUnsignedVInt(in, position);
position += VIntCoding.computeVIntSize(epoch);
}
// DecoratedKey key = metadata.decorateKey(ByteBufferUtil.readWithVIntLength(in));
int keyLength = VIntCoding.getUnsignedVInt32(in, position);
position += keyLength + VIntCoding.computeUnsignedVIntSize(keyLength);
@ -771,6 +816,7 @@ public class PartitionUpdate extends AbstractBTreePartition
try (UnfilteredRowIterator iter = update.unfilteredIterator())
{
return update.metadata.id.serializedSize()
+ (version >= MessagingService.VERSION_50 ? Epoch.serializer.serializedSize(update.metadata.epoch) : 0)
+ UnfilteredRowIteratorSerializer.serializer.serializedSize(iter, null, version, update.rowCount());
}
}
@ -964,6 +1010,7 @@ public class PartitionUpdate extends AbstractBTreePartition
isBuilt = true;
return new PartitionUpdate(metadata,
metadata.epoch,
partitionKey(),
new BTreePartitionData(columns,
merged,

View File

@ -74,8 +74,8 @@ public class CassandraCompressedStreamReader extends CassandraStreamReader
try (CompressedInputStream cis = new CompressedInputStream(inputPlus, compressionInfo, ChecksumType.CRC32, cfs::getCrcCheckChance))
{
TrackedDataInputPlus in = new TrackedDataInputPlus(cis);
deserializer = new StreamDeserializer(cfs.metadata(), in, inputVersion, getHeader(cfs.metadata()));
writer = createWriter(cfs, totalSize, repairedAt, pendingRepair, inputVersion.format);
deserializer = new StreamDeserializer(cfs.metadata(), in, inputVersion, getHeader(cfs.metadata()), session, writer);
String filename = writer.getFilename();
String sectionName = filename + '-' + fileSeqNum;
int sectionIdx = 0;

View File

@ -20,6 +20,9 @@ package org.apache.cassandra.db.streaming;
import java.io.IOError;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.ListIterator;
import java.util.concurrent.TimeUnit;
import com.google.common.base.Preconditions;
import com.google.common.collect.UnmodifiableIterator;
@ -38,6 +41,8 @@ import org.apache.cassandra.db.rows.EncodingStats;
import org.apache.cassandra.db.rows.Row;
import org.apache.cassandra.db.rows.Unfiltered;
import org.apache.cassandra.db.rows.UnfilteredRowIterator;
import org.apache.cassandra.dht.Range;
import org.apache.cassandra.dht.Token;
import org.apache.cassandra.exceptions.UnknownColumnException;
import org.apache.cassandra.io.sstable.RangeAwareSSTableWriter;
import org.apache.cassandra.io.sstable.SSTableMultiWriter;
@ -47,15 +52,19 @@ import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.io.sstable.format.Version;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.TrackedDataInputPlus;
import org.apache.cassandra.metrics.StorageMetrics;
import org.apache.cassandra.schema.TableId;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.StorageService;
import org.apache.cassandra.streaming.ProgressInfo;
import org.apache.cassandra.streaming.StreamReceivedOutOfTokenRangeException;
import org.apache.cassandra.streaming.StreamReceiver;
import org.apache.cassandra.streaming.StreamSession;
import org.apache.cassandra.streaming.compress.StreamCompressionInputStream;
import org.apache.cassandra.streaming.messages.StreamMessageHeader;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.NoSpamLogger;
import org.apache.cassandra.utils.TimeUUID;
import static org.apache.cassandra.net.MessagingService.current_version;
@ -66,6 +75,7 @@ import static org.apache.cassandra.net.MessagingService.current_version;
public class CassandraStreamReader implements IStreamReader
{
private static final Logger logger = LoggerFactory.getLogger(CassandraStreamReader.class);
private static final String logMessageTemplate = "[Stream #{}] Received streamed SSTable {} from {} containing key outside valid ranges {}";
protected final TableId tableId;
protected final long estimatedKeys;
protected final Collection<SSTableReader.PartitionPositionBounds> sections;
@ -121,8 +131,8 @@ public class CassandraStreamReader implements IStreamReader
try (StreamCompressionInputStream streamCompressionInputStream = new StreamCompressionInputStream(inputPlus, current_version))
{
TrackedDataInputPlus in = new TrackedDataInputPlus(streamCompressionInputStream);
deserializer = new StreamDeserializer(cfs.metadata(), in, inputVersion, getHeader(cfs.metadata()));
writer = createWriter(cfs, totalSize, repairedAt, pendingRepair, inputVersion.format);
deserializer = getDeserializer(cfs.metadata(), in, inputVersion, session, writer);
String sequenceName = writer.getFilename() + '-' + fileSeqNum;
long lastBytesRead = 0;
while (in.getBytesRead() < totalSize)
@ -149,6 +159,15 @@ public class CassandraStreamReader implements IStreamReader
}
}
protected StreamDeserializer getDeserializer(TableMetadata metadata,
TrackedDataInputPlus in,
Version inputVersion,
StreamSession session,
SSTableMultiWriter writer) throws IOException
{
return new StreamDeserializer(metadata, in, inputVersion, getHeader(metadata), session, writer);
}
protected SerializationHeader getHeader(TableMetadata metadata) throws UnknownColumnException
{
return header != null? header.toHeader(metadata) : null; //pre-3.0 sstable have no SerializationHeader
@ -188,29 +207,52 @@ public class CassandraStreamReader implements IStreamReader
private final SerializationHeader header;
private final DeserializationHelper helper;
private DecoratedKey key;
private DeletionTime partitionLevelDeletion;
private SSTableSimpleIterator iterator;
private Row staticRow;
private final List<Range<Token>> ownedRanges;
private final StreamSession session;
private final SSTableMultiWriter writer;
private int lastCheckedRangeIndex;
protected DecoratedKey key;
protected DeletionTime partitionLevelDeletion;
protected SSTableSimpleIterator iterator;
protected Row staticRow;
private IOException exception;
private Version version;
public StreamDeserializer(TableMetadata metadata, DataInputPlus in, Version version, SerializationHeader header) throws IOException
public StreamDeserializer(TableMetadata metadata, DataInputPlus in, Version version, SerializationHeader header, StreamSession session, SSTableMultiWriter writer) throws IOException
{
this.metadata = metadata;
this.in = in;
this.helper = new DeserializationHelper(metadata, version.correspondingMessagingVersion(), DeserializationHelper.Flag.PRESERVE_SIZE);
this.header = header;
this.version = version;
ownedRanges = Range.normalize(StorageService.instance.getLocalAndPendingRanges(metadata.keyspace));
lastCheckedRangeIndex = 0;
this.session = session;
this.writer = writer;
}
public StreamDeserializer newPartition() throws IOException
public UnfilteredRowIterator newPartition() throws IOException
{
readKey();
readPartition();
return this;
}
protected void readKey() throws IOException
{
key = metadata.partitioner.decorateKey(ByteBufferUtil.readWithShortLength(in));
lastCheckedRangeIndex = verifyKeyInOwnedRanges(key,
ownedRanges,
lastCheckedRangeIndex);
}
protected void readPartition() throws IOException
{
partitionLevelDeletion = DeletionTime.getSerializer(version).deserialize(in);
iterator = SSTableSimpleIterator.create(metadata, in, header, helper, partitionLevelDeletion);
staticRow = iterator.readStaticRow();
return this;
}
public TableMetadata metadata()
@ -291,5 +333,27 @@ public class CassandraStreamReader implements IStreamReader
public void close()
{
}
private int verifyKeyInOwnedRanges(final DecoratedKey key,
List<Range<Token>> ownedRanges,
int lastCheckedRangeIndex)
{
if (lastCheckedRangeIndex < ownedRanges.size())
{
ListIterator<Range<Token>> rangesToCheck = ownedRanges.listIterator(lastCheckedRangeIndex);
while (rangesToCheck.hasNext())
{
Range<Token> range = rangesToCheck.next();
if (range.contains(key.getToken()))
return lastCheckedRangeIndex;
lastCheckedRangeIndex++;
}
}
StorageMetrics.totalOpsForInvalidToken.inc();
NoSpamLogger.log(logger, NoSpamLogger.Level.WARN, 1, TimeUnit.SECONDS, logMessageTemplate, session.planId(), writer.getFilename(), session.peer, ownedRanges);
throw new StreamReceivedOutOfTokenRangeException(ownedRanges, key, writer.getFilename());
}
}
}

View File

@ -17,7 +17,14 @@
*/
package org.apache.cassandra.db.view;
import java.util.*;
import java.util.AbstractCollection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NavigableSet;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
@ -27,14 +34,36 @@ import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import com.google.common.collect.PeekingIterator;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.Clustering;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.DeletionInfo;
import org.apache.cassandra.db.DeletionTime;
import org.apache.cassandra.db.Keyspace;
import org.apache.cassandra.db.Mutation;
import org.apache.cassandra.db.RangeTombstone;
import org.apache.cassandra.db.ReadExecutionController;
import org.apache.cassandra.db.ReadQuery;
import org.apache.cassandra.db.SinglePartitionReadCommand;
import org.apache.cassandra.db.Slice;
import org.apache.cassandra.db.Slices;
import org.apache.cassandra.db.SystemKeyspace;
import org.apache.cassandra.db.commitlog.CommitLogPosition;
import org.apache.cassandra.db.filter.*;
import org.apache.cassandra.db.partitions.*;
import org.apache.cassandra.db.rows.*;
import org.apache.cassandra.db.filter.ClusteringIndexFilter;
import org.apache.cassandra.db.filter.ClusteringIndexNamesFilter;
import org.apache.cassandra.db.filter.ClusteringIndexSliceFilter;
import org.apache.cassandra.db.filter.ColumnFilter;
import org.apache.cassandra.db.filter.DataLimits;
import org.apache.cassandra.db.filter.RowFilter;
import org.apache.cassandra.db.partitions.PartitionUpdate;
import org.apache.cassandra.db.partitions.UnfilteredPartitionIterators;
import org.apache.cassandra.db.rows.BTreeRow;
import org.apache.cassandra.db.rows.RangeTombstoneMarker;
import org.apache.cassandra.db.rows.Row;
import org.apache.cassandra.db.rows.Rows;
import org.apache.cassandra.db.rows.Unfiltered;
import org.apache.cassandra.db.rows.UnfilteredRowIterator;
import org.apache.cassandra.dht.Token;
import org.apache.cassandra.schema.Schema;
import org.apache.cassandra.schema.TableId;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.schema.TableMetadataRef;
import org.apache.cassandra.service.StorageProxy;
@ -57,9 +86,9 @@ public class TableViews extends AbstractCollection<View>
// list is the best option.
private final List<View> views = new CopyOnWriteArrayList();
public TableViews(TableId id)
public TableViews(TableMetadata tableMetadata)
{
baseTableMetadata = Schema.instance.getTableMetadataRef(id);
baseTableMetadata = tableMetadata.ref;
}
public boolean hasViews()

View File

@ -43,6 +43,7 @@ import org.apache.cassandra.locator.RangesAtEndpoint;
import org.apache.cassandra.locator.Replicas;
import org.apache.cassandra.schema.SystemDistributedKeyspace;
import org.apache.cassandra.service.StorageService;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.Pair;
import org.apache.cassandra.utils.concurrent.Future;
@ -66,7 +67,7 @@ class ViewBuilder
private final ColumnFamilyStore baseCfs;
private final View view;
private final String ksName;
private final UUID localHostId = SystemKeyspace.getOrInitializeLocalHostId();
private final UUID localHostId;
private final Set<Range<Token>> builtRanges = Sets.newConcurrentHashSet();
private final Map<Range<Token>, Pair<Token, Long>> pendingRanges = Maps.newConcurrentMap();
private final Set<ViewBuilderTask> tasks = Sets.newConcurrentHashSet();
@ -79,6 +80,7 @@ class ViewBuilder
this.baseCfs = baseCfs;
this.view = view;
ksName = baseCfs.metadata.keyspace;
this.localHostId = ClusterMetadata.current().myNodeId().toUUID();
}
public void start()

View File

@ -110,7 +110,7 @@ public class ViewBuilderTask extends CompactionInfo.Holder implements Callable<L
UnfilteredRowIterator data = UnfilteredPartitionIterators.getOnlyElement(command.executeLocally(orderGroup), command))
{
Iterator<Collection<Mutation>> mutations = baseCfs.keyspace.viewManager
.forTable(baseCfs.metadata.id)
.forTable(baseCfs.metadata.get())
.generateViewUpdates(Collections.singleton(view), data, empty, nowInSec, true);
AtomicLong noBase = new AtomicLong(Long.MAX_VALUE);

View File

@ -28,12 +28,9 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.schema.TableId;
import org.apache.cassandra.schema.ViewMetadata;
import org.apache.cassandra.schema.*;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.partitions.*;
import org.apache.cassandra.schema.SystemDistributedKeyspace;
import org.apache.cassandra.schema.Views;
import org.apache.cassandra.service.StorageService;
import static org.apache.cassandra.config.CassandraRelevantProperties.MV_ENABLE_COORDINATOR_BATCHLOG;
@ -84,7 +81,7 @@ public class ViewManager
if (coordinatorBatchlog && keyspace.getReplicationStrategy().getReplicationFactor().allReplicas == 1)
continue;
if (!forTable(update.metadata().id).updatedViews(update).isEmpty())
if (!forTable(update.metadata()).updatedViews(update).isEmpty())
return true;
}
}
@ -97,9 +94,9 @@ public class ViewManager
return viewsByName.values();
}
public void reload(boolean buildAllViews)
public void reload(KeyspaceMetadata keyspaceMetadata)
{
Views views = keyspace.getMetadata().views;
Views views = keyspaceMetadata.views;
Map<String, ViewMetadata> newViewsByName = Maps.newHashMapWithExpectedSize(views.size());
for (ViewMetadata definition : views)
{
@ -111,10 +108,16 @@ public class ViewManager
if (!viewsByName.containsKey(entry.getKey()))
addView(entry.getValue());
}
}
if (!buildAllViews)
return;
public void buildViews()
{
Views views = keyspace.getMetadata().views;
Map<String, ViewMetadata> newViewsByName = Maps.newHashMapWithExpectedSize(views.size());
for (ViewMetadata definition : views)
{
newViewsByName.put(definition.name(), definition);
}
// Building views involves updating view build status in the system_distributed
// keyspace and therefore it requires ring information. This check prevents builds
// being submitted when Keyspaces are initialized during CassandraDaemon::setup as
@ -149,7 +152,7 @@ public class ViewManager
}
View view = new View(definition, keyspace.getColumnFamilyStore(definition.baseTableId));
forTable(view.getDefinition().baseTableId).add(view);
forTable(keyspace.getMetadata().tables.getNullable(view.getDefinition().baseTableId)).add(view);
viewsByName.put(definition.name(), view);
}
@ -166,7 +169,7 @@ public class ViewManager
return;
view.stopBuild();
forTable(view.getDefinition().baseTableId).removeByName(name);
forTable(view.getDefinition().baseTableMetadata()).removeByName(name);
SystemKeyspace.setViewRemoved(keyspace.getName(), view.name);
SystemDistributedKeyspace.setViewRemoved(keyspace.getName(), view.name);
}
@ -182,13 +185,13 @@ public class ViewManager
view.build();
}
public TableViews forTable(TableId id)
public TableViews forTable(TableMetadata metadata)
{
TableViews views = viewsByBaseTable.get(id);
TableViews views = viewsByBaseTable.get(metadata.id);
if (views == null)
{
views = new TableViews(id);
TableViews previous = viewsByBaseTable.putIfAbsent(id, views);
views = new TableViews(metadata);
TableViews previous = viewsByBaseTable.putIfAbsent(metadata.id, views);
if (previous != null)
views = previous;
}

View File

@ -24,10 +24,11 @@ import java.util.function.Predicate;
import com.google.common.collect.Iterables;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.dht.Token;
import org.apache.cassandra.locator.AbstractReplicationStrategy;
import org.apache.cassandra.locator.EndpointsForToken;
import org.apache.cassandra.locator.NetworkTopologyStrategy;
import org.apache.cassandra.locator.Replica;
import org.apache.cassandra.schema.KeyspaceMetadata;
import org.apache.cassandra.tcm.ClusterMetadata;
public final class ViewUtils
{
@ -57,11 +58,13 @@ public final class ViewUtils
*
* @return Optional.empty() if this method is called using a base token which does not belong to this replica
*/
public static Optional<Replica> getViewNaturalEndpoint(AbstractReplicationStrategy replicationStrategy, Token baseToken, Token viewToken)
public static Optional<Replica> getViewNaturalEndpoint(ClusterMetadata metadata, String keyspace, Token baseToken, Token viewToken)
{
String localDataCenter = DatabaseDescriptor.getEndpointSnitch().getLocalDatacenter();
EndpointsForToken naturalBaseReplicas = replicationStrategy.getNaturalReplicasForToken(baseToken);
EndpointsForToken naturalViewReplicas = replicationStrategy.getNaturalReplicasForToken(viewToken);
KeyspaceMetadata keyspaceMetadata = metadata.schema.getKeyspaces().getNullable(keyspace);
EndpointsForToken naturalBaseReplicas = metadata.placements.get(keyspaceMetadata.params.replication).reads.forToken(baseToken).get();
EndpointsForToken naturalViewReplicas = metadata.placements.get(keyspaceMetadata.params.replication).reads.forToken(viewToken).get();
Optional<Replica> localReplica = Iterables.tryFind(naturalViewReplicas, Replica::isSelf).toJavaUtil();
if (localReplica.isPresent())
@ -69,7 +72,7 @@ public final class ViewUtils
// We only select replicas from our own DC
// TODO: this is poor encapsulation, leaking implementation details of replication strategy
Predicate<Replica> isLocalDC = r -> !(replicationStrategy instanceof NetworkTopologyStrategy)
Predicate<Replica> isLocalDC = r -> !(keyspaceMetadata.replicationStrategy instanceof NetworkTopologyStrategy)
|| DatabaseDescriptor.getEndpointSnitch().getDatacenter(r).equals(localDataCenter);
// We have to remove any endpoint which is shared between the base and the view, as it will select itself
@ -84,7 +87,9 @@ public final class ViewUtils
// The replication strategy will be the same for the base and the view, as they must belong to the same keyspace.
// Since the same replication strategy is used, the same placement should be used and we should get the same
// number of replicas for all of the tokens in the ring.
assert baseReplicas.size() == viewReplicas.size() : "Replication strategy should have the same number of endpoints for the base and the view";
assert baseReplicas.size() == viewReplicas.size() :
String.format("Replication strategy should have the same number of endpoints for the base (%d) and the view (%d)",
baseReplicas.size(), viewReplicas.size());
int baseIdx = -1;
for (int i=0; i<baseReplicas.size(); i++)

View File

@ -0,0 +1,87 @@
/*
* 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.virtual;
import java.io.IOException;
import java.util.Date;
import org.apache.cassandra.cql3.UntypedResultSet;
import org.apache.cassandra.db.ConsistencyLevel;
import org.apache.cassandra.db.marshal.LongType;
import org.apache.cassandra.db.marshal.TimestampType;
import org.apache.cassandra.db.marshal.UTF8Type;
import org.apache.cassandra.dht.LocalPartitioner;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.tcm.Transformation;
import static java.lang.String.format;
import static org.apache.cassandra.cql3.QueryProcessor.execute;
import static org.apache.cassandra.schema.DistributedMetadataLogKeyspace.TABLE_NAME;
import static org.apache.cassandra.schema.SchemaConstants.METADATA_KEYSPACE_NAME;
final class ClusterMetadataLogTable extends AbstractVirtualTable
{
private static final String PERIOD = "period";
private static final String EPOCH = "epoch";
private static final String KIND = "kind";
private static final String TRANSFORMATION = "transformation";
private static final String ENTRY_ID = "entry_id";
private static final String ENTRY_TIME = "entry_time";
ClusterMetadataLogTable(String keyspace)
{
super(TableMetadata.builder(keyspace, "cluster_metadata_log")
.comment("cluster metadata log")
.kind(TableMetadata.Kind.VIRTUAL)
.partitioner(new LocalPartitioner(LongType.instance))
.addPartitionKeyColumn(PERIOD, LongType.instance)
.addClusteringColumn(EPOCH, LongType.instance)
.addRegularColumn(KIND, UTF8Type.instance)
.addRegularColumn(TRANSFORMATION, UTF8Type.instance)
.addRegularColumn(ENTRY_ID, LongType.instance)
.addRegularColumn(ENTRY_TIME, TimestampType.instance)
.build());
}
@Override
public DataSet data()
{
try
{
SimpleDataSet result = new SimpleDataSet(metadata());
UntypedResultSet res = execute(format("SELECT period, epoch, kind, transformation, entry_id, writetime(kind) as wt " +
"FROM %s.%s", METADATA_KEYSPACE_NAME, TABLE_NAME), ConsistencyLevel.QUORUM);
for (UntypedResultSet.Row r : res)
{
Transformation.Kind kind = Transformation.Kind.valueOf(r.getString("kind"));
Transformation transformation = kind.fromVersionedBytes(r.getBlob("transformation"));
result.row(r.getLong("period"), r.getLong("epoch"))
.column(KIND, kind.toString())
.column(TRANSFORMATION, transformation.toString())
.column(ENTRY_ID, r.getLong("entry_id"))
.column(ENTRY_TIME, new Date(r.getLong("wt") / 1000));
}
return result;
}
catch (IOException e)
{
throw new RuntimeException(e);
}
}
}

View File

@ -0,0 +1,145 @@
/*
* 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.virtual;
import java.util.HashSet;
import java.util.stream.Collectors;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.cql3.QueryProcessor;
import org.apache.cassandra.db.SystemKeyspace;
import org.apache.cassandra.db.marshal.InetAddressType;
import org.apache.cassandra.db.marshal.Int32Type;
import org.apache.cassandra.db.marshal.SetType;
import org.apache.cassandra.db.marshal.UTF8Type;
import org.apache.cassandra.db.marshal.UUIDType;
import org.apache.cassandra.dht.LocalPartitioner;
import org.apache.cassandra.gms.Gossiper;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.StorageService;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.tcm.membership.Location;
import org.apache.cassandra.tcm.membership.NodeAddresses;
import org.apache.cassandra.tcm.membership.NodeId;
import org.apache.cassandra.tcm.membership.NodeState;
import org.apache.cassandra.transport.ProtocolVersion;
import org.apache.cassandra.utils.FBUtilities;
import static org.apache.cassandra.utils.FBUtilities.getBroadcastAddressAndPort;
public class LocalTable extends AbstractVirtualTable
{
public static final String BOOTSTRAPPED = "bootstrapped";
public static final String BROADCAST_ADDRESS = "broadcast_address";
public static final String BROADCAST_PORT = "broadcast_port";
public static final String CLUSTER_NAME = "cluster_name";
public static final String LISTEN_ADDRESS = "listen_address";
public static final String LISTEN_PORT = "listen_port";
public static final String CQL_VERSION = "cql_version";
public static final String DATACENTER = "datacenter";
public static final String RACK = "rack";
public static final String HOST_ID = "host_id";
public static final String GOSSIP_GENERATION = "gossip_generation";
public static final String RELEASE_VERSION = "release_version";
public static final String NATIVE_ADDRESS = "native_address";
public static final String NATIVE_PORT = "native_port";
public static final String NATIVE_PROTOCOL_VERSION = "native_protocol_version";
public static final String PARTITIONER = "partitioner";
public static final String SCHEMA_VERSION = "schema_version";
public static final String TOKENS = "tokens";
public static final String STATE = "state";
public static final String STATUS = "status";
public static final String KEY = "local";
public static final String TRUNCATED_AT = "truncated_at";
public LocalTable(String keyspace)
{
super(TableMetadata.builder(keyspace, "local")
.comment("Information about local node")
.kind(TableMetadata.Kind.VIRTUAL)
.partitioner(new LocalPartitioner(InetAddressType.instance))
.addPartitionKeyColumn(KEY, UTF8Type.instance)
.addRegularColumn(BOOTSTRAPPED, UTF8Type.instance)
.addRegularColumn(BROADCAST_ADDRESS, InetAddressType.instance)
.addRegularColumn(BROADCAST_PORT, Int32Type.instance)
.addRegularColumn(CLUSTER_NAME, UTF8Type.instance)
.addRegularColumn(CQL_VERSION, UTF8Type.instance)
.addRegularColumn(DATACENTER, UTF8Type.instance)
.addRegularColumn(GOSSIP_GENERATION, Int32Type.instance)
.addRegularColumn(HOST_ID, UUIDType.instance)
.addRegularColumn(LISTEN_ADDRESS, InetAddressType.instance)
.addRegularColumn(LISTEN_PORT, Int32Type.instance)
.addRegularColumn(NATIVE_ADDRESS, InetAddressType.instance)
.addRegularColumn(NATIVE_PORT, Int32Type.instance)
.addRegularColumn(NATIVE_PROTOCOL_VERSION, UTF8Type.instance)
.addRegularColumn(PARTITIONER, UTF8Type.instance)
.addRegularColumn(RACK, UTF8Type.instance)
.addRegularColumn(RELEASE_VERSION, UTF8Type.instance)
.addRegularColumn(SCHEMA_VERSION, UUIDType.instance)
.addRegularColumn(STATE, UTF8Type.instance)
.addRegularColumn(STATUS, UTF8Type.instance)
.addRegularColumn(TOKENS, SetType.getInstance(UTF8Type.instance, false))
// .addRegularColumn(TRUNCATED_AT, MapType.getInstance(UUIDType.instance, UTF8Type.instance, false)) todo?
.build());
}
public DataSet data()
{
SimpleDataSet result = new SimpleDataSet(metadata());
ClusterMetadata cm = ClusterMetadata.current();
NodeId peer = cm.myNodeId();
NodeState nodeState = cm.directory.peerState(peer);
NodeAddresses addresses = cm.directory.getNodeAddresses(peer);
Location location = cm.directory.location(peer);
result.row(KEY)
.column(BOOTSTRAPPED, SystemKeyspace.BootstrapState.fromNodeState(nodeState).toString())
.column(BROADCAST_ADDRESS, addresses.broadcastAddress.getAddress())
.column(BROADCAST_PORT, addresses.broadcastAddress.getPort())
.column(CLUSTER_NAME, DatabaseDescriptor.getClusterName())
.column(CQL_VERSION, QueryProcessor.CQL_VERSION.toString())
.column(DATACENTER, location.datacenter)
.column(GOSSIP_GENERATION, Gossiper.instance.getCurrentGenerationNumber(FBUtilities.getBroadcastAddressAndPort()))
.column(HOST_ID, peer.toUUID())
.column(LISTEN_ADDRESS, addresses.localAddress.getAddress())
.column(LISTEN_PORT, addresses.localAddress.getPort())
.column(NATIVE_ADDRESS, addresses.nativeAddress.getAddress())
.column(NATIVE_PORT, addresses.nativeAddress.getPort())
.column(NATIVE_PROTOCOL_VERSION, String.valueOf(ProtocolVersion.CURRENT.asInt()))
.column(PARTITIONER, cm.partitioner.getClass().getName())
.column(RACK, location.rack)
.column(RELEASE_VERSION, cm.directory.version(peer).cassandraVersion.toString())
.column(SCHEMA_VERSION, cm.schema.getVersion())
.column(STATE, cm.directory.peerState(peer).toString())
.column(STATUS, status(cm))
.column(TOKENS, new HashSet<>(cm.tokenMap.tokens(peer).stream().map((token) -> token.getToken().getTokenValue().toString()).collect(Collectors.toList())));
//.column(TRUNCATED_AT, status(cm)); // todo?
return result;
}
private static String status(ClusterMetadata cm)
{
if (StorageService.instance.isDraining())
return StorageService.Mode.DRAINING.toString();
if (StorageService.instance.isDrained())
return StorageService.Mode.DRAINED.toString();
return cm.directory.peerState(getBroadcastAddressAndPort()).toString();
}
}

View File

@ -0,0 +1,200 @@
/*
* 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.virtual;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.cql3.QueryProcessor;
import org.apache.cassandra.db.SystemKeyspace;
import org.apache.cassandra.db.marshal.InetAddressType;
import org.apache.cassandra.db.marshal.Int32Type;
import org.apache.cassandra.db.marshal.SetType;
import org.apache.cassandra.db.marshal.UTF8Type;
import org.apache.cassandra.db.marshal.UUIDType;
import org.apache.cassandra.dht.LocalPartitioner;
import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.schema.Schema;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.tcm.membership.Location;
import org.apache.cassandra.tcm.membership.NodeAddresses;
import org.apache.cassandra.tcm.membership.NodeId;
import org.apache.cassandra.tcm.membership.NodeState;
import org.apache.cassandra.utils.FBUtilities;
import static org.apache.cassandra.db.SystemKeyspace.LEGACY_PEERS;
import static org.apache.cassandra.db.SystemKeyspace.PEERS_V2;
import static org.apache.cassandra.schema.SchemaConstants.SYSTEM_KEYSPACE_NAME;
public class PeersTable extends AbstractVirtualTable
{
public static String PEER = "peer";
public static String PEER_PORT = "peer_port";
public static String DATA_CENTER = "data_center";
public static String HOST_ID = "host_id";
public static String PREFERRED_IP = "preferred_ip";
public static String PREFERRED_PORT = "preferred_port";
public static String RACK = "rack";
public static String RELEASE_VERSION = "release_version";
public static String NATIVE_ADDRESS = "native_address";
public static String NATIVE_PORT = "native_port";
public static String SCHEMA_VERSION = "schema_version";
public static String TOKENS = "tokens";
public static String STATE = "state";
public PeersTable(String keyspace)
{
super(TableMetadata.builder(keyspace, "peers")
.comment("Peers")
.kind(TableMetadata.Kind.VIRTUAL)
.partitioner(new LocalPartitioner(InetAddressType.instance))
.addPartitionKeyColumn(PEER, InetAddressType.instance)
.addClusteringColumn(PEER_PORT, Int32Type.instance)
.addRegularColumn(DATA_CENTER, UTF8Type.instance)
.addRegularColumn(RACK, UTF8Type.instance)
.addRegularColumn(HOST_ID, UUIDType.instance)
.addRegularColumn(PREFERRED_IP, InetAddressType.instance)
.addRegularColumn(PREFERRED_PORT, Int32Type.instance)
.addRegularColumn(NATIVE_ADDRESS, InetAddressType.instance)
.addRegularColumn(NATIVE_PORT, Int32Type.instance)
.addRegularColumn(RELEASE_VERSION, UTF8Type.instance)
.addRegularColumn(SCHEMA_VERSION, UUIDType.instance)
.addRegularColumn(STATE, UTF8Type.instance)
.addRegularColumn(TOKENS, SetType.getInstance(UTF8Type.instance, false))
.build());
}
public DataSet data()
{
SimpleDataSet result = new SimpleDataSet(metadata());
ClusterMetadata metadata = ClusterMetadata.current();
for (InetAddressAndPort addr : metadata.directory.allJoinedEndpoints())
{
NodeId peer = metadata.directory.peerId(addr);
NodeAddresses addresses = metadata.directory.getNodeAddresses(peer);
result.row(addr.getAddress(), addr.getPort())
.column(DATA_CENTER, metadata.directory.location(peer).datacenter)
.column(RACK, metadata.directory.location(peer).rack)
.column(HOST_ID, peer.toUUID())
.column(PREFERRED_IP, addresses.broadcastAddress.getAddress())
.column(PREFERRED_PORT, addresses.broadcastAddress.getPort())
.column(NATIVE_ADDRESS, addresses.nativeAddress.getAddress())
.column(NATIVE_PORT, addresses.nativeAddress.getPort())
.column(RELEASE_VERSION, metadata.directory.version(peer).cassandraVersion.toString())
.column(SCHEMA_VERSION, Schema.instance.getVersion()) //TODO
.column(STATE, metadata.directory.peerState(peer).toString())
.column(TOKENS, new HashSet<>(metadata.tokenMap.tokens(peer).stream().map((token) -> token.getToken().getTokenValue().toString()).collect(Collectors.toList())));
}
return result;
}
public static void initializeLegacyPeerTables(ClusterMetadata prev, ClusterMetadata next)
{
QueryProcessor.executeInternal(String.format("TRUNCATE %s.%s", SYSTEM_KEYSPACE_NAME, PEERS_V2));
QueryProcessor.executeInternal(String.format("TRUNCATE %s.%s", SYSTEM_KEYSPACE_NAME, LEGACY_PEERS));
for (NodeId nodeId : next.directory.peerIds())
updateLegacyPeerTable(nodeId, prev, next);
}
private static String peers_v2_query = "INSERT INTO %s.%s ("
+ "peer, peer_port, "
+ "preferred_ip, preferred_port, "
+ "native_address, native_port, "
+ "data_center, rack, "
+ "host_id, "
+ "release_version, "
+ "schema_version,"
+ "tokens) " +
"VALUES " +
"(?,?,?,?,?,?,?,?,?,?,?,?)";
private static String legacy_peers_query = "INSERT INTO %s.%s ("
+ "peer, preferred_ip, rpc_address, "
+ "data_center, rack, "
+ "host_id, "
+ "release_version, "
+ "schema_version,"
+ "tokens) " +
"VALUES " +
"(?,?,?,?,?,?,?,?,?)";
private static String peers_delete_query = "DELETE FROM %s.%s WHERE peer=? and peer_port=?";
private static String legacy_peers_delete_query = "DELETE FROM %s.%s WHERE peer=?";
private static final Logger logger = LoggerFactory.getLogger(PeersTable.class);
public static void updateLegacyPeerTable(NodeId nodeId, ClusterMetadata prev, ClusterMetadata next)
{
if (nodeId.equals(next.directory.peerId(FBUtilities.getBroadcastAddressAndPort())))
return;
if (next.directory.peerState(nodeId) == null || next.directory.peerState(nodeId) == NodeState.LEFT)
{
NodeAddresses addresses = prev.directory.getNodeAddresses(nodeId);
logger.debug("Purging {} from system.peers_v2 table", addresses);
QueryProcessor.executeInternal(String.format(peers_delete_query, SYSTEM_KEYSPACE_NAME, PEERS_V2), addresses.broadcastAddress.getAddress(), addresses.broadcastAddress.getPort());
QueryProcessor.executeInternal(String.format(legacy_peers_delete_query, SYSTEM_KEYSPACE_NAME, LEGACY_PEERS), addresses.broadcastAddress.getAddress());
}
else if (NodeState.isPreJoin(next.directory.peerState(nodeId)))
{
logger.debug("{} is in pre-join state {}, not updating system.peers_v2 table", nodeId, next.directory.peerState(nodeId));
}
else
{
NodeAddresses addresses = next.directory.getNodeAddresses(nodeId);
NodeAddresses oldAddresses = prev.directory.getNodeAddresses(nodeId);
if (oldAddresses != null && !oldAddresses.equals(addresses))
{
logger.debug("Purging {} from system.peers_v2 table", oldAddresses);
QueryProcessor.executeInternal(String.format(peers_delete_query, SYSTEM_KEYSPACE_NAME, PEERS_V2), oldAddresses.broadcastAddress.getAddress(), oldAddresses.broadcastAddress.getPort());
QueryProcessor.executeInternal(String.format(legacy_peers_delete_query, SYSTEM_KEYSPACE_NAME, LEGACY_PEERS), oldAddresses.broadcastAddress.getAddress());
}
Location location = next.directory.location(nodeId);
Set<String> tokens = SystemKeyspace.tokensAsSet(next.tokenMap.tokens(nodeId));
QueryProcessor.executeInternal(String.format(peers_v2_query, SYSTEM_KEYSPACE_NAME, PEERS_V2),
addresses.broadcastAddress.getAddress(), addresses.broadcastAddress.getPort(),
addresses.broadcastAddress.getAddress(), addresses.broadcastAddress.getPort(),
addresses.nativeAddress.getAddress(), addresses.nativeAddress.getPort(),
location.datacenter, location.rack,
nodeId.toUUID(),
next.directory.version(nodeId).cassandraVersion.toString(),
next.schema.getVersion(),
tokens);
QueryProcessor.executeInternal(String.format(legacy_peers_query, SYSTEM_KEYSPACE_NAME, LEGACY_PEERS),
addresses.broadcastAddress.getAddress(), addresses.broadcastAddress.getAddress(), addresses.nativeAddress.getAddress(),
location.datacenter, location.rack,
nodeId.toUUID(),
next.directory.version(nodeId).cassandraVersion.toString(),
next.schema.getVersion(),
tokens);
}
}
}

View File

@ -52,6 +52,9 @@ public final class SystemViewsKeyspace extends VirtualKeyspace
.add(new QueriesTable(VIRTUAL_VIEWS))
.add(new LogMessagesTable(VIRTUAL_VIEWS))
.add(new SnapshotsTable(VIRTUAL_VIEWS))
.add(new PeersTable(VIRTUAL_VIEWS))
.add(new LocalTable(VIRTUAL_VIEWS))
.add(new ClusterMetadataLogTable(VIRTUAL_VIEWS))
.addAll(LocalRepairTables.getAll(VIRTUAL_VIEWS))
.addAll(CIDRFilteringMetricsTable.getAll(VIRTUAL_VIEWS))
.addAll(StorageAttachedIndexTables.getAll(VIRTUAL_VIEWS))

View File

@ -50,7 +50,7 @@ public abstract class AbstractBounds<T extends RingPosition<T>> implements Seria
public AbstractBounds(T left, T right)
{
assert left.getPartitioner() == right.getPartitioner();
assert left.getPartitioner().getClass().equals(right.getPartitioner().getClass()); // todo: is this enough?
this.left = left;
this.right = right;
}

View File

@ -17,25 +17,33 @@
*/
package org.apache.cassandra.dht;
import java.util.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.cassandra.tcm.ownership.MovementMap;
import org.apache.cassandra.utils.concurrent.Future;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.schema.Schema;
import org.apache.cassandra.db.Keyspace;
import org.apache.cassandra.dht.tokenallocator.TokenAllocation;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.gms.Gossiper;
import org.apache.cassandra.locator.AbstractReplicationStrategy;
import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.locator.TokenMetadata;
import org.apache.cassandra.service.StorageService;
import org.apache.cassandra.streaming.*;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.schema.KeyspaceMetadata;
import org.apache.cassandra.schema.Schema;
import org.apache.cassandra.streaming.StreamEvent;
import org.apache.cassandra.streaming.StreamEventHandler;
import org.apache.cassandra.streaming.StreamOperation;
import org.apache.cassandra.streaming.StreamResultFuture;
import org.apache.cassandra.streaming.StreamState;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.utils.progress.ProgressEvent;
import org.apache.cassandra.utils.progress.ProgressEventNotifierSupport;
import org.apache.cassandra.utils.progress.ProgressEventType;
@ -47,39 +55,49 @@ public class BootStrapper extends ProgressEventNotifierSupport
/* endpoint that needs to be bootstrapped */
protected final InetAddressAndPort address;
/* token of the node being bootstrapped. */
protected final Collection<Token> tokens;
protected final TokenMetadata tokenMetadata;
protected final ClusterMetadata metadata;
private final MovementMap movements;
private final MovementMap strictMovements;
public BootStrapper(InetAddressAndPort address, Collection<Token> tokens, TokenMetadata tmd)
public BootStrapper(InetAddressAndPort address,
ClusterMetadata metadata,
MovementMap movements,
MovementMap strictMovements)
{
assert address != null;
assert tokens != null && !tokens.isEmpty();
this.address = address;
this.tokens = tokens;
this.tokenMetadata = tmd;
this.metadata = metadata;
this.movements = movements;
this.strictMovements = strictMovements;
}
public Future<StreamState> bootstrap(StreamStateStore stateStore, boolean useStrictConsistency)
public Future<StreamState> bootstrap(StreamStateStore stateStore, boolean useStrictConsistency, InetAddressAndPort beingReplaced)
{
logger.trace("Beginning bootstrap process");
RangeStreamer streamer = new RangeStreamer(tokenMetadata,
tokens,
address,
RangeStreamer streamer = new RangeStreamer(metadata,
StreamOperation.BOOTSTRAP,
useStrictConsistency,
DatabaseDescriptor.getEndpointSnitch(),
stateStore,
true,
DatabaseDescriptor.getStreamingConnectionsPerHost());
final Collection<String> nonLocalStrategyKeyspaces = Schema.instance.distributedKeyspaces().names();
DatabaseDescriptor.getStreamingConnectionsPerHost(),
movements,
strictMovements);
if (beingReplaced != null)
streamer.addSourceFilter(new RangeStreamer.ExcludedSourcesFilter(Collections.singleton(beingReplaced)));
final Collection<String> nonLocalStrategyKeyspaces = Schema.instance.getNonLocalStrategyKeyspaces().names();
if (nonLocalStrategyKeyspaces.isEmpty())
logger.debug("Schema does not contain any non-local keyspaces to stream on bootstrap");
for (String keyspaceName : nonLocalStrategyKeyspaces)
{
AbstractReplicationStrategy strategy = Keyspace.open(keyspaceName).getReplicationStrategy();
streamer.addRanges(keyspaceName, strategy.getPendingAddressRanges(tokenMetadata, tokens, address));
KeyspaceMetadata ksm = metadata.schema.getKeyspaces().get(keyspaceName).get();
if (ksm.params.replication.isMeta())
continue;
streamer.addKeyspaceToFetch(keyspaceName);
}
StreamResultFuture bootstrapStreamResult = streamer.fetchAsync();
@ -153,7 +171,7 @@ public class BootStrapper extends ProgressEventNotifierSupport
* otherwise, if allocationKeyspace is specified use the token allocation algorithm to generate suitable tokens
* else choose num_tokens tokens at random
*/
public static Collection<Token> getBootstrapTokens(final TokenMetadata metadata, InetAddressAndPort address, long schemaTimeoutMillis, long ringTimeoutMillis) throws ConfigurationException
public static Collection<Token> getBootstrapTokens(final ClusterMetadata metadata, InetAddressAndPort address) throws ConfigurationException
{
String allocationKeyspace = DatabaseDescriptor.getAllocateTokensForKeyspace();
Integer allocationLocalRf = DatabaseDescriptor.getAllocateTokensForLocalRf();
@ -174,10 +192,10 @@ public class BootStrapper extends ProgressEventNotifierSupport
throw new ConfigurationException("num_tokens must be >= 1");
if (allocationKeyspace != null)
return allocateTokens(metadata, address, allocationKeyspace, numTokens, schemaTimeoutMillis, ringTimeoutMillis);
return allocateTokens(metadata, address, allocationKeyspace, numTokens);
if (allocationLocalRf != null)
return allocateTokens(metadata, address, allocationLocalRf, numTokens, schemaTimeoutMillis, ringTimeoutMillis);
return allocateTokens(metadata, address, allocationLocalRf, numTokens);
if (numTokens == 1)
logger.warn("Picking random token for a single vnode. You should probably add more vnodes and/or use the automatic token allocation mechanism.");
@ -187,32 +205,26 @@ public class BootStrapper extends ProgressEventNotifierSupport
return tokens;
}
private static Collection<Token> getSpecifiedTokens(final TokenMetadata metadata,
private static Collection<Token> getSpecifiedTokens(final ClusterMetadata metadata,
Collection<String> initialTokens)
{
logger.info("tokens manually specified as {}", initialTokens);
List<Token> tokens = new ArrayList<>(initialTokens.size());
for (String tokenString : initialTokens)
{
Token token = metadata.partitioner.getTokenFactory().fromString(tokenString);
if (metadata.getEndpoint(token) != null)
Token token = metadata.tokenMap.partitioner().getTokenFactory().fromString(tokenString);
if (metadata.tokenMap.owner(token) != null)
throw new ConfigurationException("Bootstrapping to existing token " + tokenString + " is not allowed (decommission/removenode the old node first).");
tokens.add(token);
}
return tokens;
}
static Collection<Token> allocateTokens(final TokenMetadata metadata,
static Collection<Token> allocateTokens(final ClusterMetadata metadata,
InetAddressAndPort address,
String allocationKeyspace,
int numTokens,
long schemaTimeoutMillis,
long ringTimeoutMillis)
int numTokens)
{
StorageService.instance.waitForSchema(schemaTimeoutMillis, ringTimeoutMillis);
if (!FBUtilities.getBroadcastAddressAndPort().equals(InetAddressAndPort.getLoopbackAddress()))
Gossiper.waitToSettle();
Keyspace ks = Keyspace.open(allocationKeyspace);
if (ks == null)
throw new ConfigurationException("Problem opening token allocation keyspace " + allocationKeyspace);
@ -224,33 +236,35 @@ public class BootStrapper extends ProgressEventNotifierSupport
}
static Collection<Token> allocateTokens(final TokenMetadata metadata,
static Collection<Token> allocateTokens(final ClusterMetadata metadata,
InetAddressAndPort address,
int rf,
int numTokens,
long schemaTimeoutMillis,
long ringTimeoutMillis)
int numTokens)
{
StorageService.instance.waitForSchema(schemaTimeoutMillis, ringTimeoutMillis);
if (!FBUtilities.getBroadcastAddressAndPort().equals(InetAddressAndPort.getLoopbackAddress()))
Gossiper.waitToSettle();
Collection<Token> tokens = TokenAllocation.allocateTokens(metadata, rf, address, numTokens);
BootstrapDiagnostics.tokensAllocated(address, metadata, rf, numTokens, tokens);
return tokens;
}
public static Collection<Token> getRandomTokens(TokenMetadata metadata, int numTokens)
public static Set<Token> getRandomTokens(ClusterMetadata metadata, int numTokens)
{
Set<Token> tokens = new HashSet<>(numTokens);
while (tokens.size() < numTokens)
{
Token token = metadata.partitioner.getRandomToken();
if (metadata.getEndpoint(token) == null)
Token token = metadata.tokenMap.partitioner().getRandomToken();
if (metadata.tokenMap.owner(token) == null)
tokens.add(token);
}
logger.info("Generated random tokens. tokens are {}", tokens);
return tokens;
}
public String toString()
{
return "BootStrapper{" +
"address=" + address +
", metadata=" + metadata +
'}';
}
}

View File

@ -19,12 +19,13 @@
package org.apache.cassandra.dht;
import java.util.Collection;
import com.google.common.collect.ImmutableList;
import org.apache.cassandra.dht.BootstrapEvent.BootstrapEventType;
import org.apache.cassandra.diag.DiagnosticEventService;
import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.locator.TokenMetadata;
import org.apache.cassandra.tcm.ClusterMetadata;
/**
* Utility methods for bootstrap related activities.
@ -50,38 +51,38 @@ final class BootstrapDiagnostics
ImmutableList.copyOf(initialTokens)));
}
static void useRandomTokens(InetAddressAndPort address, TokenMetadata metadata, int numTokens, Collection<Token> tokens)
static void useRandomTokens(InetAddressAndPort address, ClusterMetadata metadata, int numTokens, Collection<Token> tokens)
{
if (isEnabled(BootstrapEventType.BOOTSTRAP_USING_RANDOM_TOKENS))
service.publish(new BootstrapEvent(BootstrapEventType.BOOTSTRAP_USING_RANDOM_TOKENS,
address,
metadata.cloneOnlyTokenMap(),
metadata,
null,
null,
numTokens,
ImmutableList.copyOf(tokens)));
}
static void tokensAllocated(InetAddressAndPort address, TokenMetadata metadata,
static void tokensAllocated(InetAddressAndPort address, ClusterMetadata metadata,
String allocationKeyspace, int numTokens, Collection<Token> tokens)
{
if (isEnabled(BootstrapEventType.TOKENS_ALLOCATED))
service.publish(new BootstrapEvent(BootstrapEventType.TOKENS_ALLOCATED,
address,
metadata.cloneOnlyTokenMap(),
metadata,
allocationKeyspace,
null,
numTokens,
ImmutableList.copyOf(tokens)));
}
static void tokensAllocated(InetAddressAndPort address, TokenMetadata metadata,
static void tokensAllocated(InetAddressAndPort address, ClusterMetadata metadata,
int rf, int numTokens, Collection<Token> tokens)
{
if (isEnabled(BootstrapEventType.TOKENS_ALLOCATED))
service.publish(new BootstrapEvent(BootstrapEventType.TOKENS_ALLOCATED,
address,
metadata.cloneOnlyTokenMap(),
metadata,
null,
rf,
numTokens,

View File

@ -28,7 +28,7 @@ import com.google.common.collect.ImmutableCollection;
import org.apache.cassandra.diag.DiagnosticEvent;
import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.locator.TokenMetadata;
import org.apache.cassandra.tcm.ClusterMetadata;
/**
* DiagnosticEvent implementation for bootstrap related activities.
@ -38,7 +38,7 @@ final class BootstrapEvent extends DiagnosticEvent
private final BootstrapEventType type;
@Nullable
private final TokenMetadata tokenMetadata;
private final ClusterMetadata metadata;
private final InetAddressAndPort address;
@Nullable
private final String allocationKeyspace;
@ -47,12 +47,12 @@ final class BootstrapEvent extends DiagnosticEvent
private final Integer numTokens;
private final Collection<Token> tokens;
BootstrapEvent(BootstrapEventType type, InetAddressAndPort address, @Nullable TokenMetadata tokenMetadata,
BootstrapEvent(BootstrapEventType type, InetAddressAndPort address, @Nullable ClusterMetadata metadata,
@Nullable String allocationKeyspace, @Nullable Integer rf, int numTokens, ImmutableCollection<Token> tokens)
{
this.type = type;
this.address = address;
this.tokenMetadata = tokenMetadata;
this.metadata = metadata;
this.allocationKeyspace = allocationKeyspace;
this.rf = rf;
this.numTokens = numTokens;
@ -76,7 +76,7 @@ final class BootstrapEvent extends DiagnosticEvent
{
// be extra defensive against nulls and bugs
HashMap<String, Serializable> ret = new HashMap<>();
ret.put("tokenMetadata", String.valueOf(tokenMetadata));
ret.put("metadata", metadata.legacyToString());
ret.put("allocationKeyspace", allocationKeyspace);
ret.put("rf", rf);
ret.put("numTokens", numTokens);

View File

@ -62,7 +62,7 @@ abstract class ComparableObjectToken<C extends Comparable<C>> extends Token
public int compareTo(Token o)
{
if (o.getClass() != getClass())
throw new IllegalArgumentException("Invalid type of Token.compareTo() argument.");
throw new IllegalArgumentException(String.format("Invalid type of Token.compareTo() argument. %s != %s", o.getClass(), getClass()));
return token.compareTo(((ComparableObjectToken<C>) o).token);
}

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