cql/statement: Create keyspace_metadata with correct initial_tablets count

In `ks_prop_defs::as_ks_metadata(...)` a default initial tablets count
is set to 0, when tablets are enabled and the replication strategy
is NetworkReplicationStrategy.

This effectively sets _uses_tablets = false in abstract_replication_strategy
for the remaining strategies when no `tablets = {...}` options are specified.
As a consequence, it is possible to create vnode-based keyspaces even
when tablets are enforced with `tablets_mode_for_new_keyspaces`.

The patch sets a default initial tablets count to zero regardless of
the chosen replication strategy. Then each of the replication strategy
validates the options and raises a configuration exception when tablets
are not supported.

All tests are altered in the following way:
+ whenever it was correct, SimpleStrategy was replaced with NetworkTopologyStrategy
+ otherwise, tablets were explicitly disabled with ` AND tablets = {'enabled': false}`

Fixes https://github.com/scylladb/scylladb/issues/25340

Closes scylladb/scylladb#25342
This commit is contained in:
Łukasz Paszkowski
2025-08-05 13:28:59 +02:00
committed by Botond Dénes
parent 69c58c6589
commit d18eb9479f
26 changed files with 132 additions and 95 deletions

View File

@@ -411,10 +411,10 @@ bool ks_prop_defs::get_durable_writes() const {
lw_shared_ptr<data_dictionary::keyspace_metadata> ks_prop_defs::as_ks_metadata(sstring ks_name, const locator::token_metadata& tm, const gms::feature_service& feat, const db::config& cfg) {
auto sc = get_replication_strategy_class().value();
// if tablets options have not been specified, but tablets are globally enabled, set the value to 0 for N.T.S. only
// if tablets options have not been specified, but tablets are globally enabled, set the value to 0. The strategy will
// validate it and throw an error if it does not support tablets.
auto enable_tablets = feat.tablets && cfg.enable_tablets_by_default();
std::optional<unsigned> default_initial_tablets = enable_tablets && locator::abstract_replication_strategy::to_qualified_class_name(sc) == "org.apache.cassandra.locator.NetworkTopologyStrategy"
? std::optional<unsigned>(0) : std::nullopt;
std::optional<unsigned> default_initial_tablets = enable_tablets ? std::optional<unsigned>(0) : std::nullopt;
auto initial_tablets = get_initial_tablets(default_initial_tablets, cfg.enforce_tablets());
bool uses_tablets = initial_tablets.has_value();
bool rack_list_enabled = utils::get_local_injector().enter("create_with_numeric") ? false : feat.rack_list_rf;

View File

@@ -62,7 +62,7 @@ async def test_delete_partition_rows_from_table_with_mv(manager: ManagerClient)
node_count = 2
await manager.servers_add(node_count, config={'error_injections_at_startup': ['view_update_limit', 'delay_before_remote_view_update']})
cql = manager.get_cql()
async with new_test_keyspace(manager, "WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}") as ks:
async with new_test_keyspace(manager, "WITH replication = {'class': 'NetworkTopologyStrategy', 'replication_factor': 1}") as ks:
await cql.run_async(f"CREATE TABLE {ks}.tab (key int, c int, PRIMARY KEY (key, c))")
await insert_with_concurrency(cql, f"{ks}.tab", 200, 100)

View File

@@ -19,9 +19,9 @@ from cassandra.query import SimpleStatement # type: ignore
@pytest.mark.skip_mode(mode='release', reason="error injections aren't enabled in release mode")
async def test_mv_fail_building(manager: ManagerClient) -> None:
node_count = 3
servers = await manager.servers_add(node_count)
servers = await manager.servers_add(node_count, auto_rack_dc="dc")
cql = manager.get_cql()
async with new_test_keyspace(manager, "WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 3}") as ks:
async with new_test_keyspace(manager, "WITH replication = {'class': 'NetworkTopologyStrategy', 'replication_factor': 3}") as ks:
await cql.run_async(f"CREATE TABLE {ks}.tab (key int, c int, PRIMARY KEY (key, c))")
# Insert initial rows for building an index
for i in range(10):

View File

@@ -40,7 +40,7 @@ async def test_cancel_mapreduce(manager: ManagerClient):
[host1] = filter(lambda host: host.address == s1.ip_addr, hosts)
host_id2 = await manager.get_host_id(s2.server_id)
async with new_test_keyspace(manager, "WITH REPLICATION = {'class': 'SimpleStrategy', 'replication_factor': 1}") as ks:
async with new_test_keyspace(manager, f"WITH REPLICATION = {{'class': 'NetworkTopologyStrategy', 'dc1': {[s2.rack]}}}") as ks:
async with new_test_table(manager, ks, "pk int PRIMARY KEY, v int") as t:
# Distribute data across the nodes.
for _ in range(250):

View File

@@ -460,7 +460,7 @@ class AuditBackendSyslog(AuditBackend):
date = datetime.datetime(2000, 1, 1, 0, 0)
node = match.group("node").split(":")[0]
statement = match.group("query").replace("\\", "")
statement = match.group("query").replace("\\", "")
source = match.group("client_ip").split(":")[0]
event_time = uuid.UUID(int=idx)
t = self.named_tuple_factory(date, node, event_time, match.group("category"), match.group("cl"), match.group("error") == "true", match.group("keyspace"), statement, source, match.group("table"), match.group("username"))
@@ -829,7 +829,7 @@ class CQLAuditTester(AuditTester):
self.execute_and_validate_new_audit_entry(
session,
"CREATE KEYSPACE ks WITH replication = { 'class':'SimpleStrategy', 'replication_factor':1} AND DURABLE_WRITES = true",
"CREATE KEYSPACE ks WITH replication = { 'class':'SimpleStrategy', 'replication_factor':1} AND tablets = {'enabled': false} AND DURABLE_WRITES = true",
category="DDL",
)
self.execute_and_validate_new_audit_entry(
@@ -853,7 +853,7 @@ class CQLAuditTester(AuditTester):
keyspaces = audit_settings["audit_keyspaces"].split(",") if "audit_keyspaces" in audit_settings else []
assert "ks2" not in keyspaces
query_sequence = [
"CREATE KEYSPACE ks2 WITH replication = { 'class':'SimpleStrategy', 'replication_factor':1} AND DURABLE_WRITES = true",
"CREATE KEYSPACE ks2 WITH replication = { 'class':'SimpleStrategy', 'replication_factor':1} AND tablets = {'enabled': false} AND DURABLE_WRITES = true",
'USE "ks2"',
"ALTER KEYSPACE ks2 WITH replication = { 'class' : 'NetworkTopologyStrategy', 'dc1' : 1 } AND DURABLE_WRITES = false",
"DROP KEYSPACE ks2",
@@ -968,7 +968,7 @@ class CQLAuditTester(AuditTester):
keyspaces = audit_settings["audit_keyspaces"].split(",") if "audit_keyspaces" in audit_settings else []
assert "ks2" not in keyspaces
query_sequence = [
"CREATE KEYSPACE ks2 WITH replication = { 'class':'SimpleStrategy', 'replication_factor':1} AND DURABLE_WRITES = true",
"CREATE KEYSPACE ks2 WITH replication = { 'class':'SimpleStrategy', 'replication_factor':1} AND tablets = {'enabled': false} AND DURABLE_WRITES = true",
f"CREATE TABLE ks2.{first_table} (k int PRIMARY KEY, v1 int)",
f"ALTER TABLE ks2.{first_table} ADD v2 int",
f"INSERT INTO ks2.{first_table} (k, v1, v2) VALUES (1, 1, 1)",
@@ -1009,7 +1009,7 @@ class CQLAuditTester(AuditTester):
session = await self.prepare(create_keyspace=False, audit_settings=audit_settings)
session.execute("CREATE KEYSPACE ks WITH replication = { 'class':'SimpleStrategy', 'replication_factor':1} AND DURABLE_WRITES = true")
session.execute("CREATE KEYSPACE ks WITH replication = { 'class':'SimpleStrategy', 'replication_factor':1} AND tablets = {'enabled': false} AND DURABLE_WRITES = true")
session.execute("USE ks")

View File

@@ -53,8 +53,8 @@ async def test_pinned_cl_segment_doesnt_resurrect_data(manager: ManagerClient):
def get_cl_segments():
return {os.path.basename(s) for s in glob.glob(os.path.join(cl_path, "CommitLog-*"))}
async with new_test_keyspace(manager, "with replication = {'class': 'SimpleStrategy', 'replication_factor': 1}") as ks1, \
new_test_keyspace(manager, "with replication = {'class': 'SimpleStrategy', 'replication_factor': 1}") as ks2:
async with new_test_keyspace(manager, "with replication = {'class': 'NetworkTopologyStrategy', 'replication_factor': 1}") as ks1, \
new_test_keyspace(manager, "with replication = {'class': 'NetworkTopologyStrategy', 'replication_factor': 1}") as ks2:
tbl1 = f"{ks1}.tbl1"
tbl2 = f"{ks2}.tbl2"
await cql.run_async(f"create table {tbl1} (pk int, ck int, primary key(pk, ck))")

View File

@@ -19,6 +19,7 @@ from test.pylib.tablets import get_tablet_replicas
from test.pylib.scylla_cluster import ReplaceConfig
from test.pylib.util import gather_safely, wait_for
from test.cqlpy import nodetool
from test.cluster.util import get_topology_coordinator, find_server_by_host_id, keyspace_has_tablets, new_test_keyspace, new_test_table
@@ -97,11 +98,11 @@ async def test_limited_concurrency_of_writes(manager: ManagerClient):
"""
node1 = await manager.server_add(config={
"error_injections_at_startup": ["decrease_max_size_of_hints_in_progress"]
})
node2 = await manager.server_add()
}, property_file = {"dc":"dc1", "rack":"rack1"})
node2 = await manager.server_add(property_file = {"dc":"dc1", "rack":"rack2"})
cql = await manager.get_cql_exclusive(node1)
async with new_test_keyspace(manager, "WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 2}") as ks:
async with new_test_keyspace(manager, "WITH replication = {'class': 'NetworkTopologyStrategy', 'replication_factor': 2}") as ks:
table = f"{ks}.t"
await cql.run_async(f"CREATE TABLE {table} (pk int primary key, v int)")
@@ -128,10 +129,10 @@ async def test_sync_point(manager: ManagerClient):
live nodes.
"""
node_count = 3
[node1, node2, node3] = await manager.servers_add(node_count)
[node1, node2, node3] = await manager.servers_add(node_count, auto_rack_dc="dc1")
cql = manager.get_cql()
async with new_test_keyspace(manager, "WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 3}") as ks:
async with new_test_keyspace(manager, "WITH replication = {'class': 'NetworkTopologyStrategy', 'replication_factor': 3}") as ks:
table = f"{ks}.t"
await cql.run_async(f"CREATE TABLE {table} (pk int primary key, v int)")
@@ -297,12 +298,13 @@ async def test_draining_hints(manager: ManagerClient):
This test verifies that all hints are drained when a node is being decommissioned.
"""
s1, s2, _ = await manager.servers_add(3)
s1, s2, s3 = await manager.servers_add(3, auto_rack_dc="dc")
cql = manager.get_cql()
await manager.api.set_logger_level(s1.ip_addr, "hints_manager", "trace")
await cql.run_async("CREATE KEYSPACE ks WITH REPLICATION = {'class': 'SimpleStrategy', 'replication_factor': 3}")
await cql.run_async("CREATE KEYSPACE ks WITH REPLICATION = {'class': 'NetworkTopologyStrategy', 'replication_factor': 3}")
await cql.run_async("CREATE TABLE ks.t (pk int PRIMARY KEY, v int)")
await manager.server_stop_gracefully(s2.server_id)
@@ -314,6 +316,7 @@ async def test_draining_hints(manager: ManagerClient):
sync_point = await create_sync_point(manager.api.client, s1.ip_addr)
await manager.server_start(s2.server_id)
await cql.run_async(f"ALTER KEYSPACE ks WITH REPLICATION = {{'class': 'NetworkTopologyStrategy', 'dc': {[s2.rack, s3.rack]}}}")
async with asyncio.TaskGroup() as tg:
_ = tg.create_task(manager.decommission_node(s1.server_id, timeout=60))
_ = tg.create_task(await_sync_point(manager.api.client, s1.ip_addr, sync_point, 60))
@@ -325,15 +328,14 @@ async def test_canceling_hint_draining(manager: ManagerClient):
This test verifies that draining hints is canceled as soon as we issue a shutdown,
but it's resumed after starting the node again.
"""
s1, s2, s3 = await manager.servers_add(3, auto_rack_dc="dc")
s1, s2, _ = await manager.servers_add(3)
cql = manager.get_cql()
host_id2 = await manager.get_host_id(s2.server_id)
await manager.api.set_logger_level(s1.ip_addr, "hints_manager", "trace")
await cql.run_async("CREATE KEYSPACE ks WITH REPLICATION = {'class': 'SimpleStrategy', 'replication_factor': 3}")
await cql.run_async("CREATE KEYSPACE ks WITH REPLICATION = {'class': 'NetworkTopologyStrategy', 'replication_factor': 3}")
await cql.run_async("CREATE TABLE ks.t (pk int PRIMARY KEY, v int)")
await manager.server_stop_gracefully(s2.server_id)
@@ -345,6 +347,9 @@ async def test_canceling_hint_draining(manager: ManagerClient):
sync_point = await create_sync_point(manager.api.client, s1.ip_addr)
await manager.api.enable_injection(s1.ip_addr, "hinted_handoff_pause_hint_replay", False, {})
nodetool.excludenode(cql, host_id2)
await cql.run_async(f"ALTER KEYSPACE ks WITH REPLICATION = {{'class': 'NetworkTopologyStrategy', 'dc': {[s1.rack, s3.rack]}}}")
await manager.remove_node(s1.server_id, s2.server_id)
await manager.server_stop_gracefully(s1.server_id)

View File

@@ -23,7 +23,7 @@ async def test_broken_bootstrap(manager: ManagerClient):
server_a = await manager.server_add()
server_b = await manager.server_add(start=False)
async with new_test_keyspace(manager, "WITH REPLICATION = {'class': 'SimpleStrategy', 'replication_factor': 1}") as ks:
async with new_test_keyspace(manager, "WITH REPLICATION = {'class': 'NetworkTopologyStrategy', 'replication_factor': 1}") as ks:
table = f"{ks}.test"
await manager.cql.run_async(f"CREATE TABLE {table} (a int PRIMARY KEY, b int)")
for i in range(100):

View File

@@ -21,7 +21,7 @@ async def test_cas_semaphore(manager):
host = await wait_for_cql_and_get_hosts(manager.cql, {servers[0]}, time.time() + 60)
async with new_test_keyspace(manager, "WITH REPLICATION = {'class': 'SimpleStrategy', 'replication_factor': 1}") as ks:
async with new_test_keyspace(manager, "WITH REPLICATION = {'class': 'NetworkTopologyStrategy', 'replication_factor': 1}") as ks:
table = f"{ks}.test"
await manager.cql.run_async(f"CREATE TABLE {table} (a int PRIMARY KEY, b int)")

View File

@@ -28,7 +28,7 @@ async def test_banned_node_notification(manager: ManagerClient) -> None:
config = {
'failure_detector_timeout_in_ms': 2000
}
srvs = await manager.servers_add(3, config=config)
srvs = await manager.servers_add(3, config=config, auto_rack_dc="dc")
cql = manager.get_cql()
# Pause one of the servers so other nodes mark it as dead and we can remove it.

View File

@@ -21,14 +21,14 @@ async def test_node_shutdown_waits_for_pending_requests(manager: ManagerClient)
"""Reproducer for #16382"""
logger.info('start two nodes')
servers = await manager.servers_add(servers_num=2)
servers = await manager.servers_add(servers_num=2, auto_rack_dc="dc")
cql = manager.get_cql()
logger.info(f'wait for host for the node {servers[0]}, servers {servers}')
h0 = (await wait_for_cql_and_get_hosts(cql, [servers[0]], time.time() + 60))[0]
logger.info('create keyspace and table')
async with new_test_keyspace(manager, "with replication = {'class': 'SimpleStrategy', 'replication_factor': 2}") as ks:
async with new_test_keyspace(manager, "with replication = {'class': 'NetworkTopologyStrategy', 'replication_factor': 2}") as ks:
await cql.run_async(f'create table {ks}.test_table (pk int primary key)')
logger.info('insert test row into the table')

View File

@@ -45,7 +45,7 @@ async def test_query_rebounce(manager: ManagerClient):
servers = await manager.running_servers()
cql = manager.get_cql()
async with new_test_keyspace(manager, "with replication = {'class': 'SimpleStrategy', 'replication_factor': 1} and tablets = {'enabled': false};") as ks:
async with new_test_keyspace(manager, "with replication = {'class': 'NetworkTopologyStrategy', 'replication_factor': 1};") as ks:
await cql.run_async(f"create table {ks}.lwt (a int, b int, primary key(a));")
await cql.run_async(f"insert into {ks}.lwt (a,b ) values (1, 10);")

View File

@@ -196,13 +196,13 @@ async def test_cannot_run_operations(manager: ManagerClient, raft_op_timeout: in
'value': '500'
}
]
})]
}, property_file={"dc": "dc1", "rack": "rack1"})]
logger.info("starting second and third nodes (followers)")
servers += await manager.servers_add(servers_num=2)
servers += await manager.servers_add(servers_num=2, property_file={"dc": "dc1", "rack": "rack2"})
logger.info('create keyspace and table')
ks = await create_new_test_keyspace(manager.get_cql(), "with replication = {'class': 'SimpleStrategy', 'replication_factor': 2}")
ks = await create_new_test_keyspace(manager.get_cql(), "with replication = {'class': 'NetworkTopologyStrategy', 'replication_factor': 2}")
await manager.get_cql().run_async(f'create table {ks}.test_table (pk int primary key)')
logger.info("stopping the second and third nodes")

View File

@@ -49,7 +49,7 @@ async def test_raft_snapshot_truncation(manager: ManagerClient):
logger.info(f"Log size on {s1}: {log_size}")
assert (log_size > 0)
ks = await create_new_test_keyspace(cql, "with replication = {'class': 'SimpleStrategy', 'replication_factor': 1}")
ks = await create_new_test_keyspace(cql, "with replication = {'class': 'NetworkTopologyStrategy', 'replication_factor': 1}")
log_size = await get_raft_log_size(cql, h1)
logger.info(f"After add keyspace Log size on {s1}: {log_size}")
@@ -80,11 +80,11 @@ async def test_raft_snapshot_truncation(manager: ManagerClient):
await asyncio.gather(*errs)
original_snap_id = await get_raft_snap_id(cql, h1)
# Create 3 keyspaces.
keyspaces = []
for i in range(3):
keyspaces.append(await create_new_test_keyspace(cql, "with replication = {'class': 'SimpleStrategy', 'replication_factor': 1}"))
keyspaces.append(await create_new_test_keyspace(cql, "with replication = {'class': 'NetworkTopologyStrategy', 'replication_factor': 1}"))
# Drop 2 keyspaces.
for i in range(2):

View File

@@ -25,15 +25,15 @@ logger = logging.getLogger(__name__)
async def test_hints_manager_shutdown_hang(manager: ManagerClient) -> None:
"""Reproducer for #8079"""
s1 = await manager.server_add(config={
'error_injections_at_startup': ['decrease_hints_flush_period']
})
s2 = await manager.server_add()
'error_injections_at_startup': ['decrease_hints_flush_period'],
}, property_file={"dc": "dc1", "rack": "rack1"})
s2 = await manager.server_add(property_file={"dc": "dc1", "rack": "rack2"})
await wait_for_token_ring_and_group0_consistency(manager, time.time() + 30)
cql = manager.get_cql()
logger.info("Create keyspace and table")
async with new_test_keyspace(manager, "with replication = {'class': 'SimpleStrategy', 'replication_factor': 2}") as ks:
async with new_test_keyspace(manager, "with replication = {'class': 'NetworkTopologyStrategy', 'replication_factor': 2}") as ks:
await cql.run_async(f"create table {ks}.t (pk int primary key)")
logger.info(f"Stop {s2}")

View File

@@ -128,7 +128,7 @@ async def test_default_compression_on_upgrade(manager: ManagerClient, scylla_202
logger.info("Creating a test keyspace")
cql = manager.get_cql()
await cql.run_async("CREATE KEYSPACE test_ks WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}")
await cql.run_async("CREATE KEYSPACE test_ks WITH replication = {'class': 'NetworkTopologyStrategy', 'replication_factor': 1}")
await create_table_and_check_compression(cql, "test_ks", "table_before_upgrade", "org.apache.cassandra.io.compress.LZ4Compressor", "before upgrade")
@@ -252,7 +252,7 @@ async def test_cql_base_tables_respect_compression_config(manager: ManagerClient
await wait_for_cql_and_get_hosts(cql, [server], time.time() + 60)
ks = f"cql_aux_test_{int(time.time())}"
await cql.run_async(f"CREATE KEYSPACE {ks} WITH replication = {{'class': 'SimpleStrategy', 'replication_factor': 1}}")
await cql.run_async(f"CREATE KEYSPACE {ks} WITH replication = {{'class': 'NetworkTopologyStrategy', 'replication_factor': 1}}")
await cql.run_async(f"CREATE TABLE {ks}.base (pk int PRIMARY KEY, v int)")
try:
@@ -294,7 +294,7 @@ async def test_cql_aux_tables_respect_compression_config(manager: ManagerClient)
await wait_for_cql_and_get_hosts(cql, [server], time.time() + 60)
ks = f"cql_aux_test_{int(time.time())}"
await cql.run_async(f"CREATE KEYSPACE {ks} WITH replication = {{'class': 'SimpleStrategy', 'replication_factor': 1}}")
await cql.run_async(f"CREATE KEYSPACE {ks} WITH replication = {{'class': 'NetworkTopologyStrategy', 'replication_factor': 1}}")
await cql.run_async(f"CREATE TABLE {ks}.base (pk int PRIMARY KEY, v int) WITH compression = {{ 'sstable_compression': 'DeflateCompressor' }}")
await cql.run_async(f"CREATE MATERIALIZED VIEW {ks}.mv AS SELECT * FROM {ks}.base WHERE pk IS NOT NULL AND v IS NOT NULL PRIMARY KEY (v, pk)")

View File

@@ -890,46 +890,58 @@ async def test_read_of_pending_replica_during_migration(manager: ManagerClient,
# This test checks that --enable-tablets option and the TABLETS parameters of the CQL CREATE KEYSPACE
# statemement are mutually correct from the "the least surprising behavior" concept. See comments inside
# the test code for more details.
@pytest.mark.parametrize("with_tablets", [True, False])
# statement are mutually correct.
@pytest.mark.parametrize("tablets_mode_for_new_keyspaces", ["enabled", "disabled", "enforced"])
@pytest.mark.parametrize("cql_tablets_params", ["enabled", "disabled", None])
@pytest.mark.parametrize("replication_strategy", ["NetworkTopologyStrategy", "SimpleStrategy", "EverywhereStrategy", "LocalStrategy"])
@pytest.mark.asyncio
async def test_keyspace_creation_cql_vs_config_sanity(manager: ManagerClient, with_tablets, replication_strategy):
cfg = {'tablets_mode_for_new_keyspaces': 'enabled' if with_tablets else 'disabled'}
async def test_keyspace_creation_cql_vs_config_sanity(manager: ManagerClient, tablets_mode_for_new_keyspaces, cql_tablets_params, replication_strategy):
cfg = {'tablets_mode_for_new_keyspaces': tablets_mode_for_new_keyspaces}
server = await manager.server_add(config=cfg)
cql = manager.get_cql()
# Tablets are only possible when the replication strategy is NetworkTopology
tablets_possible = (replication_strategy == 'NetworkTopologyStrategy')
tablets_enabled_by_default = tablets_possible and with_tablets
"""
tablets_mode_for_new_keyspaces | enabled | disabled | enforced |
|--------------------|--------------------|--------------------|
AND tablets = {} | enabled | disabled | enabled | disabled | enabled | disabled |
| None | | | None | None | |
-------------------------------|---------|----------|---------|----------|--------------------|
"NetworkTopologyStrategy" | OK | OK | OK | OK | OK | Error |
"SimpleStrategy" | Error | OK | Error | OK | Error | Error |
"EverywhereStrategy" | Error | OK | Error | OK | Error | Error |
"LocalStrategy" | Error | OK | Error | OK | Error | Error |
"""
no_misconfiguration = (
(replication_strategy == "NetworkTopologyStrategy" and (tablets_mode_for_new_keyspaces != "enforced" or cql_tablets_params != "disabled"))
or (tablets_mode_for_new_keyspaces == "enabled" and cql_tablets_params == "disabled")
or (tablets_mode_for_new_keyspaces == "disabled" and cql_tablets_params != "enabled")
)
expect_tablets = (replication_strategy == "NetworkTopologyStrategy") and (
(tablets_mode_for_new_keyspaces == "enforced" and cql_tablets_params != "disabled")
or (tablets_mode_for_new_keyspaces == "enabled" and cql_tablets_params != "disabled")
or (tablets_mode_for_new_keyspaces == "disabled" and cql_tablets_params == "enabled")
)
# First, check if a kesypace is able to be created with default CQL statement that
# doesn't contain tablets parameters. When possible, tablets should be activated
async with new_test_keyspace(manager, f"WITH replication = {{'class': '{replication_strategy}', 'replication_factor': 1}}") as ks:
res = cql.execute(f"SELECT initial_tablets FROM system_schema.scylla_keyspaces WHERE keyspace_name = '{ks}'").one()
if tablets_enabled_by_default:
assert res.initial_tablets == 0
else:
assert res is None
# Next, check that explicit CQL request for enabling tablets can only be satisfied when
# tablets are possible. Tablets must be activated in this case
if tablets_possible:
if no_misconfiguration:
expectation = does_not_raise()
else:
expectation = pytest.raises(ConfigurationException)
with expectation:
ks = await create_new_test_keyspace(cql, f"WITH replication = {{'class': '{replication_strategy}', 'replication_factor': 1}} AND TABLETS = {{'enabled': true}}")
res = cql.execute(f"SELECT initial_tablets FROM system_schema.scylla_keyspaces WHERE keyspace_name = '{ks}'").one()
assert res.initial_tablets == 0
tablets_opt = ""
if cql_tablets_params == "enabled":
tablets_opt = f"AND TABLETS = {{'enabled': true }}"
if cql_tablets_params == "disabled":
tablets_opt = f"AND TABLETS = {{'enabled': false }}"
ks = await create_new_test_keyspace(cql, f"WITH replication = {{'class': '{replication_strategy}', 'replication_factor': 1}} {tablets_opt}")
res = cql.execute(f"SELECT initial_tablets FROM system_schema.scylla_keyspaces WHERE keyspace_name = '{ks}'")
if expect_tablets:
assert res.one().initial_tablets == 0
else:
assert not res
await cql.run_async(f"drop keyspace {ks}")
# Finally, check that explicitly disabling tablets in CQL results in vnode-based keyspace
# whenever tablets are enabled or not in config
async with new_test_keyspace(manager, f"WITH replication = {{'class': '{replication_strategy}', 'replication_factor': 1}} AND TABLETS = {{'enabled': false}}") as ks:
res = cql.execute(f"SELECT initial_tablets FROM system_schema.scylla_keyspaces WHERE keyspace_name = '{ks}'").one()
assert res is None
@pytest.mark.asyncio
@pytest.mark.skip_mode(mode='release', reason='error injections are not supported in release mode')

View File

@@ -296,14 +296,14 @@ def testKeyspace(cql):
n = unique_name()
assertInvalidThrow(cql, n, SyntaxException, "CREATE KEYSPACE %s testXYZ ")
execute(cql, n, "CREATE KEYSPACE %s WITH replication = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 }")
execute(cql, n, "CREATE KEYSPACE %s WITH replication = { 'class' : 'NetworkTopologyStrategy', 'replication_factor' : 1 }")
execute(cql, n, "DROP KEYSPACE %s")
# The original Cassandra test checked that a 53-character keyspace name doesn't work
# but Scylla increased the 48-character limit to 192 characters, so this test was modified
# to check length of 500, to produce the same outcome on Scylla and Cassandra.
too_long_keyspace_name = "k" * 500
assertInvalid(cql, "",
f"CREATE KEYSPACE {too_long_keyspace_name} WITH replication = {{ 'class' : 'SimpleStrategy', 'replication_factor' : 1 }}")
assertInvalid(cql, "",
f"CREATE KEYSPACE {too_long_keyspace_name} WITH replication = {{ 'class' : 'NetworkTopologyStrategy', 'replication_factor' : 1 }}")
# FIXME: Cassandra throws InvalidRequest here, but Scylla uses
# ConfigurationException. We shouldn't have done that... But I consider
@@ -311,7 +311,7 @@ def testKeyspace(cql):
# Maybe we should reconsider, and not allow ConfigurationException...
assertInvalidThrow(cql, "", (InvalidRequest, ConfigurationException), "DROP KEYSPACE non_existing")
execute(cql, n, "CREATE KEYSPACE %s WITH replication = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 }")
execute(cql, n, "CREATE KEYSPACE %s WITH replication = { 'class' : 'NetworkTopologyStrategy', 'replication_factor' : 1 }")
# clean-up
execute(cql, n, "DROP KEYSPACE %s")

View File

@@ -302,13 +302,13 @@ def testCasAndListIndex(cql, test_keyspace, is_scylla):
def testDropCreateKeyspaceIfNotExists(cql, test_keyspace):
keyspace = unique_name()
# create and confirm
execute(cql, test_keyspace, "CREATE KEYSPACE IF NOT EXISTS " + keyspace + " WITH replication = { 'class':'SimpleStrategy', 'replication_factor':1} and durable_writes = true ")
execute(cql, test_keyspace, "CREATE KEYSPACE IF NOT EXISTS " + keyspace + " WITH replication = { 'class':'NetworkTopologyStrategy', 'replication_factor':1} and durable_writes = true ")
assertRows(execute(cql, test_keyspace, "select durable_writes from system_schema.keyspaces where keyspace_name = ?",
keyspace),
row(true))
# unsuccessful create since it's already there, confirm settings don't change
execute(cql, test_keyspace, "CREATE KEYSPACE IF NOT EXISTS " + keyspace + " WITH replication = {'class':'SimpleStrategy', 'replication_factor':1} and durable_writes = false ")
execute(cql, test_keyspace, "CREATE KEYSPACE IF NOT EXISTS " + keyspace + " WITH replication = {'class':'NetworkTopologyStrategy', 'replication_factor':1} and durable_writes = false ")
assertRows(execute(cql, test_keyspace, "select durable_writes from system_schema.keyspaces where keyspace_name = ?",
keyspace),

View File

@@ -190,6 +190,14 @@ def parse_keyspace_table(name, separator_chars = '.'):
m = re.match(pat, name)
return m.group('keyspace'), m.group('table')
def excludenode(cql, excluded_host_id: str):
if has_rest_api(cql):
requests.post(f'{rest_api_url(cql)}/storage_service/exclude_node/', params={'hosts': excluded_host_id})
else:
run_nodetool(cql, "excludenode", excluded_host_id)
class no_autocompaction_context:
"""Disable autocompaction for the enclosed scope, for the provided keyspace(s) or keyspace.table(s).
"""

View File

@@ -17,6 +17,7 @@ from contextlib import ExitStack
from .util import new_type, unique_name, new_test_table, new_test_keyspace, new_function, new_aggregate, \
new_cql, keyspace_has_tablets, unique_name_prefix, new_session, new_user, new_materialized_view, \
new_secondary_index
from .conftest import has_tablets
from .test_service_levels import MAX_USER_SERVICE_LEVELS
from cassandra.protocol import InvalidRequest, Unauthorized
from collections.abc import Iterable
@@ -1628,7 +1629,8 @@ def new_random_keyspace(cql):
options["replication_factor"] = random.randrange(1, 6)
options_str = ", ".join([f"'{k}': '{v}'" for (k, v) in options.items()])
extra = ""
if options["class"] == "NetworkTopologyStrategy" and options["replication_factor"] != 1:
# Cassandra does not have tablets and thus does not even support tablets syntax.
if not has_tablets or options["class"] == "SimpleStrategy" or options["replication_factor"] != 1:
extra = " and tablets = { 'enabled': false }"
write = random.choice(writes)

View File

@@ -2,6 +2,7 @@ import pytest
from contextlib import ExitStack
import re
from .util import unique_name, config_value_context, new_test_keyspace, ScyllaMetrics
from .conftest import has_tablets
from cassandra.protocol import ConfigurationException
# Tests for the replication_strategy_{warn,fail}_list guardrail. Because
@@ -37,6 +38,11 @@ def ks_opts(strategy, rf, dc=None, tablets=True):
return opts
def get_replication_strategy_ks_opts(strategy: str, rf: int, dc=None) -> str:
# If tablets syntax is not supported (e.g. in cassandra) we don't add it to the ks options
return ks_opts(strategy, rf, dc=dc, tablets=has_tablets and strategy == 'NetworkTopologyStrategy')
def create_ks_and_assert_warnings_and_errors(cql, ks_opts, metric_name=None,
warnings=[], failures=[]):
before = get_metric(cql, metric_name) if metric_name else None
@@ -63,13 +69,13 @@ def create_ks_and_assert_warnings_and_errors(cql, ks_opts, metric_name=None,
def test_given_default_config_when_creating_ks_should_only_produce_warning_for_simple_strategy(cql, this_dc):
create_ks_and_assert_warnings_and_errors(cql, ks_opts('SimpleStrategy', 3),
create_ks_and_assert_warnings_and_errors(cql, get_replication_strategy_ks_opts('SimpleStrategy', 3),
metric_name='scylla_cql_replication_strategy_warn_list_violations',
warnings=[STRATEGY_WARN_RE.format(strategy='SimpleStrategy')])
for strategy, dc in {'NetworkTopologyStrategy': this_dc, 'EverywhereStrategy': 'replication_factor',
'LocalStrategy': 'replication_factor'}.items():
create_ks_and_assert_warnings_and_errors(cql, ks_opts(strategy, 1, dc=dc),
create_ks_and_assert_warnings_and_errors(cql, get_replication_strategy_ks_opts(strategy, 1, dc=dc),
warnings=[MINIMUM_RF_WARN_RE.format(dc=re.escape(dc), rf=1, threshold=3)])
@@ -80,7 +86,7 @@ def test_given_cleared_guardrails_when_creating_ks_should_not_get_warning_nor_er
for strategy, dc in {'SimpleStrategy': 'replication_factor', 'NetworkTopologyStrategy': this_dc,
'EverywhereStrategy': 'replication_factor', 'LocalStrategy': 'replication_factor'}.items():
create_ks_and_assert_warnings_and_errors(cql, ks_opts(strategy, 1, dc=dc),
create_ks_and_assert_warnings_and_errors(cql, get_replication_strategy_ks_opts(strategy, 1, dc=dc),
warnings=[MINIMUM_RF_WARN_RE.format(dc=re.escape(dc), rf=1, threshold=3)])
@@ -90,7 +96,7 @@ def test_given_non_empty_warn_list_when_creating_ks_should_only_warn_when_listed
'SimpleStrategy,LocalStrategy,NetworkTopologyStrategy,EverywhereStrategy'))
for strategy, dc in {'SimpleStrategy': 'replication_factor', 'NetworkTopologyStrategy': this_dc,
'EverywhereStrategy': 'replication_factor', 'LocalStrategy': 'replication_factor'}.items():
create_ks_and_assert_warnings_and_errors(cql, ks_opts(strategy, 1, dc=dc),
create_ks_and_assert_warnings_and_errors(cql, get_replication_strategy_ks_opts(strategy, 1, dc=dc),
warnings=[STRATEGY_WARN_RE.format(strategy=strategy),
MINIMUM_RF_WARN_RE.format(dc=re.escape(dc), rf=1, threshold=3)])
@@ -106,11 +112,11 @@ def test_given_non_empty_warn_and_fail_lists_when_creating_ks_should_fail_query_
'EverywhereStrategy': 'replication_factor', 'LocalStrategy': 'replication_factor'}.items():
# note: even though warn list is not empty, no warnings should be generated, because failures come first -
# we don't want to issue a warning and also fail the query at the same time
create_ks_and_assert_warnings_and_errors(cql, ks_opts(strategy, 1, dc=dc),
create_ks_and_assert_warnings_and_errors(cql, get_replication_strategy_ks_opts(strategy, 1, dc=dc),
failures=[STRATEGY_FAIL_RE.format(strategy=strategy)])
# Verify metric increment and exact error message (docs/cql/guardrails.rst).
create_ks_and_assert_warnings_and_errors(cql, ks_opts('SimpleStrategy', 3),
create_ks_and_assert_warnings_and_errors(cql, get_replication_strategy_ks_opts('SimpleStrategy', 3),
metric_name='scylla_cql_replication_strategy_fail_list_violations',
failures=[STRATEGY_FAIL_RE.format(strategy='SimpleStrategy')])
@@ -149,7 +155,7 @@ def test_given_rf_and_strategy_guardrails_when_creating_ks_should_print_2_warnin
with ExitStack() as config_modifications:
config_modifications.enter_context(config_value_context(cql, 'replication_strategy_warn_list', 'SimpleStrategy'))
config_modifications.enter_context(config_value_context(cql, 'minimum_replication_factor_warn_threshold', '3'))
create_ks_and_assert_warnings_and_errors(cql, ks_opts('SimpleStrategy', 1),
create_ks_and_assert_warnings_and_errors(cql, get_replication_strategy_ks_opts('SimpleStrategy', 1),
metric_name='scylla_cql_minimum_replication_factor_warn_violations',
warnings=[MINIMUM_RF_WARN_RE.format(
dc='replication_factor', rf=1, threshold=3),
@@ -160,7 +166,7 @@ def test_given_rf_and_strategy_guardrails_when_violating_fail_rf_limit_and_warn_
with ExitStack() as config_modifications:
config_modifications.enter_context(config_value_context(cql, 'replication_strategy_warn_list', 'SimpleStrategy'))
config_modifications.enter_context(config_value_context(cql, 'minimum_replication_factor_fail_threshold', '3'))
create_ks_and_assert_warnings_and_errors(cql, ks_opts('SimpleStrategy', 1),
create_ks_and_assert_warnings_and_errors(cql, get_replication_strategy_ks_opts('SimpleStrategy', 1),
metric_name='scylla_cql_minimum_replication_factor_fail_violations',
failures=[MINIMUM_RF_FAIL_RE.format(
dc='replication_factor', rf=1, threshold=3)])
@@ -170,19 +176,20 @@ def test_given_rf_and_strategy_guardrails_when_violating_fail_strategy_limit_sho
with ExitStack() as config_modifications:
config_modifications.enter_context(config_value_context(cql, 'replication_strategy_fail_list', 'SimpleStrategy'))
config_modifications.enter_context(config_value_context(cql, 'minimum_replication_factor_fail_threshold', '3'))
create_ks_and_assert_warnings_and_errors(cql, ks_opts('SimpleStrategy', 1),
create_ks_and_assert_warnings_and_errors(cql, get_replication_strategy_ks_opts('SimpleStrategy', 1),
failures=[STRATEGY_FAIL_RE.format(strategy='SimpleStrategy')])
def test_given_restrict_replication_simplestrategy_when_it_is_set_should_emulate_old_behavior(cql):
with ExitStack() as config_modifications:
config_modifications.enter_context(config_value_context(cql, 'restrict_replication_simplestrategy', 'true'))
create_ks_and_assert_warnings_and_errors(cql, ks_opts('SimpleStrategy', 3),
create_ks_and_assert_warnings_and_errors(cql, get_replication_strategy_ks_opts('SimpleStrategy', 3),
failures=[STRATEGY_FAIL_RE.format(strategy='SimpleStrategy')])
config_modifications.enter_context(config_value_context(cql, 'restrict_replication_simplestrategy', 'warn'))
create_ks_and_assert_warnings_and_errors(cql, ks_opts('SimpleStrategy', 3),
create_ks_and_assert_warnings_and_errors(cql, get_replication_strategy_ks_opts('SimpleStrategy', 3),
warnings=[STRATEGY_WARN_RE.format(strategy='SimpleStrategy')])
def test_config_replication_strategy_warn_list_roundtrips_quotes(cql):
# Use direct SELECT/UPDATE to avoid trippy config_value_context behavior
value = cql.execute("SELECT value FROM system.config WHERE name = 'replication_strategy_warn_list'").one().value

View File

@@ -6,6 +6,7 @@
# ALTER KEYSPACE
from .util import new_test_keyspace, unique_name
from .conftest import has_tablets
import pytest
from cassandra.protocol import SyntaxException, AlreadyExists, InvalidRequest, ConfigurationException
from threading import Thread
@@ -70,8 +71,10 @@ def test_create_keyspace_if_not_exists(cql, this_dc):
# A second invocation with IF NOT EXISTS is fine:
cql.execute("CREATE KEYSPACE IF NOT EXISTS test_create_keyspace_if_not_exists WITH REPLICATION = { 'class' : 'NetworkTopologyStrategy', '" + this_dc + "' : 1 }")
# It doesn't matter if the second invocation has different parameters,
# they are ignored.
cql.execute("CREATE KEYSPACE IF NOT EXISTS test_create_keyspace_if_not_exists WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 2 }")
# they are ignored. Disable tablets with SimpleStrategy with the tablets
# syntax is supported
tablets_opts = " AND TABLETS = { 'enabled': false }" if has_tablets else ""
cql.execute("CREATE KEYSPACE IF NOT EXISTS test_create_keyspace_if_not_exists WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 2 }" + tablets_opts)
cql.execute("DROP KEYSPACE test_create_keyspace_if_not_exists")
# We treat ALTER to numeric RF of same count as no-op.

View File

@@ -38,7 +38,7 @@ def padded_name(length):
# Created to avoid passing the same replication option in every tests.
@contextmanager
def new_keyspace(cql, ks_name=unique_name()):
cql.execute(f"CREATE KEYSPACE {ks_name} WITH REPLICATION = {{'class': 'SimpleStrategy', 'replication_factor': 1}}")
cql.execute(f"CREATE KEYSPACE {ks_name} WITH REPLICATION = {{'class': 'NetworkTopologyStrategy', 'replication_factor': 1}}")
try:
yield ks_name
finally:

View File

@@ -333,7 +333,7 @@ def test_grant_revoke_udf_permissions(cql):
# and yet it's not enforced
def test_grant_revoke_alter_udf_permissions(cassandra_bug, cql):
schema = "a int primary key"
with new_test_keyspace(cql, "WITH REPLICATION = { 'class': 'SimpleStrategy', 'replication_factor': 1 }") as keyspace:
with new_test_keyspace(cql, "WITH REPLICATION = { 'class': 'NetworkTopologyStrategy', 'replication_factor': 1 }") as keyspace:
with new_test_table(cql, keyspace, schema) as table:
fun_body_lua = "(i int) CALLED ON NULL INPUT RETURNS int LANGUAGE lua AS 'return 42;'"
fun_body_java = "(i int) CALLED ON NULL INPUT RETURNS int LANGUAGE java AS 'return 42;'"
@@ -358,7 +358,7 @@ def test_grant_revoke_alter_udf_permissions(cassandra_bug, cql):
check_enforced(cql, username, permission='ALTER', resource=f'all functions in keyspace {keyspace}',
function=lambda: user_session.execute(f"CREATE OR REPLACE FUNCTION {keyspace}.{fun} {fun_body}"))
check_enforced(cql, username, permission='ALTER', resource='all functions',
function=lambda: user_session.execute(f"CREATE OR REPLACE FUNCTION {keyspace}.{fun} {fun_body}"))
function=lambda: user_session.execute(f"CREATE OR REPLACE FUNCTION {keyspace}.{fun} {fun_body}"))
check_enforced(cql, username, permission='ALTER', resource=f'FUNCTION {keyspace}.{fun}(int)',
function=lambda: user_session.execute(f"CREATE OR REPLACE FUNCTION {keyspace}.{fun} {fun_body}"))

View File

@@ -57,7 +57,7 @@ def test_alter_cannot_change_vnodes_to_tablets(cql, skip_without_tablets):
# Converting vnodes-based keyspace to tablets-based in not implemented yet
def test_alter_vnodes_ks_doesnt_enable_tablets(cql, skip_without_tablets):
ksdef = "WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1};"
ksdef = "WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1} AND tablets = {'enabled': false};"
with new_test_keyspace(cql, ksdef) as keyspace:
cql.execute(f"ALTER KEYSPACE {keyspace} WITH replication = {{'class': 'NetworkTopologyStrategy'}};")