use PVE::Network;
use PVE::Firewall;
use PVE::API2::Firewall::VM;
-use PVE::HA::Env::PVE2;
-use PVE::HA::Config;
+
+BEGIN {
+ if (!$ENV{PVE_GENERATING_DOCS}) {
+ require PVE::HA::Env::PVE2;
+ import PVE::HA::Env::PVE2;
+ require PVE::HA::Config;
+ import PVE::HA::Config;
+ }
+}
use Data::Dumper; # fixme: remove
die "no storage ID specified (and no default storage)\n" if !$storeid;
$rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
} else {
- $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
+ PVE::Storage::check_volume_access($rpcenv, $authuser, $storecfg, $vmid, $volid);
}
});
};
$res->{$ds} = PVE::QemuServer::print_drive($vmid, $disk);
} else {
- $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
+ PVE::Storage::check_volume_access($rpcenv, $authuser, $storecfg, $vmid, $volid);
my $volid_is_new = 1;
die "pipe requires cli environment\n"
if $rpcenv->{type} ne 'cli';
} else {
- $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $archive);
+ PVE::Storage::check_volume_access($rpcenv, $authuser, $storecfg, $vmid, $archive);
$archive = PVE::Storage::abs_filesystem_path($storecfg, $archive);
}
}
{ subdir => 'rrd' },
{ subdir => 'rrddata' },
{ subdir => 'monitor' },
+ { subdir => 'agent' },
{ subdir => 'snapshot' },
{ subdir => 'spiceproxy' },
{ subdir => 'sendkey' },
optional => 1,
},
migration_network => {
- type => 'string',
- format => 'CIDR',
+ type => 'string', format => 'CIDR',
description => "CIDR of the (sub) network that is used for migration.",
optional => 1,
},
machine => get_standard_option('pve-qm-machine'),
+ targetstorage => {
+ description => "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
+ type => 'string',
+ optional => 1
+ }
},
},
returns => {
raise_param_exc({ migration_network => "Only root may use this option." })
if $migration_network && $authuser ne 'root@pam';
+ my $targetstorage = extract_param($param, 'targetstorage');
+ raise_param_exc({ targetstorage => "Only root may use this option." })
+ if $targetstorage && $authuser ne 'root@pam';
+
+ raise_param_exc({ targetstorage => "targetstorage can only by used with migratedfrom." })
+ if $targetstorage && !$migratedfrom;
+
# read spice ticket from STDIN
my $spice_ticket;
if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type} eq 'cli')) {
syslog('info', "start VM $vmid: $upid\n");
PVE::QemuServer::vm_start($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
- $machine, $spice_ticket, $migration_network, $migration_type);
+ $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
return;
};
}
}
- my $realcmd = sub {
- my $upid = shift;
+ if (PVE::HA::Config::vm_is_ha_managed($vmid) &&
+ ($rpcenv->{type} ne 'ha')) {
+
+ my $hacmd = sub {
+ my $upid = shift;
- syslog('info', "shutdown VM $vmid: $upid\n");
+ my $service = "vm:$vmid";
- PVE::QemuServer::vm_stop($storecfg, $vmid, $skiplock, 0, $param->{timeout},
- $shutdown, $param->{forceStop}, $keepActive);
+ my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
- return;
- };
+ print "Executing HA stop for VM $vmid\n";
+
+ PVE::Tools::run_command($cmd);
+
+ return;
+ };
+
+ return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
- return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
+ } else {
+
+ my $realcmd = sub {
+ my $upid = shift;
+
+ syslog('info', "shutdown VM $vmid: $upid\n");
+
+ PVE::QemuServer::vm_stop($storecfg, $vmid, $skiplock, 0, $param->{timeout},
+ $shutdown, $param->{forceStop}, $keepActive);
+
+ return;
+ };
+
+ return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
+ }
}});
__PACKAGE__->register_method({
my $upid = shift;
my $newvollist = [];
+ my $jobs = {};
eval {
local $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = sub { die "interrupted by signal\n"; };
PVE::Storage::activate_volumes($storecfg, $vollist, $snapname);
+ my $total_jobs = scalar(keys %{$drives});
+ my $i = 1;
+
foreach my $opt (keys %$drives) {
my $drive = $drives->{$opt};
+ my $skipcomplete = ($total_jobs != $i); # finish after last drive
my $newdrive = PVE::QemuServer::clone_disk($storecfg, $vmid, $running, $opt, $drive, $snapname,
- $newid, $storage, $format, $fullclone->{$opt}, $newvollist);
+ $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
+ $jobs, $skipcomplete, $oldconf->{agent});
$newconf->{$opt} = PVE::QemuServer::print_drive($vmid, $newdrive);
PVE::QemuConfig->write_config($newid, $newconf);
+ $i++;
}
delete $newconf->{lock};
if (my $err = $@) {
unlink $conffile;
+ eval { PVE::QemuServer::qemu_blockjobs_cancel($vmid, $jobs) };
+
sleep 1; # some storage like rbd need to wait before release volume - really?
foreach my $volid (@$newvollist) {
proxyto => 'node',
description => "Move volume to different storage.",
permissions => {
- description => "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
- "and 'Datastore.AllocateSpace' permissions on the storage.",
- check =>
- [ 'and',
- ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
- ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
- ],
+ description => "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
+ check => [ 'and',
+ ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
+ ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
+ ],
},
parameters => {
additionalProperties => 0,
- properties => {
+ properties => {
node => get_standard_option('pve-node'),
vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
disk => {
PVE::QemuConfig->add_unused_volume($conf, $old_volid) if !$param->{delete};
+ # convert moved disk to base if part of template
+ PVE::QemuServer::template_create($vmid, $conf, $disk)
+ if PVE::QemuConfig->is_template($conf);
+
PVE::QemuConfig->write_config($vmid, $conf);
eval {
migration_type => {
type => 'string',
enum => ['secure', 'insecure'],
- description => "Migration traffic is encrypted using an SSH " .
- "tunnel by default. On secure, completely private networks " .
- "this can be disabled to increase performance.",
+ description => "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
optional => 1,
},
migration_network => {
- type => 'string',
- format => 'CIDR',
+ type => 'string', format => 'CIDR',
description => "CIDR of the (sub) network that is used for migration.",
optional => 1,
},
+ "with-local-disks" => {
+ type => 'boolean',
+ description => "Enable live storage migration for local disk",
+ optional => 1,
+ },
+ targetstorage => get_standard_option('pve-storage-id', {
+ description => "Default target storage.",
+ optional => 1,
+ completion => \&PVE::QemuServer::complete_storage,
+ }),
},
},
returns => {
my $vmid = extract_param($param, 'vmid');
+ raise_param_exc({ targetstorage => "Live storage migration can only be done online." })
+ if !$param->{online} && $param->{targetstorage};
+
raise_param_exc({ force => "Only root may use this option." })
if $param->{force} && $authuser ne 'root@pam';
proxyto => 'node',
description => "Execute Qemu monitor commands.",
permissions => {
- check => ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
+ description => "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
+ check => ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
},
parameters => {
additionalProperties => 0,
code => sub {
my ($param) = @_;
+ my $rpcenv = PVE::RPCEnvironment::get();
+ my $authuser = $rpcenv->get_user();
+
+ my $is_ro = sub {
+ my $command = shift;
+ return $command =~ m/^\s*info(\s+|$)/
+ || $command =~ m/^\s*help\s*$/;
+ };
+
+ $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
+ if !&$is_ro($param->{command});
+
my $vmid = $param->{vmid};
my $conf = PVE::QemuConfig->load_config ($vmid); # check if VM exists
return $res;
}});
+my $guest_agent_commands = [
+ 'ping',
+ 'get-time',
+ 'info',
+ 'fsfreeze-status',
+ 'fsfreeze-freeze',
+ 'fsfreeze-thaw',
+ 'fstrim',
+ 'network-get-interfaces',
+ 'get-vcpus',
+ 'get-fsinfo',
+ 'get-memory-blocks',
+ 'get-memory-block-info',
+ 'suspend-hybrid',
+ 'suspend-ram',
+ 'suspend-disk',
+ 'shutdown',
+ ];
+
+__PACKAGE__->register_method({
+ name => 'agent',
+ path => '{vmid}/agent',
+ method => 'POST',
+ protected => 1,
+ proxyto => 'node',
+ description => "Execute Qemu Guest Agent commands.",
+ permissions => {
+ check => ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
+ },
+ parameters => {
+ additionalProperties => 0,
+ properties => {
+ node => get_standard_option('pve-node'),
+ vmid => get_standard_option('pve-vmid', {
+ completion => \&PVE::QemuServer::complete_vmid_running }),
+ command => {
+ type => 'string',
+ description => "The QGA command.",
+ enum => $guest_agent_commands,
+ },
+ },
+ },
+ returns => {
+ type => 'object',
+ description => "Returns an object with a single `result` property. The type of that
+property depends on the executed command.",
+ },
+ code => sub {
+ my ($param) = @_;
+
+ my $vmid = $param->{vmid};
+
+ my $conf = PVE::QemuConfig->load_config ($vmid); # check if VM exists
+
+ die "No Qemu Guest Agent\n" if !defined($conf->{agent});
+ die "VM $vmid is not running\n" if !PVE::QemuServer::check_running($vmid);
+
+ my $cmd = $param->{command};
+
+ my $res = PVE::QemuServer::vm_mon_cmd($vmid, "guest-$cmd");
+
+ return { result => $res };
+ }});
+
__PACKAGE__->register_method({
name => 'resize_vm',
path => '{vmid}/resize',
$newsize += $size if $ext;
$newsize = int($newsize);
- die "unable to skrink disk size\n" if $newsize < $size;
+ die "shrinking disks is not supported\n" if $newsize < $size;
return if $size == $newsize;