1{ pkgs, lib, ... }:
2
3let
4 addrShared = "192.168.0.1";
5 addrHostA = "192.168.0.10";
6 addrHostB = "192.168.0.11";
7
8 mkUcarpHost =
9 addr:
10 {
11 config,
12 pkgs,
13 lib,
14 ...
15 }:
16 {
17 networking.interfaces.eth1.ipv4.addresses = lib.mkForce [
18 {
19 address = addr;
20 prefixLength = 24;
21 }
22 ];
23
24 networking.ucarp = {
25 enable = true;
26 interface = "eth1";
27 srcIp = addr;
28 vhId = 1;
29 passwordFile = "${pkgs.writeText "ucarp-pass" "secure"}";
30 addr = addrShared;
31 upscript = pkgs.writeScript "upscript" ''
32 #!/bin/sh
33 ${pkgs.iproute2}/bin/ip addr add "$2"/24 dev "$1"
34 '';
35 downscript = pkgs.writeScript "downscript" ''
36 #!/bin/sh
37 ${pkgs.iproute2}/bin/ip addr del "$2"/24 dev "$1"
38 '';
39 };
40 };
41in
42{
43 name = "ucarp";
44 meta.maintainers = with lib.maintainers; [ oxzi ];
45
46 nodes = {
47 hostA = mkUcarpHost addrHostA;
48 hostB = mkUcarpHost addrHostB;
49 };
50
51 testScript = ''
52 def is_master(host):
53 ipOutput = host.succeed("ip addr show dev eth1")
54 return "inet ${addrShared}/24" in ipOutput
55
56
57 start_all()
58
59 # First, let both hosts start and let a master node be selected
60 for host, peer in [(hostA, "${addrHostB}"), (hostB, "${addrHostA}")]:
61 host.wait_for_unit("ucarp.service")
62 host.succeed(f"ping -c 1 {peer}")
63
64 hostA.sleep(5)
65
66 hostA_master, hostB_master = is_master(hostA), is_master(hostB)
67 assert hostA_master != hostB_master, "only one master node is allowed"
68
69 master_host = hostA if hostA_master else hostB
70 backup_host = hostB if hostA_master else hostA
71
72 # Let's crash the master host and let the backup take over
73 master_host.crash()
74
75 backup_host.sleep(5)
76 assert is_master(backup_host), "backup did not take over"
77 '';
78}