use PVE::Cluster qw(cfs_register_file cfs_read_file cfs_write_file cfs_lock_file);
use PVE::INotify;
use PVE::ProcFSTools;
+use PVE::QemuConfig;
use PVE::QMPClient;
use PVE::RPCEnvironment;
use Time::HiRes qw(gettimeofday);
use File::Copy qw(copy);
+use URI::Escape;
my $qemu_snap_storage = {rbd => 1, sheepdog => 1};
numa => {
optional => 1,
type => 'boolean',
- description => "Enable/disable Numa.",
+ description => "Enable/disable NUMA.",
default => 0,
},
vcpus => {
vga => {
optional => 1,
type => 'string',
- description => "Select VGA type. If you want to use high resolution modes (>= 1280x1024x16) then you should use option 'std' or 'vmware'. Default is 'std' for win8/win7/w2k8, and 'cirrur' for other OS types. Option 'qxl' enables the SPICE display sever. You can also run without any graphic card using a serial devive as terminal.",
+ description => "Select the VGA type. If you want to use high resolution" .
+ " modes (>= 1280x1024x16) then you should use the options " .
+ "'std' or 'vmware'. Default is 'std' for win8/win7/w2k8, and " .
+ "'cirrus' for other OS types. The 'qxl' option enables the SPICE " .
+ "display sever. For win* OS you can select how many independent " .
+ "displays you want, Linux guests can add displays them self. " .
+ "You can also run without any graphic card, using a serial device" .
+ " as terminal.",
enum => [qw(std cirrus vmware qxl serial0 serial1 serial2 serial3 qxl2 qxl3 qxl4)],
},
watchdog => {
optional => 1,
type => 'string', format => 'pve-qm-watchdog',
typetext => '[[model=]i6300esb|ib700] [,[action=]reset|shutdown|poweroff|pause|debug|none]',
- description => "Create a virtual hardware watchdog device. Once enabled (by a guest action), the watchdog must be periodically polled by an agent inside the guest or else the guest will be restarted (or execute the action specified)",
+ description => "Create a virtual hardware watchdog device. Once enabled" .
+ " (by a guest action), the watchdog must be periodically polled " .
+ "by an agent inside the guest or else the watchdog will reset " .
+ "the guest (or execute the respective action specified)",
},
startdate => {
optional => 1,
optional => 1,
type => 'boolean',
default => 1,
- description => "Enable/disable the usb tablet device. This device is usually needed to allow absolute mouse positioning with VNC. Else the mouse runs out of sync with normal VNC clients. If you're running lots of console-only guests on one host, you may consider disabling this to save some context switches. This is turned of by default if you use spice (vga=qxl).",
+ description => "Enable/disable the USB tablet device. This device is " .
+ "usually needed to allow absolute mouse positioning with VNC. " .
+ "Else the mouse runs out of sync with normal VNC clients. " .
+ "If you're running lots of console-only guests on one host, " .
+ "you may consider disabling this to save some context switches. " .
+ "This is turned off by default if you use spice (-vga=qxl).",
},
migrate_speed => {
optional => 1,
},
serial => {
type => 'string',
+ format => 'urlencoded',
format_description => 'serial',
- description => "The drive's reported serial number.",
+ description => "The drive's reported serial number, url-encoded.",
optional => 1,
}
);
my %model_fmt = (
model => {
type => 'string',
+ format => 'urlencoded',
format_description => 'model',
- description => "The drive's reported model name.",
+ description => "The drive's reported model name, url-encoded.",
optional => 1,
},
);
$device = "ide-$devicetype,bus=ide.$controller,unit=$unit,drive=drive-$drive->{interface}$drive->{index},id=$drive->{interface}$drive->{index}";
if ($devicetype eq 'hd' && (my $model = $drive->{model})) {
+ $model = URI::Escape::uri_unescape($model);
$device .= ",model=$model";
}
} elsif ($drive->{interface} eq 'sata'){
return $initiator;
}
-my @qemu_drive_options = qw(heads secs cyls trans media format cache snapshot rerror werror aio discard iops iops_rd iops_wr iops_max iops_rd_max iops_wr_max serial);
sub print_drive_full {
my ($storecfg, $vmid, $drive) = @_;
}
my $opts = '';
+ my @qemu_drive_options = qw(heads secs cyls trans media format cache snapshot rerror werror aio discard iops iops_rd iops_wr iops_max iops_rd_max iops_wr_max);
foreach my $o (@qemu_drive_options) {
- next if $o eq 'bootindex';
$opts .= ",$o=$drive->{$o}" if $drive->{$o};
}
+ if (my $serial = $drive->{serial}) {
+ $serial = URI::Escape::uri_unescape($serial);
+ $opts .= ",serial=$serial";
+ }
$opts .= ",format=$format" if $format && !$drive->{format};
}
}
-sub add_unused_volume {
- my ($config, $volid) = @_;
-
- my $key;
- for (my $ind = $MAX_UNUSED_DISKS - 1; $ind >= 0; $ind--) {
- my $test = "unused$ind";
- if (my $vid = $config->{$test}) {
- return if $vid eq $volid; # do not add duplicates
- } else {
- $key = $test;
- }
- }
-
- die "To many unused volume - please delete them first.\n" if !$key;
-
- $config->{$key} = $volid;
-
- return $key;
-}
-
sub vm_is_volid_owner {
my ($storecfg, $vmid, $volid) = @_;
if (!drive_is_cdrom($drive)) {
my $volid = $drive->{file};
if (vm_is_volid_owner($storecfg, $vmid, $volid)) {
- add_unused_volume($conf, $volid, $vmid);
+ PVE::QemuConfig->add_unused_volume($conf, $volid, $vmid);
}
}
}
}
}
-sub lock_config_full {
- my ($vmid, $timeout, $code, @param) = @_;
-
- my $filename = config_file_lock($vmid);
-
- my $res = lock_file($filename, $timeout, $code, @param);
-
- die $@ if $@;
-
- return $res;
-}
-
-sub lock_config_mode {
- my ($vmid, $timeout, $shared, $code, @param) = @_;
-
- my $filename = config_file_lock($vmid);
-
- my $res = lock_file_full($filename, $timeout, $shared, $code, @param);
-
- die $@ if $@;
-
- return $res;
-}
-
-sub lock_config {
- my ($vmid, $code, @param) = @_;
-
- return lock_config_full($vmid, 10, $code, @param);
-}
-
-sub cfs_config_path {
- my ($vmid, $node) = @_;
-
- $node = $nodename if !$node;
- return "nodes/$node/qemu-server/$vmid.conf";
-}
-
sub check_iommu_support{
#fixme : need to check IOMMU support
#http://www.linux-kvm.org/page/How_to_assign_devices_with_VT-d_in_KVM
}
-sub config_file {
- my ($vmid, $node) = @_;
-
- my $cfspath = cfs_config_path($vmid, $node);
- return "/etc/pve/$cfspath";
-}
-
-sub config_file_lock {
- my ($vmid) = @_;
-
- return "$lock_dir/lock-$vmid.conf";
-}
-
sub touch_config {
my ($vmid) = @_;
- my $conf = config_file($vmid);
+ my $conf = PVE::QemuConfig->config_file($vmid);
utime undef, undef, $conf;
}
sub destroy_vm {
my ($storecfg, $vmid, $keep_empty_config, $skiplock) = @_;
- my $conffile = config_file($vmid);
+ my $conffile = PVE::QemuConfig->config_file($vmid);
- my $conf = load_config($vmid);
+ my $conf = PVE::QemuConfig->load_config($vmid);
- check_lock($conf) if !$skiplock;
+ PVE::QemuConfig->check_lock($conf) if !$skiplock;
# only remove disks owned by this VM
foreach_drive($conf, sub {
warn $@ if $@;
}
-sub load_config {
- my ($vmid, $node) = @_;
-
- my $cfspath = cfs_config_path($vmid, $node);
-
- my $conf = PVE::Cluster::cfs_read_file($cfspath);
-
- die "no such VM ('$vmid')\n" if !defined($conf);
-
- return $conf;
-}
-
sub parse_vm_config {
my ($filename, $raw) = @_;
return $raw;
}
-sub write_config {
- my ($vmid, $conf) = @_;
-
- my $cfspath = cfs_config_path($vmid);
-
- PVE::Cluster::cfs_write_file($cfspath, $conf);
-}
-
sub load_defaults {
my $res = {};
return $nodehash
}
-sub check_lock {
- my ($conf) = @_;
-
- die "VM is locked ($conf->{lock})\n" if $conf->{lock};
-}
-
sub check_cmdline {
my ($pidfile, $pid) = @_;
sub check_running {
my ($vmid, $nocheck, $node) = @_;
- my $filename = config_file($vmid, $node);
+ my $filename = PVE::QemuConfig->config_file($vmid, $node);
die "unable to find configuration file for VM $vmid - no such machine\n"
if !$nocheck && ! -f $filename;
foreach my $vmid (keys %$list) {
next if $opt_vmid && ($vmid ne $opt_vmid);
- my $cfspath = cfs_config_path($vmid);
+ my $cfspath = PVE::QemuConfig->cfs_config_path($vmid);
my $conf = PVE::Cluster::cfs_read_file($cfspath) || {};
my $d = {};
$d->{diskread} = 0;
$d->{diskwrite} = 0;
- $d->{template} = is_template($conf);
+ $d->{template} = PVE::QemuConfig->is_template($conf);
$res->{$vmid} = $d;
}
#if dimm_memory is not aligned to dimm map
if($current_size > $memory) {
$conf->{memory} = $current_size;
- write_config($vmid, $conf);
+ PVE::QemuConfig->write_config($vmid, $conf);
}
});
}
}
#update conf after each succesful module hotplug
$conf->{memory} = $current_size;
- write_config($vmid, $conf);
+ PVE::QemuConfig->write_config($vmid, $conf);
});
} else {
$conf->{memory} = $current_size;
eval { qemu_objectdel($vmid, "mem-$name"); };
- write_config($vmid, $conf);
+ PVE::QemuConfig->write_config($vmid, $conf);
});
}
}
}
if ($changes) {
- write_config($vmid, $conf);
- $conf = load_config($vmid); # update/reload
+ PVE::QemuConfig->write_config($vmid, $conf);
+ $conf = PVE::QemuConfig->load_config($vmid); # update/reload
}
my $hotplug_features = parse_hotplug_features(defined($conf->{hotplug}) ? $conf->{hotplug} : '1');
# save new config if hotplug was successful
delete $conf->{$opt};
vmconfig_undelete_pending_option($conf, $opt);
- write_config($vmid, $conf);
- $conf = load_config($vmid); # update/reload
+ PVE::QemuConfig->write_config($vmid, $conf);
+ $conf = PVE::QemuConfig->load_config($vmid); # update/reload
}
}
# save new config if hotplug was successful
$conf->{$opt} = $value;
delete $conf->{pending}->{$opt};
- write_config($vmid, $conf);
- $conf = load_config($vmid); # update/reload
+ PVE::QemuConfig->write_config($vmid, $conf);
+ $conf = PVE::QemuConfig->load_config($vmid); # update/reload
}
}
}
my $pending_delete_hash = split_flagged_list($conf->{pending}->{delete});
while (my ($opt, $force) = each %$pending_delete_hash) {
die "internal error" if $opt =~ m/^unused/;
- $conf = load_config($vmid); # update/reload
+ $conf = PVE::QemuConfig->load_config($vmid); # update/reload
if (!defined($conf->{$opt})) {
vmconfig_undelete_pending_option($conf, $opt);
- write_config($vmid, $conf);
+ PVE::QemuConfig->write_config($vmid, $conf);
} elsif (is_valid_drivename($opt)) {
vmconfig_delete_or_detach_drive($vmid, $storecfg, $conf, $opt, $force);
vmconfig_undelete_pending_option($conf, $opt);
delete $conf->{$opt};
- write_config($vmid, $conf);
+ PVE::QemuConfig->write_config($vmid, $conf);
} else {
vmconfig_undelete_pending_option($conf, $opt);
delete $conf->{$opt};
- write_config($vmid, $conf);
+ PVE::QemuConfig->write_config($vmid, $conf);
}
}
- $conf = load_config($vmid); # update/reload
+ $conf = PVE::QemuConfig->load_config($vmid); # update/reload
foreach my $opt (keys %{$conf->{pending}}) { # add/change
- $conf = load_config($vmid); # update/reload
+ $conf = PVE::QemuConfig->load_config($vmid); # update/reload
if (defined($conf->{$opt}) && ($conf->{$opt} eq $conf->{pending}->{$opt})) {
# skip if nothing changed
}
delete $conf->{pending}->{$opt};
- write_config($vmid, $conf);
+ PVE::QemuConfig->write_config($vmid, $conf);
}
}
die "internal error" if $opt !~ m/net(\d+)/;
my $iface = "tap${vmid}i$1";
- if (&$safe_num_ne($oldnet->{rate}, $newnet->{rate})) {
- PVE::Network::tap_rate_limit($iface, $newnet->{rate});
- }
-
if (&$safe_string_ne($oldnet->{bridge}, $newnet->{bridge}) ||
&$safe_num_ne($oldnet->{tag}, $newnet->{tag}) ||
&$safe_string_ne($oldnet->{trunks}, $newnet->{trunks}) ||
&$safe_num_ne($oldnet->{firewall}, $newnet->{firewall})) {
PVE::Network::tap_unplug($iface);
- PVE::Network::tap_plug($iface, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall}, $newnet->{trunks});
+ PVE::Network::tap_plug($iface, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall}, $newnet->{trunks}, $newnet->{rate});
+ } elsif (&$safe_num_ne($oldnet->{rate}, $newnet->{rate})) {
+ # Rate can be applied on its own but any change above needs to
+ # include the rate in tap_plug since OVS resets everything.
+ PVE::Network::tap_rate_limit($iface, $newnet->{rate});
}
if (&$safe_string_ne($oldnet->{link_down}, $newnet->{link_down})) {
my ($storecfg, $vmid, $statefile, $skiplock, $migratedfrom, $paused,
$forcemachine, $spice_ticket) = @_;
- lock_config($vmid, sub {
- my $conf = load_config($vmid, $migratedfrom);
+ PVE::QemuConfig->lock_config($vmid, sub {
+ my $conf = PVE::QemuConfig->load_config($vmid, $migratedfrom);
- die "you can't start a vm if it's a template\n" if is_template($conf);
+ die "you can't start a vm if it's a template\n" if PVE::QemuConfig->is_template($conf);
- check_lock($conf) if !$skiplock;
+ PVE::QemuConfig->check_lock($conf) if !$skiplock;
die "VM $vmid already running\n" if check_running($vmid, undef, $migratedfrom);
if (!$statefile && scalar(keys %{$conf->{pending}})) {
vmconfig_apply_pending($vmid, $conf, $storecfg);
- $conf = load_config($vmid); # update/reload
+ $conf = PVE::QemuConfig->load_config($vmid); # update/reload
}
my $defaults = load_defaults();
sub vm_commandline {
my ($storecfg, $vmid) = @_;
- my $conf = load_config($vmid);
+ my $conf = PVE::QemuConfig->load_config($vmid);
my $defaults = load_defaults();
sub vm_reset {
my ($vmid, $skiplock) = @_;
- lock_config($vmid, sub {
+ PVE::QemuConfig->lock_config($vmid, sub {
- my $conf = load_config($vmid);
+ my $conf = PVE::QemuConfig->load_config($vmid);
- check_lock($conf) if !$skiplock;
+ PVE::QemuConfig->check_lock($conf) if !$skiplock;
vm_mon_cmd($vmid, "system_reset");
});
if ($migratedfrom){
my $pid = check_running($vmid, $nocheck, $migratedfrom);
kill 15, $pid if $pid;
- my $conf = load_config($vmid, $migratedfrom);
+ my $conf = PVE::QemuConfig->load_config($vmid, $migratedfrom);
vm_stop_cleanup($storecfg, $vmid, $conf, $keepActive, 0);
return;
}
- lock_config($vmid, sub {
+ PVE::QemuConfig->lock_config($vmid, sub {
my $pid = check_running($vmid, $nocheck);
return if !$pid;
my $conf;
if (!$nocheck) {
- $conf = load_config($vmid);
- check_lock($conf) if !$skiplock;
+ $conf = PVE::QemuConfig->load_config($vmid);
+ PVE::QemuConfig->check_lock($conf) if !$skiplock;
if (!defined($timeout) && $shutdown && $conf->{startup}) {
my $opts = PVE::JSONSchema::pve_parse_startup_order($conf->{startup});
$timeout = $opts->{down} if $opts->{down};
sub vm_suspend {
my ($vmid, $skiplock) = @_;
- lock_config($vmid, sub {
+ PVE::QemuConfig->lock_config($vmid, sub {
- my $conf = load_config($vmid);
+ my $conf = PVE::QemuConfig->load_config($vmid);
- check_lock($conf) if !($skiplock || ($conf->{lock} && $conf->{lock} eq 'backup'));
+ PVE::QemuConfig->check_lock($conf)
+ if !($skiplock || PVE::QemuConfig->has_lock($conf, 'backup'));
vm_mon_cmd($vmid, "stop");
});
sub vm_resume {
my ($vmid, $skiplock, $nocheck) = @_;
- lock_config($vmid, sub {
+ PVE::QemuConfig->lock_config($vmid, sub {
if (!$nocheck) {
- my $conf = load_config($vmid);
+ my $conf = PVE::QemuConfig->load_config($vmid);
- check_lock($conf) if !($skiplock || ($conf->{lock} && $conf->{lock} eq 'backup'));
+ PVE::QemuConfig->check_lock($conf)
+ if !($skiplock || PVE::QemuConfig->has_lock($conf, 'backup'));
vm_mon_cmd($vmid, "cont");
sub vm_sendkey {
my ($vmid, $skiplock, $key) = @_;
- lock_config($vmid, sub {
+ PVE::QemuConfig->lock_config($vmid, sub {
- my $conf = load_config($vmid);
+ my $conf = PVE::QemuConfig->load_config($vmid);
# there is no qmp command, so we use the human monitor command
vm_human_monitor_command($vmid, "sendkey $key");
sub vm_destroy {
my ($storecfg, $vmid, $skiplock) = @_;
- lock_config($vmid, sub {
+ PVE::QemuConfig->lock_config($vmid, sub {
- my $conf = load_config($vmid);
+ my $conf = PVE::QemuConfig->load_config($vmid);
if (!check_running($vmid)) {
destroy_vm($storecfg, $vmid, undef, $skiplock);
next if !$path; # just to be sure
next if $usedpath->{$path};
$changes = 1;
- add_unused_volume($conf, $volid);
+ PVE::QemuConfig->add_unused_volume($conf, $volid);
$usedpath->{$path} = 1; # avoid to add more than once (aliases)
}
my $updatefn = sub {
my ($vmid) = @_;
- my $conf = load_config($vmid);
+ my $conf = PVE::QemuConfig->load_config($vmid);
- check_lock($conf);
+ PVE::QemuConfig->check_lock($conf);
my $vm_volids = {};
foreach my $volid (keys %$volid_hash) {
my $changes = update_disksize($vmid, $conf, $vm_volids);
- write_config($vmid, $conf) if $changes;
+ PVE::QemuConfig->write_config($vmid, $conf) if $changes;
};
if (defined($vmid)) {
if ($nolock) {
&$updatefn($vmid);
} else {
- lock_config($vmid, $updatefn, $vmid);
+ PVE::QemuConfig->lock_config($vmid, $updatefn, $vmid);
}
} else {
my $vmlist = config_list();
if ($nolock) {
&$updatefn($vmid);
} else {
- lock_config($vmid, $updatefn, $vmid);
+ PVE::QemuConfig->lock_config($vmid, $updatefn, $vmid);
}
}
}
my $rpcenv = PVE::RPCEnvironment::get();
- my $conffile = config_file($vmid);
+ my $conffile = PVE::QemuConfig->config_file($vmid);
my $tmpfn = "$conffile.$$.tmp";
# Note: $oldconf is undef if VM does not exists
- my $oldconf = PVE::Cluster::cfs_read_file(cfs_config_path($vmid));
+ my $cfs_path = PVE::QemuConfig->cfs_config_path($vmid);
+ my $oldconf = PVE::Cluster::cfs_read_file($cfs_path);
my $print_devmap = sub {
my $virtdev_hash = {};
my $storecfg = cfs_read_file('storage.cfg');
# destroy existing data - keep empty config
- my $vmcfgfn = config_file($vmid);
+ my $vmcfgfn = PVE::QemuConfig->config_file($vmid);
destroy_vm($storecfg, $vmid, 1) if -f $vmcfgfn;
my $tocmd = "/usr/lib/qemu-server/qmextract";
local $ENV{VZDUMP_VMID} = $vmid;
local $ENV{VZDUMP_USER} = $user;
- my $conffile = config_file($vmid);
+ my $conffile = PVE::QemuConfig->config_file($vmid);
my $tmpfn = "$conffile.$$.tmp";
# disable interrupts (always do cleanups)
warn $@ if $@;
};
-
-# Internal snapshots
-
-# NOTE: Snapshot create/delete involves several non-atomic
-# action, and can take a long time.
-# So we try to avoid locking the file and use 'lock' variable
-# inside the config file instead.
-
-my $snapshot_copy_config = sub {
- my ($source, $dest) = @_;
-
- foreach my $k (keys %$source) {
- next if $k eq 'snapshots';
- next if $k eq 'snapstate';
- next if $k eq 'snaptime';
- next if $k eq 'vmstate';
- next if $k eq 'lock';
- next if $k eq 'digest';
- next if $k eq 'description';
- next if $k =~ m/^unused\d+$/;
-
- $dest->{$k} = $source->{$k};
- }
-};
-
-my $snapshot_apply_config = sub {
- my ($conf, $snap) = @_;
-
- # copy snapshot list
- my $newconf = {
- snapshots => $conf->{snapshots},
- };
-
- # keep description and list of unused disks
- foreach my $k (keys %$conf) {
- next if !($k =~ m/^unused\d+$/ || $k eq 'description');
- $newconf->{$k} = $conf->{$k};
- }
-
- &$snapshot_copy_config($snap, $newconf);
-
- return $newconf;
-};
-
sub foreach_writable_storage {
my ($conf, $func) = @_;
}
}
-my $alloc_vmstate_volid = sub {
- my ($storecfg, $vmid, $conf, $snapname) = @_;
-
- # Note: we try to be smart when selecting a $target storage
-
- my $target;
-
- # search shared storage first
- foreach_writable_storage($conf, sub {
- my ($sid) = @_;
- my $scfg = PVE::Storage::storage_config($storecfg, $sid);
- return if !$scfg->{shared};
-
- $target = $sid if !$target || $scfg->{path}; # prefer file based storage
- });
-
- if (!$target) {
- # now search local storage
- foreach_writable_storage($conf, sub {
- my ($sid) = @_;
- my $scfg = PVE::Storage::storage_config($storecfg, $sid);
- return if $scfg->{shared};
-
- $target = $sid if !$target || $scfg->{path}; # prefer file based storage;
- });
- }
-
- $target = 'local' if !$target;
-
- my $driver_state_size = 500; # assume 32MB is enough to safe all driver state;
- # we abort live save after $conf->{memory}, so we need at max twice that space
- my $size = $conf->{memory}*2 + $driver_state_size;
-
- my $name = "vm-$vmid-state-$snapname";
- my $scfg = PVE::Storage::storage_config($storecfg, $target);
- $name .= ".raw" if $scfg->{path}; # add filename extension for file base storage
- my $volid = PVE::Storage::vdisk_alloc($storecfg, $target, $vmid, 'raw', $name, $size*1024);
-
- return $volid;
-};
-
-sub snapshot_save_vmstate {
- my ($vmid, $conf, $snapname, $storecfg) = @_;
-
- my $snap = $conf->{snapshots}->{$snapname};
-
- $snap->{vmstate} = &$alloc_vmstate_volid($storecfg, $vmid, $conf, $snapname);
- # always overwrite machine if we save vmstate. This makes sure we
- # can restore it later using correct machine type
- $snap->{machine} = get_current_qemu_machine($vmid);
-}
-
-sub snapshot_prepare {
- my ($vmid, $snapname, $save_vmstate, $comment) = @_;
-
- my $snap;
-
- my $updatefn = sub {
-
- my $conf = load_config($vmid);
-
- die "you can't take a snapshot if it's a template\n"
- if is_template($conf);
-
- check_lock($conf);
-
- $conf->{lock} = 'snapshot';
-
- die "snapshot name '$snapname' already used\n"
- if defined($conf->{snapshots}->{$snapname});
-
- my $storecfg = PVE::Storage::config();
- die "snapshot feature is not available\n"
- if !has_feature('snapshot', $conf, $storecfg, undef, undef, $snapname eq 'vzdump');
-
- $snap = $conf->{snapshots}->{$snapname} = {};
-
- if ($save_vmstate && check_running($vmid)) {
- snapshot_save_vmstate($vmid, $conf, $snapname, $storecfg);
- }
-
- &$snapshot_copy_config($conf, $snap);
-
- $snap->{snapstate} = "prepare";
- $snap->{snaptime} = time();
- $snap->{description} = $comment if $comment;
-
- write_config($vmid, $conf);
- };
-
- lock_config($vmid, $updatefn);
-
- return $snap;
-}
-
-sub snapshot_commit {
- my ($vmid, $snapname) = @_;
-
- my $updatefn = sub {
-
- my $conf = load_config($vmid);
-
- die "missing snapshot lock\n"
- if !($conf->{lock} && $conf->{lock} eq 'snapshot');
-
- my $snap = $conf->{snapshots}->{$snapname};
-
- die "snapshot '$snapname' does not exist\n" if !defined($snap);
-
- die "wrong snapshot state\n"
- if !($snap->{snapstate} && $snap->{snapstate} eq "prepare");
-
- delete $snap->{snapstate};
- delete $conf->{lock};
-
- $conf->{parent} = $snapname;
-
- write_config($vmid, $conf);
- };
-
- lock_config($vmid, $updatefn);
-}
-
-sub snapshot_rollback {
- my ($vmid, $snapname) = @_;
-
- my $prepare = 1;
-
- my $storecfg = PVE::Storage::config();
-
- my $conf = load_config($vmid);
-
- my $get_snapshot_config = sub {
-
- die "you can't rollback if vm is a template\n" if is_template($conf);
-
- my $res = $conf->{snapshots}->{$snapname};
-
- die "snapshot '$snapname' does not exist\n" if !defined($res);
-
- return $res;
- };
-
- my $snap = &$get_snapshot_config();
-
- foreach_drive($snap, sub {
- my ($ds, $drive) = @_;
-
- return if drive_is_cdrom($drive);
-
- my $volid = $drive->{file};
-
- PVE::Storage::volume_rollback_is_possible($storecfg, $volid, $snapname);
- });
-
- my $updatefn = sub {
-
- $conf = load_config($vmid);
-
- $snap = &$get_snapshot_config();
-
- die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
- if $snap->{snapstate};
-
- if ($prepare) {
- check_lock($conf);
- vm_stop($storecfg, $vmid, undef, undef, 5, undef, undef);
- }
-
- die "unable to rollback vm $vmid: vm is running\n"
- if check_running($vmid);
-
- if ($prepare) {
- $conf->{lock} = 'rollback';
- } else {
- die "got wrong lock\n" if !($conf->{lock} && $conf->{lock} eq 'rollback');
- delete $conf->{lock};
- }
-
- my $forcemachine;
-
- if (!$prepare) {
- my $has_machine_config = defined($conf->{machine});
-
- # copy snapshot config to current config
- $conf = &$snapshot_apply_config($conf, $snap);
- $conf->{parent} = $snapname;
-
- # Note: old code did not store 'machine', so we try to be smart
- # and guess the snapshot was generated with kvm 1.4 (pc-i440fx-1.4).
- $forcemachine = $conf->{machine} || 'pc-i440fx-1.4';
- # we remove the 'machine' configuration if not explicitly specified
- # in the original config.
- delete $conf->{machine} if $snap->{vmstate} && !$has_machine_config;
- }
-
- write_config($vmid, $conf);
-
- if (!$prepare && $snap->{vmstate}) {
- my $statefile = PVE::Storage::path($storecfg, $snap->{vmstate});
- vm_start($storecfg, $vmid, $statefile, undef, undef, undef, $forcemachine);
- }
- };
-
- lock_config($vmid, $updatefn);
-
- foreach_drive($snap, sub {
- my ($ds, $drive) = @_;
-
- return if drive_is_cdrom($drive);
-
- my $volid = $drive->{file};
- my $device = "drive-$ds";
-
- PVE::Storage::volume_snapshot_rollback($storecfg, $volid, $snapname);
- });
-
- $prepare = 0;
- lock_config($vmid, $updatefn);
-}
-
-my $savevm_wait = sub {
- my ($vmid) = @_;
-
- for(;;) {
- my $stat = vm_mon_cmd_nocheck($vmid, "query-savevm");
- if (!$stat->{status}) {
- die "savevm not active\n";
- } elsif ($stat->{status} eq 'active') {
- sleep(1);
- next;
- } elsif ($stat->{status} eq 'completed') {
- last;
- } else {
- die "query-savevm returned status '$stat->{status}'\n";
- }
- }
-};
-
sub do_snapshots_with_qemu {
my ($storecfg, $volid) = @_;
return 1;
}
-sub check_freeze_needed {
- my ($vmid, $config, $save_vmstate) = @_;
-
- my $running = check_running($vmid);
- if ($save_vmstate) {
- return ($running, $running && $config->{agent} && qga_check_running($vmid));
- } else {
- return ($running, 0);
- }
-}
-
-sub snapshot_create {
- my ($vmid, $snapname, $save_vmstate, $comment) = @_;
-
- my $snap = snapshot_prepare($vmid, $snapname, $save_vmstate, $comment);
-
- $save_vmstate = 0 if !$snap->{vmstate}; # vm is not running
-
- my $config = load_config($vmid);
-
- my ($running, $freezefs) = check_freeze_needed($vmid, $config, $snap->{vmstate});
-
- my $drivehash = {};
-
- eval {
- if ($freezefs) {
- eval { vm_mon_cmd($vmid, "guest-fsfreeze-freeze"); };
- warn "guest-fsfreeze-freeze problems - $@" if $@;
- }
-
- # create internal snapshots of all drives
-
- my $storecfg = PVE::Storage::config();
-
- if ($running) {
- if ($snap->{vmstate}) {
- my $path = PVE::Storage::path($storecfg, $snap->{vmstate});
- vm_mon_cmd($vmid, "savevm-start", statefile => $path);
- &$savevm_wait($vmid);
- } else {
- vm_mon_cmd($vmid, "savevm-start");
- }
- };
-
- foreach_drive($snap, sub {
- my ($ds, $drive) = @_;
-
- return if drive_is_cdrom($drive);
-
- my $volid = $drive->{file};
- my $device = "drive-$ds";
-
- qemu_volume_snapshot($vmid, $device, $storecfg, $volid, $snapname);
- $drivehash->{$ds} = 1;
- });
- };
- my $err = $@;
-
- if ($running) {
- eval { vm_mon_cmd($vmid, "savevm-end") };
- warn $@ if $@;
-
- if ($freezefs) {
- eval { vm_mon_cmd($vmid, "guest-fsfreeze-thaw"); };
- warn "guest-fsfreeze-thaw problems - $@" if $@;
- }
-
- # savevm-end is async, we need to wait
- for (;;) {
- my $stat = vm_mon_cmd_nocheck($vmid, "query-savevm");
- if (!$stat->{bytes}) {
- last;
- } else {
- print "savevm not yet finished\n";
- sleep(1);
- next;
- }
- }
- }
-
- if ($err) {
- warn "snapshot create failed: starting cleanup\n";
- eval { snapshot_delete($vmid, $snapname, 1, $drivehash); };
- warn $@ if $@;
- die $err;
- }
-
- snapshot_commit($vmid, $snapname);
-}
-
-# Note: $drivehash is only set when called from snapshot_create.
-sub snapshot_delete {
- my ($vmid, $snapname, $force, $drivehash) = @_;
-
- my $prepare = 1;
-
- my $snap;
- my $unused = [];
-
- my $unlink_parent = sub {
- my ($confref, $new_parent) = @_;
-
- if ($confref->{parent} && $confref->{parent} eq $snapname) {
- if ($new_parent) {
- $confref->{parent} = $new_parent;
- } else {
- delete $confref->{parent};
- }
- }
- };
-
- my $updatefn = sub {
- my ($remove_drive) = @_;
-
- my $conf = load_config($vmid);
-
- if (!$drivehash) {
- check_lock($conf);
- die "you can't delete a snapshot if vm is a template\n"
- if is_template($conf);
- }
-
- $snap = $conf->{snapshots}->{$snapname};
-
- die "snapshot '$snapname' does not exist\n" if !defined($snap);
-
- # remove parent refs
- if (!$prepare) {
- &$unlink_parent($conf, $snap->{parent});
- foreach my $sn (keys %{$conf->{snapshots}}) {
- next if $sn eq $snapname;
- &$unlink_parent($conf->{snapshots}->{$sn}, $snap->{parent});
- }
- }
-
- if ($remove_drive) {
- if ($remove_drive eq 'vmstate') {
- delete $snap->{$remove_drive};
- } else {
- my $drive = parse_drive($remove_drive, $snap->{$remove_drive});
- my $volid = $drive->{file};
- delete $snap->{$remove_drive};
- add_unused_volume($conf, $volid);
- }
- }
-
- if ($prepare) {
- $snap->{snapstate} = 'delete';
- } else {
- delete $conf->{snapshots}->{$snapname};
- delete $conf->{lock} if $drivehash;
- foreach my $volid (@$unused) {
- add_unused_volume($conf, $volid);
- }
- }
-
- write_config($vmid, $conf);
- };
-
- lock_config($vmid, $updatefn);
-
- # now remove vmstate file
-
- my $storecfg = PVE::Storage::config();
-
- if ($snap->{vmstate}) {
- eval { PVE::Storage::vdisk_free($storecfg, $snap->{vmstate}); };
- if (my $err = $@) {
- die $err if !$force;
- warn $err;
- }
- # save changes (remove vmstate from snapshot)
- lock_config($vmid, $updatefn, 'vmstate') if !$force;
- };
-
- # now remove all internal snapshots
- foreach_drive($snap, sub {
- my ($ds, $drive) = @_;
-
- return if drive_is_cdrom($drive);
-
- my $volid = $drive->{file};
- my $device = "drive-$ds";
-
- if (!$drivehash || $drivehash->{$ds}) {
- eval { qemu_volume_snapshot_delete($vmid, $device, $storecfg, $volid, $snapname); };
- if (my $err = $@) {
- die $err if !$force;
- warn $err;
- }
- }
-
- # save changes (remove drive fron snapshot)
- lock_config($vmid, $updatefn, $ds) if !$force;
- push @$unused, $volid;
- });
-
- # now cleanup config
- $prepare = 0;
- lock_config($vmid, $updatefn);
-}
-
-sub has_feature {
- my ($feature, $conf, $storecfg, $snapname, $running, $backup_only) = @_;
-
- my $err;
- foreach_drive($conf, sub {
- my ($ds, $drive) = @_;
-
- return if drive_is_cdrom($drive);
- return if $backup_only && !$drive->{backup};
- my $volid = $drive->{file};
- $err = 1 if !PVE::Storage::volume_has_feature($storecfg, $feature, $volid, $snapname, $running);
- });
-
- return $err ? 0 : 1;
-}
-
sub template_create {
my ($vmid, $conf, $disk) = @_;
my $voliddst = PVE::Storage::vdisk_create_base($storecfg, $volid);
$drive->{file} = $voliddst;
$conf->{$ds} = print_drive($vmid, $drive);
- write_config($vmid, $conf);
+ PVE::QemuConfig->write_config($vmid, $conf);
});
}
-sub is_template {
- my ($conf) = @_;
-
- return 1 if defined $conf->{template} && $conf->{template} == 1;
-}
-
sub qemu_img_convert {
my ($src_volid, $dst_volid, $size, $snapname) = @_;