Merge lp:~mpontillo/maas/bug-1553617-bonds-with-no-parents into lp:~maas-committers/maas/trunk

Proposed by Mike Pontillo
Status: Merged
Approved by: Mike Pontillo
Approved revision: no longer in the source branch.
Merged at revision: 4741
Proposed branch: lp:~mpontillo/maas/bug-1553617-bonds-with-no-parents
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 368 lines (+180/-26)
2 files modified
src/provisioningserver/utils/ipaddr.py (+73/-7)
src/provisioningserver/utils/tests/test_ipaddr.py (+107/-19)
To merge this branch: bzr merge lp:~mpontillo/maas/bug-1553617-bonds-with-no-parents
Reviewer Review Type Date Requested Status
Blake Rouse (community) Approve
Review via email: mp+288211@code.launchpad.net

Commit message

Fix bug #1553617: rack registration fails when bond interfaces are present.

 * When parsing '/sbin/ip addr' and gathering driver information, ignore
   bonds with no backing interfaces.
 * After parsing '/sbin/ip addr', cross-reference data from
   /proc/net/bonding/* and update interface MACs with the original
   hardware MAC. (After a bond interface is added, the interface MAC is
   updated to be the same as the bond MAC, which was throwing off the data
   MAAS gathered.)

To post a comment you must log in.
Revision history for this message
Mike Pontillo (mpontillo) wrote :

Updated this branch to fix the second issue blocking rack registration.

Revision history for this message
Blake Rouse (blake-rouse) wrote :

Looks good. Well sleuthed! Just one question on the filter on bonds that don't have any members.

review: Approve
Revision history for this message
Mike Pontillo (mpontillo) :
Revision history for this message
MAAS Lander (maas-lander) wrote :
Download full text (1.0 MiB)

The attempt to merge lp:~mpontillo/maas/bug-1553617-bonds-with-no-parents into lp:maas failed. Below is the output from the failed tests.

Hit:1 http://security.ubuntu.com/ubuntu xenial-security InRelease
Get:2 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial InRelease [95.8 kB]
Hit:3 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial-updates InRelease
Hit:4 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial-backports InRelease
Get:5 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial/main Sources [1,109 kB]
Get:6 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial/main amd64 Packages [1,443 kB]
Fetched 2,648 kB in 0s (2,703 kB/s)
Reading package lists...
sudo DEBIAN_FRONTEND=noninteractive apt-get -y \
    --no-install-recommends install apache2 archdetect-deb authbind bind9 bind9utils build-essential bzr-builddeb chromium-browser chromium-chromedriver curl daemontools debhelper dh-apport dh-systemd distro-info dnsutils firefox freeipmi-tools git gjs ipython isc-dhcp-common libjs-angularjs libjs-jquery libjs-jquery-hotkeys libjs-yui3-full libjs-yui3-min libpq-dev make nodejs-legacy npm postgresql pxelinux python3-all python3-apt python3-bson python3-convoy python3-coverage python3-crochet python3-cssselect python3-curtin python3-dev python3-distro-info python3-django python3-django-nose python3-django-piston3 python3-docutils python3-formencode python3-hivex python3-httplib2 python3-jinja2 python3-jsonschema python3-lxml python3-mock python3-netaddr python3-netifaces python3-oauth python3-oauthlib python3-openssl python3-paramiko python3-petname python3-pexpect python3-psycopg2 python3-pyinotify python3-pyparsing python3-pyvmomi python3-requests python3-seamicroclient python3-setuptools python3-simplestreams python3-sphinx python3-tempita python3-twisted python3-txtftp python3-tz python3-yaml python3-zope.interface python-bson python-crochet python-django python-django-piston python-djorm-ext-pgarray python-formencode python-lxml python-netaddr python-netifaces python-pocket-lint python-psycopg2 python-tempita python-twisted python-yaml socat syslinux-common tgt ubuntu-cloudimage-keyring wget xvfb
Reading package lists...
Building dependency tree...
Reading state information...
apache2 is already the newest version (2.4.18-1ubuntu1).
archdetect-deb is already the newest version (1.114ubuntu3).
authbind is already the newest version (2.1.1+nmu1).
bind9 is already the newest version (1:9.10.3.dfsg.P2-5).
bind9utils is already the newest version (1:9.10.3.dfsg.P2-5).
build-essential is already the newest version (12.1ubuntu2).
curl is already the newest version (7.47.0-1ubuntu2).
debhelper is already the newest version (9.20160115ubuntu2).
dh-apport is already the newest version (2.20-0ubuntu3).
dh-systemd is already the newest version (1.29ubuntu1).
distro-info is already the newest version (0.14build1).
dnsutils is already the newest version (1:9.10.3.dfsg.P2-5).
firefox is already the newest version (45.0+build2-0ubuntu1).
freeipmi-tools is already the newest version (1.4.11-1ubuntu1).
git is already the newest version (1:2.7.0-1).
isc-dhcp-common is already the newest version ...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/provisioningserver/utils/ipaddr.py'
--- src/provisioningserver/utils/ipaddr.py 2016-03-03 20:40:10 +0000
+++ src/provisioningserver/utils/ipaddr.py 2016-03-07 19:23:24 +0000
@@ -1,4 +1,4 @@
1# Copyright 2015 Canonical Ltd. This software is licensed under the1# Copyright 2015-2016 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Utility to parse 'ip addr [show]'.4"""Utility to parse 'ip addr [show]'.
@@ -221,7 +221,7 @@
221221
222def get_interface_type(222def get_interface_type(
223 ifname, sys_class_net="/sys/class/net",223 ifname, sys_class_net="/sys/class/net",
224 proc_net_vlan='/proc/net/vlan'):224 proc_net="/proc/net"):
225 """Heuristic to return the type of the given interface.225 """Heuristic to return the type of the given interface.
226226
227 The given interface must be able to be found in /sys/class/net/ifname.227 The given interface must be able to be found in /sys/class/net/ifname.
@@ -270,7 +270,7 @@
270 bond_dir = os.path.join(sys_path, 'bonding')270 bond_dir = os.path.join(sys_path, 'bonding')
271 if os.path.isdir(bond_dir):271 if os.path.isdir(bond_dir):
272 return 'ethernet.bond'272 return 'ethernet.bond'
273 if os.path.isfile('%s/%s' % (proc_net_vlan, ifname)):273 if os.path.isfile(os.path.join(proc_net, "vlan", ifname)):
274 return 'ethernet.vlan'274 return 'ethernet.vlan'
275 device_path = os.path.join(sys_path, 'device')275 device_path = os.path.join(sys_path, 'device')
276 if os.path.islink(device_path):276 if os.path.islink(device_path):
@@ -291,22 +291,88 @@
291 return 'unknown-%d' % iftype291 return 'unknown-%d' % iftype
292292
293293
294def annotate_with_driver_information(interfaces):294def _parse_proc_net_bonding(file):
295 """Parse the given file, which must be a path to a file in the format
296 that is used for file in `/proc/net/bonding/<interface>`.
297
298 Returns a dictionary mapping each interface name found in the file to
299 its original MAC address.
300 """
301 interfaces = {}
302 current_iface = None
303 with open(file) as f:
304 for line in f.readlines():
305 line = line.strip()
306 slave_iface = line.split("Slave Interface: ")
307 if len(slave_iface) == 2:
308 current_iface = slave_iface[1]
309 hw_addr = line.split("Permanent HW addr: ")
310 if len(hw_addr) == 2:
311 interfaces[current_iface] = hw_addr[1]
312 return interfaces
313
314
315def annotate_with_proc_net_bonding_original_macs(
316 interfaces, proc_net="/proc/net"):
317 """Repairs the MAC addresses of bond members in the specified structure.
318
319 Given the specified interfaces structure, uses the data in
320 `/proc/net/bonding/*` to determine if any of the interfaces
321 in the structure are bond members. If so, modifies their MAC address,
322 setting it back to the original hardware MAC. (When an interface is added
323 to a bond, its MAC address is set to the bond MAC, and subsequently
324 reported in commands like "ip addr".)
325 """
326 proc_net_bonding = os.path.join(proc_net, "bonding")
327 if os.path.isdir(proc_net_bonding):
328 bonds = os.listdir(proc_net_bonding)
329 for bond in bonds:
330 parent_macs = _parse_proc_net_bonding(
331 os.path.join(proc_net_bonding, bond))
332 for interface in parent_macs:
333 if interface in interfaces:
334 interfaces[interface]['mac'] = parent_macs[interface]
335 return interfaces
336
337
338def annotate_with_driver_information(
339 interfaces, sys_class_net="/sys/class/net", proc_net="/proc/net"):
295 """Determines driver information for each of the given interfaces.340 """Determines driver information for each of the given interfaces.
296341
297 Annotates the given dictionary to update it with driver information342 Annotates the given dictionary to update it with driver information
298 (if found) for each interface.343 (if found) for each interface.
344
345 Deletes bond interfaces if they are not configured.
346
347 :param interfaces: interfaces dictionary from `parse_ip_addr()`.
348 :param proc_net: path to /proc/net
349 :param sys_class_net: path to /sys/class/net
299 """350 """
351 interfaces = annotate_with_proc_net_bonding_original_macs(
352 interfaces, proc_net=proc_net)
353 bogus_interfaces = []
300 for name in interfaces:354 for name in interfaces:
301 iface = interfaces[name]355 iface = interfaces[name]
302 iftype = get_interface_type(name)356 iftype = get_interface_type(
357 name, sys_class_net=sys_class_net, proc_net=proc_net)
303 interfaces[name]['type'] = iftype358 interfaces[name]['type'] = iftype
304 if iftype == 'ethernet.bond':359 if iftype == 'ethernet.bond':
305 iface['bonded_interfaces'] = get_bonded_interfaces(name)360 bond_parents = get_bonded_interfaces(
361 name, sys_class_net=sys_class_net)
362 if len(bond_parents) > 0:
363 iface['bonded_interfaces'] = bond_parents
364 else:
365 # If we found a bond interface with no parents, just pretend
366 # it doesn't exist. The MAAS model assumes bonds must have
367 # backing interfaces.
368 bogus_interfaces.append(name)
306 elif iftype == 'ethernet.vlan':369 elif iftype == 'ethernet.vlan':
307 iface['vid'] = get_vid_from_ifname(name)370 iface['vid'] = get_vid_from_ifname(name)
308 elif iftype == 'ethernet.bridge':371 elif iftype == 'ethernet.bridge':
309 iface['bridged_interfaces'] = get_bridged_interfaces(name)372 iface['bridged_interfaces'] = get_bridged_interfaces(
373 name, sys_class_net=sys_class_net)
374 for name in bogus_interfaces:
375 del interfaces[name]
310 return interfaces376 return interfaces
311377
312378
313379
=== modified file 'src/provisioningserver/utils/tests/test_ipaddr.py'
--- src/provisioningserver/utils/tests/test_ipaddr.py 2016-03-04 00:56:41 +0000
+++ src/provisioningserver/utils/tests/test_ipaddr.py 2016-03-07 19:23:24 +0000
@@ -21,6 +21,7 @@
21 _add_additional_interface_properties,21 _add_additional_interface_properties,
22 _parse_interface_definition,22 _parse_interface_definition,
23 annotate_with_driver_information,23 annotate_with_driver_information,
24 annotate_with_proc_net_bonding_original_macs,
24 get_bonded_interfaces,25 get_bonded_interfaces,
25 get_interface_type,26 get_interface_type,
26 get_ip_addr,27 get_ip_addr,
@@ -33,6 +34,7 @@
33from testtools.matchers import (34from testtools.matchers import (
34 Contains,35 Contains,
35 Equals,36 Equals,
37 HasLength,
36 Not,38 Not,
37)39)
3840
@@ -347,17 +349,19 @@
347 self.assertThat(ip_link['ens11'], Not(Contains('inet')))349 self.assertThat(ip_link['ens11'], Not(Contains('inet')))
348350
349351
350class TestGetInterfaceType(MAASTestCase):352class FakeSysProcTestCase(MAASTestCase):
351353
352 def setUp(self):354 def setUp(self):
353 super(TestGetInterfaceType, self).setUp()355 super().setUp()
354 self.tmp_sys_net = mkdtemp('maas-unit-tests.sys-class-net')356 self.tmp_sys_net = mkdtemp('maas-unit-tests.sys-class-net')
355 self.tmp_proc_net_vlan = mkdtemp('maas-unit-tests.proc-net-vlan')357 self.tmp_proc_net = mkdtemp('maas-unit-tests.proc-net')
358 os.mkdir(os.path.join(self.tmp_proc_net, "vlan"))
359 os.mkdir(os.path.join(self.tmp_proc_net, "bonding"))
356360
357 def tearDown(self):361 def tearDown(self):
358 super(TestGetInterfaceType, self).tearDown()362 super().tearDown()
359 rmtree(self.tmp_sys_net)363 rmtree(self.tmp_sys_net)
360 rmtree(self.tmp_proc_net_vlan)364 rmtree(self.tmp_proc_net)
361365
362 def createInterfaceType(366 def createInterfaceType(
363 self, ifname, iftype, is_bridge=False, is_vlan=False,367 self, ifname, iftype, is_bridge=False, is_vlan=False,
@@ -371,7 +375,7 @@
371 if is_bridge:375 if is_bridge:
372 os.mkdir(os.path.join(ifdir, 'bridge'))376 os.mkdir(os.path.join(ifdir, 'bridge'))
373 if is_vlan:377 if is_vlan:
374 with open(os.path.join(self.tmp_proc_net_vlan, ifname), 'w'):378 with open(os.path.join(self.tmp_proc_net, "vlan", ifname), 'w'):
375 pass # Just touch.379 pass # Just touch.
376 if is_bond:380 if is_bond:
377 os.mkdir(os.path.join(ifdir, 'bonding'))381 os.mkdir(os.path.join(ifdir, 'bonding'))
@@ -395,6 +399,9 @@
395 def createEthernetInterface(self, ifname, **kwargs):399 def createEthernetInterface(self, ifname, **kwargs):
396 self.createInterfaceType(ifname, 1, **kwargs)400 self.createInterfaceType(ifname, 1, **kwargs)
397401
402
403class TestGetInterfaceType(FakeSysProcTestCase):
404
398 def test__identifies_missing_interface(self):405 def test__identifies_missing_interface(self):
399 self.assertThat(get_interface_type(406 self.assertThat(get_interface_type(
400 'eth0', sys_class_net=self.tmp_sys_net),407 'eth0', sys_class_net=self.tmp_sys_net),
@@ -405,7 +412,7 @@
405 self.createEthernetInterface('br0', is_bridge=True)412 self.createEthernetInterface('br0', is_bridge=True)
406 self.assertThat(get_interface_type(413 self.assertThat(get_interface_type(
407 'br0', sys_class_net=self.tmp_sys_net,414 'br0', sys_class_net=self.tmp_sys_net,
408 proc_net_vlan=self.tmp_proc_net_vlan),415 proc_net=self.tmp_proc_net),
409 Equals('ethernet.bridge')416 Equals('ethernet.bridge')
410 )417 )
411418
@@ -413,7 +420,7 @@
413 self.createEthernetInterface('bond0', is_bond=True)420 self.createEthernetInterface('bond0', is_bond=True)
414 self.assertThat(get_interface_type(421 self.assertThat(get_interface_type(
415 'bond0', sys_class_net=self.tmp_sys_net,422 'bond0', sys_class_net=self.tmp_sys_net,
416 proc_net_vlan=self.tmp_proc_net_vlan),423 proc_net=self.tmp_proc_net),
417 Equals('ethernet.bond')424 Equals('ethernet.bond')
418 )425 )
419426
@@ -429,7 +436,7 @@
429 self.createEthernetInterface('vlan42', is_vlan=True)436 self.createEthernetInterface('vlan42', is_vlan=True)
430 self.assertThat(get_interface_type(437 self.assertThat(get_interface_type(
431 'vlan42', sys_class_net=self.tmp_sys_net,438 'vlan42', sys_class_net=self.tmp_sys_net,
432 proc_net_vlan=self.tmp_proc_net_vlan),439 proc_net=self.tmp_proc_net),
433 Equals('ethernet.vlan')440 Equals('ethernet.vlan')
434 )441 )
435442
@@ -437,7 +444,7 @@
437 self.createEthernetInterface('eth0', is_physical=True)444 self.createEthernetInterface('eth0', is_physical=True)
438 self.assertThat(get_interface_type(445 self.assertThat(get_interface_type(
439 'eth0', sys_class_net=self.tmp_sys_net,446 'eth0', sys_class_net=self.tmp_sys_net,
440 proc_net_vlan=self.tmp_proc_net_vlan),447 proc_net=self.tmp_proc_net),
441 Equals('ethernet.physical')448 Equals('ethernet.physical')
442 )449 )
443450
@@ -445,7 +452,7 @@
445 self.createEthernetInterface('wlan0', is_wireless=True)452 self.createEthernetInterface('wlan0', is_wireless=True)
446 self.assertThat(get_interface_type(453 self.assertThat(get_interface_type(
447 'wlan0', sys_class_net=self.tmp_sys_net,454 'wlan0', sys_class_net=self.tmp_sys_net,
448 proc_net_vlan=self.tmp_proc_net_vlan),455 proc_net=self.tmp_proc_net),
449 Equals('ethernet.wireless')456 Equals('ethernet.wireless')
450 )457 )
451458
@@ -453,7 +460,7 @@
453 self.createEthernetInterface('eth1')460 self.createEthernetInterface('eth1')
454 self.assertThat(get_interface_type(461 self.assertThat(get_interface_type(
455 'eth1', sys_class_net=self.tmp_sys_net,462 'eth1', sys_class_net=self.tmp_sys_net,
456 proc_net_vlan=self.tmp_proc_net_vlan),463 proc_net=self.tmp_proc_net),
457 Equals('ethernet')464 Equals('ethernet')
458 )465 )
459466
@@ -461,7 +468,7 @@
461 self.createLoopbackInterface('lo')468 self.createLoopbackInterface('lo')
462 self.assertThat(get_interface_type(469 self.assertThat(get_interface_type(
463 'lo', sys_class_net=self.tmp_sys_net,470 'lo', sys_class_net=self.tmp_sys_net,
464 proc_net_vlan=self.tmp_proc_net_vlan),471 proc_net=self.tmp_proc_net),
465 Equals('loopback')472 Equals('loopback')
466 )473 )
467474
@@ -469,7 +476,7 @@
469 self.createIpIpInterface('tun0')476 self.createIpIpInterface('tun0')
470 self.assertThat(get_interface_type(477 self.assertThat(get_interface_type(
471 'tun0', sys_class_net=self.tmp_sys_net,478 'tun0', sys_class_net=self.tmp_sys_net,
472 proc_net_vlan=self.tmp_proc_net_vlan),479 proc_net=self.tmp_proc_net),
473 Equals('ipip')480 Equals('ipip')
474 )481 )
475482
@@ -477,18 +484,16 @@
477 self.createInterfaceType('avian0', 1149)484 self.createInterfaceType('avian0', 1149)
478 self.assertThat(get_interface_type(485 self.assertThat(get_interface_type(
479 'avian0', sys_class_net=self.tmp_sys_net,486 'avian0', sys_class_net=self.tmp_sys_net,
480 proc_net_vlan=self.tmp_proc_net_vlan),487 proc_net=self.tmp_proc_net),
481 Equals('unknown-1149')488 Equals('unknown-1149')
482 )489 )
483490
484491
485class TestAnnotateWithDriverInformation(MAASTestCase):492class TestAnnotateWithDriverInformation(FakeSysProcTestCase):
486493
487 def test__populates_interface_type_for_each_interface(self):494 def test__populates_interface_type_for_each_interface(self):
488 # Note: this is more of an end-to-end test, since we call495 # Note: this is more of an end-to-end test, since we call
489 # "/sbin/ip addr" on the host running the tests. This is necessary496 # "/sbin/ip addr" on the host running the tests.
490 # because we don't have dependency injection for the directory names
491 # all the way through.
492 ip_addr_output = check_output(['/sbin/ip', 'addr'])497 ip_addr_output = check_output(['/sbin/ip', 'addr'])
493 interfaces = parse_ip_addr(ip_addr_output)498 interfaces = parse_ip_addr(ip_addr_output)
494 interfaces_with_types = annotate_with_driver_information(interfaces)499 interfaces_with_types = annotate_with_driver_information(interfaces)
@@ -502,6 +507,89 @@
502 elif iface['type'] == 'ethernet.bridge':507 elif iface['type'] == 'ethernet.bridge':
503 self.expectThat(iface, Contains('bridged_interfaces'))508 self.expectThat(iface, Contains('bridged_interfaces'))
504509
510 def test__ignores_bond_interfaces_with_no_parents(self):
511 interfaces = {
512 'eth0': {},
513 'eth1': {},
514 'bond0': {},
515 'bond1': {},
516 }
517 self.createEthernetInterface('eth0', is_physical=True)
518 self.createEthernetInterface('eth1', is_physical=True)
519 self.createEthernetInterface(
520 'bond0', is_bond=True, bonded_interfaces=['eth0, eth1'])
521 self.createEthernetInterface(
522 'bond1', is_bond=True, bonded_interfaces=[])
523 interfaces = annotate_with_driver_information(
524 interfaces, sys_class_net=self.tmp_sys_net,
525 proc_net=self.tmp_proc_net)
526 self.expectThat(interfaces, HasLength(3))
527 self.expectThat(interfaces, Contains('eth0'))
528 self.expectThat(interfaces, Contains('eth1'))
529 self.expectThat(interfaces, Contains('bond0'))
530
531 def test__finds_bond_members_original_mac_addresses(self):
532 testdata = dedent("""\
533 Ethernet Channel Bonding Driver: v3.7.1 (April 27, 2011)
534
535 Bonding Mode: fault-tolerance (active-backup)
536 Primary Slave: None
537 Currently Active Slave: ens11
538 MII Status: up
539 MII Polling Interval (ms): 100
540 Up Delay (ms): 200
541 Down Delay (ms): 0
542
543 Slave Interface: ens11
544 MII Status: up
545 Speed: Unknown
546 Duplex: Unknown
547 Link Failure Count: 0
548 Permanent HW addr: 52:54:00:ea:1c:fc
549 Slave queue ID: 0
550
551 Slave Interface: ens3
552 MII Status: up
553 Speed: Unknown
554 Duplex: Unknown
555 Link Failure Count: 0
556 Permanent HW addr: 52:54:00:13:0e:6f
557 Slave queue ID: 0
558 """)
559 proc_net_bonding_bond0 = os.path.join(
560 self.tmp_proc_net, "bonding", "bond0")
561 with open(proc_net_bonding_bond0, mode='w') as f:
562 f.write(testdata)
563 interfaces = {
564 "ens3": {"mac": "00:01:02:03:04:05"},
565 "ens11": {"mac": "01:02:03:04:05:06"},
566 }
567 annotate_with_proc_net_bonding_original_macs(
568 interfaces, proc_net=self.tmp_proc_net)
569 self.assertEqual(
570 {
571 "ens3": {"mac": "52:54:00:13:0e:6f"},
572 "ens11": {"mac": "52:54:00:ea:1c:fc"},
573 },
574 interfaces
575 )
576
577 def test__ignores_missing_proc_net_bonding(self):
578 os.rmdir(os.path.join(self.tmp_proc_net, "bonding"))
579 interfaces = {
580 "ens3": {"mac": "00:01:02:03:04:05"},
581 "ens11": {"mac": "01:02:03:04:05:06"},
582 }
583 annotate_with_proc_net_bonding_original_macs(
584 interfaces, proc_net=self.tmp_proc_net)
585 self.assertEqual(
586 {
587 "ens3": {"mac": "00:01:02:03:04:05"},
588 "ens11": {"mac": "01:02:03:04:05:06"},
589 },
590 interfaces
591 )
592
505593
506class TestGetIPAddr(MAASTestCase):594class TestGetIPAddr(MAASTestCase):
507 """Tests for `get_ip_addr`, `get_ip_addr_json`, `get_mac_addresses`."""595 """Tests for `get_ip_addr`, `get_ip_addr_json`, `get_mac_addresses`."""