1{
2 name = "zrepl";
3
4 nodes = {
5 source =
6 { nodes, pkgs, ... }:
7 {
8 config = {
9 # Prerequisites for ZFS and tests.
10 virtualisation.emptyDiskImages = [
11 2048
12 ];
13
14 boot.supportedFilesystems = [ "zfs" ];
15 environment.systemPackages = [
16 pkgs.parted
17 pkgs.zrepl
18 ];
19 networking.firewall.allowedTCPPorts = [ 8888 ];
20 networking.hostId = "deadbeef";
21 services.zrepl = {
22 enable = true;
23 settings = {
24 # Enable Prometheus output for status assertions.
25 global.monitoring = [
26 {
27 type = "prometheus";
28 listen = ":9811";
29 }
30 ];
31 # Create a periodic snapshot job for an ephemeral zpool.
32 jobs = [
33 {
34 name = "snapshots";
35 type = "snap";
36
37 filesystems."tank/data" = true;
38 snapshotting = {
39 type = "periodic";
40 prefix = "zrepl_";
41 interval = "10s";
42 };
43
44 pruning.keep = [
45 {
46 type = "last_n";
47 count = 8;
48 }
49 ];
50 }
51 {
52 name = "backup-target";
53 type = "source";
54
55 serve = {
56 type = "tcp";
57 listen = ":8888";
58
59 clients = {
60 "${nodes.target.networking.primaryIPAddress}" = "${nodes.target.networking.hostName}";
61 "${nodes.target.networking.primaryIPv6Address}" = "${nodes.target.networking.hostName}";
62 };
63 };
64 filesystems."tank/data" = true;
65 # Snapshots are handled by the separate snap job
66 snapshotting = {
67 type = "manual";
68 };
69 }
70 ];
71 };
72 };
73 };
74 };
75
76 target =
77 { pkgs, ... }:
78 {
79 config = {
80 # Prerequisites for ZFS and tests.
81 virtualisation.emptyDiskImages = [
82 2048
83 ];
84
85 boot.supportedFilesystems = [ "zfs" ];
86 environment.systemPackages = [
87 pkgs.parted
88 pkgs.zrepl
89 ];
90 networking.hostId = "deadd0d0";
91 services.zrepl = {
92 enable = true;
93 settings = {
94 # Enable Prometheus output for status assertions.
95 global.monitoring = [
96 {
97 type = "prometheus";
98 listen = ":9811";
99 }
100 ];
101 jobs = [
102 {
103 name = "source-pull";
104 type = "pull";
105
106 connect = {
107 type = "tcp";
108 address = "source:8888";
109 };
110 root_fs = "tank/zrepl/source";
111 interval = "15s";
112 recv = {
113 placeholder = {
114 encryption = "off";
115 };
116 };
117 pruning = {
118 keep_sender = [
119 {
120 type = "regex";
121 regex = ".*";
122 }
123 ];
124 keep_receiver = [
125 {
126 type = "grid";
127 grid = "1x1h(keep=all) | 24x1h";
128 regex = "^zrepl_";
129 }
130 ];
131 };
132 }
133 ];
134 };
135 };
136 };
137 };
138 };
139
140 testScript = ''
141 start_all()
142
143 with subtest("Wait for zrepl and network ready"):
144 for machine in source, target:
145 machine.systemctl("start network-online.target")
146 machine.wait_for_unit("network-online.target")
147 machine.wait_for_unit("zrepl.service")
148
149 with subtest("Create tank zpool"):
150 for machine in source, target:
151 machine.succeed(
152 "parted --script /dev/vdb mklabel gpt",
153 "zpool create tank /dev/vdb",
154 )
155
156 # Create ZFS datasets
157 source.succeed("zfs create tank/data")
158 target.succeed("zfs create -p tank/zrepl/source")
159
160 with subtest("Check for completed zrepl snapshot on target"):
161 # zrepl periodic snapshot job creates a snapshot with this prefix.
162 target.wait_until_succeeds("zfs list -t snapshot | grep -q tank/zrepl/source/tank/data@zrepl_")
163
164 with subtest("Check for completed zrepl bookmark on source"):
165 source.wait_until_succeeds("zfs list -t bookmark | grep -q tank/data#zrepl_")
166
167 with subtest("Verify HTTP monitoring server is configured"):
168 out = source.succeed("curl -f localhost:9811/metrics")
169
170 assert (
171 "zrepl_start_time" in out
172 ), "zrepl start time metric was not found in Prometheus output"
173
174 assert (
175 "zrepl_zfs_snapshot_duration_count{filesystem=\"tank/data\"}" in out
176 ), "zrepl snapshot counter for tank/data was not found in Prometheus output"
177 '';
178}