|
| 1 | +# Copyright 2024-present ScyllaDB |
| 2 | +# |
| 3 | +# SPDX-License-Identifier: LicenseRef-ScyllaDB-Source-Available-1.0 |
| 4 | + |
| 5 | +# Tests for the Scylla-only "tablets" feature. |
| 6 | +# |
| 7 | +# Ideally, tablets are just an implementation detail (replacing the |
| 8 | +# old vnodes), that the DynamoDB API user would not even be aware |
| 9 | +# of. So there should be very few, if any, tests in this file. |
| 10 | +# However, temporarily - while the tablets feature is only partially |
| 11 | +# working and turned off by default (see issue #21989) - it is useful |
| 12 | +# to have here a few tests that clarify the situation and how to |
| 13 | +# override it. Most of these tests, or perhaps even this entire file, |
| 14 | +# will probably go away eventually. |
| 15 | + |
| 16 | +import pytest |
| 17 | +import boto3 |
| 18 | +from botocore.exceptions import ClientError |
| 19 | + |
| 20 | +from .util import new_test_table |
| 21 | + |
| 22 | +# All tests in this file are scylla-only |
| 23 | +@pytest.fixture(scope="function", autouse=True) |
| 24 | +def all_tests_are_scylla_only(scylla_only): |
| 25 | + pass |
| 26 | + |
| 27 | +# Utility function for checking if a given table is using tablets |
| 28 | +# or not. We rely on some knowledge of Alternator internals: |
| 29 | +# 1. For table with name X, Scylla creates a keyspace called alternator_X |
| 30 | +# 2. We can read a CQL system table using the ".scylla.alternator." prefix. |
| 31 | +def uses_tablets(dynamodb, table): |
| 32 | + info = dynamodb.Table('.scylla.alternator.system_schema.scylla_keyspaces') |
| 33 | + try: |
| 34 | + response = info.query( |
| 35 | + KeyConditions={'keyspace_name': { |
| 36 | + 'AttributeValueList': ['alternator_'+table.name], |
| 37 | + 'ComparisonOperator': 'EQ'}}) |
| 38 | + except dynamodb.meta.client.exceptions.ResourceNotFoundException: |
| 39 | + # The internal Scylla table doesn't even exist, either this isn't |
| 40 | + # Scylla or it's older Scylla and doesn't use tablets. |
| 41 | + return False |
| 42 | + if not 'Items' in response or not response['Items']: |
| 43 | + return False |
| 44 | + if 'initial_tablets' in response['Items'][0] and response['Items'][0]['initial_tablets']: |
| 45 | + return True |
| 46 | + return False |
| 47 | + |
| 48 | +# Right now, new Alternator tables are created *without* tablets. |
| 49 | +# This test should be changed if this default ever changes. |
| 50 | +def test_default_tablets(dynamodb): |
| 51 | + schema = { |
| 52 | + 'KeySchema': [ { 'AttributeName': 'p', 'KeyType': 'HASH' } ], |
| 53 | + 'AttributeDefinitions': [ { 'AttributeName': 'p', 'AttributeType': 'S' }]} |
| 54 | + with new_test_table(dynamodb, **schema) as table: |
| 55 | + # Change this assertion if Alternator's default changes! |
| 56 | + assert not uses_tablets(dynamodb, table) |
| 57 | + |
| 58 | +# Tests for the initial_tablets tag. Currently, it is considered |
| 59 | +# experimental, and named "experimental:initial_tablets", but perhaps |
| 60 | +# in the future it will graduate out of experimental status and |
| 61 | +# the prefix will be replaced by "system:". |
| 62 | +initial_tablets_tag = 'experimental:initial_tablets' |
| 63 | + |
| 64 | +# Check that a table created with a number as initial_tablets will use |
| 65 | +# tablets. Different numbers have different meanings (0 asked to use |
| 66 | +# default number, any other number overrides the default) but they |
| 67 | +# all enable tablets. |
| 68 | +def test_initial_tablets_number(dynamodb): |
| 69 | + for value in ['0', '4']: |
| 70 | + schema = { |
| 71 | + 'Tags': [{'Key': initial_tablets_tag, 'Value': value}], |
| 72 | + 'KeySchema': [ { 'AttributeName': 'p', 'KeyType': 'HASH' } ], |
| 73 | + 'AttributeDefinitions': [ { 'AttributeName': 'p', 'AttributeType': 'S' }]} |
| 74 | + with new_test_table(dynamodb, **schema) as table: |
| 75 | + assert uses_tablets(dynamodb, table) |
| 76 | + |
| 77 | +# Check that a table created with a non-number (e.g., the string "none") |
| 78 | +# as initial_tablets, will not use tablets. |
| 79 | +def test_initial_tablets_number(dynamodb): |
| 80 | + schema = { |
| 81 | + 'Tags': [{'Key': initial_tablets_tag, 'Value': 'none'}], |
| 82 | + 'KeySchema': [ { 'AttributeName': 'p', 'KeyType': 'HASH' } ], |
| 83 | + 'AttributeDefinitions': [ { 'AttributeName': 'p', 'AttributeType': 'S' }]} |
| 84 | + with new_test_table(dynamodb, **schema) as table: |
| 85 | + assert not uses_tablets(dynamodb, table) |
| 86 | + |
| 87 | +# Before Alternator TTL is supported with tablets (#16567), let's verify |
| 88 | +# that enabling TTL results in an orderly error. This test should be deleted |
| 89 | +# when #16567 is fixed. |
| 90 | +def test_ttl_enable_error_with_tablets(dynamodb): |
| 91 | + with new_test_table(dynamodb, |
| 92 | + Tags=[{'Key': initial_tablets_tag, 'Value': '4'}], |
| 93 | + KeySchema=[ { 'AttributeName': 'p', 'KeyType': 'HASH' }, ], |
| 94 | + AttributeDefinitions=[ { 'AttributeName': 'p', 'AttributeType': 'S' } ]) as table: |
| 95 | + with pytest.raises(ClientError, match='ValidationException.*tablets'): |
| 96 | + table.meta.client.update_time_to_live(TableName=table.name, |
| 97 | + TimeToLiveSpecification={'AttributeName': 'expiration', 'Enabled': True}) |
| 98 | + |
| 99 | +# Before Alternator Streams is supported with tablets (#16317), let's verify |
| 100 | +# that enabling Streams results in an orderly error. This test should be |
| 101 | +# deleted when #16317 is fixed. |
| 102 | +def test_streams_enable_error_with_tablets(dynamodb): |
| 103 | + # Test attempting to create a table already with streams |
| 104 | + with pytest.raises(ClientError, match='ValidationException.*tablets'): |
| 105 | + with new_test_table(dynamodb, |
| 106 | + Tags=[{'Key': initial_tablets_tag, 'Value': '4'}], |
| 107 | + StreamSpecification={'StreamEnabled': True, 'StreamViewType': 'KEYS_ONLY'}, |
| 108 | + KeySchema=[ { 'AttributeName': 'p', 'KeyType': 'HASH' }, ], |
| 109 | + AttributeDefinitions=[ { 'AttributeName': 'p', 'AttributeType': 'S' } ]) as table: |
| 110 | + pass |
| 111 | + # Test attempting to add a stream to an existing table |
| 112 | + with new_test_table(dynamodb, |
| 113 | + Tags=[{'Key': initial_tablets_tag, 'Value': '4'}], |
| 114 | + KeySchema=[ { 'AttributeName': 'p', 'KeyType': 'HASH' }, ], |
| 115 | + AttributeDefinitions=[ { 'AttributeName': 'p', 'AttributeType': 'S' } ]) as table: |
| 116 | + with pytest.raises(ClientError, match='ValidationException.*tablets'): |
| 117 | + table.update(StreamSpecification={'StreamEnabled': True, 'StreamViewType': 'KEYS_ONLY'}); |
| 118 | + |
| 119 | +# Currently, LWT is not supported for tablets because of known bugs |
| 120 | +# (see #18068). We still allow creating an Alternator table with |
| 121 | +# tablets and using LWT for write isolation (always_use_lwt), |
| 122 | +# but the writes themselves will fail. When #18068 is fixed, this |
| 123 | +# test should be fixed to expect success - not failure. |
| 124 | +def test_alternator_tablets_and_lwt(dynamodb): |
| 125 | + schema = { |
| 126 | + 'Tags': [ |
| 127 | + {'Key': initial_tablets_tag, 'Value': '0'}, |
| 128 | + {'Key': 'system:write_isolation', 'Value': 'always_use_lwt'}], |
| 129 | + 'KeySchema': [ { 'AttributeName': 'p', 'KeyType': 'HASH' } ], |
| 130 | + 'AttributeDefinitions': [ { 'AttributeName': 'p', 'AttributeType': 'S' }]} |
| 131 | + with new_test_table(dynamodb, **schema) as table: |
| 132 | + assert uses_tablets(dynamodb, table) |
| 133 | + # This put_item should pass after #18068 is fixed: |
| 134 | + with pytest.raises(ClientError, match="InternalServerError"): |
| 135 | + table.put_item(Item={'p': 'hello'}) |
| 136 | + assert table.get_item(Key={'p': 'hello'})['Item'] == {'p': 'hello'} |
| 137 | + |
| 138 | +# An Alternator table created tablets and with a write isolation |
| 139 | +# mode that doesn't use LWT ("forbid_rmw") works normally, even |
| 140 | +# before #18068 is fixed. |
| 141 | +def test_alternator_tablets_without_lwt(dynamodb): |
| 142 | + schema = { |
| 143 | + 'Tags': [ |
| 144 | + {'Key': initial_tablets_tag, 'Value': '0'}, |
| 145 | + {'Key': 'system:write_isolation', 'Value': 'forbid_rmw'}], |
| 146 | + 'KeySchema': [ { 'AttributeName': 'p', 'KeyType': 'HASH' } ], |
| 147 | + 'AttributeDefinitions': [ { 'AttributeName': 'p', 'AttributeType': 'S' }]} |
| 148 | + with new_test_table(dynamodb, **schema) as table: |
| 149 | + assert uses_tablets(dynamodb, table) |
| 150 | + table.put_item(Item={'p': 'hello'}) |
| 151 | + assert table.get_item(Key={'p': 'hello'})['Item'] == {'p': 'hello'} |
0 commit comments