use PVE::QemuConfig;
use PVE::QemuServer;
+use PVE::QemuServer::Helpers;
use PVE::QemuServer::Machine;
use PVE::QemuServer::Monitor qw(mon_cmd);
$self->{vm_was_paused} = 0;
if (!PVE::QemuServer::check_running($vmid)) {
$self->{vm_was_running} = 0;
- } elsif (PVE::QemuServer::vm_is_paused($vmid)) {
+ } elsif (PVE::QemuServer::vm_is_paused($vmid, 0)) {
+ # Do not treat a suspended VM as paused, as it would cause us to skip
+ # fs-freeze even if the VM wakes up before we reach qga_fs_freeze.
$self->{vm_was_paused} = 1;
}
}
next if !$path;
- my ($size, $format) = eval { PVE::Storage::volume_size_info($self->{storecfg}, $volid, 5) };
- die "no such volume '$volid'\n" if $@;
+ my ($size, $format);
+ if ($storeid) {
+ # The call in list context can be expensive for certain plugins like RBD, just get size
+ $size = eval { PVE::Storage::volume_size_info($self->{storecfg}, $volid, 5) };
+ die "cannot determine size of volume '$volid' - $@\n" if $@;
+
+ my $scfg = PVE::Storage::storage_config($self->{storecfg}, $storeid);
+ $format = PVE::QemuServer::qemu_img_format($scfg, $volname);
+ } else {
+ ($size, $format) = eval {
+ PVE::Storage::volume_size_info($self->{storecfg}, $volid, 5);
+ };
+ die "cannot determine size and format of volume '$volid' - $@\n" if $@;
+ }
my $diskinfo = {
path => $path,
volid => $volid,
storeid => $storeid,
+ size => $size,
format => $format,
virtdev => $ds,
qmdevice => "drive-$ds",
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 $outfd = IO::File->new(">$outfile") or die "unable to open '$outfile' - $!\n";
+ my $conffd = IO::File->new($conffile, 'r') or die "unable to open '$conffile' - $!\n";
my $found_snapshot;
my $found_pending;
}
}
+sub get_and_check_pbs_encryption_config {
+ my ($self) = @_;
+
+ my $opts = $self->{vzdump}->{opts};
+ my $scfg = $opts->{scfg};
+
+ my $keyfile = PVE::Storage::PBSPlugin::pbs_encryption_key_file_name($scfg, $opts->{storage});
+ my $master_keyfile = PVE::Storage::PBSPlugin::pbs_master_pubkey_file_name($scfg, $opts->{storage});
+
+ if (-e $keyfile) {
+ if (-e $master_keyfile) {
+ $self->loginfo("enabling encryption with master key feature");
+ return ($keyfile, $master_keyfile);
+ } elsif ($scfg->{'master-pubkey'}) {
+ die "master public key configured but no key file found\n";
+ } else {
+ $self->loginfo("enabling encryption");
+ return ($keyfile, undef);
+ }
+ } else {
+ my $encryption_fp = $scfg->{'encryption-key'};
+ die "encryption configured ('$encryption_fp') but no encryption key file found!\n"
+ if $encryption_fp;
+ if (-e $master_keyfile) {
+ $self->log(
+ 'warn',
+ "backup target storage is configured with master-key, but no encryption key set!"
+ ." Ignoring master key settings and creating unencrypted backup."
+ );
+ }
+ return (undef, undef);
+ }
+ die "internal error - unhandled case for getting & checking PBS encryption ($keyfile, $master_keyfile)!";
+}
+
+my sub cleanup_fleecing_images {
+ my ($self, $disks) = @_;
+
+ for my $di ($disks->@*) {
+ if (my $volid = $di->{'fleece-volid'}) {
+ eval { PVE::Storage::vdisk_free($self->{storecfg}, $volid); };
+ $self->log('warn', "error removing fleecing image '$volid' - $@") if $@;
+ }
+ }
+}
+
+my sub allocate_fleecing_images {
+ my ($self, $disks, $vmid, $fleecing_storeid, $format) = @_;
+
+ die "internal error - no fleecing storage specified\n" if !$fleecing_storeid;
+
+ # TODO what about potential left-over images from a failed attempt? Just
+ # auto-remove? While unlikely, could conflict with manually created image from user...
+
+ eval {
+ my $n = 0; # counter for fleecing image names
+
+ for my $di ($disks->@*) {
+ next if $di->{virtdev} =~ m/^(?:tpmstate|efidisk)\d$/; # too small to be worth it
+ if ($di->{type} eq 'block' || $di->{type} eq 'file') {
+ my $scfg = PVE::Storage::storage_config($self->{storecfg}, $fleecing_storeid);
+ my $name = "vm-$vmid-fleece-$n";
+ $name .= ".$format" if $scfg->{path};
+
+ my $size = PVE::Tools::convert_size($di->{size}, 'b' => 'kb');
+
+ $di->{'fleece-volid'} = PVE::Storage::vdisk_alloc(
+ $self->{storecfg}, $fleecing_storeid, $vmid, $format, $name, $size);
+
+ $n++;
+ } else {
+ die "implement me (type '$di->{type}')";
+ }
+ }
+ };
+ if (my $err = $@) {
+ cleanup_fleecing_images($self, $disks);
+ die $err;
+ }
+}
+
+my sub detach_fleecing_images {
+ my ($disks, $vmid) = @_;
+
+ return if !PVE::QemuServer::Helpers::vm_running_locally($vmid);
+
+ for my $di ($disks->@*) {
+ if (my $volid = $di->{'fleece-volid'}) {
+ my $devid = "$di->{qmdevice}-fleecing";
+ $devid =~ s/^drive-//; # re-added by qemu_drivedel()
+ eval { PVE::QemuServer::qemu_drivedel($vmid, $devid) };
+ }
+ }
+}
+
+my sub attach_fleecing_images {
+ my ($self, $disks, $vmid, $format) = @_;
+
+ # unconditionally try to remove potential left-overs from a previous backup
+ detach_fleecing_images($disks, $vmid);
+
+ my $vollist = [ map { $_->{'fleece-volid'} } grep { $_->{'fleece-volid'} } $disks->@* ];
+ PVE::Storage::activate_volumes($self->{storecfg}, $vollist);
+
+ for my $di ($disks->@*) {
+ if (my $volid = $di->{'fleece-volid'}) {
+ $self->loginfo("$di->{qmdevice}: attaching fleecing image $volid to QEMU");
+
+ my $path = PVE::Storage::path($self->{storecfg}, $volid);
+ my $devid = "$di->{qmdevice}-fleecing";
+ my $drive = "file=$path,if=none,id=$devid,format=$format,discard=unmap";
+ # Specify size explicitly, to make it work if storage backend rounded up size for
+ # fleecing image when allocating.
+ $drive .= ",size=$di->{size}" if $format eq 'raw';
+ $drive =~ s/\\/\\\\/g;
+ my $ret = PVE::QemuServer::Monitor::hmp_cmd($vmid, "drive_add auto \"$drive\"");
+ die "attaching fleecing image $volid failed - $ret\n" if $ret !~ m/OK/s;
+ }
+ }
+}
+
+my sub check_and_prepare_fleecing {
+ my ($self, $vmid, $fleecing_opts, $disks, $is_template, $qemu_support) = @_;
+
+ # Even if the VM was started specifically for fleecing, it's possible that the VM is resumed and
+ # then starts doing IO. For VMs that are not resumed the fleecing images will just stay empty,
+ # so there is no big cost.
+
+ my $use_fleecing = $fleecing_opts && $fleecing_opts->{enabled} && !$is_template;
+
+ if ($use_fleecing && !defined($qemu_support->{'backup-fleecing'})) {
+ $self->log(
+ 'warn',
+ "running QEMU version does not support backup fleecing - continuing without",
+ );
+ $use_fleecing = 0;
+ }
+
+ if ($use_fleecing) {
+ my ($default_format, $valid_formats) = PVE::Storage::storage_default_format(
+ $self->{storecfg}, $fleecing_opts->{storage});
+ my $format = scalar(grep { $_ eq 'qcow2' } $valid_formats->@*) ? 'qcow2' : 'raw';
+
+ allocate_fleecing_images($self, $disks, $vmid, $fleecing_opts->{storage}, $format);
+ attach_fleecing_images($self, $disks, $vmid, $format);
+ }
+
+ return $use_fleecing;
+}
+
sub archive_pbs {
my ($self, $task, $vmid) = @_;
my $fingerprint = $scfg->{fingerprint};
my $repo = PVE::PBSClient::get_repository($scfg);
my $password = PVE::Storage::PBSPlugin::pbs_get_password($scfg, $opts->{storage});
- my $keyfile = PVE::Storage::PBSPlugin::pbs_encryption_key_file_name($scfg, $opts->{storage});
- my $master_keyfile = PVE::Storage::PBSPlugin::pbs_master_pubkey_file_name($scfg, $opts->{storage});
+ my ($keyfile, $master_keyfile) = $self->get_and_check_pbs_encryption_config();
my $diskcount = scalar(@{$task->{disks}});
- # proxmox-backup-client can only handle raw files and block devs
- # only use it (directly) for disk-less VMs
+ # proxmox-backup-client can only handle raw files and block devs, so only use it (directly) for
+ # disk-less VMs
if (!$diskcount) {
- my @pathlist;
$self->loginfo("backup contains no disks");
local $ENV{PBS_PASSWORD} = $password;
if (defined(my $ns = $scfg->{namespace})) {
push @$cmd, '--ns', $ns;
}
+ if (defined($keyfile)) {
+ push @$cmd, '--keyfile', $keyfile;
+ push @$cmd, '--master-pubkey-file', $master_keyfile if defined($master_keyfile);
+ }
push @$cmd, "qemu-server.conf:$conffile";
push @$cmd, "fw.conf:$firewall" if -e $firewall;
# get list early so we die on unkown drive types before doing anything
my $devlist = _get_task_devlist($task);
+ my $use_fleecing;
$self->enforce_vm_running_for_backup($vmid);
$self->{qmeventd_fh} = PVE::QemuServer::register_qmeventd_handle($vmid);
}
}
- if (!defined($qemu_support->{"pbs-masterkey"}) && -e $master_keyfile) {
- $self->loginfo("WARNING: backup target is configured with master key, but running QEMU version does not support master keys.");
- $self->loginfo("Please make sure you've installed the latest version and the VM has been restarted to use master key feature.");
- $master_keyfile = undef; # skip rest of master key handling below
- }
+ # pve-qemu supports it since 5.2.0-1 (PVE 6.4), so safe to die since PVE 8
+ die "master key configured but running QEMU version does not support master keys\n"
+ if !defined($qemu_support->{'pbs-masterkey'}) && defined($master_keyfile);
$attach_tpmstate_drive->($self, $task, $vmid);
+ my $is_template = PVE::QemuConfig->is_template($self->{vmlist}->{$vmid});
+
+ $use_fleecing = check_and_prepare_fleecing(
+ $self, $vmid, $opts->{fleecing}, $task->{disks}, $is_template, $qemu_support);
+
my $fs_frozen = $self->qga_fs_freeze($task, $vmid);
my $params = {
devlist => $devlist,
'config-file' => $conffile,
};
+ $params->{fleecing} = JSON::true if $use_fleecing;
+
if (defined(my $ns = $scfg->{namespace})) {
$params->{'backup-ns'} = $ns;
}
$params->{fingerprint} = $fingerprint if defined($fingerprint);
$params->{'firewall-file'} = $firewall if -e $firewall;
- if (-e $keyfile) {
- $self->loginfo("enabling encryption");
+
+ $params->{encrypt} = defined($keyfile) ? JSON::true : JSON::false;
+ if (defined($keyfile)) {
$params->{keyfile} = $keyfile;
- $params->{encrypt} = JSON::true;
- if (defined($master_keyfile)) {
- if (-e $master_keyfile) {
- $self->loginfo("enabling master key feature");
- $params->{"master-keyfile"} = $master_keyfile;
- } elsif ($scfg->{'master-pubkey'}) {
- die "master public key configured but no key file found\n";
- }
- }
- } else {
- my $encryption_fp = $scfg->{'encryption-key'};
- die "encryption configured ('$encryption_fp') but no encryption key file found!\n"
- if $encryption_fp;
- $self->loginfo("WARNING: backup target is configured with master key, but this backup is not encrypted - master key settings will be ignored!")
- if defined($master_keyfile) && -e $master_keyfile;
- $params->{encrypt} = JSON::false;
+ $params->{"master-keyfile"} = $master_keyfile if defined($master_keyfile);
}
- my $is_template = PVE::QemuConfig->is_template($self->{vmlist}->{$vmid});
$params->{'use-dirty-bitmap'} = JSON::true
if $qemu_support->{'pbs-dirty-bitmap'} && !$is_template;
}
$self->restore_vm_power_state($vmid);
+ if ($use_fleecing) {
+ detach_fleecing_images($task->{disks}, $vmid);
+ cleanup_fleecing_images($self, $task->{disks});
+ }
+
die $err if $err;
}
$speed = $opts->{bwlimit}*1024;
}
+ my $is_template = PVE::QemuConfig->is_template($self->{vmlist}->{$vmid});
+
my $diskcount = scalar(@{$task->{disks}});
- if (PVE::QemuConfig->is_template($self->{vmlist}->{$vmid}) || !$diskcount) {
+ if ($is_template || !$diskcount) {
my @pathlist;
foreach my $di (@{$task->{disks}}) {
if ($di->{type} eq 'block' || $di->{type} eq 'file') {
}
my $devlist = _get_task_devlist($task);
+ my $use_fleecing;
$self->enforce_vm_running_for_backup($vmid);
$self->{qmeventd_fh} = PVE::QemuServer::register_qmeventd_handle($vmid);
$attach_tpmstate_drive->($self, $task, $vmid);
+ $use_fleecing = check_and_prepare_fleecing(
+ $self, $vmid, $opts->{fleecing}, $task->{disks}, $is_template, $qemu_support);
+
my $outfh;
if ($opts->{stdout}) {
$outfh = $opts->{stdout};
devlist => $devlist
};
$params->{'firewall-file'} = $firewall if -e $firewall;
+ $params->{fleecing} = JSON::true if $use_fleecing;
add_backup_performance_options($params, $opts->{performance}, $qemu_support);
$qmpclient->queue_cmd($vmid, $backup_cb, 'backup', %$params);
$self->restore_vm_power_state($vmid);
+ if ($use_fleecing) {
+ detach_fleecing_images($task->{disks}, $vmid);
+ cleanup_fleecing_images($self, $task->{disks});
+ }
+
if ($err) {
if ($cpid) {
kill(9, $cpid);