]> git.proxmox.com Git - qemu-server.git/blame - PVE/VZDump/QemuServer.pm
vzdump: log QGA FS freeze/thaw try in task log
[qemu-server.git] / PVE / VZDump / QemuServer.pm
CommitLineData
1e3baf05
DM
1package PVE::VZDump::QemuServer;
2
1e3baf05
DM
3use strict;
4use warnings;
d610b145 5
1e3baf05 6use File::Basename;
d610b145
TL
7use File::Path;
8use IO::File;
9use IPC::Open3;
10
11use PVE::Cluster qw(cfs_read_file);
66ab1d91 12use PVE::INotify;
91bd6c90 13use PVE::IPCC;
d610b145 14use PVE::JSONSchema;
0a13e08e 15use PVE::QMPClient;
f5bdefa4 16use PVE::Storage::Plugin;
1e3baf05 17use PVE::Storage;
d610b145
TL
18use PVE::Tools;
19use PVE::VZDump;
20
1e3baf05 21use PVE::QemuServer;
3392d6ca 22use PVE::QemuServer::Machine;
0a13e08e 23use PVE::QemuServer::Monitor qw(mon_cmd);
1e3baf05
DM
24
25use base qw (PVE::VZDump::Plugin);
26
27sub new {
28 my ($class, $vzdump) = @_;
874a096e 29
66ab1d91 30 PVE::VZDump::check_bin('qm');
1e3baf05 31
e2812738 32 my $self = bless { vzdump => $vzdump }, $class;
1e3baf05
DM
33
34 $self->{vmlist} = PVE::QemuServer::vzlist();
35 $self->{storecfg} = PVE::Storage::config();
36
37 return $self;
38};
39
40
41sub type {
42 return 'qemu';
43}
44
45sub vmlist {
46 my ($self) = @_;
47
48 return [ keys %{$self->{vmlist}} ];
49}
50
51sub prepare {
52 my ($self, $task, $vmid, $mode) = @_;
53
54 $task->{disks} = [];
55
ffda963f 56 my $conf = $self->{vmlist}->{$vmid} = PVE::QemuConfig->load_config($vmid);
1e3baf05 57
85b84b7b
WL
58 $self->loginfo("VM Name: $conf->{name}")
59 if defined($conf->{name});
60
91bd6c90
DM
61 $self->{vm_was_running} = 1;
62 if (!PVE::QemuServer::check_running($vmid)) {
63 $self->{vm_was_running} = 0;
585b6e28
DM
64 }
65
1e3baf05
DM
66 $task->{hostname} = $conf->{name};
67
874a096e 68 my $hostname = PVE::INotify::nodename();
1e3baf05 69
b969cc68
DM
70 my $vollist = [];
71 my $drivehash = {};
1e3baf05
DM
72 PVE::QemuServer::foreach_drive($conf, sub {
73 my ($ds, $drive) = @_;
74
f5bdefa4 75 return if PVE::QemuServer::drive_is_cdrom($drive);
1e3baf05 76
b53b958b
EK
77 my $volid = $drive->{file};
78
bea021ac 79 if (defined($drive->{backup}) && !$drive->{backup}) {
b53b958b 80 $self->loginfo("exclude disk '$ds' '$volid' (backup=no)");
1e3baf05 81 return;
317c55c2 82 } elsif ($self->{vm_was_running} && $drive->{iothread}) {
3392d6ca 83 if (!PVE::QemuServer::Machine::runs_at_least_qemu_version($vmid, 4, 0, 1)) {
48343b3f
TL
84 die "disk '$ds' '$volid' (iothread=on) can't use backup feature with running QEMU " .
85 "version < 4.0.1! Either set backup=no for this drive or upgrade QEMU and restart VM\n";
86 }
b53b958b 87 } else {
0db93c2d
EK
88 my $log = "include disk '$ds' '$volid'";
89 if (defined $drive->{size}) {
90 my $readable_size = PVE::JSONSchema::format_size($drive->{size});
91 $log .= " $readable_size";
92 }
93 $self->loginfo($log);
874a096e 94 }
b969cc68 95
f5bdefa4 96 my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
b969cc68
DM
97 push @$vollist, $volid if $storeid;
98 $drivehash->{$ds} = $drive;
99 });
100
101 PVE::Storage::activate_volumes($self->{storecfg}, $vollist);
102
075b417a
DM
103 foreach my $ds (sort keys %$drivehash) {
104 my $drive = $drivehash->{$ds};
874a096e 105
1e3baf05
DM
106 my $volid = $drive->{file};
107
108 my $path;
109
f5bdefa4 110 my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
1e3baf05 111 if ($storeid) {
b969cc68 112 $path = PVE::Storage::path($self->{storecfg}, $volid);
1e3baf05
DM
113 } else {
114 $path = $volid;
115 }
116
b969cc68 117 next if !$path;
1e3baf05 118
daca220d
AD
119 my $format = undef;
120 my $size = undef;
1e3baf05 121
daca220d
AD
122 eval{
123 ($size, $format) = PVE::Storage::volume_size_info($self->{storecfg}, $volid, 5);
124 };
125 die "no such volume '$volid'\n" if $@;
91bd6c90 126
874a096e 127 my $diskinfo = { path => $path , volid => $volid, storeid => $storeid,
91bd6c90 128 format => $format, virtdev => $ds, qmdevice => "drive-$ds" };
1e3baf05
DM
129
130 if (-b $path) {
1e3baf05 131 $diskinfo->{type} = 'block';
1e3baf05 132 } else {
1e3baf05 133 $diskinfo->{type} = 'file';
1e3baf05
DM
134 }
135
136 push @{$task->{disks}}, $diskinfo;
b969cc68 137 }
1e3baf05
DM
138}
139
140sub vm_status {
141 my ($self, $vmid) = @_;
142
66ab1d91 143 my $running = PVE::QemuServer::check_running($vmid) ? 1 : 0;
874a096e
DM
144
145 return wantarray ? ($running, $running ? 'running' : 'stopped') : $running;
1e3baf05
DM
146}
147
148sub lock_vm {
149 my ($self, $vmid) = @_;
150
151 $self->cmd ("qm set $vmid --lock backup");
152}
153
154sub unlock_vm {
155 my ($self, $vmid) = @_;
156
66ab1d91 157 $self->cmd ("qm unlock $vmid");
1e3baf05
DM
158}
159
160sub stop_vm {
161 my ($self, $task, $vmid) = @_;
162
163 my $opts = $self->{vzdump}->{opts};
164
165 my $wait = $opts->{stopwait} * 60;
166 # send shutdown and wait
254575e9 167 $self->cmd ("qm shutdown $vmid --skiplock --keepActive --timeout $wait");
1e3baf05
DM
168}
169
170sub start_vm {
171 my ($self, $task, $vmid) = @_;
172
66ab1d91 173 $self->cmd ("qm start $vmid --skiplock");
1e3baf05
DM
174}
175
176sub suspend_vm {
177 my ($self, $task, $vmid) = @_;
178
66ab1d91 179 $self->cmd ("qm suspend $vmid --skiplock");
1e3baf05
DM
180}
181
182sub resume_vm {
183 my ($self, $task, $vmid) = @_;
184
66ab1d91 185 $self->cmd ("qm resume $vmid --skiplock");
1e3baf05
DM
186}
187
1e3baf05
DM
188sub assemble {
189 my ($self, $task, $vmid) = @_;
190
04096e7b 191 my $conffile = PVE::QemuConfig->config_file($vmid);
1e3baf05
DM
192
193 my $outfile = "$task->{tmpdir}/qemu-server.conf";
c05f7f3f
WL
194 my $firewall_src = "/etc/pve/firewall/$vmid.fw";
195 my $firewall_dest = "$task->{tmpdir}/qemu-server.fw";
196
197 my $outfd = IO::File->new (">$outfile") ||
198 die "unable to open '$outfile'";
199 my $conffd = IO::File->new ($conffile, 'r') ||
200 die "unable open '$conffile'";
201
202 my $found_snapshot;
391c2230 203 my $found_pending;
c05f7f3f
WL
204 while (defined (my $line = <$conffd>)) {
205 next if $line =~ m/^\#vzdump\#/; # just to be sure
206 next if $line =~ m/^\#qmdump\#/; # just to be sure
391c2230
FG
207 if ($line =~ m/^\[(.*)\]\s*$/) {
208 if ($1 =~ m/PENDING/i) {
209 $found_pending = 1;
210 } else {
211 $found_snapshot = 1;
212 }
1e3baf05 213 }
391c2230 214
c05f7f3f 215 next if $found_snapshot; # skip all snapshots data
391c2230
FG
216 next if $found_pending; # skip all pending changes
217
c05f7f3f
WL
218 if ($line =~ m/^unused\d+:\s*(\S+)\s*/) {
219 $self->loginfo("skip unused drive '$1' (not included into backup)");
220 next;
1e3baf05 221 }
c05f7f3f 222 next if $line =~ m/^lock:/ || $line =~ m/^parent:/;
91bd6c90 223
c05f7f3f
WL
224 print $outfd $line;
225 }
226
227 foreach my $di (@{$task->{disks}}) {
228 if ($di->{type} eq 'block' || $di->{type} eq 'file') {
229 my $storeid = $di->{storeid} || '';
230 my $format = $di->{format} || '';
231 print $outfd "#qmdump#map:$di->{virtdev}:$di->{qmdevice}:$storeid:$format:\n";
232 } else {
233 die "internal error";
91bd6c90 234 }
c05f7f3f 235 }
1e3baf05 236
c05f7f3f
WL
237 if ($found_snapshot) {
238 $self->loginfo("snapshots found (not included into backup)");
239 }
874a096e 240
391c2230
FG
241 if ($found_pending) {
242 $self->loginfo("pending configuration changes found (not included into backup)");
243 }
244
c05f7f3f 245 PVE::Tools::file_copy($firewall_src, $firewall_dest) if -f $firewall_src;
1e3baf05
DM
246}
247
248sub archive {
fad02a16 249 my ($self, $task, $vmid, $filename, $comp) = @_;
1e3baf05
DM
250
251 my $conffile = "$task->{tmpdir}/qemu-server.conf";
c05f7f3f 252 my $firewall = "$task->{tmpdir}/qemu-server.fw";
1e3baf05
DM
253
254 my $opts = $self->{vzdump}->{opts};
255
256 my $starttime = time ();
257
91bd6c90
DM
258 my $speed = 0;
259 if ($opts->{bwlimit}) {
874a096e 260 $speed = $opts->{bwlimit}*1024;
91bd6c90 261 }
1e3baf05 262
c82935e9
DM
263 my $diskcount = scalar(@{$task->{disks}});
264
ffda963f 265 if (PVE::QemuConfig->is_template($self->{vmlist}->{$vmid}) || !$diskcount) {
23b4120b
DM
266 my @pathlist;
267 foreach my $di (@{$task->{disks}}) {
268 if ($di->{type} eq 'block' || $di->{type} eq 'file') {
269 push @pathlist, "$di->{qmdevice}=$di->{path}";
270 } else {
271 die "implement me";
272 }
273 }
274
c82935e9
DM
275 if (!$diskcount) {
276 $self->loginfo("backup contains no disks");
277 }
278
23b4120b
DM
279 my $outcmd;
280 if ($comp) {
874a096e 281 $outcmd = "exec:$comp";
23b4120b 282 } else {
874a096e 283 $outcmd = "exec:cat";
23b4120b
DM
284 }
285
a2fab11a 286 $outcmd .= " > $filename" if !$opts->{stdout};
23b4120b 287
c05f7f3f
WL
288 my $cmd = ['/usr/bin/vma', 'create', '-v', '-c', $conffile];
289 push @$cmd, '-c', $firewall if -e $firewall;
290 push @$cmd, $outcmd, @pathlist;
23b4120b
DM
291
292 $self->loginfo("starting template backup");
293 $self->loginfo(join(' ', @$cmd));
294
295 if ($opts->{stdout}) {
296 $self->cmd($cmd, output => ">&=" . fileno($opts->{stdout}));
297 } else {
298 $self->cmd($cmd);
299 }
300
301 return;
302 }
303
304
91bd6c90 305 my $devlist = '';
1e3baf05
DM
306 foreach my $di (@{$task->{disks}}) {
307 if ($di->{type} eq 'block' || $di->{type} eq 'file') {
91bd6c90 308 $devlist .= $devlist ? ",$di->{qmdevice}" : $di->{qmdevice};
1e3baf05
DM
309 } else {
310 die "implement me";
311 }
312 }
313
91bd6c90
DM
314 my $stop_after_backup;
315 my $resume_on_backup;
316
317 my $skiplock = 1;
ab6a9a0c
WL
318 my $vm_is_running = PVE::QemuServer::check_running($vmid);
319 if (!$vm_is_running) {
91bd6c90
DM
320 eval {
321 $self->loginfo("starting kvm to execute backup task");
874a096e 322 PVE::QemuServer::vm_start($self->{storecfg}, $vmid, undef,
91bd6c90
DM
323 $skiplock, undef, 1);
324 if ($self->{vm_was_running}) {
325 $resume_on_backup = 1;
326 } else {
327 $stop_after_backup = 1;
328 }
329 };
330 if (my $err = $@) {
331 die $err;
332 }
9c502e26 333 }
91bd6c90
DM
334
335 my $cpid;
336 my $interrupt_msg = "interrupted by signal\n";
337 eval {
338 $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = $SIG{PIPE} = sub {
339 die $interrupt_msg;
340 };
341
342 my $qmpclient = PVE::QMPClient->new();
343
344 my $uuid;
345
346 my $backup_cb = sub {
347 my ($vmid, $resp) = @_;
663d6641 348 $uuid = $resp->{return}->{UUID};
91bd6c90
DM
349 };
350
351 my $outfh;
352 if ($opts->{stdout}) {
353 $outfh = $opts->{stdout};
354 } else {
355 $outfh = IO::File->new($filename, "w") ||
356 die "unable to open file '$filename' - $!\n";
357 }
358
359 my $outfileno;
360 if ($comp) {
361 my @pipefd = POSIX::pipe();
362 $cpid = fork();
363 die "unable to fork worker - $!" if !defined($cpid);
364 if ($cpid == 0) {
365 eval {
366 POSIX::close($pipefd[1]);
367 # redirect STDIN
368 my $fd = fileno(STDIN);
369 close STDIN;
370 POSIX::close(0) if $fd != 0;
874a096e 371 die "unable to redirect STDIN - $!"
91bd6c90 372 if !open(STDIN, "<&", $pipefd[0]);
874a096e 373
91bd6c90
DM
374 # redirect STDOUT
375 $fd = fileno(STDOUT);
376 close STDOUT;
377 POSIX::close (1) if $fd != 1;
378
874a096e 379 die "unable to redirect STDOUT - $!"
91bd6c90 380 if !open(STDOUT, ">&", fileno($outfh));
874a096e 381
91bd6c90
DM
382 exec($comp);
383 die "fork compressor '$comp' failed\n";
384 };
385 if (my $err = $@) {
60635a57 386 $self->logerr($err);
874a096e 387 POSIX::_exit(1);
91bd6c90 388 }
874a096e
DM
389 POSIX::_exit(0);
390 kill(-9, $$);
91bd6c90
DM
391 } else {
392 POSIX::close($pipefd[0]);
393 $outfileno = $pipefd[1];
874a096e 394 }
91bd6c90
DM
395 } else {
396 $outfileno = fileno($outfh);
397 }
398
399 my $add_fd_cb = sub {
400 my ($vmid, $resp) = @_;
401
c05f7f3f
WL
402 my $params = {
403 'backup-file' => "/dev/fdname/backup",
404 speed => $speed,
405 'config-file' => $conffile,
406 devlist => $devlist
407 };
a022e3fd 408
c05f7f3f
WL
409 $params->{'firewall-file'} = $firewall if -e $firewall;
410 $qmpclient->queue_cmd($vmid, $backup_cb, 'backup', %$params);
91bd6c90
DM
411 };
412
874a096e 413 $qmpclient->queue_cmd($vmid, $add_fd_cb, 'getfd',
91bd6c90 414 fd => $outfileno, fdname => "backup");
ab6a9a0c 415
1a988fd2
DC
416 my $agent_running = 0;
417
418 if ($self->{vmlist}->{$vmid}->{agent} && $vm_is_running) {
419 $agent_running = PVE::QemuServer::qga_check_running($vmid);
82b25dbc
TL
420 $self->loginfo("skipping guest-agent 'fs-freeze', agent configured but not running?")
421 if !$agent_running;
1a988fd2
DC
422 }
423
424 if ($agent_running){
82b25dbc 425 $self->loginfo("issuing guest-agent 'fs-freeze' command");
0a13e08e 426 eval { mon_cmd($vmid, "guest-fsfreeze-freeze"); };
ab6a9a0c
WL
427 if (my $err = $@) {
428 $self->logerr($err);
874a096e 429 }
ab6a9a0c 430 }
874a096e 431
f0f30448
WB
432 eval { $qmpclient->queue_execute() };
433 my $qmperr = $@;
91bd6c90 434
1a988fd2 435 if ($agent_running){
82b25dbc 436 $self->loginfo("issuing guest-agent 'fs-thaw' command");
0a13e08e 437 eval { mon_cmd($vmid, "guest-fsfreeze-thaw"); };
ab6a9a0c
WL
438 if (my $err = $@) {
439 $self->logerr($err);
440 }
441 }
f0f30448 442 die $qmperr if $qmperr;
874a096e 443 die $qmpclient->{errors}->{$vmid} if $qmpclient->{errors}->{$vmid};
91bd6c90
DM
444
445 if ($cpid) {
874a096e 446 POSIX::close($outfileno) == 0 ||
91bd6c90
DM
447 die "close output file handle failed\n";
448 }
449
450 die "got no uuid for backup task\n" if !$uuid;
451
452 $self->loginfo("started backup task '$uuid'");
453
454 if ($resume_on_backup) {
96f00fd8
TL
455 if (my $stoptime = $task->{vmstoptime}) {
456 my $delay = time() - $task->{vmstoptime};
457 $task->{vmstoptime} = undef; # avoid printing 'online after ..' twice
458 $self->loginfo("resuming VM again after $delay seconds");
459 } else {
460 $self->loginfo("resuming VM again");
461 }
0a13e08e 462 mon_cmd($vmid, 'cont');
91bd6c90
DM
463 }
464
465 my $status;
466 my $starttime = time ();
467 my $last_per = -1;
874a096e 468 my $last_total = 0;
91bd6c90
DM
469 my $last_zero = 0;
470 my $last_transferred = 0;
471 my $last_time = time();
472 my $transferred;
473
474 while(1) {
0a13e08e 475 $status = mon_cmd($vmid, 'query-backup');
405b913a
DM
476 my $total = $status->{total} || 0;
477 $transferred = $status->{transferred} || 0;
91bd6c90
DM
478 my $per = $total ? int(($transferred * 100)/$total) : 0;
479 my $zero = $status->{'zero-bytes'} || 0;
480 my $zero_per = $total ? int(($zero * 100)/$total) : 0;
874a096e 481
405b913a 482 die "got unexpected uuid\n" if !$status->{uuid} || ($status->{uuid} ne $uuid);
91bd6c90
DM
483
484 my $ctime = time();
485 my $duration = $ctime - $starttime;
486
487 my $rbytes = $transferred - $last_transferred;
488 my $wbytes = $rbytes - ($zero - $last_zero);
489
490 my $timediff = ($ctime - $last_time) || 1; # fixme
874a096e 491 my $mbps_read = ($rbytes > 0) ?
91bd6c90 492 int(($rbytes/$timediff)/(1000*1000)) : 0;
874a096e 493 my $mbps_write = ($wbytes > 0) ?
91bd6c90
DM
494 int(($wbytes/$timediff)/(1000*1000)) : 0;
495
496 my $statusline = "status: $per% ($transferred/$total), " .
497 "sparse ${zero_per}% ($zero), duration $duration, " .
76312b86 498 "read/write $mbps_read/$mbps_write MB/s";
405b913a
DM
499 my $res = $status->{status} || 'unknown';
500 if ($res ne 'active') {
91bd6c90
DM
501 $self->loginfo($statusline);
502 die(($status->{errmsg} || "unknown error") . "\n")
405b913a
DM
503 if $res eq 'error';
504 die "got unexpected status '$res'\n"
505 if $res ne 'done';
506 die "got wrong number of transfered bytes ($total != $transferred)\n"
507 if ($res eq 'done') && ($total != $transferred);
508
91bd6c90
DM
509 last;
510 }
511 if ($per != $last_per && ($timediff > 2)) {
512 $self->loginfo($statusline);
513 $last_per = $per;
874a096e 514 $last_total = $total if $total;
91bd6c90
DM
515 $last_zero = $zero if $zero;
516 $last_transferred = $transferred if $transferred;
517 $last_time = $ctime;
518 }
519 sleep(1);
520 }
521
522 my $duration = time() - $starttime;
523 if ($transferred && $duration) {
524 my $mb = int($transferred/(1000*1000));
525 my $mbps = int(($transferred/$duration)/(1000*1000));
526 $self->loginfo("transferred $mb MB in $duration seconds ($mbps MB/s)");
527 }
528 };
529 my $err = $@;
530
19599cd9 531 if ($err) {
60635a57 532 $self->logerr($err);
19599cd9 533 $self->loginfo("aborting backup job");
0a13e08e 534 eval { mon_cmd($vmid, 'backup-cancel'); };
60635a57
DM
535 if (my $err1 = $@) {
536 $self->logerr($err1);
537 }
19599cd9
DM
538 }
539
91bd6c90
DM
540 if ($stop_after_backup) {
541 # stop if not running
542 eval {
0a13e08e 543 my $resp = mon_cmd($vmid, 'query-status');
91bd6c90
DM
544 my $status = $resp && $resp->{status} ? $resp->{status} : 'unknown';
545 if ($status eq 'prelaunch') {
19599cd9 546 $self->loginfo("stopping kvm after backup task");
91bd6c90
DM
547 PVE::QemuServer::vm_stop($self->{storecfg}, $vmid, $skiplock);
548 } else {
549 $self->loginfo("kvm status changed after backup ('$status')" .
550 " - keep VM running");
551 }
552 }
874a096e 553 }
91bd6c90
DM
554
555 if ($err) {
874a096e
DM
556 if ($cpid) {
557 kill(9, $cpid);
91bd6c90
DM
558 waitpid($cpid, 0);
559 }
560 die $err;
561 }
562
563 if ($cpid && (waitpid($cpid, 0) > 0)) {
564 my $stat = $?;
565 my $ec = $stat >> 8;
566 my $signal = $stat & 127;
567 if ($ec || $signal) {
874a096e 568 die "$comp failed - wrong exit status $ec" .
91bd6c90
DM
569 ($signal ? " (signal $signal)\n" : "\n");
570 }
571 }
572}
573
574sub snapshot {
575 my ($self, $task, $vmid) = @_;
576
577 # nothing to do
1e3baf05
DM
578}
579
580sub cleanup {
581 my ($self, $task, $vmid) = @_;
582
91bd6c90 583 # nothing to do ?
1e3baf05
DM
584}
585
5861;