network-interfaces: reload bridges on conf changes

And adopt the tests to add an interface and remove it again.

It should work when deactivating rstp, it will not work when activating
rstp for the first bridge as then the userspace daemon is not yet
available. But once one bridge is active with stp, it should work with
the reload for any further bridge.

Fixes #21745. Also see #22547.
This commit is contained in:
Arnold Krille 2017-02-11 16:46:55 +01:00 committed by Franz Pletz
parent bf284bdfef
commit 68729958e8
No known key found for this signature in database
GPG Key ID: 846FDED7792617B4
3 changed files with 140 additions and 0 deletions

@ -239,6 +239,10 @@ let
ip link set "${i}" master "${n}"
ip link set "${i}" up
'')}
# Save list of enslaved interfaces
echo "${flip concatMapStrings v.interfaces (i: ''
${i}
'')}" > /run/${n}.interfaces
# Enable stp on the interface
${optionalString v.rstp ''
@ -250,7 +254,28 @@ let
postStop = ''
ip link set "${n}" down || true
ip link del "${n}" || true
rm -f /run/${n}.interfaces
'';
reload = ''
# Un-enslave child interfaces (old list of interfaces)
for interface in `cat /run/${n}.interfaces`; do
ip link set "$interface" nomaster up
done
# Enslave child interfaces (new list of interfaces)
${flip concatMapStrings v.interfaces (i: ''
ip link set "${i}" master "${n}"
ip link set "${i}" up
'')}
# Save list of enslaved interfaces
echo "${flip concatMapStrings v.interfaces (i: ''
${i}
'')}" > /run/${n}.interfaces
# (Un-)set stp on the bridge
echo ${if v.rstp then "2" else "0"} > /sys/class/net/${n}/bridge/stp_state
'';
reloadIfChanged = true;
});
createVswitchDevice = n: v: nameValuePair "${n}-netdev"

@ -228,6 +228,7 @@ in rec {
tests.containers-imperative = callTest tests/containers-imperative.nix {};
tests.containers-extra_veth = callTest tests/containers-extra_veth.nix {};
tests.containers-physical_interfaces = callTest tests/containers-physical_interfaces.nix {};
tests.containers-restart_networking = callTest tests/containers-restart_networking.nix {};
tests.containers-tmpfs = callTest tests/containers-tmpfs.nix {};
tests.containers-hosts = callTest tests/containers-hosts.nix {};
tests.containers-macvlans = callTest tests/containers-macvlans.nix {};

@ -0,0 +1,114 @@
# Test for NixOS' container support.
let
client_base = rec {
networking.firewall.enable = false;
containers.webserver = {
autoStart = true;
privateNetwork = true;
hostBridge = "br0";
config = {
networking.firewall.enable = false;
networking.firewall.allowPing = true;
networking.interfaces.eth0.ip4 = [
{ address = "192.168.1.122"; prefixLength = 24; }
];
};
};
};
in import ./make-test.nix ({ pkgs, lib, ...} :
{
name = "containers-restart_networking";
meta = with pkgs.stdenv.lib.maintainers; {
maintainers = [ kampfschlaefer ];
};
nodes = {
client = { lib, pkgs, ... }: client_base // {
virtualisation.vlans = [ 1 ];
networking.bridges.br0 = {
interfaces = [];
rstp = false;
};
networking.interfaces = {
eth1.ip4 = lib.mkOverride 0 [ ];
br0.ip4 = [{ address = "192.168.1.1"; prefixLength = 24; }];
};
};
client_eth1 = { lib, pkgs, ... }: client_base // {
networking.bridges.br0 = {
interfaces = [ "eth1" ];
rstp = false;
};
networking.interfaces = {
eth1.ip4 = lib.mkOverride 0 [ ];
br0.ip4 = [{ address = "192.168.1.2"; prefixLength = 24; }];
};
};
client_eth1_rstp = { lib, pkgs, ... }: client_base // {
networking.bridges.br0 = {
interfaces = [ "eth1" ];
rstp = true;
};
networking.interfaces = {
eth1.ip4 = lib.mkOverride 0 [ ];
br0.ip4 = [{ address = "192.168.1.2"; prefixLength = 24; }];
};
};
};
testScript = {nodes, ...}: let
originalSystem = nodes.client.config.system.build.toplevel;
eth1_bridged = nodes.client_eth1.config.system.build.toplevel;
eth1_rstp = nodes.client_eth1_rstp.config.system.build.toplevel;
in ''
$client->start();
$client->waitForUnit("default.target");
subtest "initial state", sub {
$client->succeed("ping 192.168.1.122 -c 1 -n >&2");
$client->succeed("nixos-container run webserver -- ping -c 1 -n 192.168.1.1 >&2");
$client->fail("ip l show eth1 |grep \"master br0\" >&2");
$client->fail("grep eth1 /run/br0.interfaces >&2");
};
subtest "interfaces without stp", sub {
$client->succeed("${eth1_bridged}/bin/switch-to-configuration test >&2");
$client->succeed("ping 192.168.1.122 -c 1 -n >&2");
$client->succeed("nixos-container run webserver -- ping -c 1 -n 192.168.1.2 >&2");
$client->succeed("ip l show eth1 |grep \"master br0\" >&2");
$client->succeed("grep eth1 /run/br0.interfaces >&2");
};
# activating rstp needs another service, therefor the bridge will restart and the container will loose its connectivity
#subtest "interfaces with rstp", sub {
# $client->succeed("${eth1_rstp}/bin/switch-to-configuration test >&2");
# $client->execute("ip -4 a >&2");
# $client->execute("ip l >&2");
#
# $client->succeed("ping 192.168.1.122 -c 1 -n >&2");
# $client->succeed("nixos-container run webserver -- ping -c 1 -n 192.168.1.2 >&2");
#
# $client->succeed("ip l show eth1 |grep \"master br0\" >&2");
# $client->succeed("grep eth1 /run/br0.interfaces >&2");
#};
subtest "back to no interfaces and no stp", sub {
$client->succeed("${originalSystem}/bin/switch-to-configuration test >&2");
$client->succeed("ping 192.168.1.122 -c 1 -n >&2");
$client->succeed("nixos-container run webserver -- ping -c 1 -n 192.168.1.1 >&2");
$client->fail("ip l show eth1 |grep \"master br0\" >&2");
$client->fail("grep eth1 /run/br0.interfaces >&2");
};
'';
})