Skip to content

Commit 75534b6

Browse files
authored
First implementation of network namespaces in addr module (#596)
1 parent dfd89ab commit 75534b6

File tree

2 files changed

+63
-8
lines changed

2 files changed

+63
-8
lines changed

test/test_modules.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -590,3 +590,15 @@ def test_addr(host):
590590

591591
for ip in google_addr.ip_addresses:
592592
assert isinstance(ip_address(ip), (IPv4Address, IPv6Address))
593+
594+
595+
@pytest.mark.testinfra_hosts("ansible://debian_buster")
596+
def test_addr_namespace(host):
597+
namespace_lookup = host.addr("localhost", "ns1")
598+
# ns1 network namespace does not exist so everything is false
599+
assert not namespace_lookup.namespace_exists
600+
assert not namespace_lookup.is_reachable
601+
assert not namespace_lookup.is_resolvable
602+
with pytest.raises(NotImplementedError):
603+
# nc is not available so an error is raised
604+
assert not namespace_lookup.port("443").is_reachable

testinfra/modules/addr.py

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,20 @@ def __init__(self, addr, port):
2020

2121
@property
2222
def is_reachable(self):
23-
if not self._addr._host.exists('nc'):
23+
"""Return if port is reachable"""
24+
if not self._addr._host.exists("nc"):
25+
if self._addr.namespace:
26+
# in this case cannot use namespace
27+
raise NotImplementedError(
28+
"nc command not available, namespace cannot be used")
2429
# Fallback to bash if netcat is not available
2530
return self._addr.run_expect(
2631
[0, 1, 124],
2732
"timeout 1 bash -c 'cat < /dev/null > /dev/tcp/%s/%s'",
2833
self._addr.name, self._port).rc == 0
2934

30-
return self._addr.run(
31-
"nc -w 1 -z %s %s", self._addr.name, self._port).rc == 0
35+
return self._addr.run("{}nc -w 1 -z {} {}".format(self._addr._prefix,
36+
self._addr.name, self._port)).rc == 0
3237

3338

3439
class Addr(Module):
@@ -47,17 +52,54 @@ class Addr(Module):
4752
True
4853
>>> google.port(666).is_reachable
4954
False
50-
"""
5155
52-
def __init__(self, name):
56+
Can also be use within a network namespace_.
57+
58+
>>> localhost = host.addr("localhost", "ns1")
59+
>>> localhost.is_resolvable
60+
True
61+
62+
Network namespaces can only be used if ip_ command is available
63+
because in this case, the module use ip-netns_ as command prefix.
64+
In the other case, it will raise NotImplementedError.
65+
66+
.. _namespace: https://man7.org/linux/man-pages/man7/namespaces.7.html
67+
.. _ip: https://man7.org/linux/man-pages/man8/ip.8.html
68+
.. _ip-netns: https://man7.org/linux/man-pages/man8/ip-netns.8.html
69+
"""
70+
def __init__(self, name, namespace=None):
5371
self._name = name
72+
self._namespace = namespace
73+
if self.namespace and not self._host.exists("ip"):
74+
raise NotImplementedError(
75+
"ip command not available, namespace cannot be used")
5476
super().__init__()
5577

5678
@property
5779
def name(self):
5880
"""Return host name"""
5981
return self._name
6082

83+
@property
84+
def namespace(self):
85+
"""Return network namespace"""
86+
return self._namespace
87+
88+
@property
89+
def _prefix(self):
90+
"""Return the prefix to use for commands"""
91+
prefix = ""
92+
if self.namespace:
93+
prefix = "ip netns exec %s " % self.namespace
94+
return prefix
95+
96+
@property
97+
def namespace_exists(self):
98+
"""Test if the network namespace exists"""
99+
# could use ip netns list instead
100+
return self.namespace and self.run_test("test -e /var/run/netns/%s",
101+
self.namespace).rc == 0
102+
61103
@property
62104
def is_resolvable(self):
63105
"""Return if address is resolvable"""
@@ -66,8 +108,8 @@ def is_resolvable(self):
66108
@property
67109
def is_reachable(self):
68110
"""Return if address is reachable"""
69-
return self.run_expect([0, 1, 2],
70-
"ping -W 1 -c 1 %s", self.name).rc == 0
111+
return self.run_expect([0, 1, 2], "{}ping -W 1 -c 1 {}".format(
112+
self._prefix, self.name)).rc == 0
71113

72114
@property
73115
def ip_addresses(self):
@@ -92,6 +134,7 @@ def __repr__(self):
92134
return "<addr %s>" % (self.name,)
93135

94136
def _resolve(self, method):
95-
result = self.run_expect([0, 2], "getent %s %s", method, self.name)
137+
result = self.run_expect([0, 1, 2], "{}getent {} {}".format(
138+
self._prefix, method, self.name))
96139
lines = result.stdout.splitlines()
97140
return list(set(line.split()[0] for line in lines))

0 commit comments

Comments
 (0)