From 8c5ce421eab7e5a4abd8e19ee8a8aba5abdefd99 Mon Sep 17 00:00:00 2001 From: Kyr Shatskyy Date: Wed, 19 Mar 2025 14:49:51 +0100 Subject: [PATCH] orchestra/remote: add resolve_ip method Add utility method to resolve a hostname from within remote host. This is useful to resolve ip address of the remote host itself, because getting ip address from transport object of ssh is not suitable because it may have only bastion host and port, which is not relevant for the purpose of configuring a cluster. Signed-off-by: Kyr Shatskyy --- teuthology/orchestra/remote.py | 40 ++++++++++++++++++++++++ teuthology/orchestra/test/test_remote.py | 25 +++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/teuthology/orchestra/remote.py b/teuthology/orchestra/remote.py index 4212ab9fb..36d695e3a 100644 --- a/teuthology/orchestra/remote.py +++ b/teuthology/orchestra/remote.py @@ -461,6 +461,46 @@ class Remote(RemoteShell): return raise RuntimeError("Could not determine interface/CIDR!") + + def resolve_ip(self, name=None, ipv='4') -> str: + """ + Resolve IP address of the remote host via remote host + + Because remote object maybe behind bastion host we need + the remote host address resolvable from remote side. + So in order to the ip address we just call `host` remotely + and parse output like: + 'smithi001.front.sepia.ceph.com has address 172.21.15.1\n' + + :param name: hostname to resolve, by defaults remote host itself. + :param ipv: the IP version, 4 or 6, defaults to 4. + + :raises: :class:`Exception`: when the hostname cannot be resolved. + :raises: :class:`ValueError`: when the ipv argument mismatch 4 or 6. + + :returns: str object, the ip addres of the remote host. + """ + hostname = name or self.hostname + version = str(ipv) + if version in ['4', '6']: + remote_host_ip = self.sh(f'host -{ipv} {hostname}') + else: + raise ValueError(f'Unknown IP version {ipv}, expected 4 or 6') + # `host -4` or `host -6` may have multiline output: a host can have + # several address; thus try and find the first suitable + for info in remote_host_ip.split("\n"): + if version == '4' and 'has address' in info: + (host, ip) = info.strip().split(' has address ') + if hostname in host: + return ip + elif version == '6' and 'has IPv6 address' in info: + (host, ip) = info.strip().split(' has IPv6 address ') + if hostname in host: + return ip + else: + raise Exception(f'Cannot get IPv{ipv} address for the host "{hostname}" via remote "{self.hostname}"') + + @property def hostname(self): if not hasattr(self, '_hostname'): diff --git a/teuthology/orchestra/test/test_remote.py b/teuthology/orchestra/test/test_remote.py index ef9fb4406..1328bc83e 100644 --- a/teuthology/orchestra/test/test_remote.py +++ b/teuthology/orchestra/test/test_remote.py @@ -221,6 +221,31 @@ class TestRemote(object): rem2._runner = m_run assert not rem2.is_container + @patch("teuthology.orchestra.remote.Remote.sh") + def test_resolve_ip(self, m_sh): + r = remote.Remote(name="jdoe@xyzzy.example.com", ssh=self.m_ssh) + m_sh.return_value = "smithi001.front.sepia.ceph.com has address 172.21.15.1\n" + ip4 = remote.Remote.resolve_ip(r, 'smithi001') + assert ip4 == "172.21.15.1" + m_sh.return_value = "\n" + try: + ip4 = remote.Remote.resolve_ip(r, 'smithi001') + except Exception as e: + assert 'Cannot get IPv4 address' in str(e) + try: + ip4 = remote.Remote.resolve_ip(r, 'smithi001', 5) + except Exception as e: + assert 'Unknown IP version' in str(e) + + m_sh.return_value = ("google.com has address 142.251.37.14\n" + "google.com has IPv6 address 2a00:1450:4016:80b::200e\n" + "google.com mail is handled by 10 smtp.google.com.\n") + ip4 = remote.Remote.resolve_ip(r, 'google.com') + assert ip4 == "142.251.37.14" + ip6 = remote.Remote.resolve_ip(r, 'google.com', '6') + assert ip6 == "2a00:1450:4016:80b::200e" + + @patch("teuthology.orchestra.remote.Remote.run") def test_write_file(self, m_run): file = "fakefile" -- 2.47.3