Merge ~raharper/cloud-init:feature/detect-netfailover into cloud-init:master
- Git
- lp:~raharper/cloud-init
- feature/detect-netfailover
- Merge into master
Status: | Merged |
---|---|
Approved by: | Ryan Harper |
Approved revision: | 5031c2a1c15f1146a42021d357b556b8b1f1520b |
Merge reported by: | Server Team CI bot |
Merged at revision: | not available |
Proposed branch: | ~raharper/cloud-init:feature/detect-netfailover |
Merge into: | cloud-init:master |
Diff against target: |
790 lines (+656/-4) 5 files modified
cloudinit/net/__init__.py (+130/-3) cloudinit/net/tests/test_init.py (+310/-0) cloudinit/sources/DataSourceOracle.py (+61/-1) cloudinit/sources/tests/test_oracle.py (+147/-0) cloudinit/tests/helpers.py (+8/-0) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Server Team CI bot | continuous-integration | Approve | |
Ryan Harper | Approve | ||
Chad Smith | Approve | ||
Review via email: mp+371895@code.launchpad.net |
Commit message
net,Oracle: Add support for netfailover detection
Add support for detecting netfailover[1] device 3-tuple in networking
layer. In the Oracle datasource ensure that if a provided network
config, either fallback or provided config includes a netfailover master
to remove any MAC address value as this can break under 3-netdev
as the other two devices have the same MAC.
1. https:/
Description of the change
Server Team CI bot (server-team-bot) wrote : | # |
Chad Smith (chad.smith) wrote : | # |
Nice, Ryan.
I tried to give an alternative to allow for consolidation of all is_netfailover* functions into a single is_netfailover_
Chad Smith (chad.smith) : | # |
Ryan Harper (raharper) : | # |
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:d09ad7e55b2
https:/
Executed test runs:
SUCCESS: Checkout
SUCCESS: Unit & Style Tests
SUCCESS: Ubuntu LTS: Build
SUCCESS: Ubuntu LTS: Integration
IN_PROGRESS: Declarative: Post Actions
Click here to trigger a rebuild:
https:/
Chad Smith (chad.smith) wrote : | # |
Thank you for the resolution here. I agree that the single function is a lot less readable than the separate is_netfail_* functions. LGTM!
Server Team CI bot (server-team-bot) wrote : | # |
Autolanding: FAILED
More details in the following jenkins job:
https:/
Executed test runs:
SUCCESS: Checkout
FAILED: Unit & Style Tests
Server Team CI bot (server-team-bot) wrote : | # |
Autolanding: FAILED
More details in the following jenkins job:
https:/
Executed test runs:
SUCCESS: Checkout
FAILED: Unit & Style Tests
Server Team CI bot (server-team-bot) wrote : | # |
Autolanding: FAILED
More details in the following jenkins job:
https:/
Executed test runs:
SUCCESS: Checkout
FAILED: Unit & Style Tests
Server Team CI bot (server-team-bot) wrote : | # |
Autolanding: FAILED
More details in the following jenkins job:
https:/
Executed test runs:
SUCCESS: Checkout
FAILED: Unit & Style Tests
Server Team CI bot (server-team-bot) wrote : | # |
Autolanding: FAILED
More details in the following jenkins job:
https:/
Executed test runs:
SUCCESS: Checkout
FAILED: Unit & Style Tests
Server Team CI bot (server-team-bot) wrote : | # |
Autolanding: FAILED
More details in the following jenkins job:
https:/
Executed test runs:
SUCCESS: Checkout
FAILED: Unit & Style Tests
Server Team CI bot (server-team-bot) wrote : | # |
Autolanding: FAILED
More details in the following jenkins job:
https:/
Executed test runs:
SUCCESS: Checkout
FAILED: Unit & Style Tests
Server Team CI bot (server-team-bot) wrote : | # |
Autolanding: FAILED
More details in the following jenkins job:
https:/
Executed test runs:
SUCCESS: Checkout
FAILED: Unit & Style Tests
Server Team CI bot (server-team-bot) wrote : | # |
Autolanding: FAILED
More details in the following jenkins job:
https:/
Executed test runs:
SUCCESS: Checkout
FAILED: Unit & Style Tests
Server Team CI bot (server-team-bot) wrote : | # |
Autolanding: FAILED
More details in the following jenkins job:
https:/
Executed test runs:
SUCCESS: Checkout
FAILED: Unit & Style Tests
Server Team CI bot (server-team-bot) wrote : | # |
Autolanding: FAILED
More details in the following jenkins job:
https:/
Executed test runs:
SUCCESS: Checkout
FAILED: Unit & Style Tests
Server Team CI bot (server-team-bot) wrote : | # |
Autolanding: FAILED
More details in the following jenkins job:
https:/
Executed test runs:
SUCCESS: Checkout
FAILED: Unit & Style Tests
Server Team CI bot (server-team-bot) wrote : | # |
Autolanding: FAILED
More details in the following jenkins job:
https:/
Executed test runs:
SUCCESS: Checkout
FAILED: Unit & Style Tests
Server Team CI bot (server-team-bot) : | # |
Server Team CI bot (server-team-bot) wrote : | # |
Autolanding: FAILED
Unapproved changes made after approval.
https:/
Executed test runs:
SUCCESS: Checkout
SUCCESS: Unit & Style Tests
SUCCESS: Ubuntu LTS: Build
SUCCESS: Ubuntu LTS: Integration
IN_PROGRESS: Declarative: Post Actions
Ryan Harper (raharper) wrote : | # |
I've force pushed a fix for the pycodestyle change after merge, so should be good now.
Server Team CI bot (server-team-bot) : | # |
Preview Diff
1 | diff --git a/cloudinit/net/__init__.py b/cloudinit/net/__init__.py | |||
2 | index ea707c0..0eb952f 100644 | |||
3 | --- a/cloudinit/net/__init__.py | |||
4 | +++ b/cloudinit/net/__init__.py | |||
5 | @@ -109,6 +109,123 @@ def is_bond(devname): | |||
6 | 109 | return os.path.exists(sys_dev_path(devname, "bonding")) | 109 | return os.path.exists(sys_dev_path(devname, "bonding")) |
7 | 110 | 110 | ||
8 | 111 | 111 | ||
9 | 112 | def is_netfailover(devname, driver=None): | ||
10 | 113 | """ netfailover driver uses 3 nics, master, primary and standby. | ||
11 | 114 | this returns True if the device is either the primary or standby | ||
12 | 115 | as these devices are to be ignored. | ||
13 | 116 | """ | ||
14 | 117 | if driver is None: | ||
15 | 118 | driver = device_driver(devname) | ||
16 | 119 | if is_netfail_primary(devname, driver) or is_netfail_standby(devname, | ||
17 | 120 | driver): | ||
18 | 121 | return True | ||
19 | 122 | return False | ||
20 | 123 | |||
21 | 124 | |||
22 | 125 | def get_dev_features(devname): | ||
23 | 126 | """ Returns a str from reading /sys/class/net/<devname>/device/features.""" | ||
24 | 127 | features = '' | ||
25 | 128 | try: | ||
26 | 129 | features = read_sys_net(devname, 'device/features') | ||
27 | 130 | except Exception: | ||
28 | 131 | pass | ||
29 | 132 | return features | ||
30 | 133 | |||
31 | 134 | |||
32 | 135 | def has_netfail_standby_feature(devname): | ||
33 | 136 | """ Return True if VIRTIO_NET_F_STANDBY bit (62) is set. | ||
34 | 137 | |||
35 | 138 | https://github.com/torvalds/linux/blob/ \ | ||
36 | 139 | 089cf7f6ecb266b6a4164919a2e69bd2f938374a/ \ | ||
37 | 140 | include/uapi/linux/virtio_net.h#L60 | ||
38 | 141 | """ | ||
39 | 142 | features = get_dev_features(devname) | ||
40 | 143 | if not features or len(features) < 64: | ||
41 | 144 | return False | ||
42 | 145 | return features[62] == "1" | ||
43 | 146 | |||
44 | 147 | |||
45 | 148 | def is_netfail_master(devname, driver=None): | ||
46 | 149 | """ A device is a "netfail master" device if: | ||
47 | 150 | |||
48 | 151 | - The device does NOT have the 'master' sysfs attribute | ||
49 | 152 | - The device driver is 'virtio_net' | ||
50 | 153 | - The device has the standby feature bit set | ||
51 | 154 | |||
52 | 155 | Return True if all of the above is True. | ||
53 | 156 | """ | ||
54 | 157 | if os.path.exists(sys_dev_path(devname, path='master')): | ||
55 | 158 | return False | ||
56 | 159 | |||
57 | 160 | if driver is None: | ||
58 | 161 | driver = device_driver(devname) | ||
59 | 162 | |||
60 | 163 | if driver != "virtio_net": | ||
61 | 164 | return False | ||
62 | 165 | |||
63 | 166 | if not has_netfail_standby_feature(devname): | ||
64 | 167 | return False | ||
65 | 168 | |||
66 | 169 | return True | ||
67 | 170 | |||
68 | 171 | |||
69 | 172 | def is_netfail_primary(devname, driver=None): | ||
70 | 173 | """ A device is a "netfail primary" device if: | ||
71 | 174 | |||
72 | 175 | - the device has a 'master' sysfs file | ||
73 | 176 | - the device driver is not 'virtio_net' | ||
74 | 177 | - the 'master' sysfs file points to device with virtio_net driver | ||
75 | 178 | - the 'master' device has the 'standby' feature bit set | ||
76 | 179 | |||
77 | 180 | Return True if all of the above is True. | ||
78 | 181 | """ | ||
79 | 182 | # /sys/class/net/<devname>/master -> ../../<master devname> | ||
80 | 183 | master_sysfs_path = sys_dev_path(devname, path='master') | ||
81 | 184 | if not os.path.exists(master_sysfs_path): | ||
82 | 185 | return False | ||
83 | 186 | |||
84 | 187 | if driver is None: | ||
85 | 188 | driver = device_driver(devname) | ||
86 | 189 | |||
87 | 190 | if driver == "virtio_net": | ||
88 | 191 | return False | ||
89 | 192 | |||
90 | 193 | master_devname = os.path.basename(os.path.realpath(master_sysfs_path)) | ||
91 | 194 | master_driver = device_driver(master_devname) | ||
92 | 195 | if master_driver != "virtio_net": | ||
93 | 196 | return False | ||
94 | 197 | |||
95 | 198 | master_has_standby = has_netfail_standby_feature(master_devname) | ||
96 | 199 | if not master_has_standby: | ||
97 | 200 | return False | ||
98 | 201 | |||
99 | 202 | return True | ||
100 | 203 | |||
101 | 204 | |||
102 | 205 | def is_netfail_standby(devname, driver=None): | ||
103 | 206 | """ A device is a "netfail standby" device if: | ||
104 | 207 | |||
105 | 208 | - The device has a 'master' sysfs attribute | ||
106 | 209 | - The device driver is 'virtio_net' | ||
107 | 210 | - The device has the standby feature bit set | ||
108 | 211 | |||
109 | 212 | Return True if all of the above is True. | ||
110 | 213 | """ | ||
111 | 214 | if not os.path.exists(sys_dev_path(devname, path='master')): | ||
112 | 215 | return False | ||
113 | 216 | |||
114 | 217 | if driver is None: | ||
115 | 218 | driver = device_driver(devname) | ||
116 | 219 | |||
117 | 220 | if driver != "virtio_net": | ||
118 | 221 | return False | ||
119 | 222 | |||
120 | 223 | if not has_netfail_standby_feature(devname): | ||
121 | 224 | return False | ||
122 | 225 | |||
123 | 226 | return True | ||
124 | 227 | |||
125 | 228 | |||
126 | 112 | def is_renamed(devname): | 229 | def is_renamed(devname): |
127 | 113 | """ | 230 | """ |
128 | 114 | /* interface name assignment types (sysfs name_assign_type attribute) */ | 231 | /* interface name assignment types (sysfs name_assign_type attribute) */ |
129 | @@ -227,6 +344,9 @@ def find_fallback_nic(blacklist_drivers=None): | |||
130 | 227 | if is_bond(interface): | 344 | if is_bond(interface): |
131 | 228 | # skip any bonds | 345 | # skip any bonds |
132 | 229 | continue | 346 | continue |
133 | 347 | if is_netfailover(interface): | ||
134 | 348 | # ignore netfailover primary/standby interfaces | ||
135 | 349 | continue | ||
136 | 230 | carrier = read_sys_net_int(interface, 'carrier') | 350 | carrier = read_sys_net_int(interface, 'carrier') |
137 | 231 | if carrier: | 351 | if carrier: |
138 | 232 | connected.append(interface) | 352 | connected.append(interface) |
139 | @@ -273,9 +393,14 @@ def generate_fallback_config(blacklist_drivers=None, config_driver=None): | |||
140 | 273 | if not target_name: | 393 | if not target_name: |
141 | 274 | # can't read any interfaces addresses (or there are none); give up | 394 | # can't read any interfaces addresses (or there are none); give up |
142 | 275 | return None | 395 | return None |
146 | 276 | target_mac = read_sys_net_safe(target_name, 'address') | 396 | |
147 | 277 | cfg = {'dhcp4': True, 'set-name': target_name, | 397 | # netfail cannot use mac for matching, they have duplicate macs |
148 | 278 | 'match': {'macaddress': target_mac.lower()}} | 398 | if is_netfail_master(target_name): |
149 | 399 | match = {'name': target_name} | ||
150 | 400 | else: | ||
151 | 401 | match = { | ||
152 | 402 | 'macaddress': read_sys_net_safe(target_name, 'address').lower()} | ||
153 | 403 | cfg = {'dhcp4': True, 'set-name': target_name, 'match': match} | ||
154 | 279 | if config_driver: | 404 | if config_driver: |
155 | 280 | driver = device_driver(target_name) | 405 | driver = device_driver(target_name) |
156 | 281 | if driver: | 406 | if driver: |
157 | @@ -661,6 +786,8 @@ def get_interfaces(): | |||
158 | 661 | continue | 786 | continue |
159 | 662 | if is_bond(name): | 787 | if is_bond(name): |
160 | 663 | continue | 788 | continue |
161 | 789 | if is_netfailover(name): | ||
162 | 790 | continue | ||
163 | 664 | mac = get_interface_mac(name) | 791 | mac = get_interface_mac(name) |
164 | 665 | # some devices may not have a mac (tun0) | 792 | # some devices may not have a mac (tun0) |
165 | 666 | if not mac: | 793 | if not mac: |
166 | diff --git a/cloudinit/net/tests/test_init.py b/cloudinit/net/tests/test_init.py | |||
167 | index d2e38f0..7259dbe 100644 | |||
168 | --- a/cloudinit/net/tests/test_init.py | |||
169 | +++ b/cloudinit/net/tests/test_init.py | |||
170 | @@ -204,6 +204,10 @@ class TestGenerateFallbackConfig(CiTestCase): | |||
171 | 204 | self.add_patch('cloudinit.net.util.is_container', 'm_is_container', | 204 | self.add_patch('cloudinit.net.util.is_container', 'm_is_container', |
172 | 205 | return_value=False) | 205 | return_value=False) |
173 | 206 | self.add_patch('cloudinit.net.util.udevadm_settle', 'm_settle') | 206 | self.add_patch('cloudinit.net.util.udevadm_settle', 'm_settle') |
174 | 207 | self.add_patch('cloudinit.net.is_netfailover', 'm_netfail', | ||
175 | 208 | return_value=False) | ||
176 | 209 | self.add_patch('cloudinit.net.is_netfail_master', 'm_netfail_master', | ||
177 | 210 | return_value=False) | ||
178 | 207 | 211 | ||
179 | 208 | def test_generate_fallback_finds_connected_eth_with_mac(self): | 212 | def test_generate_fallback_finds_connected_eth_with_mac(self): |
180 | 209 | """generate_fallback_config finds any connected device with a mac.""" | 213 | """generate_fallback_config finds any connected device with a mac.""" |
181 | @@ -268,6 +272,61 @@ class TestGenerateFallbackConfig(CiTestCase): | |||
182 | 268 | ensure_file(os.path.join(self.sysdir, 'eth0', 'bonding')) | 272 | ensure_file(os.path.join(self.sysdir, 'eth0', 'bonding')) |
183 | 269 | self.assertIsNone(net.generate_fallback_config()) | 273 | self.assertIsNone(net.generate_fallback_config()) |
184 | 270 | 274 | ||
185 | 275 | def test_generate_fallback_config_skips_netfail_devs(self): | ||
186 | 276 | """gen_fallback_config ignores netfail primary,sby no mac on master.""" | ||
187 | 277 | mac = 'aa:bb:cc:aa:bb:cc' # netfailover devs share the same mac | ||
188 | 278 | for iface in ['ens3', 'ens3sby', 'enP0s1f3']: | ||
189 | 279 | write_file(os.path.join(self.sysdir, iface, 'carrier'), '1') | ||
190 | 280 | write_file( | ||
191 | 281 | os.path.join(self.sysdir, iface, 'addr_assign_type'), '0') | ||
192 | 282 | write_file( | ||
193 | 283 | os.path.join(self.sysdir, iface, 'address'), mac) | ||
194 | 284 | |||
195 | 285 | def is_netfail(iface, _driver=None): | ||
196 | 286 | # ens3 is the master | ||
197 | 287 | if iface == 'ens3': | ||
198 | 288 | return False | ||
199 | 289 | return True | ||
200 | 290 | self.m_netfail.side_effect = is_netfail | ||
201 | 291 | |||
202 | 292 | def is_netfail_master(iface, _driver=None): | ||
203 | 293 | # ens3 is the master | ||
204 | 294 | if iface == 'ens3': | ||
205 | 295 | return True | ||
206 | 296 | return False | ||
207 | 297 | self.m_netfail_master.side_effect = is_netfail_master | ||
208 | 298 | expected = { | ||
209 | 299 | 'ethernets': { | ||
210 | 300 | 'ens3': {'dhcp4': True, 'match': {'name': 'ens3'}, | ||
211 | 301 | 'set-name': 'ens3'}}, | ||
212 | 302 | 'version': 2} | ||
213 | 303 | result = net.generate_fallback_config() | ||
214 | 304 | self.assertEqual(expected, result) | ||
215 | 305 | |||
216 | 306 | |||
217 | 307 | class TestNetFindFallBackNic(CiTestCase): | ||
218 | 308 | |||
219 | 309 | with_logs = True | ||
220 | 310 | |||
221 | 311 | def setUp(self): | ||
222 | 312 | super(TestNetFindFallBackNic, self).setUp() | ||
223 | 313 | sys_mock = mock.patch('cloudinit.net.get_sys_class_path') | ||
224 | 314 | self.m_sys_path = sys_mock.start() | ||
225 | 315 | self.sysdir = self.tmp_dir() + '/' | ||
226 | 316 | self.m_sys_path.return_value = self.sysdir | ||
227 | 317 | self.addCleanup(sys_mock.stop) | ||
228 | 318 | self.add_patch('cloudinit.net.util.is_container', 'm_is_container', | ||
229 | 319 | return_value=False) | ||
230 | 320 | self.add_patch('cloudinit.net.util.udevadm_settle', 'm_settle') | ||
231 | 321 | |||
232 | 322 | def test_generate_fallback_finds_first_connected_eth_with_mac(self): | ||
233 | 323 | """find_fallback_nic finds any connected device with a mac.""" | ||
234 | 324 | write_file(os.path.join(self.sysdir, 'eth0', 'carrier'), '1') | ||
235 | 325 | write_file(os.path.join(self.sysdir, 'eth1', 'carrier'), '1') | ||
236 | 326 | mac = 'aa:bb:cc:aa:bb:cc' | ||
237 | 327 | write_file(os.path.join(self.sysdir, 'eth1', 'address'), mac) | ||
238 | 328 | self.assertEqual('eth1', net.find_fallback_nic()) | ||
239 | 329 | |||
240 | 271 | 330 | ||
241 | 272 | class TestGetDeviceList(CiTestCase): | 331 | class TestGetDeviceList(CiTestCase): |
242 | 273 | 332 | ||
243 | @@ -365,6 +424,26 @@ class TestGetInterfaceMAC(CiTestCase): | |||
244 | 365 | expected = [('eth2', 'aa:bb:cc:aa:bb:cc', None, None)] | 424 | expected = [('eth2', 'aa:bb:cc:aa:bb:cc', None, None)] |
245 | 366 | self.assertEqual(expected, net.get_interfaces()) | 425 | self.assertEqual(expected, net.get_interfaces()) |
246 | 367 | 426 | ||
247 | 427 | @mock.patch('cloudinit.net.is_netfailover') | ||
248 | 428 | def test_get_interfaces_by_mac_skips_netfailvoer(self, m_netfail): | ||
249 | 429 | """Ignore interfaces if netfailover primary or standby.""" | ||
250 | 430 | mac = 'aa:bb:cc:aa:bb:cc' # netfailover devs share the same mac | ||
251 | 431 | for iface in ['ens3', 'ens3sby', 'enP0s1f3']: | ||
252 | 432 | write_file( | ||
253 | 433 | os.path.join(self.sysdir, iface, 'addr_assign_type'), '0') | ||
254 | 434 | write_file( | ||
255 | 435 | os.path.join(self.sysdir, iface, 'address'), mac) | ||
256 | 436 | |||
257 | 437 | def is_netfail(iface, _driver=None): | ||
258 | 438 | # ens3 is the master | ||
259 | 439 | if iface == 'ens3': | ||
260 | 440 | return False | ||
261 | 441 | else: | ||
262 | 442 | return True | ||
263 | 443 | m_netfail.side_effect = is_netfail | ||
264 | 444 | expected = [('ens3', mac, None, None)] | ||
265 | 445 | self.assertEqual(expected, net.get_interfaces()) | ||
266 | 446 | |||
267 | 368 | 447 | ||
268 | 369 | class TestInterfaceHasOwnMAC(CiTestCase): | 448 | class TestInterfaceHasOwnMAC(CiTestCase): |
269 | 370 | 449 | ||
270 | @@ -922,3 +1001,234 @@ class TestWaitForPhysdevs(CiTestCase): | |||
271 | 922 | self.m_get_iface_mac.return_value = {} | 1001 | self.m_get_iface_mac.return_value = {} |
272 | 923 | net.wait_for_physdevs(netcfg, strict=False) | 1002 | net.wait_for_physdevs(netcfg, strict=False) |
273 | 924 | self.assertEqual(5 * len(physdevs), self.m_udev_settle.call_count) | 1003 | self.assertEqual(5 * len(physdevs), self.m_udev_settle.call_count) |
274 | 1004 | |||
275 | 1005 | |||
276 | 1006 | class TestNetFailOver(CiTestCase): | ||
277 | 1007 | |||
278 | 1008 | with_logs = True | ||
279 | 1009 | |||
280 | 1010 | def setUp(self): | ||
281 | 1011 | super(TestNetFailOver, self).setUp() | ||
282 | 1012 | self.add_patch('cloudinit.net.util', 'm_util') | ||
283 | 1013 | self.add_patch('cloudinit.net.read_sys_net', 'm_read_sys_net') | ||
284 | 1014 | self.add_patch('cloudinit.net.device_driver', 'm_device_driver') | ||
285 | 1015 | |||
286 | 1016 | def test_get_dev_features(self): | ||
287 | 1017 | devname = self.random_string() | ||
288 | 1018 | features = self.random_string() | ||
289 | 1019 | self.m_read_sys_net.return_value = features | ||
290 | 1020 | |||
291 | 1021 | self.assertEqual(features, net.get_dev_features(devname)) | ||
292 | 1022 | self.assertEqual(1, self.m_read_sys_net.call_count) | ||
293 | 1023 | self.assertEqual(mock.call(devname, 'device/features'), | ||
294 | 1024 | self.m_read_sys_net.call_args_list[0]) | ||
295 | 1025 | |||
296 | 1026 | def test_get_dev_features_none_returns_empty_string(self): | ||
297 | 1027 | devname = self.random_string() | ||
298 | 1028 | self.m_read_sys_net.side_effect = Exception('error') | ||
299 | 1029 | self.assertEqual('', net.get_dev_features(devname)) | ||
300 | 1030 | self.assertEqual(1, self.m_read_sys_net.call_count) | ||
301 | 1031 | self.assertEqual(mock.call(devname, 'device/features'), | ||
302 | 1032 | self.m_read_sys_net.call_args_list[0]) | ||
303 | 1033 | |||
304 | 1034 | @mock.patch('cloudinit.net.get_dev_features') | ||
305 | 1035 | def test_has_netfail_standby_feature(self, m_dev_features): | ||
306 | 1036 | devname = self.random_string() | ||
307 | 1037 | standby_features = ('0' * 62) + '1' + '0' | ||
308 | 1038 | m_dev_features.return_value = standby_features | ||
309 | 1039 | self.assertTrue(net.has_netfail_standby_feature(devname)) | ||
310 | 1040 | |||
311 | 1041 | @mock.patch('cloudinit.net.get_dev_features') | ||
312 | 1042 | def test_has_netfail_standby_feature_short_is_false(self, m_dev_features): | ||
313 | 1043 | devname = self.random_string() | ||
314 | 1044 | standby_features = self.random_string() | ||
315 | 1045 | m_dev_features.return_value = standby_features | ||
316 | 1046 | self.assertFalse(net.has_netfail_standby_feature(devname)) | ||
317 | 1047 | |||
318 | 1048 | @mock.patch('cloudinit.net.get_dev_features') | ||
319 | 1049 | def test_has_netfail_standby_feature_not_present_is_false(self, | ||
320 | 1050 | m_dev_features): | ||
321 | 1051 | devname = self.random_string() | ||
322 | 1052 | standby_features = '0' * 64 | ||
323 | 1053 | m_dev_features.return_value = standby_features | ||
324 | 1054 | self.assertFalse(net.has_netfail_standby_feature(devname)) | ||
325 | 1055 | |||
326 | 1056 | @mock.patch('cloudinit.net.get_dev_features') | ||
327 | 1057 | def test_has_netfail_standby_feature_no_features_is_false(self, | ||
328 | 1058 | m_dev_features): | ||
329 | 1059 | devname = self.random_string() | ||
330 | 1060 | standby_features = None | ||
331 | 1061 | m_dev_features.return_value = standby_features | ||
332 | 1062 | self.assertFalse(net.has_netfail_standby_feature(devname)) | ||
333 | 1063 | |||
334 | 1064 | @mock.patch('cloudinit.net.has_netfail_standby_feature') | ||
335 | 1065 | @mock.patch('cloudinit.net.os.path.exists') | ||
336 | 1066 | def test_is_netfail_master(self, m_exists, m_standby): | ||
337 | 1067 | devname = self.random_string() | ||
338 | 1068 | driver = 'virtio_net' | ||
339 | 1069 | m_exists.return_value = False # no master sysfs attr | ||
340 | 1070 | m_standby.return_value = True # has standby feature flag | ||
341 | 1071 | self.assertTrue(net.is_netfail_master(devname, driver)) | ||
342 | 1072 | |||
343 | 1073 | @mock.patch('cloudinit.net.sys_dev_path') | ||
344 | 1074 | def test_is_netfail_master_checks_master_attr(self, m_sysdev): | ||
345 | 1075 | devname = self.random_string() | ||
346 | 1076 | driver = 'virtio_net' | ||
347 | 1077 | m_sysdev.return_value = self.random_string() | ||
348 | 1078 | self.assertFalse(net.is_netfail_master(devname, driver)) | ||
349 | 1079 | self.assertEqual(1, m_sysdev.call_count) | ||
350 | 1080 | self.assertEqual(mock.call(devname, path='master'), | ||
351 | 1081 | m_sysdev.call_args_list[0]) | ||
352 | 1082 | |||
353 | 1083 | @mock.patch('cloudinit.net.has_netfail_standby_feature') | ||
354 | 1084 | @mock.patch('cloudinit.net.os.path.exists') | ||
355 | 1085 | def test_is_netfail_master_wrong_driver(self, m_exists, m_standby): | ||
356 | 1086 | devname = self.random_string() | ||
357 | 1087 | driver = self.random_string() | ||
358 | 1088 | self.assertFalse(net.is_netfail_master(devname, driver)) | ||
359 | 1089 | |||
360 | 1090 | @mock.patch('cloudinit.net.has_netfail_standby_feature') | ||
361 | 1091 | @mock.patch('cloudinit.net.os.path.exists') | ||
362 | 1092 | def test_is_netfail_master_has_master_attr(self, m_exists, m_standby): | ||
363 | 1093 | devname = self.random_string() | ||
364 | 1094 | driver = 'virtio_net' | ||
365 | 1095 | m_exists.return_value = True # has master sysfs attr | ||
366 | 1096 | self.assertFalse(net.is_netfail_master(devname, driver)) | ||
367 | 1097 | |||
368 | 1098 | @mock.patch('cloudinit.net.has_netfail_standby_feature') | ||
369 | 1099 | @mock.patch('cloudinit.net.os.path.exists') | ||
370 | 1100 | def test_is_netfail_master_no_standby_feat(self, m_exists, m_standby): | ||
371 | 1101 | devname = self.random_string() | ||
372 | 1102 | driver = 'virtio_net' | ||
373 | 1103 | m_exists.return_value = False # no master sysfs attr | ||
374 | 1104 | m_standby.return_value = False # no standby feature flag | ||
375 | 1105 | self.assertFalse(net.is_netfail_master(devname, driver)) | ||
376 | 1106 | |||
377 | 1107 | @mock.patch('cloudinit.net.has_netfail_standby_feature') | ||
378 | 1108 | @mock.patch('cloudinit.net.os.path.exists') | ||
379 | 1109 | @mock.patch('cloudinit.net.sys_dev_path') | ||
380 | 1110 | def test_is_netfail_primary(self, m_sysdev, m_exists, m_standby): | ||
381 | 1111 | devname = self.random_string() | ||
382 | 1112 | driver = self.random_string() # device not virtio_net | ||
383 | 1113 | master_devname = self.random_string() | ||
384 | 1114 | m_sysdev.return_value = "%s/%s" % (self.random_string(), | ||
385 | 1115 | master_devname) | ||
386 | 1116 | m_exists.return_value = True # has master sysfs attr | ||
387 | 1117 | self.m_device_driver.return_value = 'virtio_net' # master virtio_net | ||
388 | 1118 | m_standby.return_value = True # has standby feature flag | ||
389 | 1119 | self.assertTrue(net.is_netfail_primary(devname, driver)) | ||
390 | 1120 | self.assertEqual(1, self.m_device_driver.call_count) | ||
391 | 1121 | self.assertEqual(mock.call(master_devname), | ||
392 | 1122 | self.m_device_driver.call_args_list[0]) | ||
393 | 1123 | self.assertEqual(1, m_standby.call_count) | ||
394 | 1124 | self.assertEqual(mock.call(master_devname), | ||
395 | 1125 | m_standby.call_args_list[0]) | ||
396 | 1126 | |||
397 | 1127 | @mock.patch('cloudinit.net.has_netfail_standby_feature') | ||
398 | 1128 | @mock.patch('cloudinit.net.os.path.exists') | ||
399 | 1129 | @mock.patch('cloudinit.net.sys_dev_path') | ||
400 | 1130 | def test_is_netfail_primary_wrong_driver(self, m_sysdev, m_exists, | ||
401 | 1131 | m_standby): | ||
402 | 1132 | devname = self.random_string() | ||
403 | 1133 | driver = 'virtio_net' | ||
404 | 1134 | self.assertFalse(net.is_netfail_primary(devname, driver)) | ||
405 | 1135 | |||
406 | 1136 | @mock.patch('cloudinit.net.has_netfail_standby_feature') | ||
407 | 1137 | @mock.patch('cloudinit.net.os.path.exists') | ||
408 | 1138 | @mock.patch('cloudinit.net.sys_dev_path') | ||
409 | 1139 | def test_is_netfail_primary_no_master(self, m_sysdev, m_exists, m_standby): | ||
410 | 1140 | devname = self.random_string() | ||
411 | 1141 | driver = self.random_string() # device not virtio_net | ||
412 | 1142 | m_exists.return_value = False # no master sysfs attr | ||
413 | 1143 | self.assertFalse(net.is_netfail_primary(devname, driver)) | ||
414 | 1144 | |||
415 | 1145 | @mock.patch('cloudinit.net.has_netfail_standby_feature') | ||
416 | 1146 | @mock.patch('cloudinit.net.os.path.exists') | ||
417 | 1147 | @mock.patch('cloudinit.net.sys_dev_path') | ||
418 | 1148 | def test_is_netfail_primary_bad_master(self, m_sysdev, m_exists, | ||
419 | 1149 | m_standby): | ||
420 | 1150 | devname = self.random_string() | ||
421 | 1151 | driver = self.random_string() # device not virtio_net | ||
422 | 1152 | master_devname = self.random_string() | ||
423 | 1153 | m_sysdev.return_value = "%s/%s" % (self.random_string(), | ||
424 | 1154 | master_devname) | ||
425 | 1155 | m_exists.return_value = True # has master sysfs attr | ||
426 | 1156 | self.m_device_driver.return_value = 'XXXX' # master not virtio_net | ||
427 | 1157 | self.assertFalse(net.is_netfail_primary(devname, driver)) | ||
428 | 1158 | |||
429 | 1159 | @mock.patch('cloudinit.net.has_netfail_standby_feature') | ||
430 | 1160 | @mock.patch('cloudinit.net.os.path.exists') | ||
431 | 1161 | @mock.patch('cloudinit.net.sys_dev_path') | ||
432 | 1162 | def test_is_netfail_primary_no_standby(self, m_sysdev, m_exists, | ||
433 | 1163 | m_standby): | ||
434 | 1164 | devname = self.random_string() | ||
435 | 1165 | driver = self.random_string() # device not virtio_net | ||
436 | 1166 | master_devname = self.random_string() | ||
437 | 1167 | m_sysdev.return_value = "%s/%s" % (self.random_string(), | ||
438 | 1168 | master_devname) | ||
439 | 1169 | m_exists.return_value = True # has master sysfs attr | ||
440 | 1170 | self.m_device_driver.return_value = 'virtio_net' # master virtio_net | ||
441 | 1171 | m_standby.return_value = False # master has no standby feature flag | ||
442 | 1172 | self.assertFalse(net.is_netfail_primary(devname, driver)) | ||
443 | 1173 | |||
444 | 1174 | @mock.patch('cloudinit.net.has_netfail_standby_feature') | ||
445 | 1175 | @mock.patch('cloudinit.net.os.path.exists') | ||
446 | 1176 | def test_is_netfail_standby(self, m_exists, m_standby): | ||
447 | 1177 | devname = self.random_string() | ||
448 | 1178 | driver = 'virtio_net' | ||
449 | 1179 | m_exists.return_value = True # has master sysfs attr | ||
450 | 1180 | m_standby.return_value = True # has standby feature flag | ||
451 | 1181 | self.assertTrue(net.is_netfail_standby(devname, driver)) | ||
452 | 1182 | |||
453 | 1183 | @mock.patch('cloudinit.net.has_netfail_standby_feature') | ||
454 | 1184 | @mock.patch('cloudinit.net.os.path.exists') | ||
455 | 1185 | def test_is_netfail_standby_wrong_driver(self, m_exists, m_standby): | ||
456 | 1186 | devname = self.random_string() | ||
457 | 1187 | driver = self.random_string() | ||
458 | 1188 | self.assertFalse(net.is_netfail_standby(devname, driver)) | ||
459 | 1189 | |||
460 | 1190 | @mock.patch('cloudinit.net.has_netfail_standby_feature') | ||
461 | 1191 | @mock.patch('cloudinit.net.os.path.exists') | ||
462 | 1192 | def test_is_netfail_standby_no_master(self, m_exists, m_standby): | ||
463 | 1193 | devname = self.random_string() | ||
464 | 1194 | driver = 'virtio_net' | ||
465 | 1195 | m_exists.return_value = False # has master sysfs attr | ||
466 | 1196 | self.assertFalse(net.is_netfail_standby(devname, driver)) | ||
467 | 1197 | |||
468 | 1198 | @mock.patch('cloudinit.net.has_netfail_standby_feature') | ||
469 | 1199 | @mock.patch('cloudinit.net.os.path.exists') | ||
470 | 1200 | def test_is_netfail_standby_no_standby_feature(self, m_exists, m_standby): | ||
471 | 1201 | devname = self.random_string() | ||
472 | 1202 | driver = 'virtio_net' | ||
473 | 1203 | m_exists.return_value = True # has master sysfs attr | ||
474 | 1204 | m_standby.return_value = False # has standby feature flag | ||
475 | 1205 | self.assertFalse(net.is_netfail_standby(devname, driver)) | ||
476 | 1206 | |||
477 | 1207 | @mock.patch('cloudinit.net.is_netfail_standby') | ||
478 | 1208 | @mock.patch('cloudinit.net.is_netfail_primary') | ||
479 | 1209 | def test_is_netfailover_primary(self, m_primary, m_standby): | ||
480 | 1210 | devname = self.random_string() | ||
481 | 1211 | driver = self.random_string() | ||
482 | 1212 | m_primary.return_value = True | ||
483 | 1213 | m_standby.return_value = False | ||
484 | 1214 | self.assertTrue(net.is_netfailover(devname, driver)) | ||
485 | 1215 | |||
486 | 1216 | @mock.patch('cloudinit.net.is_netfail_standby') | ||
487 | 1217 | @mock.patch('cloudinit.net.is_netfail_primary') | ||
488 | 1218 | def test_is_netfailover_standby(self, m_primary, m_standby): | ||
489 | 1219 | devname = self.random_string() | ||
490 | 1220 | driver = self.random_string() | ||
491 | 1221 | m_primary.return_value = False | ||
492 | 1222 | m_standby.return_value = True | ||
493 | 1223 | self.assertTrue(net.is_netfailover(devname, driver)) | ||
494 | 1224 | |||
495 | 1225 | @mock.patch('cloudinit.net.is_netfail_standby') | ||
496 | 1226 | @mock.patch('cloudinit.net.is_netfail_primary') | ||
497 | 1227 | def test_is_netfailover_returns_false(self, m_primary, m_standby): | ||
498 | 1228 | devname = self.random_string() | ||
499 | 1229 | driver = self.random_string() | ||
500 | 1230 | m_primary.return_value = False | ||
501 | 1231 | m_standby.return_value = False | ||
502 | 1232 | self.assertFalse(net.is_netfailover(devname, driver)) | ||
503 | 1233 | |||
504 | 1234 | # vi: ts=4 expandtab | ||
505 | diff --git a/cloudinit/sources/DataSourceOracle.py b/cloudinit/sources/DataSourceOracle.py | |||
506 | index 1cb0636..eec8740 100644 | |||
507 | --- a/cloudinit/sources/DataSourceOracle.py | |||
508 | +++ b/cloudinit/sources/DataSourceOracle.py | |||
509 | @@ -16,7 +16,7 @@ Notes: | |||
510 | 16 | """ | 16 | """ |
511 | 17 | 17 | ||
512 | 18 | from cloudinit.url_helper import combine_url, readurl, UrlError | 18 | from cloudinit.url_helper import combine_url, readurl, UrlError |
514 | 19 | from cloudinit.net import dhcp, get_interfaces_by_mac | 19 | from cloudinit.net import dhcp, get_interfaces_by_mac, is_netfail_master |
515 | 20 | from cloudinit import net | 20 | from cloudinit import net |
516 | 21 | from cloudinit import sources | 21 | from cloudinit import sources |
517 | 22 | from cloudinit import util | 22 | from cloudinit import util |
518 | @@ -108,6 +108,56 @@ def _add_network_config_from_opc_imds(network_config): | |||
519 | 108 | 'match': {'macaddress': mac_address}} | 108 | 'match': {'macaddress': mac_address}} |
520 | 109 | 109 | ||
521 | 110 | 110 | ||
522 | 111 | def _ensure_netfailover_safe(network_config): | ||
523 | 112 | """ | ||
524 | 113 | Search network config physical interfaces to see if any of them are | ||
525 | 114 | a netfailover master. If found, we prevent matching by MAC as the other | ||
526 | 115 | failover devices have the same MAC but need to be ignored. | ||
527 | 116 | |||
528 | 117 | Note: we rely on cloudinit.net changes which prevent netfailover devices | ||
529 | 118 | from being present in the provided network config. For more details about | ||
530 | 119 | netfailover devices, refer to cloudinit.net module. | ||
531 | 120 | |||
532 | 121 | :param network_config | ||
533 | 122 | A v1 or v2 network config dict with the primary NIC, and possibly | ||
534 | 123 | secondary nic configured. This dict will be mutated. | ||
535 | 124 | |||
536 | 125 | """ | ||
537 | 126 | # ignore anything that's not an actual network-config | ||
538 | 127 | if 'version' not in network_config: | ||
539 | 128 | return | ||
540 | 129 | |||
541 | 130 | if network_config['version'] not in [1, 2]: | ||
542 | 131 | LOG.debug('Ignoring unknown network config version: %s', | ||
543 | 132 | network_config['version']) | ||
544 | 133 | return | ||
545 | 134 | |||
546 | 135 | mac_to_name = get_interfaces_by_mac() | ||
547 | 136 | if network_config['version'] == 1: | ||
548 | 137 | for cfg in [c for c in network_config['config'] if 'type' in c]: | ||
549 | 138 | if cfg['type'] == 'physical': | ||
550 | 139 | if 'mac_address' in cfg: | ||
551 | 140 | mac = cfg['mac_address'] | ||
552 | 141 | cur_name = mac_to_name.get(mac) | ||
553 | 142 | if not cur_name: | ||
554 | 143 | continue | ||
555 | 144 | elif is_netfail_master(cur_name): | ||
556 | 145 | del cfg['mac_address'] | ||
557 | 146 | |||
558 | 147 | elif network_config['version'] == 2: | ||
559 | 148 | for _, cfg in network_config.get('ethernets', {}).items(): | ||
560 | 149 | if 'match' in cfg: | ||
561 | 150 | macaddr = cfg.get('match', {}).get('macaddress') | ||
562 | 151 | if macaddr: | ||
563 | 152 | cur_name = mac_to_name.get(macaddr) | ||
564 | 153 | if not cur_name: | ||
565 | 154 | continue | ||
566 | 155 | elif is_netfail_master(cur_name): | ||
567 | 156 | del cfg['match']['macaddress'] | ||
568 | 157 | del cfg['set-name'] | ||
569 | 158 | cfg['match']['name'] = cur_name | ||
570 | 159 | |||
571 | 160 | |||
572 | 111 | class DataSourceOracle(sources.DataSource): | 161 | class DataSourceOracle(sources.DataSource): |
573 | 112 | 162 | ||
574 | 113 | dsname = 'Oracle' | 163 | dsname = 'Oracle' |
575 | @@ -208,9 +258,13 @@ class DataSourceOracle(sources.DataSource): | |||
576 | 208 | We nonetheless return cmdline provided config if present | 258 | We nonetheless return cmdline provided config if present |
577 | 209 | and fallback to generate fallback.""" | 259 | and fallback to generate fallback.""" |
578 | 210 | if self._network_config == sources.UNSET: | 260 | if self._network_config == sources.UNSET: |
579 | 261 | # this is v1 | ||
580 | 211 | self._network_config = cmdline.read_initramfs_config() | 262 | self._network_config = cmdline.read_initramfs_config() |
581 | 263 | |||
582 | 212 | if not self._network_config: | 264 | if not self._network_config: |
583 | 265 | # this is now v2 | ||
584 | 213 | self._network_config = self.distro.generate_fallback_config() | 266 | self._network_config = self.distro.generate_fallback_config() |
585 | 267 | |||
586 | 214 | if self.ds_cfg.get('configure_secondary_nics'): | 268 | if self.ds_cfg.get('configure_secondary_nics'): |
587 | 215 | try: | 269 | try: |
588 | 216 | # Mutate self._network_config to include secondary VNICs | 270 | # Mutate self._network_config to include secondary VNICs |
589 | @@ -219,6 +273,12 @@ class DataSourceOracle(sources.DataSource): | |||
590 | 219 | util.logexc( | 273 | util.logexc( |
591 | 220 | LOG, | 274 | LOG, |
592 | 221 | "Failed to fetch secondary network configuration!") | 275 | "Failed to fetch secondary network configuration!") |
593 | 276 | |||
594 | 277 | # we need to verify that the nic selected is not a netfail over | ||
595 | 278 | # device and, if it is a netfail master, then we need to avoid | ||
596 | 279 | # emitting any match by mac | ||
597 | 280 | _ensure_netfailover_safe(self._network_config) | ||
598 | 281 | |||
599 | 222 | return self._network_config | 282 | return self._network_config |
600 | 223 | 283 | ||
601 | 224 | 284 | ||
602 | diff --git a/cloudinit/sources/tests/test_oracle.py b/cloudinit/sources/tests/test_oracle.py | |||
603 | index 2a70bbc..85b6db9 100644 | |||
604 | --- a/cloudinit/sources/tests/test_oracle.py | |||
605 | +++ b/cloudinit/sources/tests/test_oracle.py | |||
606 | @@ -8,6 +8,7 @@ from cloudinit.tests import helpers as test_helpers | |||
607 | 8 | 8 | ||
608 | 9 | from textwrap import dedent | 9 | from textwrap import dedent |
609 | 10 | import argparse | 10 | import argparse |
610 | 11 | import copy | ||
611 | 11 | import httpretty | 12 | import httpretty |
612 | 12 | import json | 13 | import json |
613 | 13 | import mock | 14 | import mock |
614 | @@ -586,4 +587,150 @@ class TestNetworkConfigFromOpcImds(test_helpers.CiTestCase): | |||
615 | 586 | self.assertEqual('10.0.0.231', secondary_nic_cfg['addresses'][0]) | 587 | self.assertEqual('10.0.0.231', secondary_nic_cfg['addresses'][0]) |
616 | 587 | 588 | ||
617 | 588 | 589 | ||
618 | 590 | class TestNetworkConfigFiltersNetFailover(test_helpers.CiTestCase): | ||
619 | 591 | |||
620 | 592 | with_logs = True | ||
621 | 593 | |||
622 | 594 | def setUp(self): | ||
623 | 595 | super(TestNetworkConfigFiltersNetFailover, self).setUp() | ||
624 | 596 | self.add_patch(DS_PATH + '.get_interfaces_by_mac', | ||
625 | 597 | 'm_get_interfaces_by_mac') | ||
626 | 598 | self.add_patch(DS_PATH + '.is_netfail_master', 'm_netfail_master') | ||
627 | 599 | |||
628 | 600 | def test_ignore_bogus_network_config(self): | ||
629 | 601 | netcfg = {'something': 'here'} | ||
630 | 602 | passed_netcfg = copy.copy(netcfg) | ||
631 | 603 | oracle._ensure_netfailover_safe(passed_netcfg) | ||
632 | 604 | self.assertEqual(netcfg, passed_netcfg) | ||
633 | 605 | |||
634 | 606 | def test_ignore_network_config_unknown_versions(self): | ||
635 | 607 | netcfg = {'something': 'here', 'version': 3} | ||
636 | 608 | passed_netcfg = copy.copy(netcfg) | ||
637 | 609 | oracle._ensure_netfailover_safe(passed_netcfg) | ||
638 | 610 | self.assertEqual(netcfg, passed_netcfg) | ||
639 | 611 | |||
640 | 612 | def test_checks_v1_type_physical_interfaces(self): | ||
641 | 613 | mac_addr, nic_name = '00:00:17:02:2b:b1', 'ens3' | ||
642 | 614 | self.m_get_interfaces_by_mac.return_value = { | ||
643 | 615 | mac_addr: nic_name, | ||
644 | 616 | } | ||
645 | 617 | netcfg = {'version': 1, 'config': [ | ||
646 | 618 | {'type': 'physical', 'name': nic_name, 'mac_address': mac_addr, | ||
647 | 619 | 'subnets': [{'type': 'dhcp4'}]}]} | ||
648 | 620 | passed_netcfg = copy.copy(netcfg) | ||
649 | 621 | self.m_netfail_master.return_value = False | ||
650 | 622 | oracle._ensure_netfailover_safe(passed_netcfg) | ||
651 | 623 | self.assertEqual(netcfg, passed_netcfg) | ||
652 | 624 | self.assertEqual([mock.call(nic_name)], | ||
653 | 625 | self.m_netfail_master.call_args_list) | ||
654 | 626 | |||
655 | 627 | def test_checks_v1_skips_non_phys_interfaces(self): | ||
656 | 628 | mac_addr, nic_name = '00:00:17:02:2b:b1', 'bond0' | ||
657 | 629 | self.m_get_interfaces_by_mac.return_value = { | ||
658 | 630 | mac_addr: nic_name, | ||
659 | 631 | } | ||
660 | 632 | netcfg = {'version': 1, 'config': [ | ||
661 | 633 | {'type': 'bond', 'name': nic_name, 'mac_address': mac_addr, | ||
662 | 634 | 'subnets': [{'type': 'dhcp4'}]}]} | ||
663 | 635 | passed_netcfg = copy.copy(netcfg) | ||
664 | 636 | oracle._ensure_netfailover_safe(passed_netcfg) | ||
665 | 637 | self.assertEqual(netcfg, passed_netcfg) | ||
666 | 638 | self.assertEqual(0, self.m_netfail_master.call_count) | ||
667 | 639 | |||
668 | 640 | def test_removes_master_mac_property_v1(self): | ||
669 | 641 | nic_master, mac_master = 'ens3', self.random_string() | ||
670 | 642 | nic_other, mac_other = 'ens7', self.random_string() | ||
671 | 643 | nic_extra, mac_extra = 'enp0s1f2', self.random_string() | ||
672 | 644 | self.m_get_interfaces_by_mac.return_value = { | ||
673 | 645 | mac_master: nic_master, | ||
674 | 646 | mac_other: nic_other, | ||
675 | 647 | mac_extra: nic_extra, | ||
676 | 648 | } | ||
677 | 649 | netcfg = {'version': 1, 'config': [ | ||
678 | 650 | {'type': 'physical', 'name': nic_master, | ||
679 | 651 | 'mac_address': mac_master}, | ||
680 | 652 | {'type': 'physical', 'name': nic_other, 'mac_address': mac_other}, | ||
681 | 653 | {'type': 'physical', 'name': nic_extra, 'mac_address': mac_extra}, | ||
682 | 654 | ]} | ||
683 | 655 | |||
684 | 656 | def _is_netfail_master(iface): | ||
685 | 657 | if iface == 'ens3': | ||
686 | 658 | return True | ||
687 | 659 | return False | ||
688 | 660 | self.m_netfail_master.side_effect = _is_netfail_master | ||
689 | 661 | expected_cfg = {'version': 1, 'config': [ | ||
690 | 662 | {'type': 'physical', 'name': nic_master}, | ||
691 | 663 | {'type': 'physical', 'name': nic_other, 'mac_address': mac_other}, | ||
692 | 664 | {'type': 'physical', 'name': nic_extra, 'mac_address': mac_extra}, | ||
693 | 665 | ]} | ||
694 | 666 | oracle._ensure_netfailover_safe(netcfg) | ||
695 | 667 | self.assertEqual(expected_cfg, netcfg) | ||
696 | 668 | |||
697 | 669 | def test_checks_v2_type_ethernet_interfaces(self): | ||
698 | 670 | mac_addr, nic_name = '00:00:17:02:2b:b1', 'ens3' | ||
699 | 671 | self.m_get_interfaces_by_mac.return_value = { | ||
700 | 672 | mac_addr: nic_name, | ||
701 | 673 | } | ||
702 | 674 | netcfg = {'version': 2, 'ethernets': { | ||
703 | 675 | nic_name: {'dhcp4': True, 'critical': True, 'set-name': nic_name, | ||
704 | 676 | 'match': {'macaddress': mac_addr}}}} | ||
705 | 677 | passed_netcfg = copy.copy(netcfg) | ||
706 | 678 | self.m_netfail_master.return_value = False | ||
707 | 679 | oracle._ensure_netfailover_safe(passed_netcfg) | ||
708 | 680 | self.assertEqual(netcfg, passed_netcfg) | ||
709 | 681 | self.assertEqual([mock.call(nic_name)], | ||
710 | 682 | self.m_netfail_master.call_args_list) | ||
711 | 683 | |||
712 | 684 | def test_skips_v2_non_ethernet_interfaces(self): | ||
713 | 685 | mac_addr, nic_name = '00:00:17:02:2b:b1', 'wlps0' | ||
714 | 686 | self.m_get_interfaces_by_mac.return_value = { | ||
715 | 687 | mac_addr: nic_name, | ||
716 | 688 | } | ||
717 | 689 | netcfg = {'version': 2, 'wifis': { | ||
718 | 690 | nic_name: {'dhcp4': True, 'critical': True, 'set-name': nic_name, | ||
719 | 691 | 'match': {'macaddress': mac_addr}}}} | ||
720 | 692 | passed_netcfg = copy.copy(netcfg) | ||
721 | 693 | oracle._ensure_netfailover_safe(passed_netcfg) | ||
722 | 694 | self.assertEqual(netcfg, passed_netcfg) | ||
723 | 695 | self.assertEqual(0, self.m_netfail_master.call_count) | ||
724 | 696 | |||
725 | 697 | def test_removes_master_mac_property_v2(self): | ||
726 | 698 | nic_master, mac_master = 'ens3', self.random_string() | ||
727 | 699 | nic_other, mac_other = 'ens7', self.random_string() | ||
728 | 700 | nic_extra, mac_extra = 'enp0s1f2', self.random_string() | ||
729 | 701 | self.m_get_interfaces_by_mac.return_value = { | ||
730 | 702 | mac_master: nic_master, | ||
731 | 703 | mac_other: nic_other, | ||
732 | 704 | mac_extra: nic_extra, | ||
733 | 705 | } | ||
734 | 706 | netcfg = {'version': 2, 'ethernets': { | ||
735 | 707 | nic_extra: {'dhcp4': True, 'set-name': nic_extra, | ||
736 | 708 | 'match': {'macaddress': mac_extra}}, | ||
737 | 709 | nic_other: {'dhcp4': True, 'set-name': nic_other, | ||
738 | 710 | 'match': {'macaddress': mac_other}}, | ||
739 | 711 | nic_master: {'dhcp4': True, 'set-name': nic_master, | ||
740 | 712 | 'match': {'macaddress': mac_master}}, | ||
741 | 713 | }} | ||
742 | 714 | |||
743 | 715 | def _is_netfail_master(iface): | ||
744 | 716 | if iface == 'ens3': | ||
745 | 717 | return True | ||
746 | 718 | return False | ||
747 | 719 | self.m_netfail_master.side_effect = _is_netfail_master | ||
748 | 720 | |||
749 | 721 | expected_cfg = {'version': 2, 'ethernets': { | ||
750 | 722 | nic_master: {'dhcp4': True, 'match': {'name': nic_master}}, | ||
751 | 723 | nic_extra: {'dhcp4': True, 'set-name': nic_extra, | ||
752 | 724 | 'match': {'macaddress': mac_extra}}, | ||
753 | 725 | nic_other: {'dhcp4': True, 'set-name': nic_other, | ||
754 | 726 | 'match': {'macaddress': mac_other}}, | ||
755 | 727 | }} | ||
756 | 728 | oracle._ensure_netfailover_safe(netcfg) | ||
757 | 729 | import pprint | ||
758 | 730 | pprint.pprint(netcfg) | ||
759 | 731 | print('---- ^^ modified ^^ ---- vv original vv ----') | ||
760 | 732 | pprint.pprint(expected_cfg) | ||
761 | 733 | self.assertEqual(expected_cfg, netcfg) | ||
762 | 734 | |||
763 | 735 | |||
764 | 589 | # vi: ts=4 expandtab | 736 | # vi: ts=4 expandtab |
765 | diff --git a/cloudinit/tests/helpers.py b/cloudinit/tests/helpers.py | |||
766 | index 23fddd0..4dad2af 100644 | |||
767 | --- a/cloudinit/tests/helpers.py | |||
768 | +++ b/cloudinit/tests/helpers.py | |||
769 | @@ -6,7 +6,9 @@ import functools | |||
770 | 6 | import httpretty | 6 | import httpretty |
771 | 7 | import logging | 7 | import logging |
772 | 8 | import os | 8 | import os |
773 | 9 | import random | ||
774 | 9 | import shutil | 10 | import shutil |
775 | 11 | import string | ||
776 | 10 | import sys | 12 | import sys |
777 | 11 | import tempfile | 13 | import tempfile |
778 | 12 | import time | 14 | import time |
779 | @@ -243,6 +245,12 @@ class CiTestCase(TestCase): | |||
780 | 243 | myds.metadata.update(metadata) | 245 | myds.metadata.update(metadata) |
781 | 244 | return cloud.Cloud(myds, self.paths, sys_cfg, mydist, None) | 246 | return cloud.Cloud(myds, self.paths, sys_cfg, mydist, None) |
782 | 245 | 247 | ||
783 | 248 | @classmethod | ||
784 | 249 | def random_string(cls, length=8): | ||
785 | 250 | """ return a random lowercase string with default length of 8""" | ||
786 | 251 | return ''.join( | ||
787 | 252 | random.choice(string.ascii_lowercase) for _ in range(length)) | ||
788 | 253 | |||
789 | 246 | 254 | ||
790 | 247 | class ResourceUsingTestCase(CiTestCase): | 255 | class ResourceUsingTestCase(CiTestCase): |
791 | 248 | 256 |
PASSED: Continuous integration, rev:73af8a87bb5 ab0e72013ed44de 88dae1ac8678a2 /jenkins. ubuntu. com/server/ job/cloud- init-ci/ 1080/
https:/
Executed test runs:
SUCCESS: Checkout
SUCCESS: Unit & Style Tests
SUCCESS: Ubuntu LTS: Build
SUCCESS: Ubuntu LTS: Integration
IN_PROGRESS: Declarative: Post Actions
Click here to trigger a rebuild: /jenkins. ubuntu. com/server/ job/cloud- init-ci/ 1080//rebuild
https:/