Files
zooqueen fce0ee72e1 datastore: fix XML config tag mismatch (</clickhouse> closing tags)
Runtime regression from the final lowercase pass: it renamed <clickhouse> opening tags to
<datastore> but the / exclusion (protecting /clickhouse paths) left </clickhouse> closing tags,
so embedded + shipped configs became <datastore>...</clickhouse> -> SAXParseException 'Tag
mismatch' at runtime (compile was fine; XML is data). Renamed all <clickhouse/</clickhouse XML
tags -> datastore (101 closes across configs, embedded strings, keeper configs). Path strings
(/var/lib/clickhouse) have no angle bracket and are untouched. Build compiled; this fixes runtime
config parsing.
2026-06-22 00:25:01 -07:00

135 lines
3.5 KiB
Python

import os
import os.path
import tempfile
import pytest
from helpers.client import Client
from helpers.cluster import ClickHouseCluster
SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
MAX_RETRY = 5
cluster = ClickHouseCluster(__file__)
instance = cluster.add_instance(
"node",
main_configs=[
"configs/ssl_config.xml",
"certs/self-key.pem",
"certs/self-cert.pem",
"certs/ca-cert.pem",
],
with_zookeeper=False,
)
node1 = cluster.add_instance(
"node1",
main_configs=[
"configs/ssl_config_strict.xml",
"certs/self-key.pem",
"certs/self-cert.pem",
"certs/ca-cert.pem",
],
with_zookeeper=False,
)
@pytest.fixture(scope="module", autouse=True)
def started_cluster():
try:
cluster.start()
yield cluster
finally:
cluster.shutdown()
config_default = """<datastore>
</datastore>"""
config_accept = """<datastore>
<accept-invalid-certificate>1</accept-invalid-certificate>
</datastore>"""
config_connection_accept = """<datastore>
<connections_credentials>
<connection>
<name>{ip_address}</name>
<accept-invalid-certificate>1</accept-invalid-certificate>
</connection>
</connections_credentials>
</datastore>"""
def execute_query_native(node, query, config):
fd, config_path = tempfile.mkstemp(
prefix="client_", suffix=".xml", dir=f"{SCRIPT_DIR}/configs"
)
try:
with os.fdopen(fd, "w") as f:
f.write(config)
client = Client(
node.ip_address,
9440,
command=cluster.client_bin_path,
secure=True,
config=config_path,
)
return client.query(query)
finally:
try:
os.remove(config_path)
except FileNotFoundError:
pass
def test_default():
with pytest.raises(Exception) as err:
execute_query_native(instance, "SELECT 1", config_default)
assert "certificate verify failed" in str(err.value)
def test_accept():
assert execute_query_native(instance, "SELECT 1", config_accept) == "1\n"
def test_connection_accept():
assert (
execute_query_native(
instance,
"SELECT 1",
config_connection_accept.format(ip_address=f"{instance.ip_address}"),
)
== "1\n"
)
def test_strict_reject():
with pytest.raises(Exception) as err:
execute_query_native(node1, "SELECT 1", "<datastore></datastore>")
assert "certificate verify failed" in str(err.value)
def test_strict_reject_with_config():
with pytest.raises(Exception) as err:
execute_query_native(node1, "SELECT 1", config_accept)
# Accept both error messages due to race condition in SSL handshake:
# - "alert certificate required": TCP layer transmits Alert before close() executes
# - "Connection reset by peer": close() executes before TCP layer transmits Alert from send buffer
# Race condition: send() is async (returns after copying to kernel buffer), close() may execute
# before TCP layer actually sends the Alert packet, causing RST to be sent instead
assert "alert certificate required" in str(err.value) or "Connection reset by peer" in str(err.value)
def test_strict_connection_reject():
with pytest.raises(Exception) as err:
execute_query_native(
node1,
"SELECT 1",
config_connection_accept.format(ip_address=f"{instance.ip_address}"),
)
assert "certificate verify failed" in str(err.value)