diff --git a/tests/integration/test_backup_restore_new/configs/cas_gc.xml b/tests/integration/test_backup_restore_new/configs/cas_gc.xml new file mode 100644 index 000000000000..f415af9d6ee6 --- /dev/null +++ b/tests/integration/test_backup_restore_new/configs/cas_gc.xml @@ -0,0 +1,10 @@ + + + + + 1 + 1 + + + + diff --git a/tests/integration/test_backup_restore_new/configs/cas_storage.xml b/tests/integration/test_backup_restore_new/configs/cas_storage.xml new file mode 100644 index 000000000000..349ce11ab215 --- /dev/null +++ b/tests/integration/test_backup_restore_new/configs/cas_storage.xml @@ -0,0 +1,24 @@ + + + + + object_storage + s3 + cas + itest-backup-restore-new + http://rustfs1:11121/test/backup_restore_new/ + clickhouse + clickhouse + + + + + + + cas + + + + + + diff --git a/tests/integration/test_backup_restore_new/test.py b/tests/integration/test_backup_restore_new/test.py index c300ca2b8a7a..6c8a5124afca 100644 --- a/tests/integration/test_backup_restore_new/test.py +++ b/tests/integration/test_backup_restore_new/test.py @@ -18,9 +18,10 @@ cluster = ClickHouseCluster(__file__) instance = cluster.add_instance( "instance", - main_configs=["configs/backups_disk.xml"], + main_configs=["configs/backups_disk.xml", "configs/cas_storage.xml"], user_configs=["configs/zookeeper_retries.xml"], external_dirs=["/backups/"], + with_rustfs=True, ) instance_with_short_timeout = cluster.add_instance( "instance_with_short_timeout", @@ -30,11 +31,14 @@ ) -def create_and_fill_table(engine="MergeTree", n=100): +def create_and_fill_table(engine="MergeTree", n=100, storage_policy=None): if engine == "MergeTree": engine = "MergeTree ORDER BY y PARTITION BY x%10" instance.query("CREATE DATABASE test") - instance.query(f"CREATE TABLE test.table(x UInt32, y String) ENGINE={engine}") + create_query = f"CREATE TABLE test.table(x UInt32, y String) ENGINE={engine}" + if storage_policy is not None: + create_query += f" SETTINGS storage_policy = '{storage_policy}'" + instance.query(create_query) instance.query( f"INSERT INTO test.table SELECT number, toString(number) FROM numbers({n})" ) @@ -224,6 +228,30 @@ def test_restore_table(engine): assert instance.query("SELECT count(), sum(x) FROM test.table") == "100\t4950\n" +def test_restore_table_on_cas_disk(): + backup_name = new_backup_name() + create_and_fill_table(storage_policy="cas_policy") + + assert instance.query("SELECT count(), sum(x) FROM test.table") == "100\t4950\n" + assert ( + instance.query("SELECT storage_policy FROM system.tables WHERE name = 'table'") + == "cas_policy\n" + ) + instance.query(f"BACKUP TABLE test.table TO {backup_name}") + instance.query("DROP TABLE test.table") + instance.query(f"RESTORE TABLE test.table FROM {backup_name}") + + assert instance.query("SELECT count(), sum(x) FROM test.table") == "100\t4950\n" + assert ( + instance.query("SELECT storage_policy FROM system.tables WHERE name = 'table'") + == "cas_policy\n" + ) + assert ( + instance.query("CHECK TABLE test.table SETTINGS check_query_single_value_result = 1") + == "1\n" + ) + + @pytest.mark.parametrize( "engine", ["MergeTree", "Log", "TinyLog", "StripeLog", "Memory"] ) @@ -1504,8 +1532,9 @@ def test_system_users_async(): ) -def test_projection(): - create_and_fill_table(n=3) +@pytest.mark.parametrize("storage_policy", [None, "cas_policy"]) +def test_projection(storage_policy): + create_and_fill_table(n=3, storage_policy=storage_policy) instance.query("ALTER TABLE test.table ADD PROJECTION prjmax (SELECT MAX(x))") instance.query("INSERT INTO test.table VALUES (100, 'a'), (101, 'b')") @@ -1555,6 +1584,18 @@ def test_projection(): == "2\n" ) + if storage_policy is not None: + assert ( + instance.query("SELECT storage_policy FROM system.tables WHERE name = 'table'") + == f"{storage_policy}\n" + ) + assert ( + instance.query( + "CHECK TABLE test.table SETTINGS check_query_single_value_result = 1" + ) + == "1\n" + ) + def test_restore_table_not_evaluate_table_defaults(): instance.query("CREATE DATABASE test") @@ -1950,8 +1991,10 @@ def verify_restore_info(): assert info.bytes_read == 0 -def test_mutation(): - create_and_fill_table(engine="MergeTree ORDER BY tuple()", n=5) +def check_mutation(storage_policy=None): + create_and_fill_table( + engine="MergeTree ORDER BY tuple()", n=5, storage_policy=storage_policy + ) instance.query( "INSERT INTO test.table SELECT number, toString(number) FROM numbers(5, 5)" @@ -1973,6 +2016,12 @@ def test_mutation(): instance.query("ALTER TABLE test.table UPDATE x=x+1 WHERE 1") instance.query("ALTER TABLE test.table UPDATE x=x+1 WHERE 1") + if storage_policy is not None: + assert ( + instance.query("SELECT storage_policy FROM system.tables WHERE name = 'table'") + == f"{storage_policy}\n" + ) + assert instance.query("SELECT count() FROM test.table") == "15\n" backup_name = new_backup_name() instance.query(f"BACKUP TABLE test.table TO {backup_name}") @@ -1986,6 +2035,20 @@ def test_mutation(): instance.query("DROP TABLE test.table") instance.query(f"RESTORE TABLE test.table FROM {backup_name}") + if storage_policy is not None: + assert ( + instance.query("SELECT storage_policy FROM system.tables WHERE name = 'table'") + == f"{storage_policy}\n" + ) + assert instance.query("SELECT count() FROM test.table") == "15\n" + + +def test_mutation(): + check_mutation() + + +def test_mutation_on_cas_disk(): + check_mutation(storage_policy="cas_policy") def test_tables_dependency(): @@ -2215,12 +2278,17 @@ def test_restore_table_with_checksum_data_file_name(engine): assert instance.query("SELECT count(), sum(x) FROM test.table") == "100\t4950\n" -def test_incremental_backup_with_checksum_data_file_name(): +def check_incremental_backup_with_checksum_data_file_name(storage_policy=None): backup_name = new_backup_name() incremental_backup_name = new_backup_name() - create_and_fill_table() + create_and_fill_table(storage_policy=storage_policy) assert instance.query("SELECT count(), sum(x) FROM test.table") == "100\t4950\n" + if storage_policy is not None: + assert ( + instance.query("SELECT storage_policy FROM system.tables WHERE name = 'table'") + == f"{storage_policy}\n" + ) instance.query( f"BACKUP TABLE test.table TO {backup_name} SETTINGS data_file_name_generator='checksum'" ) @@ -2236,6 +2304,19 @@ def test_incremental_backup_with_checksum_data_file_name(): f"RESTORE TABLE test.table AS test.table2 FROM {incremental_backup_name}" ) assert instance.query("SELECT count(), sum(x) FROM test.table2") == "102\t5081\n" + if storage_policy is not None: + assert ( + instance.query("SELECT storage_policy FROM system.tables WHERE name = 'table2'") + == f"{storage_policy}\n" + ) + + +def test_incremental_backup_with_checksum_data_file_name(): + check_incremental_backup_with_checksum_data_file_name() + + +def test_incremental_backup_with_checksum_data_file_name_on_cas_disk(): + check_incremental_backup_with_checksum_data_file_name(storage_policy="cas_policy") def test_async_backup_restore_with_max_execution_time_zero(): diff --git a/tests/integration/test_backup_restore_new/test_cancel_backup.py b/tests/integration/test_backup_restore_new/test_cancel_backup.py index c6e378ca4bf6..0bcf43ebee78 100644 --- a/tests/integration/test_backup_restore_new/test_cancel_backup.py +++ b/tests/integration/test_backup_restore_new/test_cancel_backup.py @@ -1,4 +1,5 @@ import re +import time import uuid import pytest @@ -17,6 +18,8 @@ "configs/backups_disk.xml", "configs/slow_backups.xml", "configs/shutdown_cancel_backups.xml", + "configs/cas_storage.xml", + "configs/cas_gc.xml", ] node = cluster.add_instance( @@ -24,8 +27,11 @@ main_configs=main_configs, external_dirs=["/backups/"], stay_alive=True, + with_rustfs=True, ) +CAS_POOL_PREFIXES = ["backup_restore_new/blobs/"] + @pytest.fixture(scope="module", autouse=True) def start_cluster(): @@ -179,11 +185,57 @@ def cancel_restore(restore_id): assert kill_duration_ms < kill_duration_ms_threshold +def table_settings(storage_policy): + if storage_policy is None: + return "" + return f" SETTINGS storage_policy = '{storage_policy}'" + + +def count_cas_pool_objects(): + return sum( + len( + list( + cluster.rustfs_client.list_objects( + cluster.rustfs_bucket, prefix, recursive=True + ) + ) + ) + for prefix in CAS_POOL_PREFIXES + ) + + +def wait_cas_pool_settled(): + previous = count_cas_pool_objects() + for _ in range(40): + time.sleep(3) + current = count_cas_pool_objects() + if current == previous: + return current + previous = current + return previous + + +def wait_cas_pool_reclaimed(baseline): + current = count_cas_pool_objects() + for _ in range(120): + if current <= baseline: + break + time.sleep(1) + current = count_cas_pool_objects() + assert ( + current <= baseline + ), f"CAS pool objects were not reclaimed: baseline={baseline}, current={current}" + + # Test that BACKUP and RESTORE operations can be cancelled with KILL QUERY. -def test_cancel_backup(): +@pytest.mark.parametrize("storage_policy", [None, "cas_policy"]) +def test_cancel_backup(storage_policy): + cas_pool_baseline = wait_cas_pool_settled() if storage_policy == "cas_policy" else None + # We use partitioning so backups would contain more files. node.query( "CREATE TABLE tbl (x UInt64) ENGINE=MergeTree() ORDER BY tuple() PARTITION BY x%20" + + table_settings(storage_policy) ) node.query("INSERT INTO tbl SELECT number FROM numbers(500)") @@ -209,11 +261,25 @@ def test_cancel_backup(): start_restore(restore_id, backup_id) wait_restore(restore_id) + assert node.query("SELECT count(), sum(x) FROM tbl") == "500\t124750\n" + assert ( + node.query("CHECK TABLE tbl SETTINGS check_query_single_value_result = 1") + == "1\n" + ) + + if cas_pool_baseline is not None: + node.query("DROP TABLE tbl SYNC") + wait_cas_pool_reclaimed(cas_pool_baseline) + # Test that shutdown cancels a running backup and doesn't wait until it finishes. -def test_shutdown_cancel_backup(): +@pytest.mark.parametrize("storage_policy", [None, "cas_policy"]) +def test_shutdown_cancel_backup(storage_policy): + cas_pool_baseline = wait_cas_pool_settled() if storage_policy == "cas_policy" else None + node.query( "CREATE TABLE tbl (x UInt64) ENGINE=MergeTree() ORDER BY tuple() PARTITION BY x%5" + + table_settings(storage_policy) ) node.query("INSERT INTO tbl SELECT number FROM numbers(500)") @@ -237,3 +303,6 @@ def test_shutdown_cancel_backup(): f"RESTORE TABLE tbl FROM {get_backup_name(backup_id)}" ), ) + + if cas_pool_baseline is not None: + wait_cas_pool_reclaimed(cas_pool_baseline) diff --git a/tests/integration/test_backup_restore_on_cluster/concurrency_helper.py b/tests/integration/test_backup_restore_on_cluster/concurrency_helper.py index f75600d11296..724796de06c6 100644 --- a/tests/integration/test_backup_restore_on_cluster/concurrency_helper.py +++ b/tests/integration/test_backup_restore_on_cluster/concurrency_helper.py @@ -38,17 +38,50 @@ def generate_cluster_def(file: str, num_nodes: int) -> str: return str(path.absolute()) +def generate_cas_server_root_def(file: str, node_index: int) -> str: + path = ( + Path(__file__).parent + / f"_gen/cas_server_root_{Path(file).stem}_node{node_index}.xml" + ) + config = f""" + + + + itest-{Path(file).stem}-node{node_index} + + + +""" + if path.is_file() and path.read_text(encoding="utf-8") == config: + return str(path.absolute()) + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(encoding="utf-8", data=config) + return str(path.absolute()) + + def add_nodes_to_cluster( cluster: ClickHouseCluster, num_nodes: int, main_configs: List[str], user_configs: List[str], + cas_file: str = None, **kwargs ) -> List[ClickHouseInstance]: + def node_main_configs(i): + if cas_file is None: + return main_configs + return main_configs + [ + "configs/cas_storage.xml", + generate_cas_server_root_def(cas_file, i), + ] + + if cas_file is not None: + kwargs["with_rustfs"] = True + nodes = [ cluster.add_instance( f"node{i}", - main_configs=main_configs, + main_configs=node_main_configs(i), user_configs=user_configs, external_dirs=["/backups/"], macros={"replica": f"node{i}", "shard": "shard1"}, @@ -60,9 +93,11 @@ def add_nodes_to_cluster( return nodes -def create_test_table(node: ClickHouseInstance) -> None: +def create_test_table(node: ClickHouseInstance, storage_policy: str = None) -> None: + settings = f" SETTINGS storage_policy = '{storage_policy}'" if storage_policy else "" node.query( """CREATE TABLE tbl ON CLUSTER 'cluster' ( x UInt64 ) ENGINE=ReplicatedMergeTree('/clickhouse/tables/tbl/', '{replica}') ORDER BY tuple()""" + + settings ) diff --git a/tests/integration/test_backup_restore_on_cluster/configs/cas_server_root_node1.xml b/tests/integration/test_backup_restore_on_cluster/configs/cas_server_root_node1.xml new file mode 100644 index 000000000000..e240cf0308ae --- /dev/null +++ b/tests/integration/test_backup_restore_on_cluster/configs/cas_server_root_node1.xml @@ -0,0 +1,9 @@ + + + + + itest-backup-restore-on-cluster-node1 + + + + diff --git a/tests/integration/test_backup_restore_on_cluster/configs/cas_server_root_node2.xml b/tests/integration/test_backup_restore_on_cluster/configs/cas_server_root_node2.xml new file mode 100644 index 000000000000..fca0f5d5a472 --- /dev/null +++ b/tests/integration/test_backup_restore_on_cluster/configs/cas_server_root_node2.xml @@ -0,0 +1,9 @@ + + + + + itest-backup-restore-on-cluster-node2 + + + + diff --git a/tests/integration/test_backup_restore_on_cluster/configs/cas_server_root_node3.xml b/tests/integration/test_backup_restore_on_cluster/configs/cas_server_root_node3.xml new file mode 100644 index 000000000000..49b8f8620c78 --- /dev/null +++ b/tests/integration/test_backup_restore_on_cluster/configs/cas_server_root_node3.xml @@ -0,0 +1,9 @@ + + + + + itest-backup-restore-on-cluster-node3 + + + + diff --git a/tests/integration/test_backup_restore_on_cluster/configs/cas_storage.xml b/tests/integration/test_backup_restore_on_cluster/configs/cas_storage.xml new file mode 100644 index 000000000000..5884c99c7f15 --- /dev/null +++ b/tests/integration/test_backup_restore_on_cluster/configs/cas_storage.xml @@ -0,0 +1,23 @@ + + + + + object_storage + s3 + cas + http://rustfs1:11121/test/backup_restore_on_cluster/ + clickhouse + clickhouse + + + + + + + cas + + + + + + diff --git a/tests/integration/test_backup_restore_on_cluster/test.py b/tests/integration/test_backup_restore_on_cluster/test.py index 6e1e0de0ec60..33ae40071a03 100644 --- a/tests/integration/test_backup_restore_on_cluster/test.py +++ b/tests/integration/test_backup_restore_on_cluster/test.py @@ -19,6 +19,8 @@ "configs/lesser_timeouts.xml", # Default timeouts are quite big (a few minutes), the tests don't need them to be that big. ] +cas_configs = ["configs/cas_storage.xml"] + user_configs = [ "configs/allow_database_types.xml", "configs/zookeeper_retries.xml", @@ -26,30 +28,33 @@ node1 = cluster.add_instance( "node1", - main_configs=main_configs, + main_configs=main_configs + cas_configs + ["configs/cas_server_root_node1.xml"], user_configs=user_configs, external_dirs=["/backups/"], macros={"replica": "node1", "shard": "shard1"}, with_zookeeper=True, + with_rustfs=True, ) node2 = cluster.add_instance( "node2", - main_configs=main_configs, + main_configs=main_configs + cas_configs + ["configs/cas_server_root_node2.xml"], user_configs=user_configs, external_dirs=["/backups/"], macros={"replica": "node2", "shard": "shard1"}, with_zookeeper=True, + with_rustfs=True, stay_alive=True, # Necessary for the "test_stop_other_host_while_backup" test ) node3 = cluster.add_instance( "node3", - main_configs=main_configs, + main_configs=main_configs + cas_configs + ["configs/cas_server_root_node3.xml"], user_configs=user_configs, external_dirs=["/backups/"], macros={"replica": "node3", "shard": "shard1"}, with_zookeeper=True, + with_rustfs=True, ) @@ -125,6 +130,46 @@ def test_replicated_table(): ) +def test_replicated_table_on_cas_disk(): + expected = TSV([[1, "Don\\'t"], [2, "count"], [3, "your"], [4, "chickens"]]) + node1.query( + "CREATE TABLE tbl ON CLUSTER 'cluster' (" + "x UInt8, y String" + ") ENGINE=ReplicatedMergeTree('/clickhouse/tables/tbl/', '{replica}')" + "ORDER BY x SETTINGS storage_policy = 'cas_policy'" + ) + + node1.query("INSERT INTO tbl VALUES (1, 'Don''t')") + node2.query("INSERT INTO tbl VALUES (2, 'count')") + node1.query("INSERT INTO tbl SETTINGS async_insert=true VALUES (3, 'your')") + node2.query("INSERT INTO tbl SETTINGS async_insert=true VALUES (4, 'chickens')") + node1.query("SYSTEM SYNC REPLICA ON CLUSTER 'cluster' tbl") + + assert node1.query("SELECT * FROM tbl ORDER BY x") == expected + assert node2.query("SELECT * FROM tbl ORDER BY x") == expected + for node in [node1, node2]: + assert ( + node.query("SELECT storage_policy FROM system.tables WHERE name = 'tbl'") + == "cas_policy\n" + ) + + backup_name = new_backup_name() + node1.query( + f"BACKUP TABLE tbl ON CLUSTER 'cluster' TO {backup_name} SETTINGS replica_num=1" + ) + node1.query("DROP TABLE tbl ON CLUSTER 'cluster' SYNC") + node2.query(f"RESTORE TABLE tbl ON CLUSTER 'cluster' FROM {backup_name}") + node1.query("SYSTEM SYNC REPLICA ON CLUSTER 'cluster' tbl") + + assert node1.query("SELECT * FROM tbl ORDER BY x") == expected + assert node2.query("SELECT * FROM tbl ORDER BY x") == expected + for node in [node1, node2]: + assert ( + node.query("SELECT storage_policy FROM system.tables WHERE name = 'tbl'") + == "cas_policy\n" + ) + + def test_empty_replicated_table(): node1.query( "CREATE TABLE tbl ON CLUSTER 'cluster' (" diff --git a/tests/integration/test_backup_restore_on_cluster/test_concurrency.py b/tests/integration/test_backup_restore_on_cluster/test_concurrency.py index 02431752f64b..cc9b1ebe52f2 100644 --- a/tests/integration/test_backup_restore_on_cluster/test_concurrency.py +++ b/tests/integration/test_backup_restore_on_cluster/test_concurrency.py @@ -25,7 +25,9 @@ # No [Zoo]Keeper retries for tests with concurrency user_configs = ["configs/allow_database_types.xml"] -nodes = add_nodes_to_cluster(cluster, num_nodes, main_configs, user_configs, cpu_limit=12) +nodes = add_nodes_to_cluster( + cluster, num_nodes, main_configs, user_configs, cas_file=__file__, cpu_limit=12 +) node0 = nodes[0] @@ -51,8 +53,8 @@ def drop_after_test(): backup_id_counter = 0 -def create_and_fill_table() -> None: - create_test_table(node0) +def create_and_fill_table(storage_policy=None) -> None: + create_test_table(node0, storage_policy) for i, node in enumerate(nodes): node.query(f"INSERT INTO tbl VALUES ({i})") @@ -66,8 +68,23 @@ def new_backup_name(): expected_sum = num_nodes * (num_nodes - 1) // 2 -def test_replicated_table(): - create_and_fill_table() +def check_restored_table(storage_policy): + for node in nodes: + assert node.query("SELECT sum(x) FROM tbl") == TSV([expected_sum]) + if storage_policy is not None: + assert ( + node.query("SELECT storage_policy FROM system.tables WHERE name = 'tbl'") + == f"{storage_policy}\n" + ) + assert ( + node.query("CHECK TABLE tbl SETTINGS check_query_single_value_result = 1") + == "1\n" + ) + + +@pytest.mark.parametrize("storage_policy", [None, "cas_policy"]) +def test_replicated_table(storage_policy): + create_and_fill_table(storage_policy) backup_name = new_backup_name() node0.query(f"BACKUP TABLE tbl ON CLUSTER 'cluster' TO {backup_name}") @@ -76,15 +93,15 @@ def test_replicated_table(): node0.query(f"RESTORE TABLE tbl ON CLUSTER 'cluster' FROM {backup_name}") node0.query("SYSTEM SYNC REPLICA ON CLUSTER 'cluster' tbl") - for i in range(num_nodes): - assert nodes[i].query("SELECT sum(x) FROM tbl") == TSV([expected_sum]) + check_restored_table(storage_policy) num_concurrent_backups = 4 -def test_concurrent_backups_on_same_node(): - create_and_fill_table() +@pytest.mark.parametrize("storage_policy", [None, "cas_policy"]) +def test_concurrent_backups_on_same_node(storage_policy): + create_and_fill_table(storage_policy) backup_names = [new_backup_name() for _ in range(num_concurrent_backups)] @@ -113,12 +130,12 @@ def test_concurrent_backups_on_same_node(): node0.query("DROP TABLE tbl ON CLUSTER 'cluster' SYNC") node0.query(f"RESTORE TABLE tbl ON CLUSTER 'cluster' FROM {backup_name}") node0.query("SYSTEM SYNC REPLICA ON CLUSTER 'cluster' tbl") - for i in range(num_nodes): - assert nodes[i].query("SELECT sum(x) FROM tbl") == TSV([expected_sum]) + check_restored_table(storage_policy) -def test_concurrent_backups_on_different_nodes(): - create_and_fill_table() +@pytest.mark.parametrize("storage_policy", [None, "cas_policy"]) +def test_concurrent_backups_on_different_nodes(storage_policy): + create_and_fill_table(storage_policy) assert num_concurrent_backups <= num_nodes backup_names = [new_backup_name() for _ in range(num_concurrent_backups)] @@ -150,8 +167,7 @@ def test_concurrent_backups_on_different_nodes(): nodes[i].query("DROP TABLE tbl ON CLUSTER 'cluster' SYNC") nodes[i].query(f"RESTORE TABLE tbl ON CLUSTER 'cluster' FROM {backup_names[i]}") nodes[i].query("SYSTEM SYNC REPLICA ON CLUSTER 'cluster' tbl") - for j in range(num_nodes): - assert nodes[j].query("SELECT sum(x) FROM tbl") == TSV([expected_sum]) + check_restored_table(storage_policy) @pytest.mark.parametrize( diff --git a/tests/integration/test_backup_restore_s3/configs/disk_cas.xml b/tests/integration/test_backup_restore_s3/configs/disk_cas.xml new file mode 100644 index 000000000000..83392e27c7e4 --- /dev/null +++ b/tests/integration/test_backup_restore_s3/configs/disk_cas.xml @@ -0,0 +1,41 @@ + + + + + + object_storage + s3 + cas + 30 + 10000 + itest-backup-restore-s3 + http://rustfs1:11121/test/backup_restore_s3_cas/ + clickhouse + clickhouse + + + cache + disk_cas + /tmp/cas_cache/ + 1000000000 + 1 + + + + + +
+ disk_cas +
+
+
+ + +
+ disk_cas_cache +
+
+
+
+
+
diff --git a/tests/integration/test_backup_restore_s3/test.py b/tests/integration/test_backup_restore_s3/test.py index 77e4e1c0dcbb..564c62607fd5 100644 --- a/tests/integration/test_backup_restore_s3/test.py +++ b/tests/integration/test_backup_restore_s3/test.py @@ -143,6 +143,7 @@ def setup_cluster(request): "node", main_configs=[ "configs/disk_s3.xml", + "configs/disk_cas.xml", "configs/named_collection_s3_backups.xml", "configs/s3_settings.xml", "configs/blob_log.xml", @@ -154,6 +155,7 @@ def setup_cluster(request): "configs/zookeeper_retries.xml", ], with_minio=True, + with_rustfs=True, # The test compares some S3 events. We disable the remote DB disk, so it doesn't affect the comparing events. with_remote_database_disk=False, with_zookeeper=True, @@ -177,6 +179,14 @@ def cluster(setup_cluster): backup_id_counter = 0 +CAS_POLICIES = ["policy_cas", "policy_cas_cache"] + + +def s3_backup_destination(storage_policy, backup_name): + if storage_policy in CAS_POLICIES: + return f"S3('http://rustfs1:11121/test/backups/{backup_name}', 'clickhouse', 'clickhouse')" + return f"S3('http://minio1:9001/root/data/backups/{backup_name}', 'minio', '{minio_secret_key}')" + def new_backup_name(): global backup_id_counter @@ -293,6 +303,8 @@ def check_system_tables(cluster, backup_query_id=None): ("disk_s3_plain", "ObjectStorage", "S3", "Plain"), ("disk_s3_plain_rewritable", "ObjectStorage", "S3", "PlainRewritable"), ("disk_s3_restricted_user", "ObjectStorage", "S3", "Local"), + ("disk_cas", "ObjectStorage", "S3", "CAS"), + ("disk_cas_cache", "ObjectStorage", "S3", "CAS"), ) assert len(expected_disks) == len(disks) for expected_disk in expected_disks: @@ -336,6 +348,16 @@ def check_system_tables(cluster, backup_query_id=None): "disk_s3_plain", id="from_local_to_s3_plain", ), + pytest.param( + "policy_cas", + "default", + id="from_cas_to_local", + ), + pytest.param( + "policy_cas", + "disk_s3", + id="from_cas_to_s3", + ), ], ) def test_backup_to_disk(cluster, storage_policy, to_disk): @@ -531,14 +553,29 @@ def test_backup_to_s3_multipart(cluster): "policy_s3", "policy_s3_other_bucket", "policy_s3_plain_rewritable", + "policy_cas", ], ) def test_backup_to_s3_native_copy(cluster, storage_policy): backup_name = new_backup_name() - backup_destination = f"S3('http://minio1:9001/root/data/backups/{backup_name}', 'minio', '{minio_secret_key}')" + backup_destination = s3_backup_destination(storage_policy, backup_name) (backup_events, restore_events) = check_backup_and_restore( cluster, storage_policy, backup_destination ) + + if storage_policy in CAS_POLICIES: + assert ( + backup_events.get("S3CopyObject", 0) + + backup_events.get("S3UploadPartCopy", 0) + > 0 + ) + assert ( + restore_events.get("S3CopyObject", 0) + + restore_events.get("S3UploadPartCopy", 0) + == 0 + ) + return + # single part upload assert backup_events["S3CopyObject"] > 0 assert restore_events["S3CopyObject"] > 0 @@ -556,6 +593,20 @@ def test_backup_to_s3_native_copy(cluster, storage_policy): ) +def test_backup_from_cas_to_s3_different_endpoint(cluster): + backup_name = new_backup_name() + backup_destination = f"S3('http://minio1:9001/root/data/backups/{backup_name}', 'minio', '{minio_secret_key}')" + backup_events, restore_events = check_backup_and_restore( + cluster, "policy_cas", backup_destination + ) + + for events in (backup_events, restore_events): + assert events.get("S3UploadPartCopy", 0) == 0 + assert events.get("S3CopyObject", 0) == 0 + assert events.get("S3GetObject", 0) > 0 + assert events.get("S3PutObject", 0) + events.get("S3UploadPart", 0) > 0 + + @pytest.mark.parametrize( "storage_policy", [ @@ -707,9 +758,8 @@ def test_backup_with_fs_cache( cluster, in_cache_initially, allow_backup_read_cache, allow_s3_native_copy ): storage_policy = "policy_s3_cache" - backup_name = new_backup_name() - backup_destination = f"S3('http://minio1:9001/root/data/backups/{backup_name}', 'minio', '{minio_secret_key}')" + backup_destination = s3_backup_destination(storage_policy, backup_name) insert_settings = { "enable_filesystem_cache_on_write_operations": int(in_cache_initially) diff --git a/tests/integration/test_backup_restore_storage_policy/configs/storage_config.xml b/tests/integration/test_backup_restore_storage_policy/configs/storage_config.xml index de9f68cbe870..6c841d4612e5 100644 --- a/tests/integration/test_backup_restore_storage_policy/configs/storage_config.xml +++ b/tests/integration/test_backup_restore_storage_policy/configs/storage_config.xml @@ -9,6 +9,15 @@ local /var/lib/disks/two/ + + object_storage + s3 + cas + itest-backup-restore-storage-policy + http://rustfs1:11121/test/backup_restore_storage_policy/ + clickhouse + clickhouse + @@ -25,6 +34,13 @@ + + + + cas + + + diff --git a/tests/integration/test_backup_restore_storage_policy/test.py b/tests/integration/test_backup_restore_storage_policy/test.py index b2e907dcaaa7..e45402408c6a 100644 --- a/tests/integration/test_backup_restore_storage_policy/test.py +++ b/tests/integration/test_backup_restore_storage_policy/test.py @@ -9,6 +9,7 @@ instance = cluster.add_instance( "instance", main_configs=["configs/storage_config.xml"], + with_rustfs=True, ) @@ -42,6 +43,12 @@ def create_table_backup(backup_name, storage_policy=None): create_query += f" SETTINGS storage_policy = '{storage_policy}'" instance.query(create_query) instance.query("INSERT INTO test.table SELECT number FROM numbers(10)") + assert instance.query("SELECT count(), sum(x) FROM test.table") == "10\t45\n" + if storage_policy is not None: + assert ( + instance.query("SELECT storage_policy FROM system.tables WHERE name='table'") + == f"{storage_policy}\n" + ) instance.query(f"BACKUP TABLE test.table TO {backup_name}") instance.query("DROP TABLE test.table SYNC") @@ -63,6 +70,11 @@ def restore_table(backup_name, storage_policy=None): ("policy1", "policy2", "policy2"), ("policy1", "", "default"), ("policy1", None, "policy1"), + ("cas_policy", None, "cas_policy"), + ("cas_policy", "policy1", "policy1"), + (None, "cas_policy", "cas_policy"), + ("policy1", "cas_policy", "cas_policy"), + ("cas_policy", "cas_policy", "cas_policy"), ], ) def test_storage_policies(origin_policy, restore_policy, expected_policy): @@ -74,3 +86,14 @@ def test_storage_policies(origin_policy, restore_policy, expected_policy): instance.query("SELECT storage_policy FROM system.tables WHERE name='table'") == f"{expected_policy}\n" ) + assert instance.query("SELECT count(), sum(x) FROM test.table") == "10\t45\n" + assert ( + instance.query( + "SELECT sum(rows) FROM system.parts WHERE database = 'test' AND table = 'table' AND active" + ) + == "10\n" + ) + assert ( + instance.query("CHECK TABLE test.table SETTINGS check_query_single_value_result = 1") + == "1\n" + ) diff --git a/tests/integration/test_database_backup/configs/backups.xml b/tests/integration/test_database_backup/configs/backups.xml index 32bc9dce7eec..d791dababff4 100644 --- a/tests/integration/test_database_backup/configs/backups.xml +++ b/tests/integration/test_database_backup/configs/backups.xml @@ -17,12 +17,37 @@ plain /local_plain/ + + s3_plain + http://rustfs1:11121/test/backup_disk_s3_plain_rustfs/ + clickhouse + clickhouse + + + object_storage + s3 + cas + itest-database-backup + http://rustfs1:11121/test/database_backup_cas/ + clickhouse + clickhouse + + + + + + cas + + + + backup_disk_local backup_disk_s3_plain backup_disk_object_storage_local_plain + backup_disk_s3_plain_rustfs /backups diff --git a/tests/integration/test_database_backup/test.py b/tests/integration/test_database_backup/test.py index 9dc7392d7797..9fcafffb4efb 100644 --- a/tests/integration/test_database_backup/test.py +++ b/tests/integration/test_database_backup/test.py @@ -10,6 +10,7 @@ main_configs=["configs/backups.xml"], stay_alive=True, with_minio=True, + with_rustfs=True, ) @@ -31,6 +32,12 @@ def cleanup_backup_files(instance): for s3_object in s3_objects: minio.remove_object(cluster.minio_bucket, s3_object) + rustfs = cluster.rustfs_client + for s3_object in rustfs.list_objects( + cluster.rustfs_bucket, prefix="backup_disk_s3_plain_rustfs/", recursive=True + ): + rustfs.remove_object(cluster.rustfs_bucket, s3_object.object_name) + @pytest.mark.parametrize( "backup_destination", @@ -39,9 +46,11 @@ def cleanup_backup_files(instance): "Disk('backup_disk_local', 'test_database_backup')", "Disk('backup_disk_s3_plain', 'test_database_backup')", "Disk('backup_disk_object_storage_local_plain', 'test_database_backup')", + "Disk('backup_disk_s3_plain_rustfs', 'test_database_backup')", ], ) -def test_database_backup_database(backup_destination): +@pytest.mark.parametrize("storage_policy", ["default", "cas_policy"]) +def test_database_backup_database(backup_destination, storage_policy): cleanup_backup_files(instance) instance.query( @@ -51,13 +60,13 @@ def test_database_backup_database(backup_destination): CREATE DATABASE test_database; - CREATE TABLE test_database.test_table_1 (id UInt64, value String) ENGINE=MergeTree ORDER BY id; + CREATE TABLE test_database.test_table_1 (id UInt64, value String) ENGINE=MergeTree ORDER BY id SETTINGS storage_policy = '{storage_policy}'; INSERT INTO test_database.test_table_1 VALUES (0, 'test_database.test_table_1'); - CREATE TABLE test_database.test_table_2 (id UInt64, value String) ENGINE=MergeTree ORDER BY id; + CREATE TABLE test_database.test_table_2 (id UInt64, value String) ENGINE=MergeTree ORDER BY id SETTINGS storage_policy = '{storage_policy}'; INSERT INTO test_database.test_table_2 VALUES (0, 'test_database.test_table_2'); - CREATE TABLE test_database.test_table_3 (id UInt64, value String) ENGINE=MergeTree ORDER BY id; + CREATE TABLE test_database.test_table_3 (id UInt64, value String) ENGINE=MergeTree ORDER BY id SETTINGS storage_policy = '{storage_policy}'; INSERT INTO test_database.test_table_3 VALUES (0, 'test_database.test_table_3'); BACKUP DATABASE test_database TO {backup_destination}; @@ -109,9 +118,11 @@ def test_database_backup_database(backup_destination): "Disk('backup_disk_local', 'test_table_backup')", "Disk('backup_disk_s3_plain', 'test_table_backup')", "Disk('backup_disk_object_storage_local_plain', 'test_table_backup')", + "Disk('backup_disk_s3_plain_rustfs', 'test_table_backup')", ], ) -def test_database_backup_table(backup_destination): +@pytest.mark.parametrize("storage_policy", ["default", "cas_policy"]) +def test_database_backup_table(backup_destination, storage_policy): cleanup_backup_files(instance) instance.query( @@ -121,7 +132,7 @@ def test_database_backup_table(backup_destination): CREATE DATABASE test_database; - CREATE TABLE test_database.test_table (id UInt64, value String) ENGINE=MergeTree ORDER BY id; + CREATE TABLE test_database.test_table (id UInt64, value String) ENGINE=MergeTree ORDER BY id SETTINGS storage_policy = '{storage_policy}'; INSERT INTO test_database.test_table VALUES (0, 'test_database.test_table'); BACKUP TABLE test_database.test_table TO {backup_destination};