use PVE::Storage::Plugin;
use PVE::Storage;
use PVE::QemuServer;
+use PVE::JSONSchema;
use IO::File;
use IPC::Open3;
sub new {
my ($class, $vzdump) = @_;
-
+
PVE::VZDump::check_bin('qm');
- my $self = bless { vzdump => $vzdump };
+ my $self = bless { vzdump => $vzdump }, $class;
$self->{vmlist} = PVE::QemuServer::vzlist();
$self->{storecfg} = PVE::Storage::config();
$task->{disks} = [];
- my $conf = $self->{vmlist}->{$vmid} = PVE::QemuServer::load_config($vmid);
+ my $conf = $self->{vmlist}->{$vmid} = PVE::QemuConfig->load_config($vmid);
+
+ $self->loginfo("VM Name: $conf->{name}")
+ if defined($conf->{name});
$self->{vm_was_running} = 1;
if (!PVE::QemuServer::check_running($vmid)) {
$task->{hostname} = $conf->{name};
- my $hostname = PVE::INotify::nodename();
+ my $hostname = PVE::INotify::nodename();
my $vollist = [];
my $drivehash = {};
return if PVE::QemuServer::drive_is_cdrom($drive);
- if (defined($drive->{backup}) && $drive->{backup} eq "no") {
- $self->loginfo("exclude disk '$ds' (backup=no)");
- return;
- }
-
my $volid = $drive->{file};
+ if (defined($drive->{backup}) && !$drive->{backup}) {
+ $self->loginfo("exclude disk '$ds' '$volid' (backup=no)");
+ return;
+ } elsif ($drive->{iothread}) {
+ die "disk '$ds' '$volid' (iothread=on) can't use backup feature currently. Please set backup=no for this drive";
+ } else {
+ my $log = "include disk '$ds' '$volid'";
+ if (defined $drive->{size}) {
+ my $readable_size = PVE::JSONSchema::format_size($drive->{size});
+ $log .= " $readable_size";
+ }
+ $self->loginfo($log);
+ }
+
my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
push @$vollist, $volid if $storeid;
$drivehash->{$ds} = $drive;
foreach my $ds (sort keys %$drivehash) {
my $drive = $drivehash->{$ds};
-
+
my $volid = $drive->{file};
my $path;
};
die "no such volume '$volid'\n" if $@;
- my $diskinfo = { path => $path , volid => $volid, storeid => $storeid,
+ my $diskinfo = { path => $path , volid => $volid, storeid => $storeid,
format => $format, virtdev => $ds, qmdevice => "drive-$ds" };
if (-b $path) {
my ($self, $vmid) = @_;
my $running = PVE::QemuServer::check_running($vmid) ? 1 : 0;
-
- return wantarray ? ($running, $running ? 'running' : 'stopped') : $running;
+
+ return wantarray ? ($running, $running ? 'running' : 'stopped') : $running;
}
sub lock_vm {
sub assemble {
my ($self, $task, $vmid) = @_;
- my $conffile = PVE::QemuServer::config_file ($vmid);
+ my $conffile = PVE::QemuConfig->config_file($vmid);
my $outfile = "$task->{tmpdir}/qemu-server.conf";
-
- my $outfd;
- my $conffd;
-
- eval {
-
- $outfd = IO::File->new (">$outfile") ||
- die "unable to open '$outfile'";
- $conffd = IO::File->new ($conffile, 'r') ||
- die "unable open '$conffile'";
-
- my $found_snapshot;
- while (defined (my $line = <$conffd>)) {
- next if $line =~ m/^\#vzdump\#/; # just to be sure
- next if $line =~ m/^\#qmdump\#/; # just to be sure
- if ($line =~ m/^\[.*\]\s*$/) {
+ my $firewall_src = "/etc/pve/firewall/$vmid.fw";
+ my $firewall_dest = "$task->{tmpdir}/qemu-server.fw";
+
+ my $outfd = IO::File->new (">$outfile") ||
+ die "unable to open '$outfile'";
+ my $conffd = IO::File->new ($conffile, 'r') ||
+ die "unable open '$conffile'";
+
+ my $found_snapshot;
+ my $found_pending;
+ while (defined (my $line = <$conffd>)) {
+ next if $line =~ m/^\#vzdump\#/; # just to be sure
+ next if $line =~ m/^\#qmdump\#/; # just to be sure
+ if ($line =~ m/^\[(.*)\]\s*$/) {
+ if ($1 =~ m/PENDING/i) {
+ $found_pending = 1;
+ } else {
$found_snapshot = 1;
}
- next if $found_snapshot; # skip all snapshots data
- if ($line =~ m/^unused\d+:\s*(\S+)\s*/) {
- $self->loginfo("skip unused drive '$1' (not included into backup)");
- next;
- }
- next if $line =~ m/^lock:/ || $line =~ m/^parent:/;
-
- print $outfd $line;
}
- foreach my $di (@{$task->{disks}}) {
- if ($di->{type} eq 'block' || $di->{type} eq 'file') {
- my $storeid = $di->{storeid} || '';
- my $format = $di->{format} || '';
- print $outfd "#qmdump#map:$di->{virtdev}:$di->{qmdevice}:$storeid:$format:\n";
- } else {
- die "internal error";
- }
+ next if $found_snapshot; # skip all snapshots data
+ next if $found_pending; # skip all pending changes
+
+ if ($line =~ m/^unused\d+:\s*(\S+)\s*/) {
+ $self->loginfo("skip unused drive '$1' (not included into backup)");
+ next;
}
+ next if $line =~ m/^lock:/ || $line =~ m/^parent:/;
+
+ print $outfd $line;
+ }
- if ($found_snapshot) {
- $self->loginfo("snapshots found (not included into backup)");
+ foreach my $di (@{$task->{disks}}) {
+ if ($di->{type} eq 'block' || $di->{type} eq 'file') {
+ my $storeid = $di->{storeid} || '';
+ my $format = $di->{format} || '';
+ print $outfd "#qmdump#map:$di->{virtdev}:$di->{qmdevice}:$storeid:$format:\n";
+ } else {
+ die "internal error";
}
- };
- my $err = $@;
+ }
+
+ if ($found_snapshot) {
+ $self->loginfo("snapshots found (not included into backup)");
+ }
+
+ if ($found_pending) {
+ $self->loginfo("pending configuration changes found (not included into backup)");
+ }
- close ($outfd) if $outfd;
- close ($conffd) if $conffd;
-
- die $err if $err;
+ PVE::Tools::file_copy($firewall_src, $firewall_dest) if -f $firewall_src;
}
sub archive {
my ($self, $task, $vmid, $filename, $comp) = @_;
my $conffile = "$task->{tmpdir}/qemu-server.conf";
+ my $firewall = "$task->{tmpdir}/qemu-server.fw";
my $opts = $self->{vzdump}->{opts};
my $speed = 0;
if ($opts->{bwlimit}) {
- $speed = $opts->{bwlimit}*1024;
+ $speed = $opts->{bwlimit}*1024;
+ }
+
+ my $diskcount = scalar(@{$task->{disks}});
+
+ if (PVE::QemuConfig->is_template($self->{vmlist}->{$vmid}) || !$diskcount) {
+ my @pathlist;
+ foreach my $di (@{$task->{disks}}) {
+ if ($di->{type} eq 'block' || $di->{type} eq 'file') {
+ push @pathlist, "$di->{qmdevice}=$di->{path}";
+ } else {
+ die "implement me";
+ }
+ }
+
+ if (!$diskcount) {
+ $self->loginfo("backup contains no disks");
+ }
+
+ my $outcmd;
+ if ($comp) {
+ $outcmd = "exec:$comp";
+ } else {
+ $outcmd = "exec:cat";
+ }
+
+ $outcmd .= " > $filename" if !$opts->{stdout};
+
+ my $cmd = ['/usr/bin/vma', 'create', '-v', '-c', $conffile];
+ push @$cmd, '-c', $firewall if -e $firewall;
+ push @$cmd, $outcmd, @pathlist;
+
+ $self->loginfo("starting template backup");
+ $self->loginfo(join(' ', @$cmd));
+
+ if ($opts->{stdout}) {
+ $self->cmd($cmd, output => ">&=" . fileno($opts->{stdout}));
+ } else {
+ $self->cmd($cmd);
+ }
+
+ return;
}
+
my $devlist = '';
foreach my $di (@{$task->{disks}}) {
if ($di->{type} eq 'block' || $di->{type} eq 'file') {
my $resume_on_backup;
my $skiplock = 1;
-
- if (!PVE::QemuServer::check_running($vmid)) {
+ my $vm_is_running = PVE::QemuServer::check_running($vmid);
+ if (!$vm_is_running) {
eval {
$self->loginfo("starting kvm to execute backup task");
- PVE::QemuServer::vm_start($self->{storecfg}, $vmid, undef,
+ PVE::QemuServer::vm_start($self->{storecfg}, $vmid, undef,
$skiplock, undef, 1);
if ($self->{vm_was_running}) {
$resume_on_backup = 1;
my $backup_cb = sub {
my ($vmid, $resp) = @_;
- $uuid = $resp->{return};
+ $uuid = $resp->{return}->{UUID};
};
my $outfh;
my $fd = fileno(STDIN);
close STDIN;
POSIX::close(0) if $fd != 0;
- die "unable to redirect STDIN - $!"
+ die "unable to redirect STDIN - $!"
if !open(STDIN, "<&", $pipefd[0]);
-
+
# redirect STDOUT
$fd = fileno(STDOUT);
close STDOUT;
POSIX::close (1) if $fd != 1;
- die "unable to redirect STDOUT - $!"
+ die "unable to redirect STDOUT - $!"
if !open(STDOUT, ">&", fileno($outfh));
-
+
exec($comp);
die "fork compressor '$comp' failed\n";
};
if (my $err = $@) {
- warn $err;
- POSIX::_exit(1);
+ $self->logerr($err);
+ POSIX::_exit(1);
}
- POSIX::_exit(0);
- kill(-9, $$);
+ POSIX::_exit(0);
+ kill(-9, $$);
} else {
POSIX::close($pipefd[0]);
$outfileno = $pipefd[1];
- }
+ }
} else {
$outfileno = fileno($outfh);
}
my $add_fd_cb = sub {
my ($vmid, $resp) = @_;
- $qmpclient->queue_cmd($vmid, $backup_cb, 'backup',
- backupfile => "/dev/fdname/backup",
- speed => $speed,
- 'config-filename' => $conffile,
- devlist => $devlist);
+ my $params = {
+ 'backup-file' => "/dev/fdname/backup",
+ speed => $speed,
+ 'config-file' => $conffile,
+ devlist => $devlist
+ };
+
+ $params->{'firewall-file'} = $firewall if -e $firewall;
+ $qmpclient->queue_cmd($vmid, $backup_cb, 'backup', %$params);
};
-
- $qmpclient->queue_cmd($vmid, $add_fd_cb, 'getfd',
+ $qmpclient->queue_cmd($vmid, $add_fd_cb, 'getfd',
fd => $outfileno, fdname => "backup");
- $qmpclient->queue_execute();
- die $qmpclient->{errors}->{$vmid} if $qmpclient->{errors}->{$vmid};
+ my $agent_running = 0;
+
+ if ($self->{vmlist}->{$vmid}->{agent} && $vm_is_running) {
+ $agent_running = PVE::QemuServer::qga_check_running($vmid);
+ }
+
+ if ($agent_running){
+ eval { PVE::QemuServer::vm_mon_cmd($vmid, "guest-fsfreeze-freeze"); };
+ if (my $err = $@) {
+ $self->logerr($err);
+ }
+ }
+
+ eval { $qmpclient->queue_execute() };
+ my $qmperr = $@;
+
+ if ($agent_running){
+ eval { PVE::QemuServer::vm_mon_cmd($vmid, "guest-fsfreeze-thaw"); };
+ if (my $err = $@) {
+ $self->logerr($err);
+ }
+ }
+ die $qmperr if $qmperr;
+ die $qmpclient->{errors}->{$vmid} if $qmpclient->{errors}->{$vmid};
if ($cpid) {
- POSIX::close($outfileno) == 0 ||
+ POSIX::close($outfileno) == 0 ||
die "close output file handle failed\n";
}
my $status;
my $starttime = time ();
my $last_per = -1;
- my $last_total = 0;
+ my $last_total = 0;
my $last_zero = 0;
my $last_transferred = 0;
my $last_time = time();
while(1) {
$status = PVE::QemuServer::vm_mon_cmd($vmid, 'query-backup');
- my $total = $status->{total};
- $transferred = $status->{transferred};
+ my $total = $status->{total} || 0;
+ $transferred = $status->{transferred} || 0;
my $per = $total ? int(($transferred * 100)/$total) : 0;
my $zero = $status->{'zero-bytes'} || 0;
my $zero_per = $total ? int(($zero * 100)/$total) : 0;
-
- die "got unexpected uuid\n" if $status->{uuid} ne $uuid;
+
+ die "got unexpected uuid\n" if !$status->{uuid} || ($status->{uuid} ne $uuid);
my $ctime = time();
my $duration = $ctime - $starttime;
my $wbytes = $rbytes - ($zero - $last_zero);
my $timediff = ($ctime - $last_time) || 1; # fixme
- my $mbps_read = ($rbytes > 0) ?
+ my $mbps_read = ($rbytes > 0) ?
int(($rbytes/$timediff)/(1000*1000)) : 0;
- my $mbps_write = ($wbytes > 0) ?
+ my $mbps_write = ($wbytes > 0) ?
int(($wbytes/$timediff)/(1000*1000)) : 0;
my $statusline = "status: $per% ($transferred/$total), " .
"sparse ${zero_per}% ($zero), duration $duration, " .
- "$mbps_read/$mbps_write MB/s";
- if ($status->{status} ne 'active') {
+ "read/write $mbps_read/$mbps_write MB/s";
+ my $res = $status->{status} || 'unknown';
+ if ($res ne 'active') {
$self->loginfo($statusline);
die(($status->{errmsg} || "unknown error") . "\n")
- if $status->{status} eq 'error';
+ if $res eq 'error';
+ die "got unexpected status '$res'\n"
+ if $res ne 'done';
+ die "got wrong number of transfered bytes ($total != $transferred)\n"
+ if ($res eq 'done') && ($total != $transferred);
+
last;
}
if ($per != $last_per && ($timediff > 2)) {
$self->loginfo($statusline);
$last_per = $per;
- $last_total = $total if $total;
+ $last_total = $total if $total;
$last_zero = $zero if $zero;
$last_transferred = $transferred if $transferred;
$last_time = $ctime;
my $err = $@;
if ($err) {
+ $self->logerr($err);
$self->loginfo("aborting backup job");
- eval { PVE::QemuServer::vm_mon_cmd($vmid, 'backup_cancel'); };
- warn $@ if $@;
+ eval { PVE::QemuServer::vm_mon_cmd($vmid, 'backup-cancel'); };
+ if (my $err1 = $@) {
+ $self->logerr($err1);
+ }
}
if ($stop_after_backup) {
" - keep VM running");
}
}
- }
+ }
if ($err) {
- if ($cpid) {
- kill(-9, $cpid);
+ if ($cpid) {
+ kill(9, $cpid);
waitpid($cpid, 0);
}
die $err;
my $ec = $stat >> 8;
my $signal = $stat & 127;
if ($ec || $signal) {
- die "$comp failed - wrong exit status $ec" .
+ die "$comp failed - wrong exit status $ec" .
($signal ? " (signal $signal)\n" : "\n");
}
}