Files
scylladb/test/cqlpy/test_guardrail_replication_strategy.py
2025-04-11 14:55:04 +02:00

157 lines
10 KiB
Python

import pytest
from contextlib import ExitStack
from .util import unique_name, config_value_context, new_test_keyspace
from cassandra.protocol import ConfigurationException
# Tests for the replication_strategy_{warn,fail}_list guardrail. Because
# this feature does not exist in Cassandra , *all* tests in this file are
# Scylla-only. Let's mark them all scylla_only with an autouse fixture:
@pytest.fixture(scope="function", autouse=True)
def all_tests_are_scylla_only(scylla_only):
pass
def create_ks_and_assert_warnings_and_errors(cql, ks_opts, warnings_count=None, expected_warning=None, unexpected_warning=None, fails_count=0):
if fails_count:
with pytest.raises(ConfigurationException):
with new_test_keyspace(cql, ks_opts):
pass
else:
keyspace = unique_name()
response_future = cql.execute_async("CREATE KEYSPACE " + keyspace + " " + ks_opts)
response_future.result()
cql.execute("DROP KEYSPACE " + keyspace)
if warnings_count is not None:
if warnings_count:
assert response_future.warnings is not None and len(response_future.warnings) == warnings_count
else:
assert response_future.warnings is None
if expected_warning is not None:
assert response_future.warnings is not None and expected_warning in "\n".join(response_future.warnings)
if unexpected_warning is not None and response_future.warnings is not None:
assert not unexpected_warning in "\n".join(response_future.warnings)
def test_given_default_config_when_creating_ks_should_only_produce_warning_for_simple_strategy(cql, this_dc):
ks_opts = " WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 3 }"
create_ks_and_assert_warnings_and_errors(cql, ks_opts, expected_warning='replication_strategy_warn_list', fails_count=0)
for key, value in {'NetworkTopologyStrategy': this_dc, 'EverywhereStrategy': 'replication_factor',
'LocalStrategy': 'replication_factor'}.items():
create_ks_and_assert_warnings_and_errors(cql, f" WITH REPLICATION = {{ 'class' : '{key}', '{value}' : 1 }}",
unexpected_warning='replication_strategy_warn_list', fails_count=0)
def test_given_cleared_guardrails_when_creating_ks_should_not_get_warning_nor_error(cql, this_dc):
with ExitStack() as config_modifications:
config_modifications.enter_context(config_value_context(cql, 'replication_strategy_warn_list', '[]'))
config_modifications.enter_context(config_value_context(cql, 'replication_strategy_fail_list', '[]'))
for key, value in {'SimpleStrategy': 'replication_factor', 'NetworkTopologyStrategy': this_dc,
'EverywhereStrategy': 'replication_factor', 'LocalStrategy': 'replication_factor'}.items():
create_ks_and_assert_warnings_and_errors(cql, f" WITH REPLICATION = {{ 'class' : '{key}', '{value}' : 1 }}",
unexpected_warning='replication_strategy_warn_list', fails_count=0)
def test_given_non_empty_warn_list_when_creating_ks_should_only_warn_when_listed_strategy_used(cql, this_dc):
with ExitStack() as config_modifications:
config_modifications.enter_context(config_value_context(cql, 'replication_strategy_warn_list',
'SimpleStrategy,LocalStrategy,NetworkTopologyStrategy,EverywhereStrategy'))
for key, value in {'SimpleStrategy': 'replication_factor', 'NetworkTopologyStrategy': this_dc,
'EverywhereStrategy': 'replication_factor', 'LocalStrategy': 'replication_factor'}.items():
create_ks_and_assert_warnings_and_errors(cql, f" WITH REPLICATION = {{ 'class' : '{key}', '{value}' : 1 }}",
expected_warning='replication_strategy_warn_list', fails_count=0)
def test_given_non_empty_warn_and_fail_lists_when_creating_ks_should_fail_query_when_listed_strategy_used(cql, this_dc):
with ExitStack() as config_modifications:
config_modifications.enter_context(
config_value_context(cql, 'replication_strategy_warn_list', 'SimpleStrategy,EverywhereStrategy'))
config_modifications.enter_context(config_value_context(cql, 'replication_strategy_fail_list',
'SimpleStrategy,LocalStrategy,'
'NetworkTopologyStrategy,EverywhereStrategy'))
for key, value in {'SimpleStrategy': 'replication_factor', 'NetworkTopologyStrategy': this_dc,
'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, f" WITH REPLICATION = {{ 'class' : '{key}', '{value}' : 1 }}",
warnings_count=0, fails_count=1)
def test_given_already_existing_ks_when_altering_ks_should_validate_against_discouraged_strategies(cql, this_dc, has_tablets):
with ExitStack() as config_modifications:
# place 1 strategy on warn list, 1 strategy on fail list and leave remaining strategies unspecified,
# i.e. let them be allowed
config_modifications.enter_context(
config_value_context(cql, 'replication_strategy_warn_list', 'SimpleStrategy'))
config_modifications.enter_context(
config_value_context(cql, 'replication_strategy_fail_list', 'EverywhereStrategy'))
if has_tablets:
extra_opts = " AND TABLETS = {'enabled': false}"
else:
extra_opts = ""
# create a ks with "allowed" strategy
with new_test_keyspace(cql, " WITH REPLICATION = { 'class' : 'NetworkTopologyStrategy', '" + this_dc + "' : 3 }" + extra_opts) as keyspace:
# alter this ks to use other strategy that is NOT present on any list
response_future = cql.execute_async(
"ALTER KEYSPACE " + keyspace + " WITH REPLICATION = { 'class' : 'LocalStrategy', 'replication_factor' : 3 }")
response_future.result()
assert response_future.warnings is None
# alter this ks to use strategy that is present on the warn list
response_future = cql.execute_async(
"ALTER KEYSPACE " + keyspace + " WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 3 }")
response_future.result()
assert response_future.warnings is not None and len(response_future.warnings) == 1
# alter this ks to use strategy that is present on the fail list
with pytest.raises(ConfigurationException):
cql.execute_async(
"ALTER KEYSPACE " + keyspace + " WITH REPLICATION = { 'class' : 'EverywhereStrategy', 'replication_factor' : 3 }").result()
def test_given_rf_and_strategy_guardrails_when_creating_ks_should_print_2_warnings_if_both_violated(cql):
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,
f" WITH REPLICATION = {{ 'class' : 'SimpleStrategy', 'replication_factor' : 1 }}",
warnings_count=2, fails_count=0)
def test_given_rf_and_strategy_guardrails_when_violating_fail_rf_limit_and_warn_strategy_limit_should_fail_the_query_without_warning(cql):
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, f" WITH REPLICATION = {{ 'class' : 'SimpleStrategy', 'replication_factor' : 1 }}",
warnings_count=0, fails_count=1)
def test_given_rf_and_strategy_guardrails_when_violating_fail_strategy_limit_should_fail_the_query(cql):
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, f" WITH REPLICATION = {{ 'class' : 'SimpleStrategy', 'replication_factor' : 1 }}",
warnings_count=0, fails_count=1)
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, f" WITH REPLICATION = {{ 'class' : 'SimpleStrategy', 'replication_factor' : 3 }}",
warnings_count=0, fails_count=1)
config_modifications.enter_context(config_value_context(cql, 'restrict_replication_simplestrategy', 'warn'))
create_ks_and_assert_warnings_and_errors(cql, f" WITH REPLICATION = {{ 'class' : 'SimpleStrategy', 'replication_factor' : 3 }}",
warnings_count=1, fails_count=0)
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
assert value == '["SimpleStrategy"]' # our lovely default
# try without quotes
cql.execute("UPDATE system.config SET value = '[SimpleStrategy]' WHERE name = 'replication_strategy_warn_list'")
# reproduces #
cql.execute("UPDATE system.config SET value = '[\"SimpleStrategy\"]' WHERE name = 'replication_strategy_warn_list'")