diff -Nru netplan.io-0.40.2.1/debian/changelog netplan.io-0.40.2.2/debian/changelog --- netplan.io-0.40.2.1/debian/changelog 2018-10-22 18:58:40.000000000 +0000 +++ netplan.io-0.40.2.2/debian/changelog 2018-11-21 19:28:42.000000000 +0000 @@ -1,3 +1,11 @@ +netplan.io (0.40.2.2) cosmic; urgency=medium + + * Fix idempotency in renaming: bond members should be exempt from rename, as + they may all share a single MAC for the bond device. (LP: #1802322) + * tests/integration.py: add test designed to catch the above regression. + + -- Mathieu Trudel-Lapierre Wed, 21 Nov 2018 14:28:42 -0500 + netplan.io (0.40.2.1) cosmic; urgency=medium * Fix typo breaking rename on 'netplan apply'. (LP: #1770082) diff -Nru netplan.io-0.40.2.1/netplan/cli/commands/apply.py netplan.io-0.40.2.2/netplan/cli/commands/apply.py --- netplan.io-0.40.2.1/netplan/cli/commands/apply.py 2018-10-22 18:54:38.000000000 +0000 +++ netplan.io-0.40.2.2/netplan/cli/commands/apply.py 2018-11-21 19:28:42.000000000 +0000 @@ -111,6 +111,21 @@ utils.systemctl_network_manager('start', sync=sync) @staticmethod + def is_composite_member(composites, phy): # pragma: nocover (covered in autopkgtest) + """ + Is this physical interface a member of a 'composite' virtual + interface? (bond, bridge) + """ + for composite in composites: + for id, settings in composite.items(): + members = settings.get('interfaces', []) + for iface in members: + if iface == phy: + return True + + return False + + @staticmethod def process_link_changes(interfaces, config_manager): # pragma: nocover (covered in autopkgtest) """ Go through the pending changes and pick what needs special @@ -120,6 +135,7 @@ changes = {} phys = dict(config_manager.physical_interfaces) + composite_interfaces = [config_manager.bridges, config_manager.bonds] # TODO (cyphermox): factor out some of this matching code (and make it # pretty) in its own module. @@ -147,6 +163,15 @@ # /sys/class/net/ens3/device -> ../../../virtio0 # /sys/class/net/ens3/device/driver -> ../../../../bus/virtio/drivers/virtio_net for interface in interfaces: + if interface not in phys: + # do not rename virtual devices + logging.debug('Skipping non-physical interface: %s', interface) + continue + if NetplanApply.is_composite_member(composite_interfaces, interface): + logging.debug('Skipping composite member %s', interface) + # do not rename members of virtual devices. MAC addresses + # may be the same for all interface members. + continue # try to get the device's driver for matching. devdir = os.path.join('/sys/class/net', interface) try: diff -Nru netplan.io-0.40.2.1/tests/integration.py netplan.io-0.40.2.2/tests/integration.py --- netplan.io-0.40.2.1/tests/integration.py 2018-10-22 18:53:48.000000000 +0000 +++ netplan.io-0.40.2.2/tests/integration.py 2018-11-21 19:28:42.000000000 +0000 @@ -1671,6 +1671,47 @@ with open('/sys/class/net/mybond/bonding/arp_validate') as f: self.assertEqual(f.read().strip(), 'all 3') + def test_bond_mac_rename(self): + self.setup_eth(None) + self.start_dnsmasq(None, self.dev_e2_ap) + self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'mybond'], stderr=subprocess.DEVNULL) + with open(self.config, 'w') as f: + f.write('''network: + renderer: %(r)s + ethernets: + ethbn1: + match: {name: %(ec)s} + dhcp4: no + ethbn2: + match: {name: %(e2c)s} + dhcp4: no + bonds: + mybond: + interfaces: [ethbn1, ethbn2] + macaddress: 00:0a:f7:72:a7:28 + mtu: 9000 + addresses: [ 192.168.5.9/24 ] + gateway4: 192.168.5.1 + parameters: + down-delay: 0 + lacp-rate: fast + mii-monitor-interval: 100 + mode: 802.3ad + transmit-hash-policy: layer3+4 + up-delay: 0 + ''' % {'r': self.backend, 'ec': self.dev_e_client, 'e2c': self.dev_e2_client}) + self.generate_and_settle() + self.assert_iface_up(self.dev_e_client, + ['master mybond', '00:0a:f7:72:a7:28'], + ['inet ']) + self.assert_iface_up(self.dev_e2_client, + ['master mybond', '00:0a:f7:72:a7:28'], + ['inet ']) + self.assert_iface_up('mybond', + ['inet 192.168.5.[0-9]+/24']) + with open('/sys/class/net/mybond/bonding/slaves') as f: + self.assertIn(self.dev_e_client, f.read().strip()) + def test_bridge_anonymous(self): self.setup_eth(None) self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'mybr'], stderr=subprocess.DEVNULL)