diff -Nru netplan.io-0.40.2.1/debian/changelog netplan.io-0.40.1~18.04.2ubuntu1/debian/changelog --- netplan.io-0.40.2.1/debian/changelog 2018-10-22 18:58:40.000000000 +0000 +++ netplan.io-0.40.1~18.04.2ubuntu1/debian/changelog 2018-11-09 15:52:33.000000000 +0000 @@ -1,14 +1,20 @@ -netplan.io (0.40.2.1) cosmic; urgency=medium +netplan.io (0.40.1~18.04.2ubuntu1) xenial; urgency=medium + + * Fix netplan apply on composite devices and memebers (LP: #1802322) + + -- Ryan Harper Fri, 09 Nov 2018 09:52:33 -0600 + +netplan.io (0.40.1~18.04.2) bionic; urgency=medium * Fix typo breaking rename on 'netplan apply'. (LP: #1770082) - -- Mathieu Trudel-Lapierre Mon, 22 Oct 2018 14:58:40 -0400 + -- Mathieu Trudel-Lapierre Mon, 22 Oct 2018 15:02:30 -0400 -netplan.io (0.40.2) cosmic; urgency=medium +netplan.io (0.40.1~18.04.1) bionic; urgency=medium - * tests/integration.py: Mark regexes with r to pacify pycodestyle's W605. + * Backport netplan 0.40.1 to 18.04. (LP: #1793309) - -- Mathieu Trudel-Lapierre Thu, 04 Oct 2018 16:02:53 -0400 + -- Mathieu Trudel-Lapierre Thu, 04 Oct 2018 13:59:42 -0400 netplan.io (0.40.1) cosmic; urgency=medium diff -Nru netplan.io-0.40.2.1/netplan/cli/commands/apply.py netplan.io-0.40.1~18.04.2ubuntu1/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.1~18.04.2ubuntu1/netplan/cli/commands/apply.py 2018-11-09 15:52:33.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 phy: + # do not rename virtual devices + logging.debug('Skipping non-physical interface: %s', interface) + continue + if NetplanApply.is_composite_member(composite_interfaces, phy): + 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.1~18.04.2ubuntu1/tests/integration.py --- netplan.io-0.40.2.1/tests/integration.py 2018-10-22 18:53:48.000000000 +0000 +++ netplan.io-0.40.1~18.04.2ubuntu1/tests/integration.py 2018-11-09 15:52:33.000000000 +0000 @@ -64,7 +64,7 @@ # set regulatory domain "EU", so that we can use 80211.a 5 GHz channels out = subprocess.check_output(['iw', 'reg', 'get'], universal_newlines=True) - m = re.match(r'^(?:global\n)?country (\S+):', out) + m = re.match('^(?:global\n)?country (\S+):', out) assert m klass.orig_country = m.group(1) subprocess.check_call(['iw', 'reg', 'set', 'EU']) @@ -217,6 +217,12 @@ if start_dnsmasq: self.start_dnsmasq(ipv6_mode, self.dev_e_ap) + def get_mac(self, iface): + sys_path = '/sys/class/net/%s/address' % iface + if os.path.exists(sys_path): + with open(sys_path, 'r') as f: + return f.read().rstrip() + # # Internal implementation details # @@ -425,7 +431,7 @@ expected_state = (self.backend == 'NetworkManager') and 'connected' or 'unmanaged' out = subprocess.check_output(['nmcli', 'dev'], universal_newlines=True) for i in [self.dev_e_client, self.dev_e2_client, 'mybr']: - self.assertRegex(out, r'%s\s+(ethernet|bridge)\s+%s' % (i, expected_state)) + self.assertRegex(out, '%s\s+(ethernet|bridge)\s+%s' % (i, expected_state)) def test_eth_mtu(self): self.setup_eth(None) @@ -1090,7 +1096,7 @@ expected_state = (self.backend == 'NetworkManager') and 'connected' or 'unmanaged' out = subprocess.check_output(['nmcli', 'dev'], universal_newlines=True) for i in [self.dev_e_client, self.dev_e2_client]: - self.assertRegex(out, r'%s\s+(ethernet|bridge)\s+%s' % (i, expected_state)) + self.assertRegex(out, '%s\s+(ethernet|bridge)\s+%s' % (i, expected_state)) with open('/etc/resolv.conf') as f: resolv_conf = f.read() @@ -1297,6 +1303,7 @@ def test_mix_bridge_on_bond(self): self.setup_eth(None) + self.start_dnsmasq(None, self.dev_e2_ap) self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'bond0'], stderr=subprocess.DEVNULL) self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'br0'], stderr=subprocess.DEVNULL) self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'br1'], stderr=subprocess.DEVNULL) @@ -1312,6 +1319,7 @@ interfaces: [ethb2] parameters: mode: balance-rr + mii-monitor-interval: 5 ethernets: ethbn: match: {name: %(ec)s} @@ -1671,6 +1679,54 @@ with open('/sys/class/net/mybond/bonding/arp_validate') as f: self.assertEqual(f.read().strip(), 'all 3') + def test_bond_no_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: {macaddress: %(ec)s} + set-name: ethbn1 + ethbn2: + match: {macaddress: %(e2c)s} + set-name: ethbn2 + bonds: + mybond: + interfaces: [ethbn1, ethbn2] + macaddress: %(ec)s + parameters: + down-delay: 0 + lacp-rate: fast + mii-monitor-interval: 100 + mode: 802.3ad + transmit-hash-policy: layer3+4 + up-delay: 0 + dhcp4: yes''' % {'r': self.backend, 'ec': self.get_mac(self.dev_e_client), + 'e2c': self.get_mac(self.dev_e2_client)}) + self.generate_and_settle() + self.assert_iface_up(self.dev_e_client, + ['master mybond'], + ['inet ']) + self.assert_iface_up('mybond', + ['inet 192.168.5.[0-9]+/24']) + + # down bond, and both bridge interfaces + for iface in ['mybond', 'ethbn1', 'ethbn2']: + subprocess.check_call(['ip', 'link', 'set', 'down', iface]) + + self.generate_and_settle() + + # re-assert ifaces are up + self.assert_iface_up(self.dev_e_client, + ['master mybond'], + ['inet ']) + self.assert_iface_up('mybond', + ['inet 192.168.5.[0-9]+/24']) + + def test_bridge_anonymous(self): self.setup_eth(None) self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'mybr'], stderr=subprocess.DEVNULL)