diff options
author | David Michael <david.michael@coreos.com> | 2016-09-27 15:18:14 -0700 |
---|---|---|
committer | David Michael <david.michael@coreos.com> | 2016-12-01 14:41:51 -0800 |
commit | a09dc5467a3d289a53ef3ea87d3b155d8d0551c9 (patch) | |
tree | 85a69dde6004c10cc899c9010dc4e649509c1cfc | |
parent | ec89276c2ab345b84c2dab4c35826de41aa6fd0f (diff) |
networkd: support marking links unmanaged
-rw-r--r-- | man/systemd.network.xml | 12 | ||||
-rw-r--r-- | src/network/networkd-link.c | 3 | ||||
-rw-r--r-- | src/network/networkd-network-gperf.gperf | 1 | ||||
-rw-r--r-- | src/network/networkd-network.h | 1 | ||||
-rwxr-xr-x | test/networkd-test.py | 116 |
5 files changed, 133 insertions, 0 deletions
diff --git a/man/systemd.network.xml b/man/systemd.network.xml index 99283813fd..53c49f817f 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -232,6 +232,18 @@ the network otherwise.</para> </listitem> </varlistentry> + <varlistentry> + <term><varname>Unmanaged=</varname></term> + <listitem> + <para>A boolean. When <literal>yes</literal>, no attempts are + made to bring up or configure matching links, equivalent to + when there are no matching network files. Defaults to + <literal>no</literal>.</para> + <para>This is useful for preventing later matching network + files from interfering with certain interfaces that are fully + controlled by other applications.</para> + </listitem> + </varlistentry> </variablelist> </refsect1> diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index cb7df633b7..643578c164 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -2523,6 +2523,9 @@ static int link_initialized_and_synced(sd_netlink *rtnl, sd_netlink_message *m, if (r == -ENOENT) { link_enter_unmanaged(link); return 1; + } else if (r == 0 && network->unmanaged) { + link_enter_unmanaged(link); + return 0; } else if (r < 0) return r; diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index 463f4595c1..0a5cd763dd 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -29,6 +29,7 @@ Match.Architecture, config_parse_net_condition, Link.MACAddress, config_parse_hwaddr, 0, offsetof(Network, mac) Link.MTUBytes, config_parse_iec_size, 0, offsetof(Network, mtu) Link.ARP, config_parse_tristate, 0, offsetof(Network, arp) +Link.Unmanaged, config_parse_bool, 0, offsetof(Network, unmanaged) Network.Description, config_parse_string, 0, offsetof(Network, description) Network.Bridge, config_parse_netdev, 0, offsetof(Network, bridge) Network.Bond, config_parse_netdev, 0, offsetof(Network, bond) diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index 4dbc19fc3b..e05dccda29 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -176,6 +176,7 @@ struct Network { struct ether_addr *mac; size_t mtu; int arp; + bool unmanaged; uint32_t iaid; DUID duid; diff --git a/test/networkd-test.py b/test/networkd-test.py index 4837c71652..aed5139275 100755 --- a/test/networkd-test.py +++ b/test/networkd-test.py @@ -92,6 +92,45 @@ class NetworkdTestingUtilities: dropin.write(contents) self.addCleanup(os.remove, dropin_path) + def assert_link_states(self, **kwargs): + """Match networkctl link states to the given ones. + + Each keyword argument should be the name of a network interface + with its expected value of the "SETUP" column in output from + networkctl. The interfaces have five seconds to come online + before the check is performed. Every specified interface must + be present in the output, and any other interfaces found in the + output are ignored. + + A special interface state "managed" is supported, which matches + any value in the "SETUP" column other than "unmanaged". + """ + if not kwargs: + return + interfaces = set(kwargs) + + # Wait for the requested interfaces, but don't fail for them. + subprocess.call([NETWORKD_WAIT_ONLINE, '--timeout=5'] + + ['--interface=%s' % iface for iface in kwargs]) + + # Validate each link state found in the networkctl output. + out = subprocess.check_output(['networkctl', '--no-legend']).rstrip() + for line in out.decode('utf-8').split('\n'): + fields = line.split() + if len(fields) >= 5 and fields[1] in kwargs: + iface = fields[1] + expected = kwargs[iface] + actual = fields[-1] + if (actual != expected and + not (expected == 'managed' and actual != 'unmanaged')): + self.fail("Link %s expects state %s, found %s" % + (iface, expected, actual)) + interfaces.remove(iface) + + # Ensure that all requested interfaces have been covered. + if interfaces: + self.fail("Missing links in status output: %s" % interfaces) + class ClientTestBase(NetworkdTestingUtilities): """Provide common methods for testing networkd against servers.""" @@ -720,6 +759,83 @@ DNS=127.0.0.1''') raise +class UnmanagedClientTest(unittest.TestCase, NetworkdTestingUtilities): + """Test if networkd manages the correct interfaces.""" + + def setUp(self): + """Write .network files to match the named veth devices.""" + # Define the veth+peer pairs to be created. + # Their pairing doesn't actually matter, only their names do. + self.veths = { + 'm1def': 'm0unm', + 'm1man': 'm1unm', + } + + # Define the contents of .network files to be read in order. + self.configs = ( + "[Match]\nName=m1def\n", + "[Match]\nName=m1unm\n[Link]\nUnmanaged=yes\n", + "[Match]\nName=m1*\n[Link]\nUnmanaged=no\n", + ) + + # Write out the .network files to be cleaned up automatically. + for i, config in enumerate(self.configs): + self.write_network("%02d-test.network" % i, config) + + def tearDown(self): + """Stop networkd.""" + subprocess.call(['systemctl', 'stop', 'systemd-networkd']) + + def create_iface(self): + """Create temporary veth pairs for interface matching.""" + for veth, peer in self.veths.items(): + subprocess.check_call(['ip', 'link', 'add', + 'name', veth, 'type', 'veth', + 'peer', 'name', peer]) + self.addCleanup(subprocess.call, + ['ip', 'link', 'del', 'dev', peer]) + + def test_unmanaged_setting(self): + """Verify link states with Unmanaged= settings, hot-plug.""" + subprocess.check_call(['systemctl', 'start', 'systemd-networkd']) + self.create_iface() + self.assert_link_states(m1def='managed', + m1man='managed', + m1unm='unmanaged', + m0unm='unmanaged') + + def test_unmanaged_setting_coldplug(self): + """Verify link states with Unmanaged= settings, cold-plug.""" + self.create_iface() + subprocess.check_call(['systemctl', 'start', 'systemd-networkd']) + self.assert_link_states(m1def='managed', + m1man='managed', + m1unm='unmanaged', + m0unm='unmanaged') + + def test_catchall_config(self): + """Verify link states with a catch-all config, hot-plug.""" + # Don't actually catch ALL interfaces. It messes up the host. + self.write_network('all.network', "[Match]\nName=m[01]???\n") + subprocess.check_call(['systemctl', 'start', 'systemd-networkd']) + self.create_iface() + self.assert_link_states(m1def='managed', + m1man='managed', + m1unm='unmanaged', + m0unm='managed') + + def test_catchall_config_coldplug(self): + """Verify link states with a catch-all config, cold-plug.""" + # Don't actually catch ALL interfaces. It messes up the host. + self.write_network('all.network', "[Match]\nName=m[01]???\n") + self.create_iface() + subprocess.check_call(['systemctl', 'start', 'systemd-networkd']) + self.assert_link_states(m1def='managed', + m1man='managed', + m1unm='unmanaged', + m0unm='managed') + + if __name__ == '__main__': unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout, verbosity=2)) |