]> git.proxmox.com Git - pve-manager.git/blame - test/ReplicationTestEnv.pm
implement basic wake on LAN functionality
[pve-manager.git] / test / ReplicationTestEnv.pm
CommitLineData
d0920c29
DM
1package ReplicationTestEnv;
2
3use strict;
4use warnings;
5use JSON;
c5014e65
DM
6use Clone 'clone';
7use File::Basename;
d0920c29
DM
8
9use lib ('.', '../..');
10
11use Data::Dumper;
12
13use PVE::INotify;
14use PVE::Cluster;
15use PVE::Storage;
d255af01
DM
16use PVE::ReplicationConfig;
17use PVE::ReplicationState;
810c6776 18use PVE::API2::Replication;
d0920c29
DM
19use PVE::Replication;
20use PVE::QemuConfig;
21use PVE::LXC::Config;
22
810c6776 23
d0920c29
DM
24use Test::MockModule;
25
26our $mocked_nodename = 'node1';
27
28our $mocked_replication_jobs = {};
29
d092dc4f
DM
30my $pve_replication_config_module = Test::MockModule->new('PVE::ReplicationConfig');
31my $pve_replication_state_module = Test::MockModule->new('PVE::ReplicationState');
d0920c29
DM
32
33our $mocked_vm_configs = {};
34
35our $mocked_ct_configs = {};
36
12c206a5
WB
37my $mocked_get_members = sub {
38 return {
39 node1 => { online => 1 },
40 node2 => { online => 1 },
41 node3 => { online => 1 },
42 };
43};
44
d0920c29
DM
45my $mocked_vmlist = sub {
46 my $res = {};
47
48 foreach my $id (keys %$mocked_ct_configs) {
49 my $d = $mocked_ct_configs->{$id};
50 $res->{$id} = { 'type' => 'lxc', 'node' => $d->{node}, 'version' => 1 };
51 }
52 foreach my $id (keys %$mocked_vm_configs) {
53 my $d = $mocked_vm_configs->{$id};
54 $res->{$id} = { 'type' => 'qemu', 'node' => $d->{node}, 'version' => 1 };
55 }
56
57 return { 'ids' => $res };
58};
59
331025d9
DM
60my $mocked_get_ssh_info = sub {
61 my ($node, $network_cidr) = @_;
62
63 return { node => $node };
64};
65
66my $mocked_ssh_info_to_command = sub {
67 my ($info, @extra_options) = @_;
68
69 return ['fake_ssh', $info->{name}, @extra_options];
70};
d0920c29
DM
71
72my $statefile = ".mocked_repl_state";
73
74unlink $statefile;
d255af01
DM
75$PVE::ReplicationState::state_path = $statefile;
76$PVE::ReplicationState::state_lock = ".mocked_repl_state_lock";
5b358450 77$PVE::API2::Replication::pvesr_lock_path = ".mocked_pvesr_lock";
72741c0b
WB
78$PVE::GuestHelpers::lockdir = ".mocked_pve-manager_lock";
79
80if (!mkdir($PVE::GuestHelpers::lockdir) && !$!{EEXIST}) {
81 # If we cannot create the guest helper lockdir we'll loop endlessly, so die
82 # if it fails.
83 die "mkdir($PVE::GuestHelpers::lockdir): $!\n";
84}
d0920c29 85
d0920c29
DM
86my $pve_cluster_module = Test::MockModule->new('PVE::Cluster');
87
88my $pve_inotify_module = Test::MockModule->new('PVE::INotify');
89
90my $mocked_qemu_load_conf = sub {
91 my ($class, $vmid, $node) = @_;
92
93 $node = $mocked_nodename if !$node;
94
95 my $conf = $mocked_vm_configs->{$vmid};
96
97 die "no such vm '$vmid'" if !defined($conf);
98 die "vm '$vmid' on wrong node" if $conf->{node} ne $node;
99
100 return $conf;
101};
102
103my $pve_qemuserver_module = Test::MockModule->new('PVE::QemuServer');
104
105my $pve_qemuconfig_module = Test::MockModule->new('PVE::QemuConfig');
106
107my $mocked_lxc_load_conf = sub {
108 my ($class, $vmid, $node) = @_;
109
110 $node = $mocked_nodename if !$node;
111
112 my $conf = $mocked_ct_configs->{$vmid};
113
114 die "no such ct '$vmid'" if !defined($conf);
115 die "ct '$vmid' on wrong node" if $conf->{node} ne $node;
116
117 return $conf;
118};
119
120my $pve_lxc_config_module = Test::MockModule->new('PVE::LXC::Config');
121
d092dc4f 122my $mocked_replication_config_new = sub {
d0920c29 123
c5014e65
DM
124 my $res = clone($mocked_replication_jobs);
125
d0920c29
DM
126 return bless { ids => $res }, 'PVE::ReplicationConfig';
127};
128
129my $mocked_storage_config = {
130 ids => {
131 local => {
132 type => 'dir',
133 shared => 0,
134 content => {
135 'iso' => 1,
136 'backup' => 1,
137 },
138 path => "/var/lib/vz",
139 },
140 'local-zfs' => {
141 type => 'zfspool',
142 pool => 'nonexistent-testpool',
143 shared => 0,
144 content => {
145 'images' => 1,
146 'rootdir' => 1
147 },
148 },
149 },
150};
151
152my $pve_storage_module = Test::MockModule->new('PVE::Storage');
c5014e65 153
4550bb78
DM
154my $mocked_storage_content = {};
155
156sub register_mocked_volid {
157 my ($volid, $snapname) = @_;
158
159 my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid);
331025d9 160 my $scfg = $mocked_storage_config->{ids}->{$storeid} ||
4550bb78
DM
161 die "no such storage '$storeid'\n";
162
163 my $d = $mocked_storage_content->{$storeid}->{$volname} //= {};
164
165 $d->{$snapname} = 1 if $snapname;
166}
167
168my $mocked_volume_snapshot_list = sub {
169 my ($cfg, $volid, $prefix) = @_;
170
171 my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid);
172 my $snaps = [];
173
174 if (my $d = $mocked_storage_content->{$storeid}->{$volname}) {
175 $snaps = [keys %$d];
176 }
177
178 return $snaps;
179};
180
181my $mocked_volume_snapshot = sub {
182 my ($cfg, $volid, $snap) = @_;
183
184 my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid);
185
186 my $d = $mocked_storage_content->{$storeid}->{$volname};
187 die "no such volid '$volid'\n" if !$d;
188 $d->{$snap} = 1;
189};
190
191my $mocked_volume_snapshot_delete = sub {
192 my ($cfg, $volid, $snap, $running) = @_;
193
194 my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid);
195 my $d = $mocked_storage_content->{$storeid}->{$volname};
196 die "no such volid '$volid'\n" if !$d;
197 delete $d->{$snap} || die "no such snapshot '$snap' on '$volid'\n";
198};
199
f842e812
DM
200my $pve_replication_module = Test::MockModule->new('PVE::Replication');
201
202my $mocked_job_logfile_name = sub {
203 my ($jobid) = @_;
204
205 return ".mocked_replication_log_$jobid";
206};
207
208my $mocked_log_time = 0;
209
210my $mocked_get_log_time = sub {
211 return $mocked_log_time;
212};
213
0d7c2683
FG
214my $locks = {};
215
216my $mocked_cfs_lock_file = sub {
217 my ($filename, $timeout, $code, @param) = @_;
218
219 die "$filename already locked\n" if ($locks->{$filename});
220
221 $locks->{$filename} = 1;
222
223 my $res = $code->(@param);
224
225 delete $locks->{$filename};
226
227 return $res;
228};
229
230my $mocked_cfs_write_file = sub {
231 my ($filename, $cfg) = @_;
232
233 die "wrong file - $filename\n" if $filename ne 'replication.cfg';
234
235 $cfg->write_config(); # checks but no actual write to pmxcfs
236};
237
d0920c29 238sub setup {
d092dc4f 239 $pve_replication_state_module->mock(job_logfile_name => $mocked_job_logfile_name);
f842e812
DM
240 $pve_replication_module->mock(get_log_time => $mocked_get_log_time);
241
d0920c29 242 $pve_storage_module->mock(config => sub { return $mocked_storage_config; });
4550bb78
DM
243 $pve_storage_module->mock(volume_snapshot_list => $mocked_volume_snapshot_list);
244 $pve_storage_module->mock(volume_snapshot => $mocked_volume_snapshot);
245 $pve_storage_module->mock(volume_snapshot_delete => $mocked_volume_snapshot_delete);
d0920c29 246
0d7c2683
FG
247 $pve_replication_config_module->mock(
248 new => $mocked_replication_config_new,
249 lock => sub { $mocked_cfs_lock_file->('replication.cfg', undef, $_[0]); },
250 write => sub { $mocked_cfs_write_file->('replication.cfg', $_[0]); },
251 );
d0920c29
DM
252 $pve_qemuserver_module->mock(check_running => sub { return 0; });
253 $pve_qemuconfig_module->mock(load_config => $mocked_qemu_load_conf);
254
255 $pve_lxc_config_module->mock(load_config => $mocked_lxc_load_conf);
256
331025d9
DM
257 $pve_cluster_module->mock(
258 get_ssh_info => $mocked_get_ssh_info,
259 ssh_info_to_command => $mocked_ssh_info_to_command,
12c206a5 260 get_vmlist => sub { return $mocked_vmlist->(); },
b03b4749 261 get_members => $mocked_get_members,
0d7c2683
FG
262 cfs_update => sub {},
263 cfs_lock_file => $mocked_cfs_lock_file,
264 cfs_write_file => $mocked_cfs_write_file,
265 );
d0920c29
DM
266 $pve_inotify_module->mock('nodename' => sub { return $mocked_nodename; });
267};
268
c5014e65
DM
269# code to generate/conpare test logs
270
271my $logname;
272my $logfh;
273
274sub openlog {
275 my ($filename) = @_;
276
277 if (!$filename) {
278 # compute from $0
279 $filename = basename($0);
280 if ($filename =~ m/^(\S+)\.pl$/) {
281 $filename = "$1.log";
282 } else {
283 die "unable to compute log name for $0";
284 }
285 }
286
287 die "log already open" if defined($logname);
288
289 open (my $fh, ">", "$filename.tmp") ||
290 die "unable to open log - $!";
291
292 $logname = $filename;
293 $logfh = $fh;
294}
295
c5014e65
DM
296sub commit_log {
297
298 close($logfh);
299
300 if (-f $logname) {
301 my $diff = `diff -u '$logname' '$logname.tmp'`;
302 if ($diff) {
303 warn "got unexpeted output\n";
304 print "# diff -u '$logname' '$logname.tmp'\n";
305 print $diff;
306 exit(-1);
307 }
308 } else {
309 rename("$logname.tmp", $logname) || die "rename log failed - $!";
310 }
311}
312
313my $status;
314
315# helper to track job status
316sub track_jobs {
317 my ($ctime) = @_;
318
f842e812
DM
319 $mocked_log_time = $ctime;
320
c364b61f
DM
321 my $logmsg = sub {
322 my ($msg) = @_;
323
f842e812
DM
324 print "$msg\n";
325 print $logfh "$msg\n";
c364b61f
DM
326 };
327
c5014e65 328 if (!$status) {
d092dc4f 329 $status = PVE::ReplicationState::job_status();
c5014e65
DM
330 foreach my $jobid (sort keys %$status) {
331 my $jobcfg = $status->{$jobid};
f842e812 332 $logmsg->("$ctime $jobid: new job next_sync => $jobcfg->{next_sync}");
c5014e65
DM
333 }
334 }
335
2aa02957 336 PVE::API2::Replication::run_jobs($ctime, $logmsg, 1);
c5014e65 337
d092dc4f 338 my $new = PVE::ReplicationState::job_status();
c5014e65
DM
339
340 # detect removed jobs
341 foreach my $jobid (sort keys %$status) {
342 if (!$new->{$jobid}) {
f842e812 343 $logmsg->("$ctime $jobid: vanished job");
c5014e65
DM
344 }
345 }
346
347 foreach my $jobid (sort keys %$new) {
348 my $jobcfg = $new->{$jobid};
349 my $oldcfg = $status->{$jobid};
350 if (!$oldcfg) {
f842e812 351 $logmsg->("$ctime $jobid: new job next_sync => $jobcfg->{next_sync}");
c5014e65
DM
352 next; # no old state to compare
353 } else {
354 foreach my $k (qw(target guest vmtype next_sync)) {
355 my $changes = '';
356 if ($oldcfg->{$k} ne $jobcfg->{$k}) {
357 $changes .= ', ' if $changes;
358 $changes .= "$k => $jobcfg->{$k}";
359 }
f842e812 360 $logmsg->("$ctime $jobid: changed config $changes") if $changes;
c5014e65
DM
361 }
362 }
363
364 my $oldstate = $oldcfg->{state};
356fbf79 365
c5014e65
DM
366 my $state = $jobcfg->{state};
367
368 my $changes = '';
e137f69f 369 foreach my $k (qw(last_node last_try last_sync fail_count error)) {
c5014e65 370 if (($oldstate->{$k} // '') ne ($state->{$k} // '')) {
39c41c9d 371 my $value = $state->{$k} // '';
c5014e65
DM
372 chomp $value;
373 $changes .= ', ' if $changes;
374 $changes .= "$k => $value";
375 }
376 }
f842e812 377 $logmsg->("$ctime $jobid: changed state $changes") if $changes;
c5014e65 378
356fbf79
DM
379 my $old_storeid_list = $oldstate->{storeid_list};
380 my $storeid_list = $state->{storeid_list};
381
382 my $storeid_list_changes = 0;
383 foreach my $storeid (@$storeid_list) {
384 next if grep { $_ eq $storeid } @$old_storeid_list;
385 $storeid_list_changes = 1;
386 }
387
388 foreach my $storeid (@$old_storeid_list) {
389 next if grep { $_ eq $storeid } @$storeid_list;
390 $storeid_list_changes = 1;
391 }
392
f842e812 393 $logmsg->("$ctime $jobid: changed storeid list " . join(',', @$storeid_list))
356fbf79 394 if $storeid_list_changes;
c5014e65
DM
395 }
396 $status = $new;
397}
d0920c29
DM
398
399
4001;