diff -Nru nplan-0.13~16.04/debian/changelog nplan-0.14~16.04/debian/changelog --- nplan-0.13~16.04/debian/changelog 2016-10-20 14:44:36.000000000 +0000 +++ nplan-0.14~16.04/debian/changelog 2016-11-09 16:00:31.000000000 +0000 @@ -1,3 +1,21 @@ +nplan (0.14~16.04) xenial; urgency=medium + + * Update to 0.14 for xenial. + * tests/integration.py: Re-enable search domain check, that fix landed in + xenial. + + -- Martin Pitt Wed, 09 Nov 2016 17:00:31 +0100 + +nplan (0.14) zesty; urgency=medium + + * tests/generate.py: Introduce macros for commonly expected networkd output + * networkd: Use NetworkManager compatible DHCP route metrics (LP: #1639754) + * doc/netplan.md: Fix wrong wifi reference in "br0" example + * doc/netplan.md: Clarify introduction + * tests/integration.py: Fix race condition with waiting for networkd + + -- Martin Pitt Wed, 09 Nov 2016 16:57:49 +0100 + nplan (0.13~16.04) xenial; urgency=medium [ Martin Pitt ] diff -Nru nplan-0.13~16.04/doc/netplan.md nplan-0.14~16.04/doc/netplan.md --- nplan-0.13~16.04/doc/netplan.md 2016-10-20 14:23:58.000000000 +0000 +++ nplan-0.14~16.04/doc/netplan.md 2016-11-09 15:57:49.000000000 +0000 @@ -13,13 +13,11 @@ ``/{lib,etc,run}/netplan/*.yaml`` and writes configuration to ``/run`` to hand off control of devices to the specified networking daemon. - - Wifi and WWAN get managed by NetworkManager - - Any other configured devices get handled by systemd-networkd by default, + - Configured devices get handled by systemd-networkd by default, unless explicitly marked as managed by a specific renderer (NetworkManager) - Devices not covered by the network config do not get touched at all. - Usable in initramfs (few dependencies and fast) - No persistent generated config, only original YAML config - - Default policy applies with no config file present - Parser supports multiple config files to allow applications like libvirt or lxd to package up expected network config (``virbr0``, ``lxdbr0``), or to change the global default policy to use NetworkManager for everything. @@ -333,7 +331,7 @@ # set-name: allowed br0: # IDs of the components; switchports expands into multiple interfaces - interfaces: [my_ap, switchports] + interfaces: [wlp1s0, switchports] dhcp4: true routes: - to: 0.0.0.0/0 diff -Nru nplan-0.13~16.04/src/networkd.c nplan-0.14~16.04/src/networkd.c --- nplan-0.13~16.04/src/networkd.c 2016-10-20 14:23:58.000000000 +0000 +++ nplan-0.14~16.04/src/networkd.c 2016-11-09 15:57:49.000000000 +0000 @@ -169,6 +169,10 @@ g_string_append_printf(s, "VLAN=%s\n", nd->id); } + /* NetworkManager compatible route metrics */ + if (def->dhcp4 || def->dhcp6) + g_string_append_printf(s, "\n[DHCP]\nRouteMetric=%i\n", (def->type == ND_WIFI ? 600 : 100)); + g_string_free_to_file(s, rootdir, path, ".network"); } diff -Nru nplan-0.13~16.04/tests/generate.py nplan-0.14~16.04/tests/generate.py --- nplan-0.13~16.04/tests/generate.py 2016-10-20 14:23:58.000000000 +0000 +++ nplan-0.14~16.04/tests/generate.py 2016-11-09 15:57:49.000000000 +0000 @@ -32,6 +32,12 @@ # make sure we fail on criticals os.environ['G_DEBUG'] = 'fatal-criticals' +# common patterns for expected output +ND_DHCP4 = '[Match]\nName=%s\n\n[Network]\nDHCP=ipv4\n\n[DHCP]\nRouteMetric=100\n' +ND_WIFI_DHCP4 = '[Match]\nName=%s\n\n[Network]\nDHCP=ipv4\n\n[DHCP]\nRouteMetric=600\n' +ND_DHCP6 = '[Match]\nName=%s\n\n[Network]\nDHCP=ipv6\n\n[DHCP]\nRouteMetric=100\n' +ND_DHCPYES = '[Match]\nName=%s\n\n[Network]\nDHCP=yes\n\n[DHCP]\nRouteMetric=100\n' + class TestBase(unittest.TestCase): def setUp(self): @@ -305,7 +311,7 @@ engreen: dhcp4: y''') - self.assert_networkd({'engreen.network': '[Match]\nName=engreen\n\n[Network]\nDHCP=ipv4\n'}) + self.assert_networkd({'engreen.network': ND_DHCP4 % 'engreen'}) def test_eth_match_dhcp4(self): self.generate('''network: @@ -316,7 +322,7 @@ driver: ixgbe dhcp4: true''') - self.assert_networkd({'def1.network': '[Match]\nDriver=ixgbe\n\n[Network]\nDHCP=ipv4\n'}) + self.assert_networkd({'def1.network': '[Match]\nDriver=ixgbe\n\n[Network]\nDHCP=ipv4\n\n[DHCP]\nRouteMetric=100\n'}) self.assert_udev('ACTION=="add|change", SUBSYSTEM=="net", ENV{ID_NET_DRIVER}=="ixgbe", ENV{NM_UNMANAGED}="1"\n') def test_eth_match_name(self): @@ -328,7 +334,7 @@ name: green dhcp4: true''') - self.assert_networkd({'def1.network': '[Match]\nName=green\n\n[Network]\nDHCP=ipv4\n'}) + self.assert_networkd({'def1.network': ND_DHCP4 % 'green'}) self.assert_nm(None, '''[keyfile] # devices managed by networkd unmanaged-devices+=interface-name:green,''') @@ -346,7 +352,7 @@ # the .network needs to match on the renamed name self.assert_networkd({'def1.link': '[Match]\nOriginalName=green\n\n[Link]\nName=blue\nWakeOnLan=off\n', - 'def1.network': '[Match]\nName=blue\n\n[Network]\nDHCP=ipv4\n'}) + 'def1.network': ND_DHCP4 % 'blue'}) self.assert_nm(None, '''[keyfile] # devices managed by networkd unmanaged-devices+=interface-name:blue,''') @@ -359,7 +365,7 @@ match: {name: "*"} dhcp4: true''') - self.assert_networkd({'def1.network': '[Match]\nName=*\n\n[Network]\nDHCP=ipv4\n'}) + self.assert_networkd({'def1.network': ND_DHCP4 % '*'}) self.assert_nm(None, '''[keyfile] # devices managed by networkd unmanaged-devices+=interface-name:*,''') @@ -373,7 +379,7 @@ match: {} dhcp4: true''') - self.assert_networkd({'def1.network': '[Match]\n\n[Network]\nDHCP=ipv4\n'}) + self.assert_networkd({'def1.network': '[Match]\n\n[Network]\nDHCP=ipv4\n\n[DHCP]\nRouteMetric=100\n'}) self.assert_nm(None, '''[keyfile] # devices managed by networkd unmanaged-devices+=type:ethernet,''') @@ -388,7 +394,16 @@ name: en1s* macaddress: 00:11:22:33:44:55 dhcp4: on''') - self.assert_networkd({'def1.network': '[Match]\nMACAddress=00:11:22:33:44:55\nName=en1s*\n\n[Network]\nDHCP=ipv4\n'}) + self.assert_networkd({'def1.network': '''[Match] +MACAddress=00:11:22:33:44:55 +Name=en1s* + +[Network] +DHCP=ipv4 + +[DHCP] +RouteMetric=100 +'''}) self.assert_nm(None, '''[keyfile] # devices managed by networkd unmanaged-devices+=mac:00:11:22:33:44:55,''') @@ -401,7 +416,7 @@ eth0: dhcp4: true''') - self.assert_networkd({'eth0.network': '[Match]\nName=eth0\n\n[Network]\nDHCP=ipv4\n'}) + self.assert_networkd({'eth0.network': ND_DHCP4 % 'eth0'}) self.assert_nm(None, '''[keyfile] # devices managed by networkd unmanaged-devices+=interface-name:eth0,''') @@ -418,7 +433,7 @@ eth0: dhcp4: true''') - self.assert_networkd({'eth0.network': '[Match]\nName=eth0\n\n[Network]\nDHCP=ipv4\n'}) + self.assert_networkd({'eth0.network': ND_DHCP4 % 'eth0'}) self.assert_nm(None, '''[keyfile] # devices managed by networkd unmanaged-devices+=interface-name:eth0,''') @@ -436,7 +451,7 @@ renderer: networkd dhcp4: true''') - self.assert_networkd({'eth0.network': '[Match]\nName=eth0\n\n[Network]\nDHCP=ipv4\n'}) + self.assert_networkd({'eth0.network': ND_DHCP4 % 'eth0'}) self.assert_nm(None, '''[keyfile] # devices managed by networkd unmanaged-devices+=interface-name:eth0,''') @@ -447,14 +462,14 @@ version: 2 ethernets: eth0: {dhcp6: true}''') - self.assert_networkd({'eth0.network': '[Match]\nName=eth0\n\n[Network]\nDHCP=ipv6\n'}) + self.assert_networkd({'eth0.network': ND_DHCP6 % 'eth0'}) def test_eth_dhcp4_and_6(self): self.generate('''network: version: 2 ethernets: eth0: {dhcp4: true, dhcp6: true}''') - self.assert_networkd({'eth0.network': '[Match]\nName=eth0\n\n[Network]\nDHCP=yes\n'}) + self.assert_networkd({'eth0.network': ND_DHCPYES % 'eth0'}) def test_eth_manual_addresses(self): self.generate('''network: @@ -490,6 +505,9 @@ DHCP=ipv4 Address=192.168.14.2/24 Address=2001:FFfe::1/64 + +[DHCP] +RouteMetric=100 '''}) def test_wifi(self): @@ -506,7 +524,7 @@ mode: adhoc dhcp4: yes''') - self.assert_networkd({'wl0.network': '[Match]\nName=wl0\n\n[Network]\nDHCP=ipv4\n'}) + self.assert_networkd({'wl0.network': ND_WIFI_DHCP4 % 'wl0'}) self.assert_nm(None, '''[keyfile] # devices managed by networkd unmanaged-devices+=interface-name:wl0,''') @@ -567,7 +585,7 @@ dhcp4: true''') self.assert_networkd({'br0.netdev': '[NetDev]\nName=br0\nKind=bridge\n', - 'br0.network': '[Match]\nName=br0\n\n[Network]\nDHCP=ipv4\n'}) + 'br0.network': ND_DHCP4 % 'br0'}) self.assert_nm(None, '''[keyfile] # devices managed by networkd unmanaged-devices+=interface-name:br0,''') @@ -583,7 +601,7 @@ dhcp4: true''') self.assert_networkd({'br0.netdev': '[NetDev]\nName=br0\nKind=bridge\n', - 'br0.network': '[Match]\nName=br0\n\n[Network]\nDHCP=ipv4\n'}) + 'br0.network': ND_DHCP4 % 'br0'}) self.assert_nm(None, '''[keyfile] # devices managed by networkd unmanaged-devices+=interface-name:br0,''') @@ -601,7 +619,15 @@ dhcp4: true''') self.assert_networkd({'br0.netdev': '[NetDev]\nName=br0\nKind=bridge\n', - 'br0.network': '[Match]\nName=br0\n\n[Network]\nDHCP=ipv4\nAddress=1.2.3.4/12\n'}) + 'br0.network': '''[Match] +Name=br0 + +[Network] +DHCP=ipv4 +Address=1.2.3.4/12 + +[DHCP]\nRouteMetric=100 +'''}) self.assert_nm(None, '''[keyfile] # devices managed by networkd unmanaged-devices+=interface-name:br0,''') @@ -621,7 +647,7 @@ dhcp4: true''') self.assert_networkd({'br0.netdev': '[NetDev]\nName=br0\nKind=bridge\n', - 'br0.network': '[Match]\nName=br0\n\n[Network]\nDHCP=ipv4\n', + 'br0.network': ND_DHCP4 % 'br0', 'eno1.network': '[Match]\nName=eno1\n\n' '[Network]\nBridge=br0\nLinkLocalAddressing=no\nIPv6AcceptRA=no\n', 'switchports.network': '[Match]\nDriver=yayroute\n\n' @@ -635,7 +661,7 @@ dhcp4: true''') self.assert_networkd({'bn0.netdev': '[NetDev]\nName=bn0\nKind=bond\n', - 'bn0.network': '[Match]\nName=bn0\n\n[Network]\nDHCP=ipv4\n'}) + 'bn0.network': ND_DHCP4 % 'bn0'}) self.assert_nm(None, '''[keyfile] # devices managed by networkd unmanaged-devices+=interface-name:bn0,''') @@ -655,7 +681,7 @@ dhcp4: true''') self.assert_networkd({'bn0.netdev': '[NetDev]\nName=bn0\nKind=bond\n', - 'bn0.network': '[Match]\nName=bn0\n\n[Network]\nDHCP=ipv4\n', + 'bn0.network': ND_DHCP4 % 'bn0', 'eno1.network': '[Match]\nName=eno1\n\n' '[Network]\nBond=bn0\nLinkLocalAddressing=no\nIPv6AcceptRA=no\n', 'switchports.network': '[Match]\nDriver=yayroute\n\n' @@ -727,7 +753,7 @@ 'enblue.netdev': '[NetDev]\nName=enblue\nKind=vlan\n\n[VLAN]\nId=1\n', 'engreen.netdev': '[NetDev]\nName=engreen\nKind=vlan\n\n[VLAN]\nId=2\n', 'enblue.network': '[Match]\nName=enblue\n\n[Network]\nAddress=1.2.3.4/24\n', - 'engreen.network': '[Match]\nName=engreen\n\n[Network]\nDHCP=ipv6\n'}) + 'engreen.network': ND_DHCP6 % 'engreen'}) self.assert_nm(None, '''[keyfile] # devices managed by networkd unmanaged-devices+=interface-name:en1,interface-name:enblue,interface-name:engreen,''') @@ -2064,7 +2090,7 @@ dhcp4: y''', confs={'backend': 'network:\n renderer: networkd'}) - self.assert_networkd({'engreen.network': '[Match]\nName=engreen\n\n[Network]\nDHCP=ipv4\n'}) + self.assert_networkd({'engreen.network': ND_DHCP4 % 'engreen'}) self.assert_nm(None, '''[keyfile] # devices managed by networkd unmanaged-devices+=interface-name:engreen,''') @@ -2082,8 +2108,8 @@ enblue: dhcp4: true'''}) - self.assert_networkd({'enblue.network': '[Match]\nName=enblue\n\n[Network]\nDHCP=ipv4\n', - 'engreen.network': '[Match]\nName=engreen\n\n[Network]\nDHCP=ipv4\n'}) + self.assert_networkd({'enblue.network': ND_DHCP4 % 'enblue', + 'engreen.network': ND_DHCP4 % 'engreen'}) self.assert_nm(None, '''[keyfile] # devices managed by networkd unmanaged-devices+=interface-name:enblue,interface-name:engreen,''') @@ -2103,7 +2129,7 @@ dhcp4: true'''}) self.assert_networkd({'engreen.link': '[Match]\nOriginalName=engreen\n\n[Link]\nWakeOnLan=magic\n', - 'engreen.network': '[Match]\nName=engreen\n\n[Network]\nDHCP=ipv4\n'}) + 'engreen.network': ND_DHCP4 % 'engreen'}) def test_cleanup_old_config(self): self.generate('''network: @@ -2123,7 +2149,7 @@ ethernets: engreen: {dhcp4: true}''') - self.assert_networkd({'engreen.network': '[Match]\nName=engreen\n\n[Network]\nDHCP=ipv4\n'}) + self.assert_networkd({'engreen.network': ND_DHCP4 % 'engreen'}) self.assert_nm(None, '''[keyfile] # devices managed by networkd unmanaged-devices+=interface-name:engreen,''') @@ -2145,7 +2171,7 @@ dhcp4: true'''}) self.assert_networkd({'br0.netdev': '[NetDev]\nName=br0\nKind=bridge\n', - 'br0.network': '[Match]\nName=br0\n\n[Network]\nDHCP=ipv4\n', + 'br0.network': ND_DHCP4 % 'br0', 'eno1.network': '[Match]\nName=eno1\n\n' '[Network]\nBridge=br0\nLinkLocalAddressing=no\nIPv6AcceptRA=no\n', 'switchports.network': '[Match]\nDriver=yayroute\n\n' @@ -2174,9 +2200,9 @@ ethernets: {enred: {wakeonlan: true}}'''}) # b.yaml in /run/ should completely shadow b.yaml in /etc, thus no enred.link - self.assert_networkd({'engreen.network': '[Match]\nName=engreen\n\n[Network]\nDHCP=ipv4\n', - 'enred.network': '[Match]\nName=enred\n\n[Network]\nDHCP=ipv4\n', - 'enblue.network': '[Match]\nName=enblue\n\n[Network]\nDHCP=ipv4\n'}) + self.assert_networkd({'engreen.network': ND_DHCP4 % 'engreen', + 'enred.network': ND_DHCP4 % 'enred', + 'enblue.network': ND_DHCP4 % 'enblue'}) def test_def_in_lib(self): libdir = os.path.join(self.workdir.name, 'lib', 'netplan') @@ -2212,9 +2238,9 @@ version: 2 ethernets: {enred: {wakeonlan: true}}'''}) - self.assert_networkd({'engreen.network': '[Match]\nName=engreen\n\n[Network]\nDHCP=ipv4\n', + self.assert_networkd({'engreen.network': ND_DHCP4 % 'engreen', 'enred.link': '[Match]\nOriginalName=enred\n\n[Link]\nWakeOnLan=magic\n', - 'enyellow.network': '[Match]\nName=enyellow\n\n[Network]\nDHCP=ipv4\n', - 'enblue.network': '[Match]\nName=enblue\n\n[Network]\nDHCP=ipv4\n'}) + 'enyellow.network': ND_DHCP4 % 'enyellow', + 'enblue.network': ND_DHCP4 % 'enblue'}) unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout, verbosity=2)) diff -Nru nplan-0.13~16.04/tests/integration.py nplan-0.14~16.04/tests/integration.py --- nplan-0.13~16.04/tests/integration.py 2016-10-20 14:44:36.000000000 +0000 +++ nplan-0.14~16.04/tests/integration.py 2016-11-09 15:57:49.000000000 +0000 @@ -320,7 +320,7 @@ # start NM so that we can verify that it does not manage anything subprocess.check_call(['systemctl', 'start', '--no-block', 'NetworkManager.service']) # wait until networkd is done - if subprocess.call(['systemctl', 'is-active', '--quiet', 'systemd-networkd.service']) == 0: + if self.is_active('systemd-networkd.service'): if subprocess.call(['/lib/systemd/systemd-networkd-wait-online', '--quiet', '--timeout=25']) != 0: subprocess.call(['journalctl', '-b', '--no-pager', '-t', 'systemd-networkd']) st = subprocess.check_output(['networkctl'], stderr=subprocess.PIPE, universal_newlines=True) @@ -345,6 +345,14 @@ else: self.fail('timed out waiting for %s to get connected by NM:\n%s' % (iface, out.decode())) + @classmethod + def is_active(klass, unit): + '''Check if given unit is active or activating''' + + p = subprocess.Popen(['systemctl', 'is-active', unit], stdout=subprocess.PIPE) + out = p.communicate()[0] + return p.returncode == 0 or out.startswith(b'activating') + class _CommonTests: def test_eth_and_bridge(self): @@ -464,8 +472,7 @@ else: sys.stdout.write('[/etc/resolv.conf] ') sys.stdout.flush() - # LP: #1635256 - # self.assertRegex(resolv_conf, 'search.*fakesuffix') + self.assertRegex(resolv_conf, 'search.*fakesuffix') # /etc/resolve.conf often already has three nameserver entries if 'nameserver 172.1.2.3' not in resolv_conf: self.assertGreaterEqual(resolv_conf.count('nameserver'), 3)