Skip to content

Commit a3b9cff

Browse files
Danilo Egea Gondolfoslyon
Danilo Egea Gondolfo
authored andcommitted
tools/diff.py: add a temporary tool to test netplan diff
Also add a few integrations tests that use tools/diff.py to test the diff against a running system configured with Netplan.
1 parent 2e82f34 commit a3b9cff

File tree

2 files changed

+413
-0
lines changed

2 files changed

+413
-0
lines changed

tests/integration/diff.py

+283
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
#!/usr/bin/python3
2+
# Netplan Diff integration tests.
3+
#
4+
# These need to be run in a VM and do change the system
5+
# configuration.
6+
#
7+
# Copyright (C) 2023 Canonical, Ltd.
8+
# Author: Danilo Egea Gondolfo <[email protected]>
9+
#
10+
# This program is free software; you can redistribute it and/or modify
11+
# it under the terms of the GNU General Public License as published by
12+
# the Free Software Foundation; version 3.
13+
#
14+
# This program is distributed in the hope that it will be useful,
15+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
# GNU General Public License for more details.
18+
#
19+
# You should have received a copy of the GNU General Public License
20+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
21+
22+
import json
23+
import sys
24+
import subprocess
25+
import unittest
26+
27+
from base import IntegrationTestsBase, test_backends
28+
29+
30+
class _CommonTests():
31+
32+
def test_missing_netplan_ips(self):
33+
self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'dummy0'], stderr=subprocess.DEVNULL)
34+
with open(self.config, 'w') as f:
35+
f.write('''network:
36+
renderer: %(r)s
37+
version: 2
38+
dummy-devices:
39+
dummy0:
40+
dhcp4: false
41+
dhcp6: false''' % {'r': self.backend})
42+
self.generate_and_settle(['dummy0'])
43+
44+
subprocess.call(['ip', 'addr', 'add', '1.2.3.4/24', 'dev', 'dummy0'])
45+
subprocess.call(['ip', 'addr', 'add', '1.2.3.40/24', 'dev', 'dummy0'])
46+
47+
diff = json.loads(subprocess.check_output(['python3', 'tools/diff.py', '-j']))
48+
diff_ips = diff['interfaces']['dummy0']['netplan_state'].get('missing_addresses')
49+
self.assertIn('1.2.3.4/24', diff_ips)
50+
self.assertIn('1.2.3.40/24', diff_ips)
51+
52+
def test_missing_system_ips(self):
53+
self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'dummy0'], stderr=subprocess.DEVNULL)
54+
with open(self.config, 'w') as f:
55+
f.write('''network:
56+
renderer: %(r)s
57+
version: 2
58+
dummy-devices:
59+
dummy0:
60+
addresses:
61+
- 1.2.3.4/24
62+
- 1.2.3.40/24
63+
dhcp4: false
64+
dhcp6: false''' % {'r': self.backend})
65+
self.generate_and_settle(['dummy0'])
66+
67+
subprocess.call(['ip', 'addr', 'del', '1.2.3.4/24', 'dev', 'dummy0'])
68+
subprocess.call(['ip', 'addr', 'del', '1.2.3.40/24', 'dev', 'dummy0'])
69+
70+
diff = json.loads(subprocess.check_output(['python3', 'tools/diff.py', '-j']))
71+
diff_ips = diff['interfaces']['dummy0']['system_state'].get('missing_addresses', [])
72+
self.assertIn('1.2.3.4/24', diff_ips)
73+
self.assertIn('1.2.3.40/24', diff_ips)
74+
75+
def test_missing_sytem_ips_with_match(self):
76+
self.setup_eth(None)
77+
with open(self.config, 'w') as f:
78+
f.write('''network:
79+
renderer: %(r)s
80+
ethernets:
81+
mynic:
82+
match: {name: %(e2c)s}
83+
addresses:
84+
- 1.2.3.4/24
85+
- 1.2.3.40/24
86+
dhcp6: false
87+
dhcp4: false''' % {'r': self.backend, 'e2c': self.dev_e2_client})
88+
self.generate_and_settle([self.dev_e2_client])
89+
90+
subprocess.call(['ip', 'addr', 'del', '1.2.3.4/24', 'dev', self.dev_e2_client])
91+
subprocess.call(['ip', 'addr', 'del', '1.2.3.40/24', 'dev', self.dev_e2_client])
92+
93+
diff = json.loads(subprocess.check_output(['python3', 'tools/diff.py', '-j']))
94+
diff_ips = diff['interfaces'][self.dev_e2_client]['system_state'].get('missing_addresses', [])
95+
netdef_id = diff['interfaces'][self.dev_e2_client]['id']
96+
self.assertIn('1.2.3.4/24', diff_ips)
97+
self.assertIn('1.2.3.40/24', diff_ips)
98+
self.assertEqual(netdef_id, 'mynic')
99+
100+
def test_missing_interfaces(self):
101+
self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'dummy1'], stderr=subprocess.DEVNULL)
102+
with open(self.config, 'w') as f:
103+
f.write('''network:
104+
renderer: %(r)s
105+
version: 2
106+
ethernets:
107+
eth123456:
108+
dhcp4: false
109+
dhcp6: false''' % {'r': self.backend})
110+
111+
# Add an extra interface not present in any YAML
112+
subprocess.check_call(['ip', 'link', 'add', 'type', 'dummy', 'dev', 'dummy1'])
113+
subprocess.check_call(['ip', 'link', 'add', 'type', 'dummy', 'dev', 'dummy1'])
114+
diff = json.loads(subprocess.check_output(['python3', 'tools/diff.py', '-j']))
115+
missing_system = diff.get('missing_interfaces_system', {})
116+
missing_netplan = diff.get('missing_interfaces_netplan', {})
117+
self.assertIn('dummy1', missing_netplan)
118+
self.assertIn('eth123456', missing_system)
119+
120+
def test_missing_system_nameservers_addresses(self):
121+
self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'dummy0'], stderr=subprocess.DEVNULL)
122+
with open(self.config, 'w') as f:
123+
f.write('''network:
124+
renderer: %(r)s
125+
version: 2
126+
dummy-devices:
127+
dummy0:
128+
nameservers:
129+
addresses:
130+
- 1.1.1.1
131+
- 8.8.8.8
132+
addresses:
133+
- 1.2.3.4/24
134+
dhcp4: false
135+
dhcp6: false''' % {'r': self.backend})
136+
self.generate_and_settle(['dummy0'])
137+
138+
subprocess.call(['resolvectl', 'dns', 'dummy0', '8.8.8.8'])
139+
140+
diff = json.loads(subprocess.check_output(['python3', 'tools/diff.py', '-j']))
141+
diff_dns = diff['interfaces']['dummy0']['system_state'].get('missing_nameservers_addresses', [])
142+
self.assertIn('1.1.1.1', diff_dns)
143+
144+
def test_missing_netplan_nameservers_addresses(self):
145+
self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'dummy0'], stderr=subprocess.DEVNULL)
146+
with open(self.config, 'w') as f:
147+
f.write('''network:
148+
renderer: %(r)s
149+
version: 2
150+
dummy-devices:
151+
dummy0:
152+
nameservers:
153+
addresses:
154+
- 1.1.1.1
155+
addresses:
156+
- 1.2.3.4/24
157+
dhcp4: false
158+
dhcp6: false''' % {'r': self.backend})
159+
self.generate_and_settle(['dummy0'])
160+
161+
subprocess.call(['resolvectl', 'dns', 'dummy0', '1.1.1.1', '8.8.8.8'])
162+
163+
diff = json.loads(subprocess.check_output(['python3', 'tools/diff.py', '-j']))
164+
diff_dns = diff['interfaces']['dummy0']['netplan_state'].get('missing_nameservers_addresses', [])
165+
self.assertIn('8.8.8.8', diff_dns)
166+
167+
def test_missing_system_nameservers_search(self):
168+
self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'dummy0'], stderr=subprocess.DEVNULL)
169+
with open(self.config, 'w') as f:
170+
f.write('''network:
171+
renderer: %(r)s
172+
version: 2
173+
dummy-devices:
174+
dummy0:
175+
nameservers:
176+
addresses:
177+
- 1.1.1.1
178+
- 8.8.8.8
179+
search:
180+
- mynet.local
181+
- mydomain.local
182+
addresses:
183+
- 1.2.3.4/24
184+
dhcp4: false
185+
dhcp6: false''' % {'r': self.backend})
186+
self.generate_and_settle(['dummy0'])
187+
188+
subprocess.call(['resolvectl', 'domain', 'dummy0', 'mynet.local'])
189+
190+
diff = json.loads(subprocess.check_output(['python3', 'tools/diff.py', '-j']))
191+
diff_dns = diff['interfaces']['dummy0']['system_state'].get('missing_nameservers_search', [])
192+
self.assertIn('mydomain.local', diff_dns)
193+
194+
def test_missing_netplan_nameservers_search(self):
195+
self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'dummy0'], stderr=subprocess.DEVNULL)
196+
with open(self.config, 'w') as f:
197+
f.write('''network:
198+
renderer: %(r)s
199+
version: 2
200+
dummy-devices:
201+
dummy0:
202+
nameservers:
203+
search:
204+
- mynet.local
205+
addresses:
206+
- 1.1.1.1
207+
addresses:
208+
- 1.2.3.4/24
209+
dhcp4: false
210+
dhcp6: false''' % {'r': self.backend})
211+
self.generate_and_settle(['dummy0'])
212+
213+
subprocess.call(['resolvectl', 'domain', 'dummy0', 'mynet.local', 'mydomain.local'])
214+
215+
diff = json.loads(subprocess.check_output(['python3', 'tools/diff.py', '-j']))
216+
diff_dns = diff['interfaces']['dummy0']['netplan_state'].get('missing_nameservers_search', [])
217+
self.assertIn('mydomain.local', diff_dns)
218+
219+
def test_missing_netplan_route(self):
220+
self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'dummy0'], stderr=subprocess.DEVNULL)
221+
with open(self.config, 'w') as f:
222+
f.write('''network:
223+
renderer: %(r)s
224+
version: 2
225+
dummy-devices:
226+
dummy0:
227+
addresses:
228+
- 1.2.3.4/24
229+
dhcp4: false
230+
dhcp6: false''' % {'r': self.backend})
231+
self.generate_and_settle(['dummy0'])
232+
233+
subprocess.call(['ip', 'route', 'add', '3.2.1.0/24', 'via', '1.2.3.4'])
234+
235+
diff = json.loads(subprocess.check_output(['python3', 'tools/diff.py', '-j']))
236+
diff_routes = diff['interfaces']['dummy0']['netplan_state'].get('missing_routes', [])
237+
self.assertEqual(len(diff_routes), 1)
238+
route = diff_routes[0]
239+
self.assertEqual('3.2.1.0/24', route['to'])
240+
self.assertEqual('1.2.3.4', route['via'])
241+
self.assertEqual(2, route['family'])
242+
243+
def test_missing_system_route(self):
244+
self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'dummy0'], stderr=subprocess.DEVNULL)
245+
with open(self.config, 'w') as f:
246+
f.write('''network:
247+
renderer: %(r)s
248+
version: 2
249+
dummy-devices:
250+
dummy0:
251+
routes:
252+
- to: 3.2.1.0/24
253+
via: 1.2.3.4
254+
addresses:
255+
- 1.2.3.4/24
256+
dhcp4: false
257+
dhcp6: false''' % {'r': self.backend})
258+
self.generate_and_settle(['dummy0'])
259+
260+
subprocess.call(['ip', 'route', 'del', '3.2.1.0/24', 'via', '1.2.3.4'])
261+
262+
diff = json.loads(subprocess.check_output(['python3', 'tools/diff.py', '-j']))
263+
diff_routes = diff['interfaces']['dummy0']['system_state'].get('missing_routes', [])
264+
self.assertEqual(len(diff_routes), 1)
265+
route = diff_routes[0]
266+
self.assertEqual('3.2.1.0/24', route['to'])
267+
self.assertEqual('1.2.3.4', route['via'])
268+
self.assertEqual(2, route['family'])
269+
270+
271+
@unittest.skipIf("networkd" not in test_backends,
272+
"skipping as networkd backend tests are disabled")
273+
class TestNetworkd(IntegrationTestsBase, _CommonTests):
274+
backend = 'networkd'
275+
276+
277+
@unittest.skipIf("NetworkManager" not in test_backends,
278+
"skipping as NetworkManager backend tests are disabled")
279+
class TestNetworkManager(IntegrationTestsBase, _CommonTests):
280+
backend = 'NetworkManager'
281+
282+
283+
unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout, verbosity=2))

0 commit comments

Comments
 (0)