Skip to content

Commit 3970515

Browse files
committed
First version of the ip module
1 parent 50f6027 commit 3970515

File tree

3 files changed

+168
-1
lines changed

3 files changed

+168
-1
lines changed

test/test_modules.py

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -633,7 +633,7 @@ def test_addr_namespace(host):
633633
)
634634
def test_interface(host, family):
635635
# exist
636-
assert host.interface("eth0", family=family).exists
636+
assert host.interface("tap0", family=family).exists
637637
assert not host.interface("does_not_exist", family=family).exists
638638
# addresses
639639
addresses = host.interface.default(family).addresses
@@ -648,3 +648,40 @@ def test_interface(host, family):
648648
default_itf = host.interface.default(family)
649649
assert default_itf.name == "eth0"
650650
assert default_itf.exists
651+
652+
653+
def test_ip_links(host):
654+
assert host.ip.exists
655+
iphandle = host.ip()
656+
657+
links = iphandle.links()
658+
assert len(links) > 0 and len(links) < 4
659+
660+
assert links[0].get("ifname") and links[0].get("ifindex")
661+
662+
663+
def test_ip_routes(host):
664+
assert host.ip.exists
665+
iphandle = host.ip()
666+
667+
routes = iphandle.routes()
668+
assert len(routes) > 0
669+
670+
671+
def test_ip_rules(host):
672+
assert host.ip.exists
673+
iphandle = host.ip()
674+
675+
rules = iphandle.rules()
676+
assert len(rules) > 0 and len(rules) < 4
677+
assert rules[0].get("priority") == 0
678+
assert rules[0].get("src") == "all"
679+
assert rules[0].get("table") == "local"
680+
681+
682+
def test_ip_tunnels(host):
683+
assert host.ip.exists
684+
iphandle = host.ip()
685+
686+
tunnels = iphandle.tunnels()
687+
assert len(tunnels) == 0

testinfra/modules/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"group": "group:Group",
2828
"interface": "interface:Interface",
2929
"iptables": "iptables:Iptables",
30+
"ip": "ip:IP",
3031
"mount_point": "mountpoint:MountPoint",
3132
"package": "package:Package",
3233
"pip": "pip:Pip",

testinfra/modules/ip.py

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
# Licensed under the Apache License, Version 2.0 (the "License");
2+
# you may not use this file except in compliance with the License.
3+
# You may obtain a copy of the License at
4+
#
5+
# http://www.apache.org/licenses/LICENSE-2.0
6+
#
7+
# Unless required by applicable law or agreed to in writing, software
8+
# distributed under the License is distributed on an "AS IS" BASIS,
9+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
# See the License for the specific language governing permissions and
11+
# limitations under the License.
12+
13+
import functools
14+
import json
15+
import re
16+
17+
from testinfra.modules.base import Module
18+
19+
class IP(Module):
20+
"""Test network configuration via ip commands
21+
22+
>>> host.ip.rules()
23+
24+
host.ip.rules(from,to,tos,fwmark,iif,oif,pref, uidrange, ipproto, sport, dport)
25+
host.ip.routes(table, device, scope, proto, src, metric)
26+
host.ip.links()
27+
host.ip.addresses()
28+
host.ip.tunnels()
29+
30+
Optionally, the protocol family can be provided:
31+
>>> host.ip.routes("inet6", table="main")
32+
...FIX
33+
34+
Optionally, this can work inside a different network namespace:
35+
>>> host.ip.routes("inet6", "vpn")
36+
...FIX
37+
"""
38+
39+
def __init__(self, family=None, netns=None):
40+
self.family = family
41+
self.netns = netns
42+
super().__init__()
43+
44+
@property
45+
def exists(self):
46+
raise NotImplementedError
47+
48+
def addresses(self):
49+
"""Return the addresses associated with interfaces
50+
"""
51+
raise NotImplementedError
52+
53+
def links(self):
54+
"""Return links and their state
55+
"""
56+
raise NotImplementedError
57+
58+
def routes(self):
59+
"""Return the routes associated with the routing table
60+
"""
61+
raise NotImplementedError
62+
63+
def rules(self):
64+
"""Return all configured ip rules
65+
"""
66+
raise NotImplementedError
67+
68+
def tunnels(self):
69+
"""Return all configured tunnels
70+
"""
71+
raise NotImplementedError
72+
73+
def __repr__(self):
74+
return "<ip>"
75+
76+
@classmethod
77+
def get_module_class(cls, host):
78+
if host.system_info.type == "linux":
79+
return LinuxIP
80+
raise NotImplementedError
81+
82+
class LinuxIP(IP):
83+
@functools.cached_property
84+
def _ip(self):
85+
ip_cmd = self.find_command("ip")
86+
if self.netns is not None:
87+
ip_cmd = f"{ip_cmd} netns exec {self.netns} {ip_cmd}"
88+
if self.family is not None:
89+
ip_cmd = f"{ip_cmd} -f {self.family}"
90+
return ip_cmd
91+
92+
@property
93+
def exists(self):
94+
return self.run_test("{} -V".format(self._ip), self.name).rc == 0
95+
96+
def addresses(self):
97+
"""Return the addresses associated with interfaces
98+
"""
99+
cmd = f"{self._ip} --json address show"
100+
out = self.check_output(cmd)
101+
return json.loads(out)
102+
103+
def links(self):
104+
"""Return links and their state
105+
"""
106+
cmd = f"{self._ip} --json link show"
107+
out = self.check_output(cmd)
108+
return json.loads(out)
109+
110+
def routes(self):
111+
"""Return the routes installed
112+
"""
113+
cmd = f"{self._ip} --json route show table all"
114+
out = self.check_output(cmd)
115+
return json.loads(out)
116+
117+
def rules(self):
118+
"""Return the rules our routing policy consists of
119+
"""
120+
cmd = f"{self._ip} --json rule show"
121+
out = self.check_output(cmd)
122+
return json.loads(out)
123+
124+
def tunnels(self):
125+
"""Return all configured tunnels
126+
"""
127+
cmd = f"{self._ip} --json tunnel show"
128+
out = self.check_output(cmd)
129+
return json.loads(out)

0 commit comments

Comments
 (0)