Skip to content

Commit 08a966c

Browse files
committed
Adds test of Scylla Manager Alternator restore
This patch adds `test_alternator_backup_feature` which checks Scylla Manager's feature for backing up and restoring Alternator data. It reuses workflow of previous CQL backup test `test_restore_backup_with_task`, but: - it pre-creates Alternator tables, - it fills tables and runs post-restore check using YSCB. Besides plain table+data restore, it is ready to check additional features: - table names including special characters - requires changing table names in `test-cases/manager/manager-regression-alternator-singleDC-set-distro.yaml` and `sdcm/utils/alternator/consts.py` - restoring data with secondary indexes - requires: - updating YCSB to support index in scan command - setting `lsi = True, gsi = True` in ClusterTester::pre_create_alternator_backuped_tables - uncommenting additional stress command in `test-cases/manager/manager-regression-alternator-singleDC-set-distro.yaml` to include `gsi` and `lsi` tables. When defined in configuration, the tests passes to Scylla Manager additional command line parameters `--alternator-access-key-id` `--alternator-secret-access-key`. Fixes #11696
1 parent fc8512d commit 08a966c

File tree

7 files changed

+152
-12
lines changed

7 files changed

+152
-12
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#!groovy
2+
3+
// trick from https://github.com/jenkinsci/workflow-cps-global-lib-plugin/pull/43
4+
def lib = library identifier: 'sct@snapshot', retriever: legacySCM(scm)
5+
6+
managerPipeline(
7+
backend: 'aws',
8+
region: 'us-east-1',
9+
test_name: 'mgmt_cli_test.ManagerBackupTests.test_backup_alternator_feature',
10+
test_config: 'test-cases/manager/manager-regression-alternator-singleDC-set-distro.yaml',
11+
12+
post_behavior_db_nodes: 'destroy',
13+
post_behavior_loader_nodes: 'destroy',
14+
post_behavior_monitor_nodes: 'destroy',
15+
)

mgmt_cli_test.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -678,8 +678,10 @@ def verify_backup_success(self, mgr_cluster, backup_task, ks_names: list = None,
678678
if ks_names is None:
679679
ks_names = ['keyspace1']
680680
if tables_names is None:
681-
tables_names = ['standard1']
682-
ks_tables_map = {keyspace: tables_names for keyspace in ks_names}
681+
ks_tables_map = {keyspace: [self.alternator.get_table_name(
682+
keyspace) or 'standard1'] for keyspace in ks_names}
683+
else:
684+
ks_tables_map = {keyspace: tables_names for keyspace in ks_names}
683685
if truncate:
684686
for ks, tables in ks_tables_map.items():
685687
for table_name in tables:
@@ -829,9 +831,9 @@ def test_restore_multiple_backup_snapshots(self): # noqa: PLR0914
829831
read_thread = self.run_stress_thread(stress_cmd=stress, round_robin=False)
830832
self.verify_stress_thread(read_thread)
831833

832-
def test_restore_backup_with_task(self, ks_names: list = None):
834+
def test_restore_backup_with_task(self, ks_names: list = None, cluster_extra_params: dict = None):
833835
self.log.info('starting test_restore_backup_with_task')
834-
mgr_cluster = self.db_cluster.get_cluster_manager()
836+
mgr_cluster = self.db_cluster.get_cluster_manager(**(cluster_extra_params or {}))
835837
if not ks_names:
836838
ks_names = ['keyspace1']
837839
backup_task = mgr_cluster.create_backup_task(location_list=self.locations, keyspace_list=ks_names)
@@ -1005,6 +1007,13 @@ def test_backup_feature(self):
10051007
with self.subTest('Test Restore end of space'):
10061008
self.test_enospc_before_restore()
10071009

1010+
def test_alternator_backup_feature(self):
1011+
ks_names, alternator_credentials = self.pre_create_alternator_backuped_tables()
1012+
self.generate_load_and_wait_for_results(keyspace_name=ks_names)
1013+
with self.subTest('Test restore a backup with restore task'):
1014+
self.test_restore_backup_with_task(ks_names=ks_names, cluster_extra_params={
1015+
"alternator_credentials": alternator_credentials})
1016+
10081017
def test_no_delta_backup_at_disabled_compaction(self):
10091018
"""The purpose of test is to check that delta backup (no changes to DB between backups) takes time -> 0.
10101019

sdcm/mgmt/cli.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1043,7 +1043,7 @@ def get_cluster_hosts_with_ips(db_cluster):
10431043
return [[n, n.ip_address] for n in db_cluster.nodes]
10441044

10451045
def add_cluster(self, name, host=None, db_cluster=None, client_encrypt=None, disable_automatic_repair=True,
1046-
auth_token=None, credentials=None, force_non_ssl_session_port=False):
1046+
auth_token=None, credentials=None, force_non_ssl_session_port=False, alternator_credentials=None):
10471047
"""
10481048
:param name: cluster name
10491049
:param host: cluster node IP
@@ -1054,6 +1054,7 @@ def add_cluster(self, name, host=None, db_cluster=None, client_encrypt=None, dis
10541054
:param auth_token: a token used to authenticate requests to the Agent
10551055
:param credentials: a tuple of the username and password that are used to access the cluster.
10561056
:param force_non_ssl_session_port: force SM to always use the non-SSL port for TLS-enabled cluster CQL sessions.
1057+
:param alternator_credentials: a tuple of the alternator access key and secret key that are used to access the Alternator API.
10571058
:return: ManagerCluster
10581059
10591060
Add a cluster to manager
@@ -1091,6 +1092,10 @@ def add_cluster(self, name, host=None, db_cluster=None, client_encrypt=None, dis
10911092
username, password = credentials
10921093
cmd += f" --username {username} --password {password}"
10931094

1095+
if alternator_credentials:
1096+
access_key_id, secret_access_key = alternator_credentials
1097+
cmd += f" --alternator-access-key-id {access_key_id} --alternator-secret-access-key {secret_access_key}"
1098+
10941099
res_cluster_add = self.sctool.run(cmd, parse_table_res=False)
10951100
if not res_cluster_add or 'Cluster added' not in res_cluster_add.stderr:
10961101
raise ScyllaManagerError("Encountered an error on 'sctool cluster add' command response: {}".format(

sdcm/tester.py

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1184,13 +1184,9 @@ def pre_create_alternator_tables(self):
11841184
node = self.db_cluster.nodes[0]
11851185
if self.params.get('alternator_port'):
11861186
self.log.info("Going to create alternator tables")
1187-
if self.params.get('alternator_enforce_authorization'):
1188-
with self.db_cluster.cql_connection_patient(self.db_cluster.nodes[0]) as session:
1189-
session.execute("CREATE ROLE %s WITH PASSWORD = %s AND login = true AND superuser = true",
1190-
(self.params.get('alternator_access_key_id'),
1191-
self.params.get('alternator_secret_access_key')))
1187+
self.alternator.set_credentials(node=node)
11921188

1193-
tablets_enabled = is_tablets_feature_enabled(self.db_cluster.nodes[0])
1189+
tablets_enabled = is_tablets_feature_enabled(node)
11941190
prepare_cmd = self.params.get('prepare_write_cmd')
11951191
stress_cmd = self.params.get('stress_cmd')
11961192
is_ttl_in_workload = any('dynamodb.ttlKey' in str(cmd) for cmd in [prepare_cmd, stress_cmd])
@@ -1213,6 +1209,35 @@ def pre_create_alternator_tables(self):
12131209
self.alternator.update_table_ttl(node=node, table_name=alternator.consts.TABLE_NAME)
12141210
self.alternator.update_table_ttl(node=node, table_name=alternator.consts.NO_LWT_TABLE_NAME)
12151211

1212+
def pre_create_alternator_backuped_tables(self):
1213+
node = self.db_cluster.nodes[0]
1214+
if self.params.get('alternator_port'):
1215+
self.log.info("Going to create alternator tables")
1216+
self.alternator.set_credentials(node=node)
1217+
1218+
tablets_enabled = is_tablets_feature_enabled(node)
1219+
# prepare_cmd = self.params.get('stress_read_cmd')
1220+
# stress_cmd = self.params.get('stress_cmd')
1221+
# is_ttl_in_workload = any('dynamodb.ttlKey' in str(cmd) for cmd in [prepare_cmd, stress_cmd])
1222+
1223+
# Enable tablets with Alternator TTL requires (#16567).
1224+
# tablets_support_ttl = not SkipPerIssues(issues="scylladb/scylladb#16567", params=self.params)
1225+
1226+
# Enable tablets for Alternator with LWT requires issue: "[Epic] tablets: support LWT #18068"
1227+
tablets_support_lwt = not SkipPerIssues(issues="scylladb/scylladb#18068", params=self.params)
1228+
1229+
tablets_supported_alternator_workload = True # not is_ttl_in_workload or tablets_support_ttl
1230+
schema = self.params.get("dynamodb_primarykey_type")
1231+
self.alternator.create_table(node=node, schema=schema,
1232+
table_name=alternator.consts.BACKUPED_TABLE_NAME,
1233+
tablets_enabled=tablets_enabled and tablets_support_lwt and tablets_supported_alternator_workload,
1234+
lsi=False, gsi=False)
1235+
1236+
# if is_ttl_in_workload:
1237+
# self.alternator.update_table_ttl(node=node, table_name=alternator.consts.BACKUPED_TABLE_NAME)
1238+
return [self.alternator.get_keyspace_name(alternator.consts.BACKUPED_TABLE_NAME)], self.alternator.get_credentials(node=node)
1239+
return None, None
1240+
12161241
def get_nemesis_class(self):
12171242
"""
12181243
Get a Nemesis class from parameters.

sdcm/utils/alternator/api.py

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,21 @@ def get_dynamodb_api(self, node) -> AlternatorApi:
8181
self.alternator_apis[endpoint_url] = AlternatorApi(resource=resource, client=client)
8282
return self.alternator_apis[endpoint_url]
8383

84+
def set_credentials(self, node):
85+
if self.params.get('alternator_enforce_authorization'):
86+
with node.parent_cluster.cql_connection_patient(node) as session:
87+
session.execute("CREATE ROLE %s WITH PASSWORD = %s AND login = true AND superuser = true",
88+
(self.params.get('alternator_access_key_id'),
89+
self.params.get('alternator_secret_access_key')))
90+
91+
def get_credentials(self, node):
92+
access_key_id = self.params.get('alternator_access_key_id')
93+
if self.params.get('alternator_enforce_authorization'):
94+
return (access_key_id, self.get_salted_hash(node=node, username=access_key_id))
95+
else:
96+
access_key = self.params.get('alternator_access_key')
97+
return (access_key_id, access_key) if access_key_id and access_key else None
98+
8499
def set_write_isolation(self, node, isolation, table_name=consts.TABLE_NAME):
85100
dynamodb_api = self.get_dynamodb_api(node=node)
86101
isolation = isolation if not isinstance(isolation, enums.WriteIsolation) else isolation.value
@@ -96,10 +111,27 @@ def set_write_isolation(self, node, isolation, table_name=consts.TABLE_NAME):
96111

97112
def create_table(self, node,
98113
schema=enums.YCSBSchemaTypes.HASH_AND_RANGE, isolation=None, table_name=consts.TABLE_NAME,
99-
wait_until_table_exists=True, tablets_enabled: bool = False, **kwargs) -> Table:
114+
wait_until_table_exists=True, tablets_enabled: bool = False, lsi=False, gsi=False, **kwargs) -> Table:
100115
if isinstance(schema, enums.YCSBSchemaTypes):
101116
schema = schema.value
102117
schema = schemas.ALTERNATOR_SCHEMAS[schema]
118+
if lsi:
119+
schema['LocalSecondaryIndexes'] = [
120+
{
121+
'IndexName': "lsi",
122+
'KeySchema': schema['KeySchema'],
123+
'Projection': {'ProjectionType': 'ALL'}
124+
}
125+
]
126+
if gsi:
127+
schema['GlobalSecondaryIndexes'] = [
128+
{
129+
'IndexName': "gsi",
130+
'KeySchema': schema['KeySchema'],
131+
'Projection': {'ProjectionType': 'ALL'}
132+
}
133+
]
134+
103135
dynamodb_api = self.get_dynamodb_api(node=node)
104136
# Tablets feature is currently supported by Alternator, but disabled by default (since LWT is not supported).
105137
# It should be explicitly requested by the specified tag.
@@ -203,3 +235,13 @@ def delete_table(self, node, table_name: consts.TABLE_NAME, wait_until_table_rem
203235
LOGGER.info("The '{}' table successfully removed".format(table_name))
204236
else:
205237
LOGGER.info("Send request to removed '{}' table".format(table_name))
238+
239+
@staticmethod
240+
def get_keyspace_name(table_name: consts.TABLE_NAME) -> str:
241+
return consts.KEYSPACE_PREFIX + table_name
242+
243+
@staticmethod
244+
def get_table_name(keyspace_name: consts.KEYSPACE_PREFIX+consts.TABLE_NAME) -> str:
245+
if keyspace_name.startswith(consts.KEYSPACE_PREFIX):
246+
return keyspace_name[len(consts.KEYSPACE_PREFIX):]
247+
return None

sdcm/utils/alternator/consts.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,5 @@
22
RANGE_KEY_NAME = "c"
33
TABLE_NAME = "usertable"
44
NO_LWT_TABLE_NAME = "usertable_no_lwt"
5+
BACKUPED_TABLE_NAME = "usertable_backup" # Use [\.\-A-Z] when works
6+
KEYSPACE_PREFIX = "alternator_"
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
test_duration: 240
2+
3+
round_robin: true
4+
5+
stress_cmd:
6+
bin/ycsb -jvm-args='-Dorg.slf4j.simpleLogger.defaultLogLevel=OFF' load dynamodb -P workloads/workloadc -threads 200 -p table=usertable_backup
7+
-p requestdistribution=sequential -p insertorder=ordered -p insertstart=0
8+
9+
stress_read_cmd: [
10+
"bin/ycsb -jvm-args='-Dorg.slf4j.simpleLogger.defaultLogLevel=OFF' run dynamodb -P workloads/workloadc -threads 200 -p table=usertable_backup
11+
-p requestdistribution=sequential -p insertorder=ordered -p insertstart=0 -p dataintegrity=true",
12+
# "bin/ycsb -jvm-args='-Dorg.slf4j.simpleLogger.defaultLogLevel=OFF' run dynamodb -P workloads/workloadc -threads 200 -p table=usertable_backup:gsi
13+
# -p requestdistribution=sequential -p insertorder=ordered -p insertstart=0 -p readproportion=0 -p scanproportion=1",
14+
# "bin/ycsb -jvm-args='-Dorg.slf4j.simpleLogger.defaultLogLevel=OFF' run dynamodb -P workloads/workloadc -threads 200 -p table=usertable_backup:lsi
15+
# -p requestdistribution=sequential -p insertorder=ordered -p insertstart=0 -p readproportion=0 -p scanproportion=1"
16+
]
17+
18+
alternator_write_isolation: 'forbid'
19+
alternator_port: '8080'
20+
dynamodb_primarykey_type: HASH_AND_RANGE
21+
22+
#alternator_enforce_authorization: true
23+
#alternator_access_key_id: 'alternator'
24+
#alternator_secret_access_key: 'password'
25+
26+
instance_type_db: 'i4i.large'
27+
instance_type_loader: 'c6i.large'
28+
29+
region_name: 'us-east-1'
30+
n_db_nodes: '3'
31+
n_loaders: 1
32+
33+
client_encrypt: true
34+
35+
post_behavior_db_nodes: "destroy"
36+
post_behavior_loader_nodes: "destroy"
37+
post_behavior_monitor_nodes: "destroy"
38+
39+
user_prefix: manager-regression
40+
space_node_threshold: 6442
41+
42+
aws_instance_profile_name_db: 'qa-scylla-manager-backup-instance-profile'

0 commit comments

Comments
 (0)