diff --git a/sambacc/config.py b/sambacc/config.py index 2be21211..153b1f80 100644 --- a/sambacc/config.py +++ b/sambacc/config.py @@ -316,6 +316,7 @@ def ctdb_config(self) -> dict[str, str]: ctdb.setdefault("realtime_scheduling", "false") # this whole thing really needs to be turned into a real object type ctdb.setdefault("public_addresses", []) + ctdb.setdefault("ctdb_port", 0) return ctdb def domain(self) -> DomainConfig: diff --git a/sambacc/ctdb.py b/sambacc/ctdb.py index bdf74572..a6983db6 100644 --- a/sambacc/ctdb.py +++ b/sambacc/ctdb.py @@ -39,6 +39,7 @@ CTDB_CONF: str = "/etc/ctdb/ctdb.conf" CTDB_NODES: str = "/etc/ctdb/nodes" +ETC_SERVICES: str = "/etc/services" class ClusterMetaObject(typing.Protocol): @@ -181,6 +182,28 @@ def read_ctdb_nodes(path: str = CTDB_NODES) -> list[str]: return entries +def _svc_match(service_name: str, line: str) -> bool: + if not line.strip() or line.startswith("#"): + return False + first = line.split()[0] + return first == service_name + + +def ensure_ctdb_port_in_services(port: int, path: str) -> None: + try: + with open(path, "r") as fh: + lines = [line.strip() for line in fh] + except FileNotFoundError: + lines = [] + cleaned = [line for line in lines if not _svc_match("ctdb", line)] + cleaned.append(f"ctdb {port}/tcp # custom ctdb port") + cleaned.append(f"ctdb {port}/udp # custom ctdb port") + with open(path, "w") as fh: + for line in cleaned: + fh.write(line) + fh.write("\n") + + class PublicAddrAssignment(typing.TypedDict): address: str interfaces: list[str] @@ -651,6 +674,7 @@ def ensure_ctdbd_etc_files( src_path: str = SHARE_DIR, *, iconfig: typing.Optional[config.InstanceConfig] = None, + services_path: str = ETC_SERVICES, ) -> None: """Ensure certain files that ctdbd expects to exist in its etc dir do exist. @@ -664,6 +688,7 @@ def ensure_ctdbd_etc_files( link_legacy_scripts = ["00.ctdb.script"] public_addresses: list[PublicAddrAssignment] = [] + custom_ctdb_port = 0 if iconfig: ctdb_conf = iconfig.ctdb_config() # todo: when we have a real config object for ctdb conf we can drop @@ -671,6 +696,7 @@ def ensure_ctdbd_etc_files( public_addresses = typing.cast( list[PublicAddrAssignment], ctdb_conf.get("public_addresses", []) ) + custom_ctdb_port = typing.cast(int, ctdb_conf.get("ctdb_port", 0)) if public_addresses: link_legacy_scripts.append("10.interface.script") @@ -700,6 +726,8 @@ def ensure_ctdbd_etc_files( if public_addresses: pa_path = os.path.join(etc_path, "public_addresses") _ensure_public_addresses_file(pa_path, public_addresses) + if custom_ctdb_port: + ensure_ctdb_port_in_services(custom_ctdb_port, services_path) _SRC_TDB_FILES = [ diff --git a/tests/test_ctdb.py b/tests/test_ctdb.py index 61247c93..094bc2f2 100644 --- a/tests/test_ctdb.py +++ b/tests/test_ctdb.py @@ -604,3 +604,34 @@ def test_check_nodestatus(tmp_path): else: _, status = os.waitpid(pid, 0) assert status != 0 + + +def test_ensure_ctdb_port_in_services(tmp_path): + fname = tmp_path / "fake.services" + with open(fname, "w") as fh: + # random snippets from a real /etc/services + print("# this is a comment", file=fh) + print("ftp 21/tcp", file=fh) + print("ftp 21/udp fsp fspd", file=fh) + print("ssh 22/tcp", file=fh) + print("ssh 22/udp", file=fh) + print("telnet 23/tcp", file=fh) + print("telnet 23/udp", file=fh) + print("ctdb 4379/tcp # CTDB", file=fh) + print("ctdb 4379/udp # CTDB", file=fh) + print("roce 4791/udp", file=fh) + print("# a random comment...", file=fh) + print("snss 11171/udp", file=fh) + print("oemcacao-jmxmp 11172/tcp", file=fh) + + ctdb.ensure_ctdb_port_in_services(9099, fname) + + with open(fname) as fh: + content = fh.read() + assert "ctdb 9099/tcp" in content + assert "ctdb 9099/udp" in content + assert "ctdb 4379/tcp" not in content + assert "ctdb 4379/udp" not in content + # others + assert "ssh 22/tcp" in content + assert "snss 11171/udp" in content