mirror of
https://github.com/ClickHouse/ClickHouse.git
synced 2024-11-16 12:44:42 +00:00
341 lines
16 KiB
Python
341 lines
16 KiB
Python
import pytest
|
|
import random, string
|
|
from helpers.cluster import ClickHouseCluster
|
|
|
|
cluster = ClickHouseCluster(__file__)
|
|
node = cluster.add_instance("node", with_zookeeper=True)
|
|
|
|
|
|
@pytest.fixture(scope="module", autouse=True)
|
|
def started_cluster():
|
|
try:
|
|
cluster.start()
|
|
yield cluster
|
|
|
|
finally:
|
|
cluster.shutdown()
|
|
|
|
|
|
def check_logs(must_contain=[], must_not_contain=[]):
|
|
node.query("SYSTEM FLUSH LOGS")
|
|
|
|
for str in must_contain:
|
|
escaped_str = str.replace("`", "\\`").replace("[", "\\[").replace("]", "\\]")
|
|
assert node.contains_in_log(escaped_str)
|
|
|
|
for str in must_not_contain:
|
|
escaped_str = str.replace("`", "\\`").replace("[", "\\[").replace("]", "\\]")
|
|
assert not node.contains_in_log(escaped_str)
|
|
|
|
for str in must_contain:
|
|
escaped_str = str.replace("'", "\\'")
|
|
assert system_query_log_contains_search_pattern(escaped_str)
|
|
|
|
for str in must_not_contain:
|
|
escaped_str = str.replace("'", "\\'")
|
|
assert not system_query_log_contains_search_pattern(escaped_str)
|
|
|
|
|
|
# Returns true if "system.query_log" has a query matching a specified pattern.
|
|
def system_query_log_contains_search_pattern(search_pattern):
|
|
return (
|
|
int(
|
|
node.query(
|
|
f"SELECT COUNT() FROM system.query_log WHERE query LIKE '%{search_pattern}%'"
|
|
).strip()
|
|
)
|
|
>= 1
|
|
)
|
|
|
|
|
|
# Generates a random string.
|
|
def new_password(len=16):
|
|
return "".join(
|
|
random.choice(string.ascii_uppercase + string.digits) for _ in range(len)
|
|
)
|
|
|
|
|
|
# Passwords in CREATE/ALTER queries must be hidden in logs.
|
|
def test_create_alter_user():
|
|
password = new_password()
|
|
|
|
node.query(f"CREATE USER u1 IDENTIFIED BY '{password}' SETTINGS custom_a = 'a'")
|
|
node.query(
|
|
f"ALTER USER u1 IDENTIFIED BY '{password}{password}' SETTINGS custom_b = 'b'"
|
|
)
|
|
node.query(
|
|
f"CREATE USER u2 IDENTIFIED WITH plaintext_password BY '{password}' SETTINGS custom_c = 'c'"
|
|
)
|
|
|
|
assert (
|
|
node.query("SHOW CREATE USER u1")
|
|
== "CREATE USER u1 IDENTIFIED WITH sha256_password SETTINGS custom_b = \\'b\\'\n"
|
|
)
|
|
assert (
|
|
node.query("SHOW CREATE USER u2")
|
|
== "CREATE USER u2 IDENTIFIED WITH plaintext_password SETTINGS custom_c = \\'c\\'\n"
|
|
)
|
|
|
|
check_logs(
|
|
must_contain=[
|
|
"CREATE USER u1 IDENTIFIED WITH sha256_password",
|
|
"ALTER USER u1 IDENTIFIED WITH sha256_password",
|
|
"CREATE USER u2 IDENTIFIED WITH plaintext_password",
|
|
],
|
|
must_not_contain=[
|
|
password,
|
|
"IDENTIFIED WITH sha256_password BY",
|
|
"IDENTIFIED WITH sha256_hash BY",
|
|
"IDENTIFIED WITH plaintext_password BY",
|
|
],
|
|
)
|
|
|
|
node.query("DROP USER u1, u2")
|
|
|
|
|
|
def test_create_table():
|
|
password = new_password()
|
|
|
|
table_engines = [
|
|
f"MySQL('mysql57:3306', 'mysql_db', 'mysql_table', 'mysql_user', '{password}')",
|
|
f"PostgreSQL('postgres1:5432', 'postgres_db', 'postgres_table', 'postgres_user', '{password}')",
|
|
f"MongoDB('mongo1:27017', 'mongo_db', 'mongo_col', 'mongo_user', '{password}')",
|
|
f"S3('http://minio1:9001/root/data/test1.csv')",
|
|
f"S3('http://minio1:9001/root/data/test2.csv', 'CSV')",
|
|
f"S3('http://minio1:9001/root/data/test3.csv.gz', 'CSV', 'gzip')",
|
|
f"S3('http://minio1:9001/root/data/test4.csv', 'minio', '{password}', 'CSV')",
|
|
f"S3('http://minio1:9001/root/data/test5.csv.gz', 'minio', '{password}', 'CSV', 'gzip')",
|
|
]
|
|
|
|
for i, table_engine in enumerate(table_engines):
|
|
node.query(f"CREATE TABLE table{i} (x int) ENGINE = {table_engine}")
|
|
|
|
check_logs(
|
|
must_contain=[
|
|
"CREATE TABLE table0 (`x` int) ENGINE = MySQL('mysql57:3306', 'mysql_db', 'mysql_table', 'mysql_user', '[HIDDEN]')",
|
|
"CREATE TABLE table1 (`x` int) ENGINE = PostgreSQL('postgres1:5432', 'postgres_db', 'postgres_table', 'postgres_user', '[HIDDEN]')",
|
|
"CREATE TABLE table2 (`x` int) ENGINE = MongoDB('mongo1:27017', 'mongo_db', 'mongo_col', 'mongo_user', '[HIDDEN]')",
|
|
"CREATE TABLE table3 (x int) ENGINE = S3('http://minio1:9001/root/data/test1.csv')",
|
|
"CREATE TABLE table4 (x int) ENGINE = S3('http://minio1:9001/root/data/test2.csv', 'CSV')",
|
|
"CREATE TABLE table5 (x int) ENGINE = S3('http://minio1:9001/root/data/test3.csv.gz', 'CSV', 'gzip')",
|
|
"CREATE TABLE table6 (`x` int) ENGINE = S3('http://minio1:9001/root/data/test4.csv', 'minio', '[HIDDEN]', 'CSV')",
|
|
"CREATE TABLE table7 (`x` int) ENGINE = S3('http://minio1:9001/root/data/test5.csv.gz', 'minio', '[HIDDEN]', 'CSV', 'gzip')",
|
|
],
|
|
must_not_contain=[password],
|
|
)
|
|
|
|
for i in range(0, len(table_engines)):
|
|
node.query(f"DROP TABLE table{i}")
|
|
|
|
|
|
def test_create_database():
|
|
password = new_password()
|
|
|
|
database_engines = [
|
|
f"MySQL('localhost:3306', 'mysql_db', 'mysql_user', '{password}') SETTINGS connect_timeout=1, connection_max_tries=1",
|
|
# f"PostgreSQL('localhost:5432', 'postgres_db', 'postgres_user', '{password}')",
|
|
]
|
|
|
|
for i, database_engine in enumerate(database_engines):
|
|
# query_and_get_answer_with_error() is used here because we don't want to stop on error "Cannot connect to MySQL server".
|
|
# We test logging here and not actual work with MySQL server.
|
|
node.query_and_get_answer_with_error(
|
|
f"CREATE DATABASE database{i} ENGINE = {database_engine}"
|
|
)
|
|
|
|
check_logs(
|
|
must_contain=[
|
|
"CREATE DATABASE database0 ENGINE = MySQL('localhost:3306', 'mysql_db', 'mysql_user', '[HIDDEN]')",
|
|
# "CREATE DATABASE database1 ENGINE = PostgreSQL('localhost:5432', 'postgres_db', 'postgres_user', '[HIDDEN]')",
|
|
],
|
|
must_not_contain=[password],
|
|
)
|
|
|
|
for i in range(0, len(database_engines)):
|
|
node.query(f"DROP DATABASE IF EXISTS database{i}")
|
|
|
|
|
|
def test_table_functions():
|
|
password = new_password()
|
|
|
|
table_functions = [
|
|
f"mysql('mysql57:3306', 'mysql_db', 'mysql_table', 'mysql_user', '{password}')",
|
|
f"postgresql('postgres1:5432', 'postgres_db', 'postgres_table', 'postgres_user', '{password}')",
|
|
f"mongodb('mongo1:27017', 'mongo_db', 'mongo_col', 'mongo_user', '{password}', 'x int')",
|
|
f"s3('http://minio1:9001/root/data/test1.csv')",
|
|
f"s3('http://minio1:9001/root/data/test2.csv', 'CSV')",
|
|
f"s3('http://minio1:9001/root/data/test3.csv', 'minio', '{password}')",
|
|
f"s3('http://minio1:9001/root/data/test4.csv', 'CSV', 'x int')",
|
|
f"s3('http://minio1:9001/root/data/test5.csv.gz', 'CSV', 'x int', 'gzip')",
|
|
f"s3('http://minio1:9001/root/data/test6.csv', 'minio', '{password}', 'CSV')",
|
|
f"s3('http://minio1:9001/root/data/test7.csv', 'minio', '{password}', 'CSV', 'x int')",
|
|
f"s3('http://minio1:9001/root/data/test8.csv.gz', 'minio', '{password}', 'CSV', 'x int', 'gzip')",
|
|
f"s3Cluster('test_shard_localhost', 'http://minio1:9001/root/data/test1.csv', 'minio', '{password}')",
|
|
f"s3Cluster('test_shard_localhost', 'http://minio1:9001/root/data/test2.csv', 'CSV', 'x int')",
|
|
f"s3Cluster('test_shard_localhost', 'http://minio1:9001/root/data/test3.csv', 'minio', '{password}', 'CSV')",
|
|
f"remote('127.{{2..11}}', default.remote_table)",
|
|
f"remote('127.{{2..11}}', default.remote_table, rand())",
|
|
f"remote('127.{{2..11}}', default.remote_table, 'remote_user')",
|
|
f"remote('127.{{2..11}}', default.remote_table, 'remote_user', '{password}')",
|
|
f"remote('127.{{2..11}}', default.remote_table, 'remote_user', rand())",
|
|
f"remote('127.{{2..11}}', default.remote_table, 'remote_user', '{password}', rand())",
|
|
f"remote('127.{{2..11}}', 'default.remote_table', 'remote_user', '{password}', rand())",
|
|
f"remote('127.{{2..11}}', 'default', 'remote_table', 'remote_user', '{password}', rand())",
|
|
f"remote('127.{{2..11}}', numbers(10), 'remote_user', '{password}', rand())",
|
|
f"remoteSecure('127.{{2..11}}', 'default', 'remote_table', 'remote_user', '{password}')",
|
|
f"remoteSecure('127.{{2..11}}', 'default', 'remote_table', 'remote_user', rand())",
|
|
]
|
|
|
|
for i, table_function in enumerate(table_functions):
|
|
node.query(f"CREATE TABLE tablefunc{i} (x int) AS {table_function}")
|
|
|
|
check_logs(
|
|
must_contain=[
|
|
"CREATE TABLE tablefunc0 (`x` int) AS mysql('mysql57:3306', 'mysql_db', 'mysql_table', 'mysql_user', '[HIDDEN]')",
|
|
"CREATE TABLE tablefunc1 (`x` int) AS postgresql('postgres1:5432', 'postgres_db', 'postgres_table', 'postgres_user', '[HIDDEN]')",
|
|
"CREATE TABLE tablefunc2 (`x` int) AS mongodb('mongo1:27017', 'mongo_db', 'mongo_col', 'mongo_user', '[HIDDEN]', 'x int')",
|
|
"CREATE TABLE tablefunc3 (x int) AS s3('http://minio1:9001/root/data/test1.csv')",
|
|
"CREATE TABLE tablefunc4 (x int) AS s3('http://minio1:9001/root/data/test2.csv', 'CSV')",
|
|
"CREATE TABLE tablefunc5 (`x` int) AS s3('http://minio1:9001/root/data/test3.csv', 'minio', '[HIDDEN]')",
|
|
"CREATE TABLE tablefunc6 (x int) AS s3('http://minio1:9001/root/data/test4.csv', 'CSV', 'x int')",
|
|
"CREATE TABLE tablefunc7 (x int) AS s3('http://minio1:9001/root/data/test5.csv.gz', 'CSV', 'x int', 'gzip')",
|
|
"CREATE TABLE tablefunc8 (`x` int) AS s3('http://minio1:9001/root/data/test6.csv', 'minio', '[HIDDEN]', 'CSV')",
|
|
"CREATE TABLE tablefunc9 (`x` int) AS s3('http://minio1:9001/root/data/test7.csv', 'minio', '[HIDDEN]', 'CSV', 'x int')",
|
|
"CREATE TABLE tablefunc10 (`x` int) AS s3('http://minio1:9001/root/data/test8.csv.gz', 'minio', '[HIDDEN]', 'CSV', 'x int', 'gzip')",
|
|
"CREATE TABLE tablefunc11 (`x` int) AS s3Cluster('test_shard_localhost', 'http://minio1:9001/root/data/test1.csv', 'minio', '[HIDDEN]')",
|
|
"CREATE TABLE tablefunc12 (x int) AS s3Cluster('test_shard_localhost', 'http://minio1:9001/root/data/test2.csv', 'CSV', 'x int')",
|
|
"CREATE TABLE tablefunc13 (`x` int) AS s3Cluster('test_shard_localhost', 'http://minio1:9001/root/data/test3.csv', 'minio', '[HIDDEN]', 'CSV')",
|
|
"CREATE TABLE tablefunc14 (x int) AS remote('127.{2..11}', default.remote_table)",
|
|
"CREATE TABLE tablefunc15 (x int) AS remote('127.{2..11}', default.remote_table, rand())",
|
|
"CREATE TABLE tablefunc16 (x int) AS remote('127.{2..11}', default.remote_table, 'remote_user')",
|
|
"CREATE TABLE tablefunc17 (`x` int) AS remote('127.{2..11}', default.remote_table, 'remote_user', '[HIDDEN]')",
|
|
"CREATE TABLE tablefunc18 (x int) AS remote('127.{2..11}', default.remote_table, 'remote_user', rand())",
|
|
"CREATE TABLE tablefunc19 (`x` int) AS remote('127.{2..11}', default.remote_table, 'remote_user', '[HIDDEN]', rand())",
|
|
"CREATE TABLE tablefunc20 (`x` int) AS remote('127.{2..11}', 'default.remote_table', 'remote_user', '[HIDDEN]', rand())",
|
|
"CREATE TABLE tablefunc21 (`x` int) AS remote('127.{2..11}', 'default', 'remote_table', 'remote_user', '[HIDDEN]', rand())",
|
|
"CREATE TABLE tablefunc22 (`x` int) AS remote('127.{2..11}', numbers(10), 'remote_user', '[HIDDEN]', rand())",
|
|
"CREATE TABLE tablefunc23 (`x` int) AS remoteSecure('127.{2..11}', 'default', 'remote_table', 'remote_user', '[HIDDEN]')",
|
|
"CREATE TABLE tablefunc24 (x int) AS remoteSecure('127.{2..11}', 'default', 'remote_table', 'remote_user', rand())",
|
|
],
|
|
must_not_contain=[password],
|
|
)
|
|
|
|
for i in range(0, len(table_functions)):
|
|
node.query(f"DROP TABLE tablefunc{i}")
|
|
|
|
|
|
def test_encryption_functions():
|
|
plaintext = new_password()
|
|
cipher = new_password()
|
|
key = new_password(32)
|
|
iv8 = new_password(8)
|
|
iv16 = new_password(16)
|
|
add = new_password()
|
|
|
|
encryption_functions = [
|
|
f"encrypt('aes-256-ofb', '{plaintext}', '{key}')",
|
|
f"encrypt('aes-256-ofb', '{plaintext}', '{key}', '{iv16}')",
|
|
f"encrypt('aes-256-gcm', '{plaintext}', '{key}', '{iv8}')",
|
|
f"encrypt('aes-256-gcm', '{plaintext}', '{key}', '{iv8}', '{add}')",
|
|
f"decrypt('aes-256-ofb', '{cipher}', '{key}', '{iv16}')",
|
|
f"aes_encrypt_mysql('aes-256-ofb', '{plaintext}', '{key}', '{iv16}')",
|
|
f"aes_decrypt_mysql('aes-256-ofb', '{cipher}', '{key}', '{iv16}')",
|
|
f"tryDecrypt('aes-256-ofb', '{cipher}', '{key}', '{iv16}')",
|
|
]
|
|
|
|
for encryption_function in encryption_functions:
|
|
node.query(f"SELECT {encryption_function}")
|
|
|
|
check_logs(
|
|
must_contain=[
|
|
"SELECT encrypt('aes-256-ofb', '[HIDDEN]')",
|
|
"SELECT encrypt('aes-256-gcm', '[HIDDEN]')",
|
|
"SELECT decrypt('aes-256-ofb', '[HIDDEN]')",
|
|
"SELECT aes_encrypt_mysql('aes-256-ofb', '[HIDDEN]')",
|
|
"SELECT aes_decrypt_mysql('aes-256-ofb', '[HIDDEN]')",
|
|
"SELECT tryDecrypt('aes-256-ofb', '[HIDDEN]')",
|
|
],
|
|
must_not_contain=[plaintext, cipher, key, iv8, iv16, add],
|
|
)
|
|
|
|
|
|
def test_create_dictionary():
|
|
password = new_password()
|
|
|
|
node.query(
|
|
f"CREATE DICTIONARY dict1 (n int DEFAULT 0, m int DEFAULT 1) PRIMARY KEY n "
|
|
f"SOURCE(CLICKHOUSE(HOST 'localhost' PORT 9000 USER 'user1' TABLE 'test' PASSWORD '{password}' DB 'default')) "
|
|
f"LIFETIME(MIN 0 MAX 10) LAYOUT(FLAT())"
|
|
)
|
|
|
|
check_logs(
|
|
must_contain=[
|
|
"CREATE DICTIONARY dict1 (`n` int DEFAULT 0, `m` int DEFAULT 1) PRIMARY KEY n "
|
|
"SOURCE(CLICKHOUSE(HOST 'localhost' PORT 9000 USER 'user1' TABLE 'test' PASSWORD '[HIDDEN]' DB 'default')) "
|
|
"LIFETIME(MIN 0 MAX 10) LAYOUT(FLAT())"
|
|
],
|
|
must_not_contain=[password],
|
|
)
|
|
|
|
node.query("DROP DICTIONARY dict1")
|
|
|
|
|
|
def test_backup_to_s3():
|
|
node.query("CREATE TABLE temptbl (x int) ENGINE=Log")
|
|
password = new_password()
|
|
|
|
queries = [
|
|
f"BACKUP TABLE temptbl TO S3('http://minio1:9001/root/data/backups/backup1', 'minio', '{password}')",
|
|
f"RESTORE TABLE temptbl AS temptbl2 FROM S3('http://minio1:9001/root/data/backups/backup1', 'minio', '{password}')",
|
|
]
|
|
|
|
for query in queries:
|
|
# query_and_get_answer_with_error() is used here because we don't want to stop on error "Cannot connect to AWS".
|
|
# We test logging here and not actual work with AWS server.
|
|
node.query_and_get_answer_with_error(query)
|
|
|
|
check_logs(
|
|
must_contain=[
|
|
"BACKUP TABLE temptbl TO S3('http://minio1:9001/root/data/backups/backup1', 'minio', '[HIDDEN]')",
|
|
"RESTORE TABLE temptbl AS temptbl2 FROM S3('http://minio1:9001/root/data/backups/backup1', 'minio', '[HIDDEN]')",
|
|
],
|
|
must_not_contain=[password],
|
|
)
|
|
|
|
node.query("DROP TABLE IF EXISTS temptbl")
|
|
node.query("DROP TABLE IF EXISTS temptbl2")
|
|
|
|
|
|
def test_on_cluster():
|
|
password = new_password()
|
|
|
|
node.query(
|
|
f"CREATE TABLE table_oncl ON CLUSTER 'test_shard_localhost' (x int) ENGINE = MySQL('mysql57:3307', 'mysql_db', 'mysql_table', 'mysql_user', '{password}')"
|
|
)
|
|
|
|
check_logs(
|
|
must_contain=[
|
|
"CREATE TABLE table_oncl ON CLUSTER test_shard_localhost (`x` int) ENGINE = MySQL('mysql57:3307', 'mysql_db', 'mysql_table', 'mysql_user', '[HIDDEN]')",
|
|
],
|
|
must_not_contain=[password],
|
|
)
|
|
|
|
# Check logs of DDLWorker during executing of this query.
|
|
assert node.contains_in_log(
|
|
"DDLWorker: Processing task .*CREATE TABLE default\\.table_oncl UUID '[0-9a-fA-F-]*' (\\`x\\` Int32) ENGINE = MySQL('mysql57:3307', 'mysql_db', 'mysql_table', 'mysql_user', '\\[HIDDEN\\]')"
|
|
)
|
|
assert node.contains_in_log(
|
|
"DDLWorker: Executing query: .*CREATE TABLE default\\.table_oncl UUID '[0-9a-fA-F-]*' (\\`x\\` Int32) ENGINE = MySQL('mysql57:3307', 'mysql_db', 'mysql_table', 'mysql_user', '\\[HIDDEN\\]')"
|
|
)
|
|
assert node.contains_in_log(
|
|
"executeQuery: .*CREATE TABLE default\\.table_oncl UUID '[0-9a-fA-F-]*' (\\`x\\` Int32) ENGINE = MySQL('mysql57:3307', 'mysql_db', 'mysql_table', 'mysql_user', '\\[HIDDEN\\]')"
|
|
)
|
|
assert node.contains_in_log(
|
|
"DDLWorker: Executed query: .*CREATE TABLE default\\.table_oncl UUID '[0-9a-fA-F-]*' (\\`x\\` Int32) ENGINE = MySQL('mysql57:3307', 'mysql_db', 'mysql_table', 'mysql_user', '\\[HIDDEN\\]')"
|
|
)
|
|
assert system_query_log_contains_search_pattern(
|
|
"%CREATE TABLE default.table_oncl UUID \\'%\\' (`x` Int32) ENGINE = MySQL(\\'mysql57:3307\\', \\'mysql_db\\', \\'mysql_table\\', \\'mysql_user\\', \\'[HIDDEN]\\')"
|
|
)
|
|
|
|
node.query(f"DROP TABLE table_oncl")
|