blob: 334ef5a60656ad883ae342d9f742b8ede5bbb31b [file]
"""
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
import importlib.util
import hashlib
import json
import sys
import tempfile
import unittest
from pathlib import Path
from types import SimpleNamespace
from unittest.mock import MagicMock, patch
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "shared"))
PATH = ROOT / "mpacks/doris/extensions/DORIS/1.0/services/DORIS/package/scripts/runtime.py"
spec = importlib.util.spec_from_file_location("doris_runtime_test", str(PATH))
doris = importlib.util.module_from_spec(spec)
spec.loader.exec_module(doris)
def parameters():
return {"component": "check", "hostname": "fe.test", "fe_host": "fe.test", "be_host": "be.test",
"fe_http_port": "8030", "fe_rpc_port": "9020", "fe_query_port": "9030",
"fe_edit_log_port": "9010", "be_port": "9060", "be_http_port": "8040",
"be_heartbeat_port": "9050", "be_brpc_port": "8060", "java_home": "/usr/lib/jvm/java-17-openjdk",
"fe_heap_size": "2G", "be_memory_limit": "2G", "root_password": "private-test-secret"}
def settings():
return ("check", "fe.test", "be.test", "fe.test", {"fe_http_port": 8030,
"fe_rpc_port": 9020, "fe_query_port": 9030, "fe_edit_log_port": 9010,
"be_port": 9060, "be_http_port": 8040, "be_heartbeat_port": 9050,
"be_brpc_port": 8060}, Path("/usr/lib/jvm/java-17-openjdk"), "2G", "2G", "private-test-secret")
class DorisTests(unittest.TestCase):
def test_active_fe_is_not_started_until_owned_bootstrap_completes(self):
with tempfile.TemporaryDirectory() as temporary:
data = Path(temporary).resolve()
(data / "fe").mkdir()
identity = {"phase": "PENDING", "fe_host": "fe.test",
"root_password_sha256": hashlib.sha256(b"private-test-secret").hexdigest()}
marker = data / "fe/identity.json"
marker.write_text(json.dumps(identity))
with patch.object(doris, "DATA", data), patch.object(doris, "settings", return_value=("fe",)+settings()[1:]), patch.object(
doris, "unit_observation", return_value={"active_state": "active"}):
with self.assertRaises(RuntimeError):
doris.execute({"parameters": {}, "action": "status"})
marker.write_text(json.dumps(dict(identity, phase="COMPLETE")))
self.assertEqual(doris.execute({"parameters": {}, "action": "status"})["systemd"]["active_state"], "active")
def test_configure_preserves_data_directories_and_nested_configuration(self):
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary).resolve()
home, data = root / "software", root / "data"
for relative in ("fe/bin", "fe/conf/ssl", "fe/lib", "fe/meta"):
(home / relative).mkdir(parents=True)
(home / "fe/bin/start_fe.sh").write_text("#!/bin/bash\n")
(home / "fe/conf/fe.conf").write_text("# upstream\n")
(home / "fe/conf/ssl/example.conf").write_text("trusted=true\n")
def directory(path, *_):
Path(path).mkdir(parents=True, exist_ok=True)
return Path(path)
values = ("fe",) + settings()[1:]
with patch.object(doris, "HOME", home), patch.object(doris, "DATA", data), patch.object(
doris, "settings", return_value=values), patch.object(doris, "installed"), patch.object(
doris, "directory", side_effect=directory), patch.object(
doris, "owned_file", side_effect=lambda path, content, *_: Path(path).write_text(content)), patch.object(
doris, "unit"), patch.object(doris, "reload_units"):
doris.configure(parameters())
self.assertFalse((data / "fe/meta").is_symlink())
self.assertTrue((data / "fe/lib").is_symlink())
self.assertEqual((data / "fe/conf/ssl/example.conf").read_text(), "trusted=true\n")
def test_ports_placement_and_password_are_validated(self):
with tempfile.TemporaryDirectory() as temporary:
java = Path(temporary).resolve()
(java / "bin").mkdir()
(java / "bin/java").touch()
(java / "release").write_text('JAVA_VERSION="17.0.16"\n')
with patch.object(doris, "path_value", return_value=java):
self.assertEqual(doris.settings(parameters())[1:3], ("fe.test", "be.test"))
for change in ({"root_password": "short"}, {"fe_host": "foreign.test", "component": "fe"},
{"fe_query_port": "0"}, {"be_memory_limit": "1G"},
{"fe_host": "same.test", "be_host": "same.test", "fe_http_port": "8040"}):
with self.subTest(change=change), self.assertRaises(ValueError):
doris.settings(dict(parameters(), **change))
def test_system_java_alias_resolves_before_no_link_validation(self):
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary).resolve()
real = root / "java-17-runtime"
(real / "bin").mkdir(parents=True)
(real / "bin/java").touch()
(real / "release").write_text('JAVA_VERSION="17.0.16"\n')
alias = root / "java-17-openjdk"
alias.symlink_to(real)
with patch.object(doris, "path_value", side_effect=lambda value, *_: Path(value)):
self.assertEqual(doris.settings(dict(parameters(), java_home=str(alias)))[5], real)
def test_inventory_uses_exact_fe_be_and_registration_ports(self):
frontend = {"Host": "fe.test", "Alive": "true", "IsMaster": "true", "EditLogPort": "9010"}
backend = {"Host": "be.test", "Alive": "true", "HeartbeatPort": "9050", "HttpPort": "8040"}
with patch.object(doris, "_addresses", side_effect=lambda host: {host}):
self.assertEqual(doris.validate_nodes([frontend], [backend], "fe.test", "be.test",
settings()[4])["be"], "be.test")
for wrong_fe, wrong_be in ((dict(frontend, Host="foreign.test"), backend),
(frontend, dict(backend, Alive="false")),
(frontend, dict(backend, HeartbeatPort="9051"))):
with self.subTest(fe=wrong_fe, be=wrong_be), self.assertRaises(ValueError):
doris.validate_nodes([wrong_fe], [wrong_be], "fe.test", "be.test", settings()[4])
def test_owned_sql_roundtrip_rejects_wrong_result_or_failed_cleanup(self):
value = {"execution_id": "12345678-1234-4234-8234-123456789abc", "parameters": parameters()}
rows = [[{"Host": "fe.test"}], [{"Host": "be.test"}], [], [], [],
[{"row_count": 1, "value_sum": 42}], [], []]
def run(observed):
client = MagicMock()
with patch.object(doris, "settings", return_value=settings()), patch.object(
doris, "connection", return_value=client), patch.object(doris, "query", side_effect=observed), patch.object(
doris, "validate_nodes", return_value={"fe": "fe.test", "be": "be.test"}):
return doris.check(value)
self.assertEqual(run(rows)["value_sum"], 42)
wrong = list(rows)
wrong[5] = [{"row_count": 2, "value_sum": 42}]
with self.assertRaises(doris.CheckFailure) as failure:
run(wrong)
self.assertEqual(failure.exception.observation["stage"], "read")
self.assertEqual(failure.exception.observation["cleanup"], True)
failed_cleanup = list(rows)
failed_cleanup[-1] = [{"Database": "ambari_mpack_12345678123442348234123456789abc"}]
with self.assertRaises(doris.CheckFailure) as failure:
run(failed_cleanup)
self.assertEqual(failure.exception.observation["cleanup"], False)
def test_initial_root_password_recovery_uses_exact_auth_error_code(self):
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary).resolve()
(root / "fe").mkdir()
identity = {"schema_version": 1, "version": "4.1.4", "fe_host": "fe.test",
"root_password_sha256": hashlib.sha256(b"private-test-secret").hexdigest(), "phase": "PENDING"}
(root / "fe/identity.json").write_text(json.dumps(identity))
access_denied = type("OperationalError", (Exception,), {})
empty = MagicMock()
authenticated = MagicMock()
with patch.object(doris, "DATA", root), patch.object(doris, "settings", return_value=settings()), patch.dict(
sys.modules, {"pymysql": SimpleNamespace(err=SimpleNamespace(OperationalError=access_denied))}), patch.object(
doris, "connection", side_effect=[access_denied(1045, "diagnostic noise"), empty, authenticated]):
self.assertEqual(doris.bootstrap_fe(parameters())["phase"], "COMPLETE")
self.assertEqual(json.loads((root / "fe/identity.json").read_text())["phase"], "COMPLETE")
empty.cursor.return_value.__enter__.return_value.execute.assert_called_once()
if __name__ == "__main__":
unittest.main()