summaryrefslogtreecommitdiffstats
path: root/nixos/tests/netplan.nix
blob: 588bacce4b47d70bb1882ede1991ff90b7351091 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
{ pkgs, lib, ... }:

let
  # Test by creating a "group" of interfaces: a VRF, plus a dummy interface under it, to which we'll assign an IPv6 ULA address
  # This must be done for both configs under /etc ("static") and configs under /run ("dynamic"). So each test group gets an address
  test-groups = {
    "static" = "fd00::feed:cafe/32";
    "dynamic" = "fd00::dead:beef/32";
  };

  # Generate link information, interface getter commands, and configuration files, to be used on all test VMs
  # The variable respects the globs:
  # - fixtures.{static,dynamic}.links.{vrf,dummy}.{name,getter-cmd,address} (NB: address is unavailable in 'vrf')
  # - fixtures.{static,dynamic}.cfgs.{networkd,NetworkManager}.{filename,src}
  fixtures = lib.mapAttrs (
    group: address:
    let
      vrf-name = "np${group}";
    in
    {
      links.vrf.name = "${vrf-name}";
      links.vrf.getter-cmd = "ip -o link show dev ${vrf-name} type vrf";
      links.dummy.name = "${vrf-name}-lo";
      links.dummy.getter-cmd = "ip -o link show dev ${vrf-name}-lo type dummy master ${vrf-name}";
      links.dummy.address = address;
      cfgs = lib.genAttrs [ "networkd" "NetworkManager" ] (renderer: get-config-file group renderer);
    }
  ) test-groups;

  # Utility to generate netplan configuration objects
  get-config-file = (
    group: renderer:
    let
      links = fixtures.${group}.links;
      content = ''
        ---
        network:
          version: 2
          renderer: ${renderer}
          dummy-devices:
            ${links.dummy.name}:
              addresses: ["${links.dummy.address}"]
          vrfs:
            ${links.vrf.name}:
              table: 130
              interfaces: [${links.dummy.name}]
      '';
    in
    {
      filename = "10-test-${group}-${renderer}.yaml";
      src = pkgs.writeText "netplan-test-${group}-${renderer}" content;
    }
  );

in
{
  name = "netplan";
  meta.maintainers = with lib.maintainers; [
    mkg20001
  ];

  nodes = {
    networkd = {
      networking.useNetworkd = true;
      systemd.network.enable = true;
      networking.useDHCP = false;
      networking.netplan.enable = true;
      networking.netplan.configFiles = {
        ${fixtures.static.cfgs.networkd.filename} = builtins.readFile fixtures.static.cfgs.networkd.src;
      };
      systemd.tmpfiles.settings."netplan"."/run/netplan".d = {
        user = "root";
        group = "root";
        mode = "0700";
      };
    };
    networkmanager = {
      networking.networkmanager.enable = true;
      networking.netplan.enable = true;
      networking.netplan.configFiles = {
        ${fixtures.static.cfgs.NetworkManager.filename} =
          builtins.readFile fixtures.static.cfgs.NetworkManager.src;
      };
      systemd.tmpfiles.settings."netplan"."/run/netplan".d = {
        user = "root";
        group = "root";
        mode = "0700";
      };
    };
  };

  testScript = ''
    import json

    fixtures = json.loads('${builtins.toJSON fixtures}')
    start_all()

    # Test both networkd and networkmanager backends
    for machine in [networkd, networkmanager]:

      # Prepare variables for testing
      renderer = "networkd" if machine is networkd else "NetworkManager"
      configfiles = dict()
      links = dict()
      for group in ['static', 'dynamic']:
        links[group] = list(fixtures[group]['links'].items())
        configfiles[group] = fixtures[group]['cfgs'][renderer]

      # Wait for tests to be runnable
      machine.wait_for_unit("network.target")

      # Basic tests
      with subtest("Check that netplan package has been installed and is able to run"):
        machine.succeed("which netplan")
        machine.succeed("netplan --help")

      # Module tests
      with subtest("Check that the nixos module wrote the configuration file"):
        machine.succeed(f"ls /etc/netplan/{configfiles['static']['filename']}")

      # Run the interface checks twice: once before applying the dynamic config, and once after
      for dynamic_config in [False, True]:

        # If testing the synamic, write out the fixture YAML file, and apply it
        if dynamic_config:
          with subtest("Run netplan apply to configure dynamic interfaces"):
            destination_filename = f"/run/netplan/{configfiles['dynamic']['filename']}"
            machine.copy_from_host_via_shell(configfiles['dynamic']['src'], destination_filename)
            machine.succeed(f"chown root:root {destination_filename}")
            machine.succeed(f"chmod 0700 {destination_filename}")
            machine.succeed("netplan apply")

        # Check that the interfaces exist and are of the right type
        # Commands below rely on '-o pipefail' being set in the shell
        links_to_test = links['static'] + (links['dynamic'] if dynamic_config else [])
        with subtest(f"Check that netplan correctly configured the network interfaces ({'/run populated' if dynamic_config else 'right after boot'})"):
          for link_type, link in links_to_test:
            link_name = link['name']
            machine.wait_until_succeeds(f"{link['getter-cmd']} | grep {link_name}")
            # For the dummy interface, check the IP address as well
            if link_type == 'dummy':
              machine.wait_until_succeeds(f"ip -o addr show {link_name} to {link['address']} | grep {link_name}")
  '';
}