diff -Nru nplan-0.32~16.04.3/debian/changelog nplan-0.32~16.04.4/debian/changelog --- nplan-0.32~16.04.3/debian/changelog 2017-12-04 15:57:34.000000000 +0000 +++ nplan-0.32~16.04.4/debian/changelog 2018-03-02 22:02:03.000000000 +0000 @@ -1,3 +1,21 @@ +nplan (0.32~16.04.4) xenial; urgency=medium + + [ Oliver Grawert ] + * Prevent unbinding ath6kl_sdio, driver does not support it correctly. + (LP: #1741910) + + [ Mathieu Trudel-Lapierre ] + * Re-add snap support patch. (LP: #1747714) + * Fix syntax for IPv6 addresses in doc. (LP: #1735317) + * doc: routes are not top-level but per-interface. (LP: #1726695) + * Implement bridge port-priority parameter. (LP: #1735821) + * Implement "optional: true" to correctly write systemd network definitions + with "RequiredForOnline=false", such that these networks do not block boot. + (LP: #1664844) + * Various documentation fixes. (LP: #1751814) + + -- Mathieu Trudel-Lapierre Fri, 02 Mar 2018 17:02:03 -0500 + nplan (0.32~16.04.3) xenial; urgency=medium * tests/integration.py: Really fix skipping test_routes_v6 for the NM diff -Nru nplan-0.32~16.04.3/doc/example-config nplan-0.32~16.04.4/doc/example-config --- nplan-0.32~16.04.3/doc/example-config 2017-05-01 12:31:32.000000000 +0000 +++ nplan-0.32~16.04.4/doc/example-config 2018-03-02 20:41:47.000000000 +0000 @@ -12,9 +12,9 @@ dhcp4: true addresses: - 192.168.14.2/24 - - 2001:1::1/64 + - "2001:1::1/64" gateway4: 192.168.14.1 - gateway6: 2001:1::2 + gateway6: "2001:1::2" routes: - to: 11.22.0.0/16 via: 192.168.14.3 @@ -31,7 +31,7 @@ dhcp6: true switchports: # all cards on second PCI bus; unconfigured by themselves, will be added - # to br0 below + # to br0 below (note: globbing is not supported by NetworkManager) match: name: enp2* mtu: 1280 @@ -49,7 +49,6 @@ access-points: "guest": mode: ap - channel: 11 # no WPA config implies default of open bridges: # the key name is the name for virtual (created) interfaces; no match: and diff -Nru nplan-0.32~16.04.3/doc/netplan.md nplan-0.32~16.04.4/doc/netplan.md --- nplan-0.32~16.04.3/doc/netplan.md 2017-11-08 18:55:15.000000000 +0000 +++ nplan-0.32~16.04.4/doc/netplan.md 2018-03-02 20:41:47.000000000 +0000 @@ -173,7 +173,7 @@ form ``addr/prefixlen``. ``addr`` is an IPv4 or IPv6 address as recognized by **``inet_pton``**(3) and ``prefixlen`` the number of bits of the subnet. - Example: ``addresses: [192.168.14.2/24, 2001:1::1/64]`` + Example: ``addresses: [192.168.14.2/24, "2001:1::1/64"]`` ``gateway4``, ``gateway6`` (scalar) @@ -182,7 +182,7 @@ recognized by **``inet_pton``**(3). Example for IPv4: ``gateway4: 172.16.0.1`` - Example for IPv6: ``gateway6: 2001:4::1`` + Example for IPv6: ``gateway6: "2001:4::1"`` ``nameservers`` (mapping) @@ -197,8 +197,36 @@ [...] nameservers: search: [lab, home] - addresses: [8.8.8.8, FEDC::1] + addresses: [8.8.8.8, "FEDC::1"] +``macaddress`` (scalar) + +: Set the device's MAC address. The MAC address must be in the form +"XX:XX:XX:XX:XX:XX". + + Example: + + ethernets: + id0: + [...] + macaddress: 52:54:00:6b:3c:59 + +``optional`` (boolean) + +: An optional device is not required for booting. Normally, networkd +will wait some time for device to become configured before proceeding +with booting. However, if a device is marked as optional, networkd +will not wait for it. This is *only* supported by networkd, and the +default is false. + + Example: + + ethernets: + eth7: + # this is plugged into a test network that is often + # down - don't wait for it to come up during boot. + dhcp4: true + optional: true Properties for device type ``ethernets:`` ========================================= @@ -257,11 +285,16 @@ database after a packet is received. ``priority`` (scalar) - : Set the priority value for the bridge. This value should be an + : Set the priority value for the bridge. This value should be a number between ``0`` and ``65535``. Lower values mean higher priority. The bridge with the higher priority will be elected as the root bridge. + ``port-priority`` (scalar) + : Set the port priority to . The priority value is + a number between ``0`` and ``63``. This metric is used in the + designated port and root port selection algorithms. + ``forward-delay`` (scalar) : Specify the period of time the bridge will remain in Listening and Learning states before getting to the Forwarding state. This value @@ -406,6 +439,17 @@ active slave will be chosen and how recovery will be handled. The possible values are ``always``, ``better``, and ``failure``. + ``resend-igmp`` (scalar) + : In modes ``balance-rr``, ``active-backup``, ``balance-tlb`` and + ``balance-alb``, a failover can switch IGMP traffic from one + slave to another. + + This parameter specifies how many IGMP membership reports + are issued on a failover event. Values range from 0 to 255. 0 + disables sending membership reports. Otherwise, the first + membership report is sent on failover and subsequent reports + are sent at 200ms intervals. + ``learn-packet-interval`` (scalar) : Specify the interval between sending learning packets to each slave. The value range is between ``1`` and ``0x7fffffff``. The default @@ -473,12 +517,16 @@ dhcp4: true addresses: - 192.168.14.2/24 - - 2001:1::1/64 + - "2001:1::1/64" gateway4: 192.168.14.1 - gateway6: 2001:1::2 + gateway6: "2001:1::2" nameservers: search: [foo.local, bar.local] addresses: [8.8.8.8] + routes: + - to: 0.0.0.0/0 + via: 11.0.0.1 + metric: 3 lom: match: driver: ixgbe @@ -488,7 +536,7 @@ dhcp6: true switchports: # all cards on second PCI bus; unconfigured by themselves, will be added - # to br0 below + # to br0 below (note: globbing is not supported by NetworkManager) match: name: enp2* mtu: 1280 @@ -506,7 +554,6 @@ access-points: "guest": mode: ap - channel: 11 # no WPA config implies default of open bridges: # the key name is the name for virtual (created) interfaces; no match: and @@ -515,10 +562,6 @@ # IDs of the components; switchports expands into multiple interfaces interfaces: [wlp1s0, switchports] dhcp4: true - routes: - - to: 0.0.0.0/0 - via: 11.0.0.1 - metric: 3 diff -Nru nplan-0.32~16.04.3/src/netplan nplan-0.32~16.04.4/src/netplan --- nplan-0.32~16.04.3/src/netplan 2017-11-23 17:29:38.000000000 +0000 +++ nplan-0.32~16.04.4/src/netplan 2018-03-02 20:41:50.000000000 +0000 @@ -29,6 +29,9 @@ path_generate = os.environ.get('NETPLAN_GENERATE_PATH', '/lib/netplan/generate') +NM_SERVICE_NAME = 'NetworkManager.service' +NM_SNAP_SERVICE_NAME = 'snap.network-manager.networkmanager.service' + # # helper functions @@ -67,16 +70,40 @@ return args +def is_nm_snap_enabled(): # pragma: nocover (covered in autopkgtest) + return subprocess.call(['systemctl', '--quiet', 'is-enabled', NM_SNAP_SERVICE_NAME], stderr=subprocess.DEVNULL) == 0 + + +def nmcli(args): # pragma: nocover (covered in autopkgtest) + binary_name = 'nmcli' + + if is_nm_snap_enabled(): + binary_name = 'network-manager.nmcli' + + subprocess.check_call([binary_name] + args, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + + def nm_running(): # pragma: nocover (covered in autopkgtest) '''Check if NetworkManager is running''' try: - subprocess.check_call(['nmcli', 'general'], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT) + nmcli(['general']) return True except (OSError, subprocess.SubprocessError): return False +def systemctl_network_manager(action): # pragma: nocover (covered in autopkgtest) + service_name = NM_SERVICE_NAME + + # If the network-manager snap is installed use its service + # name rather than the one of the deb packaged NetworkManager + if is_nm_snap_enabled(): + service_name = NM_SNAP_SERVICE_NAME + + subprocess.check_call(['systemctl', action, '--no-block', service_name]) + + def replug(device): # pragma: nocover (covered in autopkgtest) '''Unbind and rebind device if it is down''' @@ -126,6 +153,10 @@ if 'brcmfmac' in driver_name: logging.debug('replug %s: brcmfmac drivers do not support rebinding, ignoring', device) return False + # workaround for ath6kl_sdio, interface does not work after unbinding + if 'ath6kl_sdio' in driver_name: + logging.debug('replug %s: ath6kl_sdio driver does not support rebinding, ignoring', device) + return False logging.debug('replug %s: unbinding %s from %s', device, devname, driver) with open(os.path.join(driver, 'unbind'), 'w') as f: f.write(devname) @@ -279,8 +310,12 @@ # restarting NM does not cause new config to be applied, need to shut down devices first for device in devices: # ignore failures here -- some/many devices might not be managed by NM - subprocess.call(['nmcli', 'device', 'disconnect', device], stderr=subprocess.DEVNULL) - subprocess.check_call(['systemctl', 'stop', '--no-block', 'NetworkManager.service']) + try: + nmcli(['device', 'disconnect', device]) + except subprocess.CalledProcessError: + pass + + systemctl_network_manager('stop') else: logging.debug('no netplan generated NM configuration exists') @@ -307,7 +342,7 @@ subprocess.check_call(['systemctl', 'start', '--no-block', 'systemd-networkd.service'] + [os.path.basename(f) for f in glob('/run/systemd/system/*.wants/netplan-wpa@*.service')]) if restart_nm: - subprocess.call(['systemctl', 'start', '--no-block', 'NetworkManager.service']) + systemctl_network_manager('start') def command_ifupdown_migrate(): diff -Nru nplan-0.32~16.04.3/src/networkd.c nplan-0.32~16.04.4/src/networkd.c --- nplan-0.32~16.04.3/src/networkd.c 2017-12-01 20:59:28.000000000 +0000 +++ nplan-0.32~16.04.4/src/networkd.c 2018-03-02 20:42:55.000000000 +0000 @@ -227,6 +227,9 @@ s = g_string_sized_new(200); append_match_section(def, s, TRUE); + if (def->optional) + g_string_append(s, "\n[Link]\nRequiredForOnline=no\n"); + g_string_append(s, "\n[Network]\n"); if (def->dhcp4 && def->dhcp6) g_string_append(s, "DHCP=yes\n"); @@ -261,8 +264,12 @@ if (def->bridge) { g_string_append_printf(s, "Bridge=%s\nLinkLocalAddressing=no\nIPv6AcceptRA=no\n", def->bridge); + if (def->bridge_params.path_cost || def->bridge_params.port_priority) + g_string_append_printf(s, "\n[Bridge]\n"); if (def->bridge_params.path_cost) - g_string_append_printf(s, "\n[Bridge]\nCost=%u\n", def->bridge_params.path_cost); + g_string_append_printf(s, "Cost=%u\n", def->bridge_params.path_cost); + if (def->bridge_params.port_priority) + g_string_append_printf(s, "Priority=%u\n", def->bridge_params.port_priority); } if (def->bond) { g_string_append_printf(s, "Bond=%s\nLinkLocalAddressing=no\nIPv6AcceptRA=no\n", def->bond); diff -Nru nplan-0.32~16.04.3/src/nm.c nplan-0.32~16.04.4/src/nm.c --- nplan-0.32~16.04.3/src/nm.c 2017-11-23 17:29:38.000000000 +0000 +++ nplan-0.32~16.04.4/src/nm.c 2018-03-02 20:41:47.000000000 +0000 @@ -303,8 +303,12 @@ if (def->bridge) { g_string_append_printf(s, "slave-type=bridge\nmaster=%s\n", def->bridge); + if (def->bridge_params.path_cost || def->bridge_params.port_priority) + g_string_append_printf(s, "\n[bridge-port]\n"); if (def->bridge_params.path_cost) - g_string_append_printf(s, "\n[bridge-port]\npath-cost=%u\n", def->bridge_params.path_cost); + g_string_append_printf(s, "path-cost=%u\n", def->bridge_params.path_cost); + if (def->bridge_params.port_priority) + g_string_append_printf(s, "priority=%u\n", def->bridge_params.port_priority); } if (def->bond) g_string_append_printf(s, "slave-type=bond\nmaster=%s\n", def->bond); diff -Nru nplan-0.32~16.04.3/src/parse.c nplan-0.32~16.04.4/src/parse.c --- nplan-0.32~16.04.3/src/parse.c 2017-11-23 17:29:38.000000000 +0000 +++ nplan-0.32~16.04.4/src/parse.c 2018-03-02 20:41:47.000000000 +0000 @@ -767,13 +767,50 @@ return TRUE; } +static gboolean +handle_bridge_port_priority(yaml_document_t* doc, yaml_node_t* node, const void* data, GError** error) +{ + for (yaml_node_pair_t* entry = node->data.mapping.pairs.start; entry < node->data.mapping.pairs.top; entry++) { + yaml_node_t* key, *value; + guint v; + gchar* endptr; + net_definition *component; + guint* ref_ptr; + + key = yaml_document_get_node(doc, entry->key); + assert_type(key, YAML_SCALAR_NODE); + value = yaml_document_get_node(doc, entry->value); + assert_type(value, YAML_SCALAR_NODE); + + component = g_hash_table_lookup(netdefs, scalar(key)); + if (!component) { + add_missing_node(key); + } else { + ref_ptr = ((guint*) ((void*) component + GPOINTER_TO_UINT(data))); + if (*ref_ptr) + return yaml_error(node, error, "%s: interface %s already has a port priority of %u", + cur_netdef->id, scalar(key), *ref_ptr); + + v = g_ascii_strtoull(scalar(value), &endptr, 10); + if (*endptr != '\0' || v > 63) + return yaml_error(node, error, "invalid port priority value (must be between 0 and 63): %s", + scalar(value)); + + g_debug("%s: adding port '%s' of priority: %d", cur_netdef->id, scalar(key), v); + + *ref_ptr = v; + } + } + return TRUE; +} const mapping_entry_handler bridge_params_handlers[] = { {"ageing-time", YAML_SCALAR_NODE, handle_netdef_guint, NULL, netdef_offset(bridge_params.ageing_time)}, - {"priority", YAML_SCALAR_NODE, handle_netdef_guint, NULL, netdef_offset(bridge_params.priority)}, {"forward-delay", YAML_SCALAR_NODE, handle_netdef_guint, NULL, netdef_offset(bridge_params.forward_delay)}, {"hello-time", YAML_SCALAR_NODE, handle_netdef_guint, NULL, netdef_offset(bridge_params.hello_time)}, {"max-age", YAML_SCALAR_NODE, handle_netdef_guint, NULL, netdef_offset(bridge_params.max_age)}, {"path-cost", YAML_MAPPING_NODE, handle_bridge_path_cost, NULL, netdef_offset(bridge_params.path_cost)}, + {"port-priority", YAML_MAPPING_NODE, handle_bridge_port_priority, NULL, netdef_offset(bridge_params.port_priority)}, + {"priority", YAML_SCALAR_NODE, handle_netdef_guint, NULL, netdef_offset(bridge_params.priority)}, {"stp", YAML_SCALAR_NODE, handle_netdef_bool, NULL, netdef_offset(bridge_params.stp)}, {NULL} }; diff -Nru nplan-0.32~16.04.3/src/parse.h nplan-0.32~16.04.4/src/parse.h --- nplan-0.32~16.04.3/src/parse.h 2017-11-08 18:55:15.000000000 +0000 +++ nplan-0.32~16.04.4/src/parse.h 2018-03-02 20:41:47.000000000 +0000 @@ -128,6 +128,7 @@ struct { guint ageing_time; guint priority; + guint port_priority; guint forward_delay; guint hello_time; guint max_age; diff -Nru nplan-0.32~16.04.3/tests/generate.py nplan-0.32~16.04.4/tests/generate.py --- nplan-0.32~16.04.3/tests/generate.py 2017-12-01 20:59:28.000000000 +0000 +++ nplan-0.32~16.04.4/tests/generate.py 2018-03-02 20:45:41.000000000 +0000 @@ -277,16 +277,25 @@ '''networkd output''' def test_eth_optional(self): - # TODO: cyphermox: this is to validate that "optional" does not cause - # any extra config to be generated; and will fail once it's actually - # implemented. self.generate('''network: version: 2 ethernets: eth0: dhcp6: true optional: true''') - self.assert_networkd({'eth0.network': ND_DHCP6 % 'eth0'}) + self.assert_networkd({'eth0.network': '''[Match] +Name=eth0 + +[Link] +RequiredForOnline=no + +[Network] +DHCP=ipv6 + +[DHCP] +UseMTU=true +RouteMetric=100 +'''}) def test_eth_wol(self): self.generate('''network: @@ -970,18 +979,18 @@ interfaces: [eno1, switchports] parameters: ageing-time: 50 - priority: 1000 forward-delay: 12 hello-time: 6 max-age: 24 stp: true path-cost: eno1: 70 + port-priority: + eno1: 14 dhcp4: true''') self.assert_networkd({'br0.netdev': '[NetDev]\nName=br0\nKind=bridge\n\n' '[Bridge]\nAgeingTimeSec=50\n' - 'Priority=1000\n' 'ForwardDelaySec=12\n' 'HelloTimeSec=6\n' 'MaxAgeSec=24\n' @@ -989,7 +998,7 @@ 'br0.network': ND_DHCP4 % 'br0', 'eno1.network': '[Match]\nName=eno1\n\n' '[Network]\nBridge=br0\nLinkLocalAddressing=no\nIPv6AcceptRA=no\n\n' - '[Bridge]\nCost=70\n', + '[Bridge]\nCost=70\nPriority=14\n', 'switchports.network': '[Match]\nDriver=yayroute\n\n' '[Network]\nBridge=br0\nLinkLocalAddressing=no\nIPv6AcceptRA=no\n'}) @@ -2394,6 +2403,8 @@ max-age: 24 path-cost: eno1: 70 + port-priority: + eno1: 61 stp: true dhcp4: true''') @@ -2406,6 +2417,7 @@ [bridge-port] path-cost=70 +priority=61 [ethernet] wake-on-lan=0 @@ -3454,6 +3466,52 @@ eno1: aa dhcp4: true''', expect_fail=True) + def test_bridge_invalid_dev_for_port_prio(self): + self.generate('''network: + version: 2 + ethernets: + eno1: + match: + name: eth0 + bridges: + br0: + interfaces: [eno1] + parameters: + port-priority: + eth0: 50 + dhcp4: true''', expect_fail=True) + + def test_bridge_port_prio_already_defined(self): + self.generate('''network: + version: 2 + ethernets: + eno1: + match: + name: eth0 + bridges: + br0: + interfaces: [eno1] + parameters: + port-priority: + eno1: 50 + eno1: 40 + dhcp4: true''', expect_fail=True) + + def test_bridge_invalid_port_prio(self): + self.generate('''network: + version: 2 + ethernets: + eno1: + match: + name: eth0 + bridges: + br0: + interfaces: [eno1] + parameters: + port-priority: + eno1: 257 + dhcp4: true''', expect_fail=True) + def test_bond_invalid_arp_target(self): self.generate('''network: version: 2 diff -Nru nplan-0.32~16.04.3/tests/integration.py nplan-0.32~16.04.4/tests/integration.py --- nplan-0.32~16.04.3/tests/integration.py 2017-12-04 15:21:11.000000000 +0000 +++ nplan-0.32~16.04.4/tests/integration.py 2018-03-02 21:16:52.000000000 +0000 @@ -479,7 +479,7 @@ with open('/sys/class/net/mybr/brif/%s/path_cost' % self.dev_e2_client) as f: self.assertEqual(f.read().strip(), '50') - def test_bridge_priority(self): + def test_bridge_ageing_time(self): self.setup_eth(None) self.start_dnsmasq(None, self.dev_e2_ap) with open(self.config, 'w') as f: @@ -492,7 +492,7 @@ mybr: interfaces: [ethbr] parameters: - priority: 8192 + ageing-time: 21 stp: false dhcp4: yes''' % {'r': self.backend, 'ec': self.dev_e_client, 'e2c': self.dev_e2_client}) self.generate_and_settle() @@ -508,10 +508,10 @@ universal_newlines=True).splitlines() self.assertEqual(len(lines), 1, lines) self.assertIn(self.dev_e2_client, lines[0]) - with open('/sys/class/net/mybr/bridge/priority') as f: - self.assertEqual(f.read().strip(), '8192') + with open('/sys/class/net/mybr/bridge/ageing_time') as f: + self.assertEqual(f.read().strip(), '2100') - def test_bridge_ageing_time(self): + def test_bridge_max_age(self): self.setup_eth(None) self.start_dnsmasq(None, self.dev_e2_ap) with open(self.config, 'w') as f: @@ -524,7 +524,7 @@ mybr: interfaces: [ethbr] parameters: - ageing-time: 21 + max-age: 12 stp: false dhcp4: yes''' % {'r': self.backend, 'ec': self.dev_e_client, 'e2c': self.dev_e2_client}) self.generate_and_settle() @@ -540,10 +540,10 @@ universal_newlines=True).splitlines() self.assertEqual(len(lines), 1, lines) self.assertIn(self.dev_e2_client, lines[0]) - with open('/sys/class/net/mybr/bridge/ageing_time') as f: - self.assertEqual(f.read().strip(), '2100') + with open('/sys/class/net/mybr/bridge/max_age') as f: + self.assertEqual(f.read().strip(), '1200') - def test_bridge_max_age(self): + def test_bridge_hello_time(self): self.setup_eth(None) self.start_dnsmasq(None, self.dev_e2_ap) with open(self.config, 'w') as f: @@ -556,7 +556,7 @@ mybr: interfaces: [ethbr] parameters: - max-age: 12 + hello-time: 1 stp: false dhcp4: yes''' % {'r': self.backend, 'ec': self.dev_e_client, 'e2c': self.dev_e2_client}) self.generate_and_settle() @@ -572,10 +572,10 @@ universal_newlines=True).splitlines() self.assertEqual(len(lines), 1, lines) self.assertIn(self.dev_e2_client, lines[0]) - with open('/sys/class/net/mybr/bridge/max_age') as f: - self.assertEqual(f.read().strip(), '1200') + with open('/sys/class/net/mybr/bridge/hello_time') as f: + self.assertEqual(f.read().strip(), '100') - def test_bridge_hello_time(self): + def test_bridge_priority(self): self.setup_eth(None) self.start_dnsmasq(None, self.dev_e2_ap) with open(self.config, 'w') as f: @@ -588,7 +588,7 @@ mybr: interfaces: [ethbr] parameters: - hello-time: 1 + priority: 16384 stp: false dhcp4: yes''' % {'r': self.backend, 'ec': self.dev_e_client, 'e2c': self.dev_e2_client}) self.generate_and_settle() @@ -604,8 +604,8 @@ universal_newlines=True).splitlines() self.assertEqual(len(lines), 1, lines) self.assertIn(self.dev_e2_client, lines[0]) - with open('/sys/class/net/mybr/bridge/hello_time') as f: - self.assertEqual(f.read().strip(), '100') + with open('/sys/class/net/mybr/bridge/priority') as f: + self.assertEqual(f.read().strip(), '16384') def test_bridge_forward_delay(self): self.setup_eth(None) @@ -1646,6 +1646,39 @@ self.assertIn(b'metric 799', subprocess.check_output(['ip', '-6', 'route', 'show', '2001:f00f:f00f::/64'])) + def test_bridge_port_priority(self): + self.setup_eth(None) + self.start_dnsmasq(None, self.dev_e2_ap) + with open(self.config, 'w') as f: + f.write('''network: + renderer: %(r)s + ethernets: + ethbr: + match: {name: %(e2c)s} + bridges: + mybr: + interfaces: [ethbr] + parameters: + port-priority: + ethbr: 42 + stp: false + dhcp4: yes''' % {'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, + ['inet 192.168.5.[0-9]+/24'], + ['master']) + self.assert_iface_up(self.dev_e2_client, + ['master mybr'], + ['inet ']) + self.assert_iface_up('mybr', + ['inet 192.168.6.[0-9]+/24']) + lines = subprocess.check_output(['bridge', 'link', 'show', 'mybr'], + universal_newlines=True).splitlines() + self.assertEqual(len(lines), 1, lines) + self.assertIn(self.dev_e2_client, lines[0]) + with open('/sys/class/net/mybr/brif/%s/priority' % self.dev_e2_client) as f: + self.assertEqual(f.read().strip(), '42') + class TestNetworkManager(NetworkTestBase, _CommonTests): backend = 'NetworkManager' @@ -1897,5 +1930,38 @@ with open('/sys/class/net/mybond/bonding/lp_interval') as f: self.assertEqual(f.read().strip(), '15') + def test_bridge_port_priority(self): + self.setup_eth(None) + self.start_dnsmasq(None, self.dev_e2_ap) + with open(self.config, 'w') as f: + f.write('''network: + renderer: %(r)s + ethernets: + ethbr: + match: {name: %(e2c)s} + bridges: + mybr: + interfaces: [ethbr] + parameters: + port-priority: + ethbr: 42 + stp: false + dhcp4: yes''' % {'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, + ['inet 192.168.5.[0-9]+/24'], + ['master']) + self.assert_iface_up(self.dev_e2_client, + ['master mybr'], + ['inet ']) + self.assert_iface_up('mybr', + ['inet 192.168.6.[0-9]+/24']) + lines = subprocess.check_output(['bridge', 'link', 'show', 'mybr'], + universal_newlines=True).splitlines() + self.assertEqual(len(lines), 1, lines) + self.assertIn(self.dev_e2_client, lines[0]) + with open('/sys/class/net/mybr/brif/%s/priority' % self.dev_e2_client) as f: + self.assertEqual(f.read().strip(), '42') + unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout, verbosity=2))