]> git.proxmox.com Git - pve-container.git/blame - src/test/snapshot-test.pm
Refactor config-related methods into AbstractConfig
[pve-container.git] / src / test / snapshot-test.pm
CommitLineData
0fdbd016
FG
1package PVE::LXC;
2
3use strict;
4use warnings;
5
0fff7963
DM
6use lib qw(..);
7
0fdbd016
FG
8use PVE::Storage;
9use PVE::Storage::Plugin;
10use PVE::LXC;
67afe46e 11use PVE::LXC::Config;
0fdbd016
FG
12use PVE::Tools;
13
14use Test::MockModule;
15use Test::More;
16
17my $nodename;
18my $snapshot_possible;
19my $vol_snapshot_possible = {};
20my $vol_snapshot_delete_possible = {};
5ab55c6d
FG
21my $vol_snapshot_rollback_possible = {};
22my $vol_snapshot_rollback_enabled = {};
0fdbd016
FG
23my $vol_snapshot = {};
24my $vol_snapshot_delete = {};
5ab55c6d 25my $vol_snapshot_rollback = {};
0fdbd016
FG
26my $running;
27my $freeze_possible;
5ab55c6d 28my $kill_possible;
0fdbd016
FG
29
30# Mocked methods
31
32sub mocked_volume_snapshot {
33 my ($storecfg, $volid, $snapname) = @_;
19d36a45 34 die "Storage config not mocked! aborting\n"
0fdbd016 35 if defined($storecfg);
19d36a45 36 die "volid undefined\n"
0fdbd016 37 if !defined($volid);
19d36a45 38 die "snapname undefined\n"
0fdbd016
FG
39 if !defined($snapname);
40 if ($vol_snapshot_possible->{$volid}) {
41 if (defined($vol_snapshot->{$volid})) {
42 $vol_snapshot->{$volid} .= ",$snapname";
43 } else {
44 $vol_snapshot->{$volid} = $snapname;
45 }
46 return 1;
47 } else {
19d36a45 48 die "volume snapshot disabled\n";
0fdbd016
FG
49 }
50}
51
52sub mocked_volume_snapshot_delete {
53 my ($storecfg, $volid, $snapname) = @_;
19d36a45 54 die "Storage config not mocked! aborting\n"
0fdbd016 55 if defined($storecfg);
19d36a45 56 die "volid undefined\n"
0fdbd016 57 if !defined($volid);
19d36a45 58 die "snapname undefined\n"
0fdbd016
FG
59 if !defined($snapname);
60 if ($vol_snapshot_delete_possible->{$volid}) {
61 if (defined($vol_snapshot_delete->{$volid})) {
62 $vol_snapshot_delete->{$volid} .= ",$snapname";
63 } else {
64 $vol_snapshot_delete->{$volid} = $snapname;
65 }
66 return 1;
67 } else {
19d36a45 68 die "volume snapshot delete disabled\n";
0fdbd016
FG
69 }
70}
71
5ab55c6d
FG
72sub mocked_volume_snapshot_rollback {
73 my ($storecfg, $volid, $snapname) = @_;
74 die "Storage config not mocked! aborting\n"
75 if defined($storecfg);
76 die "volid undefined\n"
77 if !defined($volid);
78 die "snapname undefined\n"
79 if !defined($snapname);
80 if ($vol_snapshot_rollback_enabled->{$volid}) {
81 if (defined($vol_snapshot_rollback->{$volid})) {
82 $vol_snapshot_rollback->{$volid} .= ",$snapname";
83 } else {
84 $vol_snapshot_rollback->{$volid} = $snapname;
85 }
86 return 1;
87 } else {
88 die "volume snapshot rollback disabled\n";
89 }
90}
91
92sub mocked_volume_rollback_is_possible {
93 my ($storecfg, $volid, $snapname) = @_;
94 die "Storage config not mocked! aborting\n"
95 if defined($storecfg);
96 die "volid undefined\n"
97 if !defined($volid);
98 die "snapname undefined\n"
99 if !defined($snapname);
100 return $vol_snapshot_rollback_possible->{$volid}
101 if ($vol_snapshot_rollback_possible->{$volid});
102 die "volume_rollback_is_possible failed\n";
103}
104
0fdbd016
FG
105sub mocked_run_command {
106 my ($cmd, %param) = @_;
107 my $cmdstring;
108 if (my $ref = ref($cmd)) {
109 $cmdstring = PVE::Tools::cmd2string($cmd);
110 if ($cmdstring =~ m/.*\/lxc-(un)?freeze.*/) {
111 return 1 if $freeze_possible;
19d36a45 112 die "lxc-[un]freeze disabled\n";
0fdbd016 113 }
5ab55c6d
FG
114 if ($cmdstring =~ m/.*\/lxc-stop.*--kill.*/) {
115 if ($kill_possible) {
116 $running = 0;
117 return 1;
118 } else {
119 return 0;
120 }
121 }
0fdbd016 122 }
e2f2d0dc 123 die "unexpected run_command call: '$cmdstring', aborting\n";
0fdbd016
FG
124}
125
126# Testing methods
127
128sub test_file {
129 my ($exp_fn, $real_fn) = @_;
130 my $ret;
131 eval {
132 $ret = system("diff -u '$exp_fn' '$real_fn'");
133 };
134 die if $@;
135 return !$ret;
136}
137
138sub testcase_prepare {
139 my ($vmid, $snapname, $save_vmstate, $comment, $exp_err) = @_;
140 subtest "Preparing snapshot '$snapname' for vm '$vmid'" => sub {
141 plan tests => 2;
142 $@ = undef;
143 eval {
144 PVE::LXC::snapshot_prepare($vmid, $snapname, $save_vmstate, $comment);
145 };
146 is($@, $exp_err, "\$@ correct");
147 ok(test_file("snapshot-expected/prepare/lxc/$vmid.conf", "snapshot-working/prepare/lxc/$vmid.conf"), "config file correct");
148 };
149}
150
151sub testcase_commit {
152 my ($vmid, $snapname, $exp_err) = @_;
153 subtest "Committing snapshot '$snapname' for vm '$vmid'" => sub {
154 plan tests => 2;
155 $@ = undef;
156 eval {
157 PVE::LXC::snapshot_commit($vmid, $snapname);
158 };
159 is($@, $exp_err, "\$@ correct");
160 ok(test_file("snapshot-expected/commit/lxc/$vmid.conf", "snapshot-working/commit/lxc/$vmid.conf"), "config file correct");
161 }
162}
163
164sub testcase_create {
165 my ($vmid, $snapname, $save_vmstate, $comment, $exp_err, $exp_vol_snap, $exp_vol_snap_delete) = @_;
166 subtest "Creating snapshot '$snapname' for vm '$vmid'" => sub {
167 plan tests => 4;
168 $vol_snapshot = {};
169 $vol_snapshot_delete = {};
170 $exp_vol_snap = {} if !defined($exp_vol_snap);
171 $exp_vol_snap_delete = {} if !defined($exp_vol_snap_delete);
172 $@ = undef;
173 eval {
174 PVE::LXC::snapshot_create($vmid, $snapname, $save_vmstate, $comment);
175 };
176 is($@, $exp_err, "\$@ correct");
177 is_deeply($vol_snapshot, $exp_vol_snap, "created correct volume snapshots");
178 is_deeply($vol_snapshot_delete, $exp_vol_snap_delete, "deleted correct volume snapshots");
179 ok(test_file("snapshot-expected/create/lxc/$vmid.conf", "snapshot-working/create/lxc/$vmid.conf"), "config file correct");
180 };
181}
182
183sub testcase_delete {
184 my ($vmid, $snapname, $force, $exp_err, $exp_vol_snap_delete) = @_;
185 subtest "Deleting snapshot '$snapname' of vm '$vmid'" => sub {
186 plan tests => 3;
187 $vol_snapshot_delete = {};
188 $exp_vol_snap_delete = {} if !defined($exp_vol_snap_delete);
189 $@ = undef;
190 eval {
191 PVE::LXC::snapshot_delete($vmid, $snapname, $force);
192 };
193 is($@, $exp_err, "\$@ correct");
194 is_deeply($vol_snapshot_delete, $exp_vol_snap_delete, "deleted correct volume snapshots");
195 ok(test_file("snapshot-expected/delete/lxc/$vmid.conf", "snapshot-working/delete/lxc/$vmid.conf"), "config file correct");
196 };
197}
198
5ab55c6d
FG
199sub testcase_rollback {
200 my ($vmid, $snapname, $exp_err, $exp_vol_snap_rollback) = @_;
201 subtest "Rolling back to snapshot '$snapname' of vm '$vmid'" => sub {
202 plan tests => 3;
203 $vol_snapshot_rollback = {};
204 $running = 1;
205 $exp_vol_snap_rollback = {} if !defined($exp_vol_snap_rollback);
206 $@ = undef;
207 eval {
208 PVE::LXC::snapshot_rollback($vmid, $snapname);
209 };
210 is($@, $exp_err, "\$@ correct");
211 is_deeply($vol_snapshot_rollback, $exp_vol_snap_rollback, "rolled back to correct volume snapshots");
212 ok(test_file("snapshot-expected/rollback/lxc/$vmid.conf", "snapshot-working/rollback/lxc/$vmid.conf"), "config file correct");
213 };
214}
215
67afe46e
FG
216# BEGIN mocked PVE::LXC::Config methods
217sub mocked_config_file_lock {
0fdbd016
FG
218 return "snapshot-working/pve-test.lock";
219}
220
67afe46e
FG
221sub mocked_cfs_config_path {
222 my ($class, $vmid, $node) = @_;
0fdbd016
FG
223
224 $node = $nodename if !$node;
225 return "snapshot-working/$node/lxc/$vmid.conf";
226}
227
67afe46e
FG
228sub mocked_load_config {
229 my ($class, $vmid, $node) = @_;
0fdbd016 230
67afe46e 231 my $filename = PVE::LXC::Config->cfs_config_path($vmid, $node);
0fdbd016
FG
232
233 my $raw = PVE::Tools::file_get_contents($filename);
234
235 my $conf = PVE::LXC::parse_pct_config($filename, $raw);
236 return $conf;
237}
238
67afe46e
FG
239sub mocked_write_config {
240 my ($class, $vmid, $conf) = @_;
0fdbd016 241
67afe46e 242 my $filename = PVE::LXC::Config->cfs_config_path($vmid);
0fdbd016
FG
243
244 if ($conf->{snapshots}) {
245 foreach my $snapname (keys %{$conf->{snapshots}}) {
246 $conf->{snapshots}->{$snapname}->{snaptime} = "1234567890"
247 if $conf->{snapshots}->{$snapname}->{snaptime};
248 }
249 }
250
251 my $raw = PVE::LXC::write_pct_config($filename, $conf);
252
253 PVE::Tools::file_set_contents($filename, $raw);
254}
255
256sub has_feature {
257 my ($feature, $conf, $storecfg, $snapname) = @_;
258 return $snapshot_possible;
259}
260
261sub check_running {
262 return $running;
263}
67afe46e 264# END mocked PVE::LXC methods
0fdbd016
FG
265
266sub sync_container_namespace {
267 return;
268}
269
270# END redefine PVE::LXC methods
271
272PVE::Tools::run_command("rm -rf snapshot-working");
273PVE::Tools::run_command("cp -a snapshot-input snapshot-working");
274
67afe46e
FG
275my $lxc_config_module = new Test::MockModule('PVE::LXC::Config');
276$lxc_config_module->mock('config_file_lock', sub { return "snapshot-working/pve-test.lock"; });
277$lxc_config_module->mock('cfs_config_path', \&mocked_cfs_config_path);
278$lxc_config_module->mock('load_config', \&mocked_load_config);
279$lxc_config_module->mock('write_config', \&mocked_write_config);
280
0fdbd016
FG
281$running = 1;
282$freeze_possible = 1;
283
e2f2d0dc
FG
284printf("\n");
285printf("Running prepare tests\n");
286printf("\n");
0fdbd016
FG
287$nodename = "prepare";
288
e2f2d0dc
FG
289printf("\n");
290printf("Setting has_feature to return true\n");
291printf("\n");
0fdbd016
FG
292$snapshot_possible = 1;
293
e2f2d0dc 294printf("Successful snapshot_prepare with no existing snapshots\n");
0fdbd016
FG
295testcase_prepare("101", "test", 0, "test comment", '');
296
e2f2d0dc 297printf("Successful snapshot_prepare with one existing snapshot\n");
0fdbd016
FG
298testcase_prepare("102", "test2", 0, "test comment", "");
299
e2f2d0dc 300printf("Expected error for snapshot_prepare on locked container\n");
67afe46e 301testcase_prepare("200", "test", 0, "test comment", "CT is locked (snapshot)\n");
0fdbd016 302
e2f2d0dc 303printf("Expected error for snapshot_prepare with duplicate snapshot name\n");
0fdbd016
FG
304testcase_prepare("201", "test", 0, "test comment", "snapshot name 'test' already used\n");
305
e2f2d0dc 306printf("Expected error for snapshot_prepare with save_vmstate\n");
0fdbd016
FG
307testcase_prepare("202", "test", 1, "test comment", "implement me - snapshot_save_vmstate\n");
308
e2f2d0dc
FG
309printf("\n");
310printf("Setting has_feature to return false\n");
311printf("\n");
0fdbd016
FG
312$snapshot_possible = 0;
313
e2f2d0dc 314printf("Expected error for snapshot_prepare if snapshots not possible\n");
0fdbd016
FG
315testcase_prepare("300", "test", 0, "test comment", "snapshot feature is not available\n");
316
e2f2d0dc
FG
317printf("\n");
318printf("Running commit tests\n");
319printf("\n");
0fdbd016
FG
320$nodename = "commit";
321
e2f2d0dc
FG
322printf("\n");
323printf("Setting has_feature to return true\n");
324printf("\n");
0fdbd016
FG
325$snapshot_possible = 1;
326
e2f2d0dc 327printf("Successful snapshot_commit with one prepared snapshot\n");
0fdbd016
FG
328testcase_commit("101", "test", "");
329
e2f2d0dc 330printf("Successful snapshot_commit with one committed and one prepared snapshot\n");
0fdbd016
FG
331testcase_commit("102", "test2", "");
332
e2f2d0dc 333printf("Expected error for snapshot_commit with no snapshot lock\n");
0fdbd016
FG
334testcase_commit("201", "test", "missing snapshot lock\n");
335
e2f2d0dc 336printf("Expected error for snapshot_commit with invalid snapshot name\n");
0fdbd016
FG
337testcase_commit("202", "test", "snapshot 'test' does not exist\n");
338
e2f2d0dc 339printf("Expected error for snapshot_commit with invalid snapshot state\n");
0fdbd016
FG
340testcase_commit("203", "test", "wrong snapshot state\n");
341
342$vol_snapshot_possible->{"local:snapshotable-disk-1"} = 1;
3c8677f8
FG
343$vol_snapshot_possible->{"local:snapshotable-disk-2"} = 1;
344$vol_snapshot_possible->{"local:snapshotable-disk-3"} = 1;
0fdbd016 345$vol_snapshot_delete_possible->{"local:snapshotable-disk-1"} = 1;
5ab55c6d 346$vol_snapshot_rollback_enabled->{"local:snapshotable-disk-1"} = 1;
3c8677f8
FG
347$vol_snapshot_rollback_enabled->{"local:snapshotable-disk-2"} = 1;
348$vol_snapshot_rollback_enabled->{"local:snapshotable-disk-3"} = 1;
5ab55c6d 349$vol_snapshot_rollback_possible->{"local:snapshotable-disk-1"} = 1;
3c8677f8
FG
350$vol_snapshot_rollback_possible->{"local:snapshotable-disk-2"} = 1;
351$vol_snapshot_rollback_possible->{"local:snapshotable-disk-3"} = 1;
352
353# possible, but fails
354$vol_snapshot_rollback_possible->{"local:snapshotable-disk-4"} = 1;
5ab55c6d 355
e2f2d0dc
FG
356printf("\n");
357printf("Setting up Mocking for PVE::Storage\n");
0fdbd016
FG
358my $storage_module = new Test::MockModule('PVE::Storage');
359$storage_module->mock('config', sub { return undef; });
360$storage_module->mock('volume_snapshot', \&mocked_volume_snapshot);
361$storage_module->mock('volume_snapshot_delete', \&mocked_volume_snapshot_delete);
5ab55c6d
FG
362$storage_module->mock('volume_snapshot_rollback', \&mocked_volume_snapshot_rollback);
363$storage_module->mock('volume_rollback_is_possible', \&mocked_volume_rollback_is_possible);
364printf("\tconfig(), volume_snapshot(), volume_snapshot_delete(), volume_snapshot_rollback() and volume_rollback_is_possible() mocked\n");
0fdbd016 365
e2f2d0dc
FG
366printf("\n");
367printf("Setting up Mocking for PVE::Tools\n");
0fdbd016
FG
368my $tools_module = new Test::MockModule('PVE::Tools');
369$tools_module->mock('run_command' => \&mocked_run_command);
e2f2d0dc 370printf("\trun_command() mocked\n");
0fdbd016
FG
371
372$nodename = "create";
e2f2d0dc
FG
373printf("\n");
374printf("Running create tests\n");
375printf("\n");
0fdbd016 376
e2f2d0dc 377printf("Successful snapshot_create with no existing snapshots\n");
0fdbd016
FG
378testcase_create("101", "test", 0, "test comment", "", { "local:snapshotable-disk-1" => "test" });
379
e2f2d0dc 380printf("Successful snapshot_create with one existing snapshots\n");
0fdbd016
FG
381testcase_create("102", "test2", 0, "test comment", "", { "local:snapshotable-disk-1" => "test2" });
382
3c8677f8
FG
383printf("Successful snapshot_create with multiple mps\n");
384testcase_create("103", "test", 0, "test comment", "", { "local:snapshotable-disk-1" => "test", "local:snapshotable-disk-2" => "test", "local:snapshotable-disk-3" => "test" });
385
e2f2d0dc 386printf("Expected error for snapshot_create when volume snapshot is not possible\n");
19d36a45 387testcase_create("201", "test", 0, "test comment", "volume snapshot disabled\n\n");
0fdbd016 388
e2f2d0dc 389printf("Expected error for snapshot_create with broken lxc-freeze\n");
0fdbd016 390$freeze_possible = 0;
3c8677f8 391testcase_create("202", "test", 0, "test comment", "lxc-[un]freeze disabled\n\n");
0fdbd016
FG
392$freeze_possible = 1;
393
3c8677f8
FG
394printf("Expected error for snapshot_create when mp volume snapshot is not possible\n");
395testcase_create("203", "test", 0, "test comment", "volume snapshot disabled\n\n", { "local:snapshotable-disk-1" => "test" }, { "local:snapshotable-disk-1" => "test" });
396
0fdbd016 397$nodename = "delete";
e2f2d0dc
FG
398printf("\n");
399printf("Running delete tests\n");
400printf("\n");
0fdbd016 401
e2f2d0dc 402printf("Successful snapshot_delete of only existing snapshot\n");
0fdbd016
FG
403testcase_delete("101", "test", 0, "", { "local:snapshotable-disk-1" => "test" });
404
e2f2d0dc 405printf("Successful snapshot_delete of leaf snapshot\n");
0fdbd016
FG
406testcase_delete("102", "test2", 0, "", { "local:snapshotable-disk-1" => "test2" });
407
e2f2d0dc 408printf("Successful snapshot_delete of root snapshot\n");
0fdbd016
FG
409testcase_delete("103", "test", 0, "", { "local:snapshotable-disk-1" => "test" });
410
e2f2d0dc 411printf("Successful snapshot_delete of intermediate snapshot\n");
0fdbd016
FG
412testcase_delete("104", "test2", 0, "", { "local:snapshotable-disk-1" => "test2" });
413
e2f2d0dc 414printf("Successful snapshot_delete with broken volume_snapshot_delete and force=1\n");
0fdbd016
FG
415testcase_delete("105", "test", 1, "");
416
3c8677f8
FG
417printf("Successful snapshot_delete with mp broken volume_snapshot_delete and force=1\n");
418testcase_delete("106", "test", 1, "", { "local:snapshotable-disk-1" => "test" });
419
e2f2d0dc 420printf("Expected error when snapshot_delete fails with broken volume_snapshot_delete and force=0\n");
19d36a45 421testcase_delete("201", "test", 0, "volume snapshot delete disabled\n");
0fdbd016 422
3c8677f8
FG
423printf("Expected error when snapshot_delete fails with broken mp volume_snapshot_delete and force=0\n");
424testcase_delete("203", "test", 0, "volume snapshot delete disabled\n", { "local:snapshotable-disk-1" => "test" });
425
e2f2d0dc 426printf("Expected error for snapshot_delete with locked config\n");
67afe46e 427testcase_delete("202", "test", 0, "CT is locked (backup)\n");
0fdbd016 428
5ab55c6d
FG
429$nodename = "rollback";
430printf("\n");
431printf("Running rollback tests\n");
432printf("\n");
433
434$kill_possible = 1;
435
436printf("Successful snapshot_rollback to only existing snapshot\n");
437testcase_rollback("101", "test", "", { "local:snapshotable-disk-1" => "test" });
438
439printf("Successful snapshot_rollback to leaf snapshot\n");
440testcase_rollback("102", "test2", "", { "local:snapshotable-disk-1" => "test2" });
441
442printf("Successful snapshot_rollback to root snapshot\n");
443testcase_rollback("103", "test", "", { "local:snapshotable-disk-1" => "test" });
444
445printf("Successful snapshot_rollback to intermediate snapshot\n");
446testcase_rollback("104", "test2", "", { "local:snapshotable-disk-1" => "test2" });
447
3c8677f8
FG
448printf("Successful snapshot_rollback with multiple mp\n");
449testcase_rollback("105", "test", "", { "local:snapshotable-disk-1" => "test", "local:snapshotable-disk-2" => "test", "local:snapshotable-disk-3" => "test" });
450
5ab55c6d
FG
451printf("Expected error for snapshot_rollback with non-existing snapshot\n");
452testcase_rollback("201", "test2", "snapshot 'test2' does not exist\n");
453
454printf("Expected error for snapshot_rollback if volume rollback not possible\n");
455testcase_rollback("202", "test", "volume_rollback_is_possible failed\n");
456
457printf("Expected error for snapshot_rollback with incomplete snapshot\n");
458testcase_rollback("203", "test", "unable to rollback to incomplete snapshot (snapstate = delete)\n");
459
460printf("Expected error for snapshot_rollback with lock\n");
67afe46e 461testcase_rollback("204", "test", "CT is locked (backup)\n");
5ab55c6d
FG
462
463printf("Expected error for snapshot_rollback with saved vmstate\n");
464testcase_rollback("205", "test", "implement me - save vmstate\n", { "local:snapshotable-disk-1" => "test" });
465
466$kill_possible = 0;
467
468printf("Expected error for snapshot_rollback with unkillable container\n");
469testcase_rollback("206", "test", "unable to rollback vm 206: vm is running\n");
0fdbd016 470
3c8677f8
FG
471$kill_possible = 1;
472
473printf("Expected error for snapshot_rollback with mp rollback_is_possible failure\n");
474testcase_rollback("207", "test", "volume_rollback_is_possible failed\n");
475
476printf("Expected error for snapshot_rollback with mp rollback failure (results in inconsistent state)\n");
477testcase_rollback("208", "test", "volume snapshot rollback disabled\n", { "local:snapshotable-disk-1" => "test", "local:snapshotable-disk-2" => "test" });
478
0fdbd016 479done_testing();