if (!$volid || $volid eq 'none') {
# nothing to check
+ } elsif ($isCDROM && ($volid eq 'cdrom')) {
+ $rpcenv->check($authuser, "/", ['Sys.Console']);
} elsif (!$isCDROM && ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/)) {
my ($storeid, $size) = ($2 || $default_storage, $3);
die "no storage ID specified (and no default storage)\n" if !$storeid;
$rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
} else {
- my $path = $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
- die "image '$path' does not exists\n" if (!(-f $path || -b $path));
+ $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
}
});
};
my $volid = $disk->{file};
- if (!$volid || $volid eq 'none') {
+ if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
$res->{$ds} = $settings->{$ds};
} elsif ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/) {
my ($storeid, $size) = ($2 || $default_storage, $3);
my $volid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $vmid,
$fmt, undef, $size*1024*1024);
$disk->{file} = $volid;
+ $disk->{size} = $size*1024*1024*1024;
push @$vollist, $volid;
delete $disk->{format}; # no longer needed
$res->{$ds} = PVE::QemuServer::print_drive($vmid, $disk);
} else {
+
my $path = $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
- die "image '$path' does not exists\n" if (!(-f $path || -b $path));
- $res->{$ds} = $settings->{$ds};
+
+ my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
+
+ my $foundvolid = undef;
+
+ if ($storeid) {
+ PVE::Storage::activate_volumes($storecfg, [ $volid ]);
+ my $dl = PVE::Storage::vdisk_list($storecfg, $storeid, undef);
+
+ PVE::Storage::foreach_volid($dl, sub {
+ my ($volumeid) = @_;
+ if($volumeid eq $volid) {
+ $foundvolid = 1;
+ return;
+ }
+ });
+ }
+
+ die "image '$path' does not exists\n" if (!(-f $path || -b $path || $foundvolid));
+
+ my ($size) = PVE::Storage::volume_size_info($storecfg, $volid, 1);
+ $disk->{size} = $size;
+ $res->{$ds} = PVE::QemuServer::print_drive($vmid, $disk);
}
});
my $check_vm_modify_config_perm = sub {
my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
- return 1 if $authuser ne 'root@pam';
+ return 1 if $authuser eq 'root@pam';
foreach my $opt (@$key_list) {
# disk checks need to be done somewhere else
if (defined($pool)) {
$rpcenv->check_pool_exist($pool);
- $rpcenv->check_perm_modify($authuser, "/pool/$pool");
}
$rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
if ($archive eq '-') {
die "pipe requires cli environment\n"
- && $rpcenv->{type} ne 'cli';
+ if $rpcenv->{type} ne 'cli';
} else {
my $path = $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $archive);
+
+ PVE::Storage::activate_volumes($storecfg, [ $archive ])
+ if PVE::Storage::parse_volume_id ($archive, 1);
+
die "can't find archive file '$archive'\n" if !($path && -f $path);
$archive = $path;
}
my $createfn = sub {
- # second test (after locking test is accurate)
+ # test after locking
die "unable to create vm $vmid: config file already exists\n"
if -f $filename;
return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
};
- return PVE::QemuServer::lock_config($vmid, $archive ? $restorefn : $createfn);
+ return PVE::QemuServer::lock_config_full($vmid, 1, $archive ? $restorefn : $createfn);
}});
__PACKAGE__->register_method({
{ subdir => 'unlink' },
{ subdir => 'vncproxy' },
{ subdir => 'migrate' },
+ { subdir => 'resize' },
{ subdir => 'rrd' },
{ subdir => 'rrddata' },
{ subdir => 'monitor' },
+ { subdir => 'snapshot' },
+ { subdir => 'rollback' },
];
return $res;
my $conf = PVE::QemuServer::load_config($param->{vmid});
+ delete $conf->{snapshots};
+
return $conf;
}});
if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
if ($force || $key =~ m/^unused/) {
eval { PVE::Storage::vdisk_free($storecfg, $volid); };
- warn $@ if $@;
+ die $@ if $@;
} else {
PVE::QemuServer::add_unused_volume($conf, $volid, $vmid);
}
- delete $conf->{$key};
}
}
+
+ delete $conf->{$key};
};
my $vmconfig_delete_option = sub {
PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
};
+my $safe_num_ne = sub {
+ my ($a, $b) = @_;
+
+ return 0 if !defined($a) && !defined($b);
+ return 1 if !defined($a);
+ return 1 if !defined($b);
+
+ return $a != $b;
+};
+
my $vmconfig_update_disk = sub {
my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value, $force) = @_;
&$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
$conf = PVE::QemuServer::load_config($vmid); # update/reload
}
+
+ if(&$safe_num_ne($drive->{mbps}, $old_drive->{mbps}) ||
+ &$safe_num_ne($drive->{mbps_rd}, $old_drive->{mbps_rd}) ||
+ &$safe_num_ne($drive->{mbps_wr}, $old_drive->{mbps_wr}) ||
+ &$safe_num_ne($drive->{iops}, $old_drive->{iops}) ||
+ &$safe_num_ne($drive->{iops_rd}, $old_drive->{iops_rd}) ||
+ &$safe_num_ne($drive->{iops_wr}, $old_drive->{iops_wr})) {
+ PVE::QemuServer::qemu_block_set_io_throttle($vmid,"drive-$opt", $drive->{mbps}*1024*1024,
+ $drive->{mbps_rd}*1024*1024, $drive->{mbps_wr}*1024*1024,
+ $drive->{iops}, $drive->{iops_rd}, $drive->{iops_wr})
+ if !PVE::QemuServer::drive_is_cdrom($drive);
+ }
}
}
if (PVE::QemuServer::check_running($vmid)) {
if ($drive->{file} eq 'none') {
- PVE::QemuServer::vm_monitor_command($vmid, "eject -f drive-$opt", 0);
+ PVE::QemuServer::vm_mon_cmd($vmid, "eject",force => JSON::true,device => "drive-$opt");
} else {
my $path = PVE::QemuServer::get_iso_path($storecfg, $vmid, $drive->{file});
- PVE::QemuServer::vm_monitor_command($vmid, "eject -f drive-$opt", 0); #force eject if locked
- PVE::QemuServer::vm_monitor_command($vmid, "change drive-$opt \"$path\"", 0) if $path;
+ PVE::QemuServer::vm_mon_cmd($vmid, "eject",force => JSON::true,device => "drive-$opt"); #force eject if locked
+ PVE::QemuServer::vm_mon_cmd($vmid, "change",device => "drive-$opt",target => "$path") if $path;
}
}
my $delVMfromPoolFn = sub {
my $usercfg = cfs_read_file("user.cfg");
- my $pool = $usercfg->{vms}->{$vmid};
- if (my $data = $usercfg->{pools}->{$pool}) {
- delete $data->{vms}->{$vmid};
- delete $usercfg->{vms}->{$vmid};
- cfs_write_file("user.cfg", $usercfg);
+ if (my $pool = $usercfg->{vms}->{$vmid}) {
+ if (my $data = $usercfg->{pools}->{$pool}) {
+ delete $data->{vms}->{$vmid};
+ delete $usercfg->{vms}->{$vmid};
+ cfs_write_file("user.cfg", $usercfg);
+ }
}
};
return $res;
}});
+my $vm_is_ha_managed = sub {
+ my ($vmid) = @_;
+
+ my $cc = PVE::Cluster::cfs_read_file('cluster.conf');
+ if (PVE::Cluster::cluster_conf_lookup_pvevm($cc, 0, $vmid, 1)) {
+ return 1;
+ }
+ return 0;
+};
+
__PACKAGE__->register_method({
name => 'vm_status',
path => '{vmid}/status/current',
# test if VM exists
my $conf = PVE::QemuServer::load_config($param->{vmid});
- my $vmstatus = PVE::QemuServer::vmstatus($param->{vmid});
+ my $vmstatus = PVE::QemuServer::vmstatus($param->{vmid}, 1);
my $status = $vmstatus->{$param->{vmid}};
- my $cc = PVE::Cluster::cfs_read_file('cluster.conf');
- if (PVE::Cluster::cluster_conf_lookup_pvevm($cc, 0, $param->{vmid}, 1)) {
- $status->{ha} = 1;
- } else {
- $status->{ha} = 0;
- }
+ $status->{ha} = &$vm_is_ha_managed($param->{vmid});
return $status;
}});
vmid => get_standard_option('pve-vmid'),
skiplock => get_standard_option('skiplock'),
stateuri => get_standard_option('pve-qm-stateuri'),
+ migratedfrom => get_standard_option('pve-node',{ optional => 1 }),
+
},
},
returns => {
raise_param_exc({ skiplock => "Only root may use this option." })
if $skiplock && $authuser ne 'root@pam';
+ my $migratedfrom = extract_param($param, 'migratedfrom');
+ raise_param_exc({ migratedfrom => "Only root may use this option." })
+ if $migratedfrom && $authuser ne 'root@pam';
+
my $storecfg = PVE::Storage::config();
- my $realcmd = sub {
- my $upid = shift;
+ if (&$vm_is_ha_managed($vmid) && !$stateuri &&
+ $rpcenv->{type} ne 'ha') {
- syslog('info', "start VM $vmid: $upid\n");
+ my $hacmd = sub {
+ my $upid = shift;
- PVE::QemuServer::vm_start($storecfg, $vmid, $stateuri, $skiplock);
+ my $service = "pvevm:$vmid";
- return;
- };
+ my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
+
+ print "Executing HA start for VM $vmid\n";
+
+ PVE::Tools::run_command($cmd);
- return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
+ return;
+ };
+
+ return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
+
+ } else {
+
+ my $realcmd = sub {
+ my $upid = shift;
+
+ syslog('info', "start VM $vmid: $upid\n");
+
+ PVE::QemuServer::vm_start($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom);
+
+ return;
+ };
+
+ return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
+ }
}});
__PACKAGE__->register_method({
node => get_standard_option('pve-node'),
vmid => get_standard_option('pve-vmid'),
skiplock => get_standard_option('skiplock'),
+ migratedfrom => get_standard_option('pve-node',{ optional => 1 }),
timeout => {
description => "Wait maximal timeout seconds.",
type => 'integer',
raise_param_exc({ keepActive => "Only root may use this option." })
if $keepActive && $authuser ne 'root@pam';
+ my $migratedfrom = extract_param($param, 'migratedfrom');
+ raise_param_exc({ migratedfrom => "Only root may use this option." })
+ if $migratedfrom && $authuser ne 'root@pam';
+
+
my $storecfg = PVE::Storage::config();
- my $realcmd = sub {
- my $upid = shift;
+ if (&$vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') {
- syslog('info', "stop VM $vmid: $upid\n");
+ my $hacmd = sub {
+ my $upid = shift;
- PVE::QemuServer::vm_stop($storecfg, $vmid, $skiplock, 0,
- $param->{timeout}, 0, 1, $keepActive);
+ my $service = "pvevm:$vmid";
- return;
- };
+ my $cmd = ['clusvcadm', '-d', $service];
- return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
+ print "Executing HA stop for VM $vmid\n";
+
+ PVE::Tools::run_command($cmd);
+
+ return;
+ };
+
+ return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
+
+ } else {
+ my $realcmd = sub {
+ my $upid = shift;
+
+ syslog('info', "stop VM $vmid: $upid\n");
+
+ PVE::QemuServer::vm_stop($storecfg, $vmid, $skiplock, 0,
+ $param->{timeout}, 0, 1, $keepActive, $migratedfrom);
+
+ return;
+ };
+
+ return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
+ }
}});
__PACKAGE__->register_method({
if !$param->{online};
}
- my $realcmd = sub {
- my $upid = shift;
+ my $storecfg = PVE::Storage::config();
+ PVE::QemuServer::check_storage_availability($storecfg, $conf, $target);
- PVE::QemuMigrate->migrate($target, $targetip, $vmid, $param);
- };
+ if (&$vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') {
+
+ my $hacmd = sub {
+ my $upid = shift;
- my $upid = $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
+ my $service = "pvevm:$vmid";
+
+ my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
+
+ print "Executing HA migrate for VM $vmid to node $target\n";
+
+ PVE::Tools::run_command($cmd);
+
+ return;
+ };
+
+ return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
+
+ } else {
+
+ my $realcmd = sub {
+ my $upid = shift;
+
+ PVE::QemuMigrate->migrate($target, $targetip, $vmid, $param);
+ };
+
+ return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
+ }
- return $upid;
}});
__PACKAGE__->register_method({
my $res = '';
eval {
- $res = PVE::QemuServer::vm_monitor_command($vmid, $param->{command});
+ $res = PVE::QemuServer::vm_human_monitor_command($vmid, $param->{command});
};
$res = "ERROR: $@" if $@;
return $res;
}});
+__PACKAGE__->register_method({
+ name => 'resize_vm',
+ path => '{vmid}/resize',
+ method => 'PUT',
+ protected => 1,
+ proxyto => 'node',
+ description => "Extend volume size.",
+ permissions => {
+ check => ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
+ },
+ parameters => {
+ additionalProperties => 0,
+ properties => {
+ node => get_standard_option('pve-node'),
+ vmid => get_standard_option('pve-vmid'),
+ skiplock => get_standard_option('skiplock'),
+ disk => {
+ type => 'string',
+ description => "The disk you want to resize.",
+ enum => [PVE::QemuServer::disknames()],
+ },
+ size => {
+ type => 'string',
+ pattern => '\+?\d+(\.\d+)?[KMGT]?',
+ description => "The new size. With the '+' sign the value is added to the actual size of the volume and without it, the value is taken as an absolute one. Shrinking disk size is not supported.",
+ },
+ digest => {
+ type => 'string',
+ description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
+ maxLength => 40,
+ optional => 1,
+ },
+ },
+ },
+ returns => { type => 'null'},
+ code => sub {
+ my ($param) = @_;
+
+ my $rpcenv = PVE::RPCEnvironment::get();
+
+ my $authuser = $rpcenv->get_user();
+
+ my $node = extract_param($param, 'node');
+
+ my $vmid = extract_param($param, 'vmid');
+
+ my $digest = extract_param($param, 'digest');
+
+ my $disk = extract_param($param, 'disk');
+
+ my $sizestr = extract_param($param, 'size');
+
+ my $skiplock = extract_param($param, 'skiplock');
+ raise_param_exc({ skiplock => "Only root may use this option." })
+ if $skiplock && $authuser ne 'root@pam';
+
+ my $storecfg = PVE::Storage::config();
+
+ my $updatefn = sub {
+
+ my $conf = PVE::QemuServer::load_config($vmid);
+
+ die "checksum missmatch (file change by other user?)\n"
+ if $digest && $digest ne $conf->{digest};
+ PVE::QemuServer::check_lock($conf) if !$skiplock;
+
+ die "disk '$disk' does not exist\n" if !$conf->{$disk};
+
+ my $drive = PVE::QemuServer::parse_drive($disk, $conf->{$disk});
+
+ my $volid = $drive->{file};
+
+ die "disk '$disk' has no associated volume\n" if !$volid;
+
+ die "you can't resize a cdrom\n" if PVE::QemuServer::drive_is_cdrom($drive);
+
+ my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid);
+
+ $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
+
+ my $size = PVE::Storage::volume_size_info($storecfg, $volid, 5);
+
+ die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
+ my ($ext, $newsize, $unit) = ($1, $2, $4);
+ if ($unit) {
+ if ($unit eq 'K') {
+ $newsize = $newsize * 1024;
+ } elsif ($unit eq 'M') {
+ $newsize = $newsize * 1024 * 1024;
+ } elsif ($unit eq 'G') {
+ $newsize = $newsize * 1024 * 1024 * 1024;
+ } elsif ($unit eq 'T') {
+ $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
+ }
+ }
+ $newsize += $size if $ext;
+ $newsize = int($newsize);
+
+ die "unable to skrink disk size\n" if $newsize < $size;
+
+ return if $size == $newsize;
+
+ PVE::Cluster::log_msg('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
+
+ PVE::QemuServer::qemu_block_resize($vmid, "drive-$disk", $storecfg, $volid, $newsize);
+
+ $drive->{size} = $newsize;
+ $conf->{$disk} = PVE::QemuServer::print_drive($vmid, $drive);
+
+ PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
+ };
+
+ PVE::QemuServer::lock_config($vmid, $updatefn);
+ return undef;
+ }});
+
+__PACKAGE__->register_method({
+ name => 'snapshot_list',
+ path => '{vmid}/snapshot',
+ method => 'GET',
+ description => "List all snapshots.",
+ permissions => {
+ check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
+ },
+ proxyto => 'node',
+ protected => 1, # qemu pid files are only readable by root
+ parameters => {
+ additionalProperties => 0,
+ properties => {
+ vmid => get_standard_option('pve-vmid'),
+ node => get_standard_option('pve-node'),
+ },
+ },
+ returns => {
+ type => 'array',
+ items => {
+ type => "object",
+ properties => {},
+ },
+ links => [ { rel => 'child', href => "{name}" } ],
+ },
+ code => sub {
+ my ($param) = @_;
+
+ my $conf = PVE::QemuServer::load_config($param->{vmid});
+ my $snaphash = $conf->{snapshots} || {};
+
+ my $res = [];
+
+ foreach my $name (keys %$snaphash) {
+ push @$res, { name => $name };
+ }
+
+ return $res;
+ }});
+
+__PACKAGE__->register_method({
+ name => 'snapshot',
+ path => '{vmid}/snapshot',
+ method => 'POST',
+ protected => 1,
+ proxyto => 'node',
+ description => "Snapshot a VM.",
+ permissions => {
+ check => ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
+ },
+ parameters => {
+ additionalProperties => 0,
+ properties => {
+ node => get_standard_option('pve-node'),
+ vmid => get_standard_option('pve-vmid'),
+ snapname => {
+ type => 'string',
+ description => "The name of the snapshot",
+ maxLength => 40,
+ },
+ vmstate => {
+ optional => 1,
+ type => 'boolean',
+ description => "Save the vmstate",
+ },
+ freezefs => {
+ optional => 1,
+ type => 'boolean',
+ description => "Freeze the filesystem",
+ },
+ },
+ },
+ returns => {
+ type => 'string',
+ description => "the task ID.",
+ },
+ code => sub {
+ my ($param) = @_;
+
+ my $rpcenv = PVE::RPCEnvironment::get();
+
+ my $authuser = $rpcenv->get_user();
+
+ my $node = extract_param($param, 'node');
+
+ my $vmid = extract_param($param, 'vmid');
+
+ my $snapname = extract_param($param, 'snapname');
+
+ my $vmstate = extract_param($param, 'vmstate');
+
+ my $freezefs = extract_param($param, 'freezefs');
+
+ # fixme: access rights?
+ # &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $conf);
+ # fixme: need to implement a check to see if all storages support snapshots
+
+ my $realcmd = sub {
+ PVE::Cluster::log_msg('info', $authuser, "snapshot VM $vmid: $snapname");
+ PVE::QemuServer::snapshot_create($vmid, $snapname, $vmstate, $freezefs);
+ };
+
+ return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
+ }});
+
+__PACKAGE__->register_method({
+ name => 'rollback',
+ path => '{vmid}/rollback',
+ method => 'POST',
+ protected => 1,
+ proxyto => 'node',
+ description => "Rollback VM state to specified snapshot.",
+ permissions => {
+ check => ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
+ },
+ parameters => {
+ additionalProperties => 0,
+ properties => {
+ node => get_standard_option('pve-node'),
+ vmid => get_standard_option('pve-vmid'),
+ snapname => {
+ type => 'string',
+ description => "The name of the snapshot",
+ maxLength => 40,
+ },
+ },
+ },
+ returns => {
+ type => 'string',
+ description => "the task ID.",
+ },
+ code => sub {
+ my ($param) = @_;
+
+ my $rpcenv = PVE::RPCEnvironment::get();
+
+ my $authuser = $rpcenv->get_user();
+
+ my $node = extract_param($param, 'node');
+
+ my $vmid = extract_param($param, 'vmid');
+
+ my $snapname = extract_param($param, 'snapname');
+
+ # fixme: access rights?
+
+ my $realcmd = sub {
+ PVE::Cluster::log_msg('info', $authuser, "rollback snapshot VM $vmid: $snapname");
+ PVE::QemuServer::snapshot_rollback($vmid, $snapname);
+ };
+
+ return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
+ }});
+
+__PACKAGE__->register_method({
+ name => 'delsnapshot',
+ path => '{vmid}/snapshot/{snapname}',
+ method => 'DELETE',
+ protected => 1,
+ proxyto => 'node',
+ description => "Delete a VM snapshot.",
+ permissions => {
+ check => ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
+ },
+ parameters => {
+ additionalProperties => 0,
+ properties => {
+ node => get_standard_option('pve-node'),
+ vmid => get_standard_option('pve-vmid'),
+ snapname => {
+ type => 'string',
+ description => "The name of the snapshot",
+ maxLength => 40,
+ },
+ },
+ },
+ returns => {
+ type => 'string',
+ description => "the task ID.",
+ },
+ code => sub {
+ my ($param) = @_;
+
+ my $rpcenv = PVE::RPCEnvironment::get();
+
+ my $authuser = $rpcenv->get_user();
+
+ my $node = extract_param($param, 'node');
+
+ my $vmid = extract_param($param, 'vmid');
+
+ my $snapname = extract_param($param, 'snapname');
+
+ # fixme: access rights?
+
+ my $realcmd = sub {
+ PVE::Cluster::log_msg('info', $authuser, "delete snapshot VM $vmid: $snapname");
+ PVE::QemuServer::snapshot_delete($vmid, $snapname);
+ };
+
+ return $rpcenv->fork_worker('qmdelsnaphot', $vmid, $authuser, $realcmd);
+ }});
+
1;