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}")
'';
}
|