def test_storage_options_nonexistent_param(cql, scylla_only): ksdef = "WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : '1' } " \ "AND STORAGE = { 'type' : 'S3', 'bucket' : '42', 'endpoint' : 'localhost', 'superfluous' : 'info' }" with pytest.raises(InvalidRequest): with new_test_keyspace(cql, ksdef): pass ksdef = "WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : '1' } " \ "AND STORAGE = { 'type' : 'LOCAL', 'superfluous' : 'info' }" with pytest.raises(InvalidRequest): with new_test_keyspace(cql, ksdef): pass
def test_storage_service_keyspaces(cql, this_dc, rest_api): with new_test_keyspace( cql, f"WITH REPLICATION = {{ 'class' : 'NetworkTopologyStrategy', '{this_dc}' : 1 }}" ) as keyspace: resp_user = rest_api.send("GET", "storage_service/keyspaces", {"type": "user"}) resp_user.raise_for_status() keyspaces_user = resp_user.json() assert keyspace in keyspaces_user assert all(not ks.startswith("system") for ks in keyspaces_user) resp_nls = rest_api.send("GET", "storage_service/keyspaces", {"type": "non_local_strategy"}) resp_nls.raise_for_status() assert keyspace in resp_nls.json() resp_all = rest_api.send("GET", "storage_service/keyspaces", {"type": "all"}) resp_all.raise_for_status() assert keyspace in resp_all.json() resp = rest_api.send("GET", "storage_service/keyspaces") resp.raise_for_status() assert keyspace in resp.json()
def test_storage_service_keyspace_scrub(cql, this_dc, rest_api): with new_test_keyspace( cql, f"WITH REPLICATION = {{ 'class' : 'NetworkTopologyStrategy', '{this_dc}' : 1 }}" ) as keyspace: with new_test_table(cql, keyspace, "a int, PRIMARY KEY (a)") as t0: with new_test_table(cql, keyspace, "a int, PRIMARY KEY (a)") as t1: test_tables = [t0.split('.')[1], t1.split('.')[1]] resp = rest_api.send( "GET", f"storage_service/keyspace_scrub/{keyspace}") resp.raise_for_status() resp = rest_api.send( "GET", f"storage_service/keyspace_scrub/{keyspace}", {"cf": f"{test_tables[1]}"}) resp.raise_for_status() resp = rest_api.send( "GET", f"storage_service/keyspace_scrub/{keyspace}", {"cf": f"{test_tables[0]},{test_tables[1]}"}) resp.raise_for_status() # non-existing table resp = rest_api.send( "POST", f"storage_service/keyspace_scrub/{keyspace}", {"cf": f"{test_tables[0]},XXX"}) assert resp.status_code == requests.codes.not_found
def test_storage_service_keyspace_bad_param(cql, this_dc, rest_api): with new_test_keyspace( cql, f"WITH REPLICATION = {{ 'class' : 'NetworkTopologyStrategy', '{this_dc}' : 1 }}" ) as keyspace: # Url must include the keyspace param. resp = rest_api.send("GET", f"storage_service/keyspace_scrub") assert resp.status_code == requests.codes.not_found # Url must include the keyspace param. # It cannot be given as an optional param resp = rest_api.send("GET", f"storage_service/keyspace_scrub", {"keyspace": "{keyspace}"}) assert resp.status_code == requests.codes.not_found # Optional param cannot use the same name as a mandatory (positional, in url) param. resp = rest_api.send("GET", f"storage_service/keyspace_scrub/{keyspace}", {"keyspace": "{keyspace}"}) assert resp.status_code == requests.codes.bad_request # Unknown parameter (See https://github.com/scylladb/scylla/pull/10090) resp = rest_api.send("GET", f"storage_service/keyspace_scrub/{keyspace}", {"foo": "bar"}) assert resp.status_code == requests.codes.bad_request
def test_storage_service_snapshot_mv_si(cql, this_dc, rest_api): resp = rest_api.send("GET", "storage_service/snapshots") resp.raise_for_status() with new_test_keyspace( cql, f"WITH REPLICATION = {{ 'class' : 'NetworkTopologyStrategy', '{this_dc}' : 1 }}" ) as keyspace: schema = 'p int, v text, primary key (p)' with new_test_table(cql, keyspace, schema) as table: with new_materialized_view( cql, table, '*', 'v, p', 'v is not null and p is not null') as mv: try: with new_test_snapshot(rest_api, keyspace, mv.split('.')[1]) as snap: pytest.fail( f"Snapshot of materialized view {mv} should have failed" ) except requests.HTTPError: pass with new_secondary_index(cql, table, 'v') as si: try: with new_test_snapshot(rest_api, keyspace, si.split('.')[1]) as snap: pytest.fail( f"Snapshot of secondary index {si} should have failed" ) except requests.HTTPError: pass
def test_alter_keyspace(cql): with new_test_keyspace( cql, "WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 }" ) as keyspace: cql.execute( f"ALTER KEYSPACE {keyspace} WITH REPLICATION = {{ 'class' : 'SimpleStrategy', 'replication_factor' : 3 }} AND DURABLE_WRITES = false" )
def test_alter_keyspace(cql, this_dc): with new_test_keyspace( cql, "WITH REPLICATION = { 'class' : 'NetworkTopologyStrategy', '" + this_dc + "' : 1 }") as keyspace: cql.execute( f"ALTER KEYSPACE {keyspace} WITH REPLICATION = {{ 'class' : 'NetworkTopologyStrategy', '{this_dc}' : 3 }} AND DURABLE_WRITES = false" )
def test_storage_options_alter_type(cql, scylla_only): ksdef = "WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : '1' } " \ "AND STORAGE = { 'type' : 'LOCAL' }" with new_test_keyspace(cql, ksdef) as keyspace: # It's not fine to change the storage type ksdef_local = "WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : '1' } " \ "AND STORAGE = { 'type' : 'S3', 'bucket' : '/b1', 'endpoint': 'localhost'}" with pytest.raises(InvalidRequest): res = cql.execute(f"ALTER KEYSPACE {keyspace} {ksdef_local}")
def test_alter_keyspace_nonexistent_dc(cql): with new_test_keyspace( cql, "WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 }" ) as keyspace: with pytest.raises(ConfigurationException): cql.execute( f"ALTER KEYSPACE {keyspace} WITH replication = {{ 'class' : 'NetworkTopologyStrategy', 'nonexistentdc' : 1 }}" )
def test_alter_keyspace_invalid(cql): with new_test_keyspace(cql, "WITH REPLICATION = { 'class' : 'NetworkTopologyStrategy', 'replication_factor' : 1 }") as keyspace: with pytest.raises(ConfigurationException): cql.execute(f"ALTER KEYSPACE {keyspace} WITH REPLICATION = {{ 'class' : 'NoSuchStrategy' }}") # SimpleStrategy, if not outright forbidden, requires a # replication_factor option. with pytest.raises(ConfigurationException): cql.execute(f"ALTER KEYSPACE {keyspace} WITH REPLICATION = {{ 'class' : 'SimpleStrategy' }}") with pytest.raises(ConfigurationException): cql.execute(f"ALTER KEYSPACE {keyspace} WITH REPLICATION = {{ 'class' : 'NetworkTopologyStrategy', 'replication_factor' : 'foo' }}")
def test_concurrent_create_and_drop_keyspace(cql, this_dc): ksdef = "WITH REPLICATION = { 'class' : 'NetworkTopologyStrategy', '" + this_dc + "' : 1 }" cfdef = "(a int PRIMARY KEY)" with new_test_keyspace(cql, ksdef) as keyspace: # The more iterations we do, the higher the chance of reproducing # this issue. On my laptop, count = 40 reproduces the bug every time. # Lower numbers have some chance of not catching the bug. If this # issue starts to xpass, we may need to increase the count. count = 40 def drops(count): for i in range(count): try: cql.execute(f"DROP KEYSPACE {keyspace}") except Exception as e: print(e) else: print("drop successful") def creates(count): for i in range(count): try: cql.execute(f"CREATE KEYSPACE {keyspace} {ksdef}") print("create keyspace successful") # Create a table in this keyspace. This creation may # race with deletion of the entire keyspace by the # parallel thread. Reproducing #8968 requires this # operation - just creating and deleting the keyspace # without anything in it did not reproduce the problem. cql.execute(f"CREATE TABLE {keyspace}.xyz {cfdef}") except Exception as e: print(e) else: print("create table successful") t1 = Thread(target=drops, args=[count]) t2 = Thread(target=creates, args=[count]) t1.start() t2.start() t1.join() t2.join() # At this point, the keyspace should either exist, or not exist. # So CREATE KEYSPACE IF NOT EXIST should ensure it does exist, # and then one DROP KEYSPACE should succeed, a second one should # fail, and finally we can recreate the keyspace as new_test_keyspace # expects it. # If any of the following statements fail, it means we reached an # invalid state. This is issue #8968. cql.execute(f"CREATE KEYSPACE IF NOT EXISTS {keyspace} {ksdef}") cql.execute(f"DROP KEYSPACE {keyspace}") # See explanation above how different versions of Cassandra and # Scylla produce different errors when dropping a non-existent ks: with pytest.raises((InvalidRequest, ConfigurationException)): cql.execute(f"DROP KEYSPACE {keyspace}") cql.execute(f"CREATE KEYSPACE {keyspace} {ksdef}")
def test_alter_keyspace_invalid(cql): with new_test_keyspace( cql, "WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 }" ) as keyspace: with pytest.raises(ConfigurationException): cql.execute( f"ALTER KEYSPACE {keyspace} WITH REPLICATION = {{ 'class' : 'NoSuchStrategy' }}" ) with pytest.raises(ConfigurationException): cql.execute( f"ALTER KEYSPACE {keyspace} WITH REPLICATION = {{ 'class' : 'SimpleStrategy' }}" ) with pytest.raises(ConfigurationException): cql.execute( f"ALTER KEYSPACE {keyspace} WITH REPLICATION = {{ 'class' : 'SimpleStrategy', 'replication_factor' : 'foo' }}" )
def test_toppartitions_pk_needs_escaping(cql, this_dc, rest_api): with new_test_keyspace( cql, f"WITH REPLICATION = {{ 'class' : 'NetworkTopologyStrategy', '{this_dc}' : 1 }}" ) as keyspace: with new_test_table(cql, keyspace, "p text PRIMARY KEY") as table: # Use a newline character as part of the partition key pk. When # toppartitions later returns it, it must escape it (as pk_json) # or yield an invalid JSON with a literal newline in a string. pk = 'hi\nhello' pk_json = r'hi\nhello' # Unfortunately, the toppartitions API doesn't let us mark the # beginning and end of the sampling period. Instead we need to # start the toppartitions for a predefined period, and in # parallel, make the request. Sad. def toppartitions(): ks, cf = table.split('.') resp = rest_api.send('GET', 'storage_service/toppartitions', { 'table_filters': f'{ks}:{cf}', 'duration': '1000' }) assert resp.ok # resp.json() will raise an error if not valid JSON resp.json() assert pk_json in resp.text def insert(): # We need to wait enough time for the toppartitions request # to have been sent, but unfortunately we don't know when # this happens because the request doesn't return until the # "duration" ends. So we hope 0.5 seconds is enough. # TODO: we can use the log to check when the toppartitions # request was received. time.sleep(0.5) stmt = cql.prepare(f"INSERT INTO {table} (p) VALUES (?)") cql.execute(stmt, [pk]) t1 = ThreadWrapper(target=toppartitions) t2 = ThreadWrapper(target=insert) t1.start() t2.start() t1.join() t2.join()
def test_storage_service_flush(cql, this_dc, rest_api): with new_test_keyspace( cql, f"WITH REPLICATION = {{ 'class' : 'NetworkTopologyStrategy', '{this_dc}' : 1 }}" ) as keyspace: with new_test_table(cql, keyspace, "p text PRIMARY KEY") as table0: ks, t0 = table0.split('.') stmt = cql.prepare(f"INSERT INTO {table0} (p) VALUES (?)") cql.execute(stmt, ["pk0"]) with new_test_table(cql, keyspace, "p text PRIMARY KEY") as table1: _, t1 = table1.split('.') stmt = cql.prepare(f"INSERT INTO {table1} (p) VALUES (?)") cql.execute(stmt, ["pk1"]) # test the keyspace_flush doesn't produce any errors when called on existing keyspace and table(s) resp = rest_api.send("POST", f"storage_service/keyspace_flush/{ks}") resp.raise_for_status() resp = rest_api.send("POST", f"storage_service/keyspace_flush/{ks}", {"cf": f"{t0}"}) resp.raise_for_status() resp = rest_api.send("POST", f"storage_service/keyspace_flush/{ks}", {"cf": f"{t0},{t1}"}) resp.raise_for_status() # test error when keyspace_flush is called on non-existing keyspace or table(s) resp = rest_api.send( "POST", f"storage_service/keyspace_flush/no_such_keyspace") assert resp.status_code == requests.codes.bad_request resp = rest_api.send("POST", f"storage_service/keyspace_flush/{ks}", {"cf": f"no_such_table"}) assert resp.status_code == requests.codes.bad_request resp = rest_api.send("POST", f"storage_service/keyspace_flush/{ks}", {"cf": f"{t0},no_such_table,{t1}"}) assert resp.status_code == requests.codes.bad_request
def test_materialized_view_pre_scrub_snapshot(cql, this_dc, rest_api): with new_test_keyspace( cql, f"WITH REPLICATION = {{ 'class' : 'NetworkTopologyStrategy', '{this_dc}' : 1 }}" ) as keyspace: schema = 'p int, v text, primary key (p)' with new_test_table(cql, keyspace, schema) as table: stmt = cql.prepare(f"INSERT INTO {table} (p, v) VALUES (?, ?)") cql.execute(stmt, [0, 'hello']) with new_materialized_view( cql, table, '*', 'v, p', 'v is not null and p is not null') as mv: resp = rest_api.send( "GET", f"storage_service/keyspace_scrub/{keyspace}") resp.raise_for_status() with new_secondary_index(cql, table, 'v') as si: resp = rest_api.send( "GET", f"storage_service/keyspace_scrub/{keyspace}") resp.raise_for_status()
def test_storage_service_keyspace_scrub_mode(cql, this_dc, rest_api): with new_test_keyspace( cql, f"WITH REPLICATION = {{ 'class' : 'NetworkTopologyStrategy', '{this_dc}' : 1 }}" ) as keyspace: with new_test_table(cql, keyspace, "a int, PRIMARY KEY (a)") as t0: with new_test_table(cql, keyspace, "a int, PRIMARY KEY (a)") as t1: test_tables = [t0.split('.')[1], t1.split('.')[1]] resp = rest_api.send( "GET", f"storage_service/keyspace_scrub/{keyspace}", { "cf": f"{test_tables[0]}", "scrub_mode": "VALIDATE" }) resp.raise_for_status() resp = rest_api.send( "GET", f"storage_service/keyspace_scrub/{keyspace}", { "cf": f"{test_tables[0]}", "scrub_mode": "XXX" }) assert resp.status_code == requests.codes.bad_request resp = rest_api.send( "GET", f"storage_service/keyspace_scrub/{keyspace}", { "cf": f"{test_tables[0]}", "quarantine_mode": "ONLY" }) resp.raise_for_status() resp = rest_api.send( "GET", f"storage_service/keyspace_scrub/{keyspace}", { "cf": f"{test_tables[0]}", "quarantine_mode": "YYY" }) assert resp.status_code == requests.codes.bad_request
def test_storage_options_local(cql, scylla_only): ksdef = "WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : '1' } " \ "AND STORAGE = { 'type' : 'LOCAL' }" with new_test_keyspace(cql, ksdef) as keyspace: res = cql.execute(f"SELECT * FROM system_schema.scylla_keyspaces WHERE keyspace_name = '{keyspace}'") assert not res.all()
def test_storage_options_required_param(cql, scylla_only): ksdef = "WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : '1' } " \ "AND STORAGE = { 'type' : 'S3', 'bucket' : '42' }" with pytest.raises(InvalidRequest): with new_test_keyspace(cql, ksdef): pass
def test_storage_service_snapshot(cql, this_dc, rest_api): resp = rest_api.send("GET", "storage_service/snapshots") resp.raise_for_status() def verify_snapshot_details(expected): resp = rest_api.send("GET", "storage_service/snapshots") found = False for data in resp.json(): if data['key'] == expected['key']: assert not found found = True sort_key = lambda v: f"{v['ks']}-{v['cf']}" value = sorted([ v for v in data['value'] if not v['ks'].startswith('system') ], key=sort_key) expected_value = sorted(expected['value'], key=sort_key) assert len(value) == len( expected_value ), f"length mismatch: expected {expected_value} but got {value}" for i in range(len(value)): v = value[i] # normalize `total` and `live` # since we care only if they are zero or not v['total'] = 1 if v['total'] else 0 v['live'] = 1 if v['live'] else 0 ev = expected_value[i] assert v == ev assert found, f"key='{expected['key']}' not found in {resp.json()}" with new_test_keyspace( cql, f"WITH REPLICATION = {{ 'class' : 'NetworkTopologyStrategy', '{this_dc}' : 1 }}" ) as keyspace0: with new_test_table(cql, keyspace0, "p text PRIMARY KEY") as table00: ks0, cf00 = table00.split('.') stmt = cql.prepare(f"INSERT INTO {table00} (p) VALUES (?)") cql.execute(stmt, ["pk0"]) # single keyspace / table with new_test_snapshot(rest_api, ks0, cf00) as snapshot0: verify_snapshot_details({ 'key': snapshot0, 'value': [{ 'ks': ks0, 'cf': cf00, 'total': 1, 'live': 0 }] }) cql.execute(f"TRUNCATE {table00}") verify_snapshot_details({ 'key': snapshot0, 'value': [{ 'ks': ks0, 'cf': cf00, 'total': 1, 'live': 1 }] }) with new_test_table(cql, keyspace0, "p text PRIMARY KEY") as table01: _, cf01 = table01.split('.') stmt = cql.prepare(f"INSERT INTO {table01} (p) VALUES (?)") cql.execute(stmt, ["pk1"]) # single keyspace / multiple tables with new_test_snapshot(rest_api, ks0, [cf00, cf01]) as snapshot1: verify_snapshot_details({ 'key': snapshot1, 'value': [{ 'ks': ks0, 'cf': cf00, 'total': 0, 'live': 0 }, { 'ks': ks0, 'cf': cf01, 'total': 1, 'live': 0 }] }) with new_test_keyspace( cql, f"WITH REPLICATION = {{ 'class' : 'NetworkTopologyStrategy', '{this_dc}' : 1 }}" ) as keyspace1: with new_test_table(cql, keyspace1, "p text PRIMARY KEY") as table10: ks1, cf10 = table10.split('.') # multiple keyspaces with new_test_snapshot(rest_api, [ks0, ks1]) as snapshot2: verify_snapshot_details({ 'key': snapshot2, 'value': [{ 'ks': ks0, 'cf': cf00, 'total': 0, 'live': 0 }, { 'ks': ks0, 'cf': cf01, 'total': 1, 'live': 0 }, { 'ks': ks1, 'cf': cf10, 'total': 0, 'live': 0 }] }) # all keyspaces with new_test_snapshot(rest_api, ) as snapshot3: verify_snapshot_details({ 'key': snapshot3, 'value': [{ 'ks': ks0, 'cf': cf00, 'total': 0, 'live': 0 }, { 'ks': ks0, 'cf': cf01, 'total': 1, 'live': 0 }, { 'ks': ks1, 'cf': cf10, 'total': 0, 'live': 0 }] })
def test_storage_options_unknown_type(cql, scylla_only): ksdef = "WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : '1' } " \ "AND STORAGE = { 'type' : 'S4', 'bucket' : '42', 'endpoint' : 'localhost' }" with pytest.raises(InvalidRequest): with new_test_keyspace(cql, ksdef): pass