]> git.proxmox.com Git - qemu-server.git/blame - PVE/API2/Qemu.pm
Add tests for parse_ovf function
[qemu-server.git] / PVE / API2 / Qemu.pm
CommitLineData
1e3baf05
DM
1package PVE::API2::Qemu;
2
3use strict;
4use warnings;
5b9d692a 5use Cwd 'abs_path';
451b2b81 6use Net::SSLeay;
47314bf5 7use UUID;
655d7462
WB
8use POSIX;
9use IO::Socket::IP;
1e3baf05 10
502d18a2 11use PVE::Cluster qw (cfs_read_file cfs_write_file);;
1e3baf05
DM
12use PVE::SafeSyslog;
13use PVE::Tools qw(extract_param);
f9bfceef 14use PVE::Exception qw(raise raise_param_exc raise_perm_exc);
1e3baf05
DM
15use PVE::Storage;
16use PVE::JSONSchema qw(get_standard_option);
17use PVE::RESTHandler;
628bb7f2 18use PVE::ReplicationConfig;
95f42d61 19use PVE::GuestHelpers;
ffda963f 20use PVE::QemuConfig;
1e3baf05 21use PVE::QemuServer;
3ea94c60 22use PVE::QemuMigrate;
1e3baf05
DM
23use PVE::RPCEnvironment;
24use PVE::AccessControl;
25use PVE::INotify;
de8f60b2 26use PVE::Network;
e9abcde6 27use PVE::Firewall;
228a998b 28use PVE::API2::Firewall::VM;
9f11fc5f
WB
29
30BEGIN {
31 if (!$ENV{PVE_GENERATING_DOCS}) {
32 require PVE::HA::Env::PVE2;
33 import PVE::HA::Env::PVE2;
34 require PVE::HA::Config;
35 import PVE::HA::Config;
36 }
37}
1e3baf05
DM
38
39use Data::Dumper; # fixme: remove
40
41use base qw(PVE::RESTHandler);
42
43my $opt_force_description = "Force physical removal. Without this, we simple remove the disk from the config file and create an additional configuration entry called 'unused[n]', which contains the volume ID. Unlink of unused[n] always cause physical removal.";
44
45my $resolve_cdrom_alias = sub {
46 my $param = shift;
47
48 if (my $value = $param->{cdrom}) {
49 $value .= ",media=cdrom" if $value !~ m/media=/;
50 $param->{ide2} = $value;
51 delete $param->{cdrom};
52 }
53};
54
bf1312d8 55my $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
ae57f6b3 56my $check_storage_access = sub {
fcbb753e 57 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
a0d1b1a2 58
ae57f6b3
DM
59 PVE::QemuServer::foreach_drive($settings, sub {
60 my ($ds, $drive) = @_;
a0d1b1a2 61
ae57f6b3 62 my $isCDROM = PVE::QemuServer::drive_is_cdrom($drive);
a0d1b1a2 63
ae57f6b3 64 my $volid = $drive->{file};
a0d1b1a2 65
09d0ee64
DM
66 if (!$volid || $volid eq 'none') {
67 # nothing to check
f5782fd0
DM
68 } elsif ($isCDROM && ($volid eq 'cdrom')) {
69 $rpcenv->check($authuser, "/", ['Sys.Console']);
bf1312d8 70 } elsif (!$isCDROM && ($volid =~ $NEW_DISK_RE)) {
a0d1b1a2
DM
71 my ($storeid, $size) = ($2 || $default_storage, $3);
72 die "no storage ID specified (and no default storage)\n" if !$storeid;
fcbb753e 73 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
c46366fd
DC
74 my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
75 raise_param_exc({ storage => "storage '$storeid' does not support vm images"})
76 if !$scfg->{content}->{images};
a0d1b1a2 77 } else {
9bb3acf1 78 PVE::Storage::check_volume_access($rpcenv, $authuser, $storecfg, $vmid, $volid);
a0d1b1a2
DM
79 }
80 });
253624c7
FG
81
82 $rpcenv->check($authuser, "/storage/$settings->{vmstatestorage}", ['Datastore.AllocateSpace'])
83 if defined($settings->{vmstatestorage});
ae57f6b3 84};
a0d1b1a2 85
9418baad 86my $check_storage_access_clone = sub {
81f043eb 87 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
6116f729 88
55173c6b
DM
89 my $sharedvm = 1;
90
6116f729
DM
91 PVE::QemuServer::foreach_drive($conf, sub {
92 my ($ds, $drive) = @_;
93
94 my $isCDROM = PVE::QemuServer::drive_is_cdrom($drive);
95
96 my $volid = $drive->{file};
97
98 return if !$volid || $volid eq 'none';
99
100 if ($isCDROM) {
101 if ($volid eq 'cdrom') {
102 $rpcenv->check($authuser, "/", ['Sys.Console']);
103 } else {
75466c4f 104 # we simply allow access
55173c6b
DM
105 my ($sid, $volname) = PVE::Storage::parse_volume_id($volid);
106 my $scfg = PVE::Storage::storage_config($storecfg, $sid);
107 $sharedvm = 0 if !$scfg->{shared};
108
6116f729
DM
109 }
110 } else {
55173c6b
DM
111 my ($sid, $volname) = PVE::Storage::parse_volume_id($volid);
112 my $scfg = PVE::Storage::storage_config($storecfg, $sid);
113 $sharedvm = 0 if !$scfg->{shared};
114
81f043eb 115 $sid = $storage if $storage;
6116f729
DM
116 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
117 }
118 });
55173c6b 119
253624c7
FG
120 $rpcenv->check($authuser, "/storage/$conf->{vmstatestorage}", ['Datastore.AllocateSpace'])
121 if defined($conf->{vmstatestorage});
122
55173c6b 123 return $sharedvm;
6116f729
DM
124};
125
ae57f6b3
DM
126# Note: $pool is only needed when creating a VM, because pool permissions
127# are automatically inherited if VM already exists inside a pool.
128my $create_disks = sub {
129 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
a0d1b1a2
DM
130
131 my $vollist = [];
a0d1b1a2 132
ae57f6b3 133 my $res = {};
64932aeb
DM
134
135 my $code = sub {
ae57f6b3
DM
136 my ($ds, $disk) = @_;
137
138 my $volid = $disk->{file};
139
f5782fd0 140 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
628e9a2b
AD
141 delete $disk->{size};
142 $res->{$ds} = PVE::QemuServer::print_drive($vmid, $disk);
17677004 143 } elsif ($volid =~ $NEW_DISK_RE) {
ae57f6b3
DM
144 my ($storeid, $size) = ($2 || $default_storage, $3);
145 die "no storage ID specified (and no default storage)\n" if !$storeid;
146 my $defformat = PVE::Storage::storage_default_format($storecfg, $storeid);
147 my $fmt = $disk->{format} || $defformat;
1a35631a 148
a1d8c038
TL
149 $size = PVE::Tools::convert_size($size, 'gb' => 'kb'); # vdisk_alloc uses kb
150
1a35631a
DC
151 my $volid;
152 if ($ds eq 'efidisk0') {
3e1f1122 153 ($volid, $size) = PVE::QemuServer::create_efidisk($storecfg, $storeid, $vmid, $fmt);
1a35631a 154 } else {
a1d8c038 155 $volid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $vmid, $fmt, undef, $size);
1a35631a 156 }
a0d1b1a2 157 push @$vollist, $volid;
a1d8c038
TL
158 $disk->{file} = $volid;
159 $disk->{size} = PVE::Tools::convert_size($size, 'kb' => 'b');
ae57f6b3
DM
160 delete $disk->{format}; # no longer needed
161 $res->{$ds} = PVE::QemuServer::print_drive($vmid, $disk);
162 } else {
eabe0da0 163
9bb3acf1 164 PVE::Storage::check_volume_access($rpcenv, $authuser, $storecfg, $vmid, $volid);
75466c4f 165
7043d946 166 my $volid_is_new = 1;
35cb731c 167
7043d946
DM
168 if ($conf->{$ds}) {
169 my $olddrive = PVE::QemuServer::parse_drive($ds, $conf->{$ds});
170 $volid_is_new = undef if $olddrive->{file} && $olddrive->{file} eq $volid;
eabe0da0 171 }
75466c4f 172
d52b8b77 173 if ($volid_is_new) {
09a89895 174
7043d946
DM
175 my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
176
09a89895 177 PVE::Storage::activate_volumes($storecfg, [ $volid ]) if $storeid;
d52b8b77
DM
178
179 my $size = PVE::Storage::volume_size_info($storecfg, $volid);
180
181 die "volume $volid does not exists\n" if !$size;
182
183 $disk->{size} = $size;
09a89895 184 }
24afaca0 185
24afaca0 186 $res->{$ds} = PVE::QemuServer::print_drive($vmid, $disk);
a0d1b1a2 187 }
64932aeb
DM
188 };
189
190 eval { PVE::QemuServer::foreach_drive($settings, $code); };
a0d1b1a2
DM
191
192 # free allocated images on error
193 if (my $err = $@) {
194 syslog('err', "VM $vmid creating disks failed");
195 foreach my $volid (@$vollist) {
196 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
197 warn $@ if $@;
198 }
199 die $err;
200 }
201
202 # modify vm config if everything went well
ae57f6b3
DM
203 foreach my $ds (keys %$res) {
204 $conf->{$ds} = $res->{$ds};
a0d1b1a2
DM
205 }
206
207 return $vollist;
208};
209
58cb690b
DC
210my $cpuoptions = {
211 'cores' => 1,
212 'cpu' => 1,
213 'cpulimit' => 1,
214 'cpuunits' => 1,
215 'numa' => 1,
216 'smp' => 1,
217 'sockets' => 1,
84b31f48 218 'vcpus' => 1,
58cb690b
DC
219};
220
221my $memoryoptions = {
222 'memory' => 1,
223 'balloon' => 1,
224 'shares' => 1,
225};
226
227my $hwtypeoptions = {
228 'acpi' => 1,
229 'hotplug' => 1,
230 'kvm' => 1,
231 'machine' => 1,
232 'scsihw' => 1,
233 'smbios1' => 1,
234 'tablet' => 1,
235 'vga' => 1,
236 'watchdog' => 1,
237};
238
6bcacc21 239my $generaloptions = {
58cb690b
DC
240 'agent' => 1,
241 'autostart' => 1,
242 'bios' => 1,
243 'description' => 1,
244 'keyboard' => 1,
245 'localtime' => 1,
246 'migrate_downtime' => 1,
247 'migrate_speed' => 1,
248 'name' => 1,
249 'onboot' => 1,
250 'ostype' => 1,
251 'protection' => 1,
252 'reboot' => 1,
253 'startdate' => 1,
254 'startup' => 1,
255 'tdf' => 1,
256 'template' => 1,
257};
258
259my $vmpoweroptions = {
260 'freeze' => 1,
261};
262
263my $diskoptions = {
264 'boot' => 1,
265 'bootdisk' => 1,
253624c7 266 'vmstatestorage' => 1,
58cb690b
DC
267};
268
a0d1b1a2 269my $check_vm_modify_config_perm = sub {
ae57f6b3 270 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
a0d1b1a2 271
6e5c4da7 272 return 1 if $authuser eq 'root@pam';
a0d1b1a2 273
ae57f6b3 274 foreach my $opt (@$key_list) {
a0d1b1a2 275 # disk checks need to be done somewhere else
74479ee9 276 next if PVE::QemuServer::is_valid_drivename($opt);
58cb690b 277 next if $opt eq 'cdrom';
63d269d7 278 next if $opt =~ m/^unused\d+$/;
a0d1b1a2 279
6bcacc21 280 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
a0d1b1a2 281 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
58cb690b 282 } elsif ($memoryoptions->{$opt}) {
a0d1b1a2 283 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
58cb690b 284 } elsif ($hwtypeoptions->{$opt}) {
a0d1b1a2 285 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
6bcacc21 286 } elsif ($generaloptions->{$opt}) {
58cb690b
DC
287 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
288 # special case for startup since it changes host behaviour
289 if ($opt eq 'startup') {
290 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
291 }
292 } elsif ($vmpoweroptions->{$opt}) {
293 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
294 } elsif ($diskoptions->{$opt}) {
295 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
a0d1b1a2
DM
296 } elsif ($opt =~ m/^net\d+$/) {
297 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
298 } else {
58cb690b
DC
299 # catches usb\d+, hostpci\d+, args, lock, etc.
300 # new options will be checked here
301 die "only root can set '$opt' config\n";
a0d1b1a2
DM
302 }
303 }
304
305 return 1;
306};
307
1e3baf05 308__PACKAGE__->register_method({
afdb31d5
DM
309 name => 'vmlist',
310 path => '',
1e3baf05
DM
311 method => 'GET',
312 description => "Virtual machine index (per node).",
a0d1b1a2
DM
313 permissions => {
314 description => "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
315 user => 'all',
316 },
1e3baf05
DM
317 proxyto => 'node',
318 protected => 1, # qemu pid files are only readable by root
319 parameters => {
320 additionalProperties => 0,
321 properties => {
322 node => get_standard_option('pve-node'),
12612b09
WB
323 full => {
324 type => 'boolean',
325 optional => 1,
326 description => "Determine the full status of active VMs.",
327 },
1e3baf05
DM
328 },
329 },
330 returns => {
331 type => 'array',
332 items => {
333 type => "object",
334 properties => {},
335 },
336 links => [ { rel => 'child', href => "{vmid}" } ],
337 },
338 code => sub {
339 my ($param) = @_;
340
a0d1b1a2
DM
341 my $rpcenv = PVE::RPCEnvironment::get();
342 my $authuser = $rpcenv->get_user();
343
12612b09 344 my $vmstatus = PVE::QemuServer::vmstatus(undef, $param->{full});
1e3baf05 345
a0d1b1a2
DM
346 my $res = [];
347 foreach my $vmid (keys %$vmstatus) {
348 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
349
350 my $data = $vmstatus->{$vmid};
184955dc 351 $data->{vmid} = int($vmid);
a0d1b1a2
DM
352 push @$res, $data;
353 }
1e3baf05 354
a0d1b1a2 355 return $res;
1e3baf05
DM
356 }});
357
d703d4c0 358
d703d4c0 359
1e3baf05 360__PACKAGE__->register_method({
afdb31d5
DM
361 name => 'create_vm',
362 path => '',
1e3baf05 363 method => 'POST',
3e16d5fc 364 description => "Create or restore a virtual machine.",
a0d1b1a2 365 permissions => {
f9bfceef
DM
366 description => "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
367 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
368 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
369 user => 'all', # check inside
a0d1b1a2 370 },
1e3baf05
DM
371 protected => 1,
372 proxyto => 'node',
373 parameters => {
374 additionalProperties => 0,
375 properties => PVE::QemuServer::json_config_properties(
376 {
377 node => get_standard_option('pve-node'),
65e866e5 378 vmid => get_standard_option('pve-vmid', { completion => \&PVE::Cluster::complete_next_vmid }),
3e16d5fc
DM
379 archive => {
380 description => "The backup file.",
381 type => 'string',
382 optional => 1,
383 maxLength => 255,
65e866e5 384 completion => \&PVE::QemuServer::complete_backup_archives,
3e16d5fc
DM
385 },
386 storage => get_standard_option('pve-storage-id', {
387 description => "Default storage.",
388 optional => 1,
335af808 389 completion => \&PVE::QemuServer::complete_storage,
3e16d5fc
DM
390 }),
391 force => {
afdb31d5 392 optional => 1,
3e16d5fc
DM
393 type => 'boolean',
394 description => "Allow to overwrite existing VM.",
51586c3a
DM
395 requires => 'archive',
396 },
397 unique => {
afdb31d5 398 optional => 1,
51586c3a
DM
399 type => 'boolean',
400 description => "Assign a unique random ethernet address.",
401 requires => 'archive',
3e16d5fc 402 },
75466c4f 403 pool => {
a0d1b1a2
DM
404 optional => 1,
405 type => 'string', format => 'pve-poolid',
406 description => "Add the VM to the specified pool.",
407 },
1e3baf05
DM
408 }),
409 },
afdb31d5 410 returns => {
5fdbe4f0
DM
411 type => 'string',
412 },
1e3baf05
DM
413 code => sub {
414 my ($param) = @_;
415
5fdbe4f0
DM
416 my $rpcenv = PVE::RPCEnvironment::get();
417
a0d1b1a2 418 my $authuser = $rpcenv->get_user();
5fdbe4f0 419
1e3baf05
DM
420 my $node = extract_param($param, 'node');
421
1e3baf05
DM
422 my $vmid = extract_param($param, 'vmid');
423
3e16d5fc
DM
424 my $archive = extract_param($param, 'archive');
425
426 my $storage = extract_param($param, 'storage');
427
51586c3a
DM
428 my $force = extract_param($param, 'force');
429
430 my $unique = extract_param($param, 'unique');
75466c4f 431
a0d1b1a2 432 my $pool = extract_param($param, 'pool');
51586c3a 433
ffda963f 434 my $filename = PVE::QemuConfig->config_file($vmid);
afdb31d5
DM
435
436 my $storecfg = PVE::Storage::config();
1e3baf05 437
3e16d5fc 438 PVE::Cluster::check_cfs_quorum();
1e3baf05 439
a0d1b1a2
DM
440 if (defined($pool)) {
441 $rpcenv->check_pool_exist($pool);
75466c4f 442 }
a0d1b1a2 443
fcbb753e 444 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
a0d1b1a2
DM
445 if defined($storage);
446
f9bfceef
DM
447 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
448 # OK
449 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
450 # OK
451 } elsif ($archive && $force && (-f $filename) &&
452 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
453 # OK: user has VM.Backup permissions, and want to restore an existing VM
454 } else {
455 raise_perm_exc();
456 }
457
afdb31d5 458 if (!$archive) {
3e16d5fc 459 &$resolve_cdrom_alias($param);
1e3baf05 460
fcbb753e 461 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
ae57f6b3
DM
462
463 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
464
3e16d5fc 465 foreach my $opt (keys %$param) {
74479ee9 466 if (PVE::QemuServer::is_valid_drivename($opt)) {
3e16d5fc
DM
467 my $drive = PVE::QemuServer::parse_drive($opt, $param->{$opt});
468 raise_param_exc({ $opt => "unable to parse drive options" }) if !$drive;
afdb31d5 469
3e16d5fc
DM
470 PVE::QemuServer::cleanup_drive_path($opt, $storecfg, $drive);
471 $param->{$opt} = PVE::QemuServer::print_drive($vmid, $drive);
472 }
1e3baf05 473 }
3e16d5fc
DM
474
475 PVE::QemuServer::add_random_macs($param);
51586c3a
DM
476 } else {
477 my $keystr = join(' ', keys %$param);
bc4dcb99
DM
478 raise_param_exc({ archive => "option conflicts with other options ($keystr)"}) if $keystr;
479
5b9d692a 480 if ($archive eq '-') {
afdb31d5 481 die "pipe requires cli environment\n"
d7810bc1 482 if $rpcenv->{type} ne 'cli';
5b9d692a 483 } else {
9bb3acf1 484 PVE::Storage::check_volume_access($rpcenv, $authuser, $storecfg, $vmid, $archive);
c9928b3d 485 $archive = PVE::Storage::abs_filesystem_path($storecfg, $archive);
971f27c4 486 }
1e3baf05
DM
487 }
488
3e16d5fc 489 my $restorefn = sub {
4d8d55f1 490 my $vmlist = PVE::Cluster::get_vmlist();
4d8d55f1 491 if ($vmlist->{ids}->{$vmid}) {
0152058a
DM
492 my $current_node = $vmlist->{ids}->{$vmid}->{node};
493 if ($current_node eq $node) {
ffda963f 494 my $conf = PVE::QemuConfig->load_config($vmid);
3e16d5fc 495
ffda963f 496 PVE::QemuConfig->check_protection($conf, "unable to restore VM $vmid");
3e16d5fc 497
4d8d55f1
AG
498 die "unable to restore vm $vmid - config file already exists\n"
499 if !$force;
500
501 die "unable to restore vm $vmid - vm is running\n"
502 if PVE::QemuServer::check_running($vmid);
3a07a8a9
FG
503
504 die "unable to restore vm $vmid - vm is a template\n"
505 if PVE::QemuConfig->is_template($conf);
506
4d8d55f1 507 } else {
0152058a 508 die "unable to restore vm $vmid - already existing on cluster node '$current_node'\n";
4d8d55f1 509 }
3e16d5fc
DM
510 }
511
512 my $realcmd = sub {
a0d1b1a2 513 PVE::QemuServer::restore_archive($archive, $vmid, $authuser, {
51586c3a 514 storage => $storage,
a0d1b1a2 515 pool => $pool,
51586c3a 516 unique => $unique });
502d18a2 517
be517049 518 PVE::AccessControl::add_vm_to_pool($vmid, $pool) if $pool;
3e16d5fc
DM
519 };
520
223e032b
WL
521 # ensure no old replication state are exists
522 PVE::ReplicationState::delete_guest_states($vmid);
523
a0d1b1a2 524 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
3e16d5fc 525 };
1e3baf05 526
1e3baf05
DM
527 my $createfn = sub {
528
191435c6 529 # test after locking
5f20325f 530 PVE::Cluster::check_vmid_unused($vmid);
1e3baf05 531
223e032b
WL
532 # ensure no old replication state are exists
533 PVE::ReplicationState::delete_guest_states($vmid);
534
5fdbe4f0 535 my $realcmd = sub {
1e3baf05 536
5fdbe4f0 537 my $vollist = [];
1e3baf05 538
1858638f
DM
539 my $conf = $param;
540
5fdbe4f0 541 eval {
ae57f6b3 542
1858638f 543 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
1e3baf05 544
5fdbe4f0 545 # try to be smart about bootdisk
74479ee9 546 my @disks = PVE::QemuServer::valid_drive_names();
5fdbe4f0
DM
547 my $firstdisk;
548 foreach my $ds (reverse @disks) {
1858638f
DM
549 next if !$conf->{$ds};
550 my $disk = PVE::QemuServer::parse_drive($ds, $conf->{$ds});
5fdbe4f0
DM
551 next if PVE::QemuServer::drive_is_cdrom($disk);
552 $firstdisk = $ds;
553 }
1e3baf05 554
1858638f
DM
555 if (!$conf->{bootdisk} && $firstdisk) {
556 $conf->{bootdisk} = $firstdisk;
5fdbe4f0 557 }
1e3baf05 558
47314bf5
DM
559 # auto generate uuid if user did not specify smbios1 option
560 if (!$conf->{smbios1}) {
561 my ($uuid, $uuid_str);
562 UUID::generate($uuid);
563 UUID::unparse($uuid, $uuid_str);
564 $conf->{smbios1} = "uuid=$uuid_str";
565 }
566
ffda963f 567 PVE::QemuConfig->write_config($vmid, $conf);
ae9ca91d 568
5fdbe4f0
DM
569 };
570 my $err = $@;
1e3baf05 571
5fdbe4f0
DM
572 if ($err) {
573 foreach my $volid (@$vollist) {
574 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
575 warn $@ if $@;
576 }
577 die "create failed - $err";
578 }
502d18a2 579
be517049 580 PVE::AccessControl::add_vm_to_pool($vmid, $pool) if $pool;
5fdbe4f0
DM
581 };
582
a0d1b1a2 583 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
5fdbe4f0
DM
584 };
585
ffda963f 586 return PVE::QemuConfig->lock_config_full($vmid, 1, $archive ? $restorefn : $createfn);
1e3baf05
DM
587 }});
588
589__PACKAGE__->register_method({
590 name => 'vmdiridx',
afdb31d5 591 path => '{vmid}',
1e3baf05
DM
592 method => 'GET',
593 proxyto => 'node',
594 description => "Directory index",
a0d1b1a2
DM
595 permissions => {
596 user => 'all',
597 },
1e3baf05
DM
598 parameters => {
599 additionalProperties => 0,
600 properties => {
601 node => get_standard_option('pve-node'),
602 vmid => get_standard_option('pve-vmid'),
603 },
604 },
605 returns => {
606 type => 'array',
607 items => {
608 type => "object",
609 properties => {
610 subdir => { type => 'string' },
611 },
612 },
613 links => [ { rel => 'child', href => "{subdir}" } ],
614 },
615 code => sub {
616 my ($param) = @_;
617
618 my $res = [
619 { subdir => 'config' },
df2a2dbb 620 { subdir => 'pending' },
1e3baf05
DM
621 { subdir => 'status' },
622 { subdir => 'unlink' },
623 { subdir => 'vncproxy' },
3ea94c60 624 { subdir => 'migrate' },
2f48a4f5 625 { subdir => 'resize' },
586bfa78 626 { subdir => 'move' },
1e3baf05
DM
627 { subdir => 'rrd' },
628 { subdir => 'rrddata' },
91c94f0a 629 { subdir => 'monitor' },
d1a47427 630 { subdir => 'agent' },
7e7d7b61 631 { subdir => 'snapshot' },
288eeea8 632 { subdir => 'spiceproxy' },
7aa608d6 633 { subdir => 'sendkey' },
228a998b 634 { subdir => 'firewall' },
1e3baf05 635 ];
afdb31d5 636
1e3baf05
DM
637 return $res;
638 }});
639
228a998b 640__PACKAGE__->register_method ({
f34ebd52 641 subclass => "PVE::API2::Firewall::VM",
228a998b
DM
642 path => '{vmid}/firewall',
643});
644
1e3baf05 645__PACKAGE__->register_method({
afdb31d5
DM
646 name => 'rrd',
647 path => '{vmid}/rrd',
1e3baf05
DM
648 method => 'GET',
649 protected => 1, # fixme: can we avoid that?
650 permissions => {
378b359e 651 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1e3baf05
DM
652 },
653 description => "Read VM RRD statistics (returns PNG)",
654 parameters => {
655 additionalProperties => 0,
656 properties => {
657 node => get_standard_option('pve-node'),
658 vmid => get_standard_option('pve-vmid'),
659 timeframe => {
660 description => "Specify the time frame you are interested in.",
661 type => 'string',
662 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
663 },
664 ds => {
665 description => "The list of datasources you want to display.",
666 type => 'string', format => 'pve-configid-list',
667 },
668 cf => {
669 description => "The RRD consolidation function",
670 type => 'string',
671 enum => [ 'AVERAGE', 'MAX' ],
672 optional => 1,
673 },
674 },
675 },
676 returns => {
677 type => "object",
678 properties => {
679 filename => { type => 'string' },
680 },
681 },
682 code => sub {
683 my ($param) = @_;
684
685 return PVE::Cluster::create_rrd_graph(
afdb31d5 686 "pve2-vm/$param->{vmid}", $param->{timeframe},
1e3baf05 687 $param->{ds}, $param->{cf});
afdb31d5 688
1e3baf05
DM
689 }});
690
691__PACKAGE__->register_method({
afdb31d5
DM
692 name => 'rrddata',
693 path => '{vmid}/rrddata',
1e3baf05
DM
694 method => 'GET',
695 protected => 1, # fixme: can we avoid that?
696 permissions => {
378b359e 697 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1e3baf05
DM
698 },
699 description => "Read VM RRD statistics",
700 parameters => {
701 additionalProperties => 0,
702 properties => {
703 node => get_standard_option('pve-node'),
704 vmid => get_standard_option('pve-vmid'),
705 timeframe => {
706 description => "Specify the time frame you are interested in.",
707 type => 'string',
708 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
709 },
710 cf => {
711 description => "The RRD consolidation function",
712 type => 'string',
713 enum => [ 'AVERAGE', 'MAX' ],
714 optional => 1,
715 },
716 },
717 },
718 returns => {
719 type => "array",
720 items => {
721 type => "object",
722 properties => {},
723 },
724 },
725 code => sub {
726 my ($param) = @_;
727
728 return PVE::Cluster::create_rrd_data(
729 "pve2-vm/$param->{vmid}", $param->{timeframe}, $param->{cf});
730 }});
731
732
733__PACKAGE__->register_method({
afdb31d5
DM
734 name => 'vm_config',
735 path => '{vmid}/config',
1e3baf05
DM
736 method => 'GET',
737 proxyto => 'node',
1e7f2726 738 description => "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
a0d1b1a2
DM
739 permissions => {
740 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
741 },
1e3baf05
DM
742 parameters => {
743 additionalProperties => 0,
744 properties => {
745 node => get_standard_option('pve-node'),
335af808 746 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
6d89b548
DM
747 current => {
748 description => "Get current values (instead of pending values).",
749 optional => 1,
750 default => 0,
751 type => 'boolean',
752 },
1e3baf05
DM
753 },
754 },
afdb31d5 755 returns => {
1e3baf05 756 type => "object",
554ac7e7
DM
757 properties => {
758 digest => {
759 type => 'string',
760 description => 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
761 }
762 },
1e3baf05
DM
763 },
764 code => sub {
765 my ($param) = @_;
766
ffda963f 767 my $conf = PVE::QemuConfig->load_config($param->{vmid});
1e3baf05 768
22c377f0 769 delete $conf->{snapshots};
6d89b548
DM
770
771 if (!$param->{current}) {
025e1d90 772 foreach my $opt (keys %{$conf->{pending}}) {
6d89b548
DM
773 next if $opt eq 'delete';
774 my $value = $conf->{pending}->{$opt};
775 next if ref($value); # just to be sure
776 $conf->{$opt} = $value;
777 }
1bc483f6
WB
778 my $pending_delete_hash = PVE::QemuServer::split_flagged_list($conf->{pending}->{delete});
779 foreach my $opt (keys %$pending_delete_hash) {
6d89b548
DM
780 delete $conf->{$opt} if $conf->{$opt};
781 }
782 }
783
1e7f2726 784 delete $conf->{pending};
22c377f0 785
1e3baf05
DM
786 return $conf;
787 }});
788
1e7f2726
DM
789__PACKAGE__->register_method({
790 name => 'vm_pending',
791 path => '{vmid}/pending',
792 method => 'GET',
793 proxyto => 'node',
794 description => "Get virtual machine configuration, including pending changes.",
795 permissions => {
796 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
797 },
798 parameters => {
799 additionalProperties => 0,
800 properties => {
801 node => get_standard_option('pve-node'),
335af808 802 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
1e7f2726
DM
803 },
804 },
805 returns => {
806 type => "array",
807 items => {
808 type => "object",
809 properties => {
810 key => {
811 description => "Configuration option name.",
812 type => 'string',
813 },
814 value => {
815 description => "Current value.",
816 type => 'string',
817 optional => 1,
818 },
819 pending => {
820 description => "Pending value.",
821 type => 'string',
822 optional => 1,
823 },
824 delete => {
1bc483f6
WB
825 description => "Indicates a pending delete request if present and not 0. " .
826 "The value 2 indicates a force-delete request.",
827 type => 'integer',
828 minimum => 0,
829 maximum => 2,
1e7f2726
DM
830 optional => 1,
831 },
832 },
833 },
834 },
835 code => sub {
836 my ($param) = @_;
837
ffda963f 838 my $conf = PVE::QemuConfig->load_config($param->{vmid});
1e7f2726 839
1bc483f6 840 my $pending_delete_hash = PVE::QemuServer::split_flagged_list($conf->{pending}->{delete});
1e7f2726
DM
841
842 my $res = [];
843
025e1d90 844 foreach my $opt (keys %$conf) {
1e7f2726
DM
845 next if ref($conf->{$opt});
846 my $item = { key => $opt };
847 $item->{value} = $conf->{$opt} if defined($conf->{$opt});
848 $item->{pending} = $conf->{pending}->{$opt} if defined($conf->{pending}->{$opt});
1bc483f6 849 $item->{delete} = ($pending_delete_hash->{$opt} ? 2 : 1) if exists $pending_delete_hash->{$opt};
1e7f2726
DM
850 push @$res, $item;
851 }
852
025e1d90 853 foreach my $opt (keys %{$conf->{pending}}) {
1e7f2726
DM
854 next if $opt eq 'delete';
855 next if ref($conf->{pending}->{$opt}); # just to be sure
0e54e1c8 856 next if defined($conf->{$opt});
1e7f2726
DM
857 my $item = { key => $opt };
858 $item->{pending} = $conf->{pending}->{$opt};
859 push @$res, $item;
860 }
861
1bc483f6 862 while (my ($opt, $force) = each %$pending_delete_hash) {
1e7f2726
DM
863 next if $conf->{pending}->{$opt}; # just to be sure
864 next if $conf->{$opt};
1bc483f6 865 my $item = { key => $opt, delete => ($force ? 2 : 1)};
1e7f2726
DM
866 push @$res, $item;
867 }
868
869 return $res;
870 }});
871
5555edea
DM
872# POST/PUT {vmid}/config implementation
873#
874# The original API used PUT (idempotent) an we assumed that all operations
875# are fast. But it turned out that almost any configuration change can
876# involve hot-plug actions, or disk alloc/free. Such actions can take long
877# time to complete and have side effects (not idempotent).
878#
7043d946 879# The new implementation uses POST and forks a worker process. We added
5555edea 880# a new option 'background_delay'. If specified we wait up to
7043d946 881# 'background_delay' second for the worker task to complete. It returns null
5555edea 882# if the task is finished within that time, else we return the UPID.
7043d946 883
5555edea
DM
884my $update_vm_api = sub {
885 my ($param, $sync) = @_;
a0d1b1a2 886
5555edea 887 my $rpcenv = PVE::RPCEnvironment::get();
1e3baf05 888
5555edea 889 my $authuser = $rpcenv->get_user();
1e3baf05 890
5555edea 891 my $node = extract_param($param, 'node');
1e3baf05 892
5555edea 893 my $vmid = extract_param($param, 'vmid');
1e3baf05 894
5555edea 895 my $digest = extract_param($param, 'digest');
1e3baf05 896
5555edea 897 my $background_delay = extract_param($param, 'background_delay');
1e3baf05 898
5555edea 899 my @paramarr = (); # used for log message
edd48c32 900 foreach my $key (sort keys %$param) {
c13e17d0 901 push @paramarr, "-$key", $param->{$key};
5555edea 902 }
0532bc63 903
5555edea
DM
904 my $skiplock = extract_param($param, 'skiplock');
905 raise_param_exc({ skiplock => "Only root may use this option." })
906 if $skiplock && $authuser ne 'root@pam';
1e3baf05 907
5555edea 908 my $delete_str = extract_param($param, 'delete');
0532bc63 909
d3df8cf3
DM
910 my $revert_str = extract_param($param, 'revert');
911
5555edea 912 my $force = extract_param($param, 'force');
1e68cb19 913
d3df8cf3 914 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
7bfdeb5f 915
5555edea 916 my $storecfg = PVE::Storage::config();
1e68cb19 917
5555edea 918 my $defaults = PVE::QemuServer::load_defaults();
1e68cb19 919
5555edea 920 &$resolve_cdrom_alias($param);
0532bc63 921
5555edea 922 # now try to verify all parameters
ae57f6b3 923
d3df8cf3
DM
924 my $revert = {};
925 foreach my $opt (PVE::Tools::split_list($revert_str)) {
926 if (!PVE::QemuServer::option_exists($opt)) {
927 raise_param_exc({ revert => "unknown option '$opt'" });
928 }
929
930 raise_param_exc({ delete => "you can't use '-$opt' and " .
931 "-revert $opt' at the same time" })
932 if defined($param->{$opt});
933
934 $revert->{$opt} = 1;
935 }
936
5555edea
DM
937 my @delete = ();
938 foreach my $opt (PVE::Tools::split_list($delete_str)) {
939 $opt = 'ide2' if $opt eq 'cdrom';
d3df8cf3 940
5555edea
DM
941 raise_param_exc({ delete => "you can't use '-$opt' and " .
942 "-delete $opt' at the same time" })
943 if defined($param->{$opt});
7043d946 944
d3df8cf3
DM
945 raise_param_exc({ revert => "you can't use '-delete $opt' and " .
946 "-revert $opt' at the same time" })
947 if $revert->{$opt};
948
5555edea
DM
949 if (!PVE::QemuServer::option_exists($opt)) {
950 raise_param_exc({ delete => "unknown option '$opt'" });
0532bc63 951 }
1e3baf05 952
5555edea
DM
953 push @delete, $opt;
954 }
955
17677004
WB
956 my $repl_conf = PVE::ReplicationConfig->new();
957 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
958 my $check_replication = sub {
959 my ($drive) = @_;
960 return if !$is_replicated;
961 my $volid = $drive->{file};
962 return if !$volid || !($drive->{replicate}//1);
963 return if PVE::QemuServer::drive_is_cdrom($drive);
964 my ($storeid, $format);
965 if ($volid =~ $NEW_DISK_RE) {
966 $storeid = $2;
967 $format = $drive->{format} || PVE::Storage::storage_default_format($storecfg, $storeid);
968 } else {
969 ($storeid, undef) = PVE::Storage::parse_volume_id($volid, 1);
970 $format = (PVE::Storage::parse_volname($storecfg, $volid))[6];
971 }
972 return if PVE::Storage::storage_can_replicate($storecfg, $storeid, $format);
9b1396ed
WB
973 my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
974 return if $scfg->{shared};
17677004
WB
975 die "cannot add non-replicatable volume to a replicated VM\n";
976 };
977
5555edea 978 foreach my $opt (keys %$param) {
74479ee9 979 if (PVE::QemuServer::is_valid_drivename($opt)) {
5555edea
DM
980 # cleanup drive path
981 my $drive = PVE::QemuServer::parse_drive($opt, $param->{$opt});
f9091201 982 raise_param_exc({ $opt => "unable to parse drive options" }) if !$drive;
5555edea 983 PVE::QemuServer::cleanup_drive_path($opt, $storecfg, $drive);
17677004 984 $check_replication->($drive);
5555edea
DM
985 $param->{$opt} = PVE::QemuServer::print_drive($vmid, $drive);
986 } elsif ($opt =~ m/^net(\d+)$/) {
987 # add macaddr
988 my $net = PVE::QemuServer::parse_net($param->{$opt});
989 $param->{$opt} = PVE::QemuServer::print_net($net);
1e68cb19 990 }
5555edea 991 }
1e3baf05 992
5555edea 993 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
ae57f6b3 994
5555edea 995 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
ae57f6b3 996
5555edea 997 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1e3baf05 998
5555edea 999 my $updatefn = sub {
1e3baf05 1000
ffda963f 1001 my $conf = PVE::QemuConfig->load_config($vmid);
1e3baf05 1002
5555edea
DM
1003 die "checksum missmatch (file change by other user?)\n"
1004 if $digest && $digest ne $conf->{digest};
1005
ffda963f 1006 PVE::QemuConfig->check_lock($conf) if !$skiplock;
7043d946 1007
d3df8cf3
DM
1008 foreach my $opt (keys %$revert) {
1009 if (defined($conf->{$opt})) {
1010 $param->{$opt} = $conf->{$opt};
1011 } elsif (defined($conf->{pending}->{$opt})) {
1012 push @delete, $opt;
1013 }
1014 }
1015
5555edea 1016 if ($param->{memory} || defined($param->{balloon})) {
6ca8b698
DM
1017 my $maxmem = $param->{memory} || $conf->{pending}->{memory} || $conf->{memory} || $defaults->{memory};
1018 my $balloon = defined($param->{balloon}) ? $param->{balloon} : $conf->{pending}->{balloon} || $conf->{balloon};
7043d946 1019
5555edea
DM
1020 die "balloon value too large (must be smaller than assigned memory)\n"
1021 if $balloon && $balloon > $maxmem;
1022 }
1e3baf05 1023
5555edea 1024 PVE::Cluster::log_msg('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1e3baf05 1025
5555edea 1026 my $worker = sub {
7bfdeb5f 1027
5555edea 1028 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
c2a64aa7 1029
202d1f45
DM
1030 # write updates to pending section
1031
3a11fadb
DM
1032 my $modified = {}; # record what $option we modify
1033
202d1f45 1034 foreach my $opt (@delete) {
3a11fadb 1035 $modified->{$opt} = 1;
ffda963f 1036 $conf = PVE::QemuConfig->load_config($vmid); # update/reload
af6d2db4 1037 if (!defined($conf->{$opt}) && !defined($conf->{pending}->{$opt})) {
d2c6bf93
FG
1038 warn "cannot delete '$opt' - not set in current configuration!\n";
1039 $modified->{$opt} = 0;
1040 next;
1041 }
1042
202d1f45 1043 if ($opt =~ m/^unused/) {
202d1f45 1044 my $drive = PVE::QemuServer::parse_drive($opt, $conf->{$opt});
ffda963f 1045 PVE::QemuConfig->check_protection($conf, "can't remove unused disk '$drive->{file}'");
4d8d55f1 1046 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
3dc38fbb
WB
1047 if (PVE::QemuServer::try_deallocate_drive($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1048 delete $conf->{$opt};
ffda963f 1049 PVE::QemuConfig->write_config($vmid, $conf);
202d1f45 1050 }
74479ee9 1051 } elsif (PVE::QemuServer::is_valid_drivename($opt)) {
ffda963f 1052 PVE::QemuConfig->check_protection($conf, "can't remove drive '$opt'");
202d1f45 1053 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
055d554d 1054 PVE::QemuServer::vmconfig_register_unused_drive($storecfg, $vmid, $conf, PVE::QemuServer::parse_drive($opt, $conf->{pending}->{$opt}))
202d1f45 1055 if defined($conf->{pending}->{$opt});
3dc38fbb 1056 PVE::QemuServer::vmconfig_delete_pending_option($conf, $opt, $force);
ffda963f 1057 PVE::QemuConfig->write_config($vmid, $conf);
202d1f45 1058 } else {
3dc38fbb 1059 PVE::QemuServer::vmconfig_delete_pending_option($conf, $opt, $force);
ffda963f 1060 PVE::QemuConfig->write_config($vmid, $conf);
202d1f45 1061 }
5d39a182 1062 }
1e3baf05 1063
202d1f45 1064 foreach my $opt (keys %$param) { # add/change
3a11fadb 1065 $modified->{$opt} = 1;
ffda963f 1066 $conf = PVE::QemuConfig->load_config($vmid); # update/reload
202d1f45
DM
1067 next if defined($conf->{pending}->{$opt}) && ($param->{$opt} eq $conf->{pending}->{$opt}); # skip if nothing changed
1068
74479ee9 1069 if (PVE::QemuServer::is_valid_drivename($opt)) {
202d1f45
DM
1070 my $drive = PVE::QemuServer::parse_drive($opt, $param->{$opt});
1071 if (PVE::QemuServer::drive_is_cdrom($drive)) { # CDROM
1072 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1073 } else {
1074 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1075 }
055d554d 1076 PVE::QemuServer::vmconfig_register_unused_drive($storecfg, $vmid, $conf, PVE::QemuServer::parse_drive($opt, $conf->{pending}->{$opt}))
202d1f45
DM
1077 if defined($conf->{pending}->{$opt});
1078
1079 &$create_disks($rpcenv, $authuser, $conf->{pending}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1080 } else {
1081 $conf->{pending}->{$opt} = $param->{$opt};
1082 }
055d554d 1083 PVE::QemuServer::vmconfig_undelete_pending_option($conf, $opt);
ffda963f 1084 PVE::QemuConfig->write_config($vmid, $conf);
202d1f45
DM
1085 }
1086
1087 # remove pending changes when nothing changed
ffda963f 1088 $conf = PVE::QemuConfig->load_config($vmid); # update/reload
c750e90a 1089 my $changes = PVE::QemuServer::vmconfig_cleanup_pending($conf);
ffda963f 1090 PVE::QemuConfig->write_config($vmid, $conf) if $changes;
202d1f45
DM
1091
1092 return if !scalar(keys %{$conf->{pending}});
1093
7bfdeb5f 1094 my $running = PVE::QemuServer::check_running($vmid);
39001640
DM
1095
1096 # apply pending changes
1097
ffda963f 1098 $conf = PVE::QemuConfig->load_config($vmid); # update/reload
39001640 1099
3a11fadb
DM
1100 if ($running) {
1101 my $errors = {};
1102 PVE::QemuServer::vmconfig_hotplug_pending($vmid, $conf, $storecfg, $modified, $errors);
1103 raise_param_exc($errors) if scalar(keys %$errors);
1104 } else {
1105 PVE::QemuServer::vmconfig_apply_pending($vmid, $conf, $storecfg, $running);
1106 }
1e68cb19 1107
915d3481 1108 return;
5d39a182
DM
1109 };
1110
5555edea
DM
1111 if ($sync) {
1112 &$worker();
1113 return undef;
1114 } else {
1115 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
fcdb0117 1116
5555edea
DM
1117 if ($background_delay) {
1118
1119 # Note: It would be better to do that in the Event based HTTPServer
7043d946 1120 # to avoid blocking call to sleep.
5555edea
DM
1121
1122 my $end_time = time() + $background_delay;
1123
1124 my $task = PVE::Tools::upid_decode($upid);
1125
1126 my $running = 1;
1127 while (time() < $end_time) {
1128 $running = PVE::ProcFSTools::check_process_running($task->{pid}, $task->{pstart});
1129 last if !$running;
1130 sleep(1); # this gets interrupted when child process ends
1131 }
1132
1133 if (!$running) {
1134 my $status = PVE::Tools::upid_read_status($upid);
1135 return undef if $status eq 'OK';
1136 die $status;
1137 }
7043d946 1138 }
5555edea
DM
1139
1140 return $upid;
1141 }
1142 };
1143
ffda963f 1144 return PVE::QemuConfig->lock_config($vmid, $updatefn);
5555edea
DM
1145};
1146
1147my $vm_config_perm_list = [
1148 'VM.Config.Disk',
1149 'VM.Config.CDROM',
1150 'VM.Config.CPU',
1151 'VM.Config.Memory',
1152 'VM.Config.Network',
1153 'VM.Config.HWType',
1154 'VM.Config.Options',
1155 ];
1156
1157__PACKAGE__->register_method({
1158 name => 'update_vm_async',
1159 path => '{vmid}/config',
1160 method => 'POST',
1161 protected => 1,
1162 proxyto => 'node',
1163 description => "Set virtual machine options (asynchrounous API).",
1164 permissions => {
1165 check => ['perm', '/vms/{vmid}', $vm_config_perm_list, any => 1],
1166 },
1167 parameters => {
1168 additionalProperties => 0,
1169 properties => PVE::QemuServer::json_config_properties(
1170 {
1171 node => get_standard_option('pve-node'),
1172 vmid => get_standard_option('pve-vmid'),
1173 skiplock => get_standard_option('skiplock'),
1174 delete => {
1175 type => 'string', format => 'pve-configid-list',
1176 description => "A list of settings you want to delete.",
1177 optional => 1,
1178 },
4c8365fa
DM
1179 revert => {
1180 type => 'string', format => 'pve-configid-list',
1181 description => "Revert a pending change.",
1182 optional => 1,
1183 },
5555edea
DM
1184 force => {
1185 type => 'boolean',
1186 description => $opt_force_description,
1187 optional => 1,
1188 requires => 'delete',
1189 },
1190 digest => {
1191 type => 'string',
1192 description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1193 maxLength => 40,
1194 optional => 1,
1195 },
1196 background_delay => {
1197 type => 'integer',
1198 description => "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1199 minimum => 1,
1200 maximum => 30,
1201 optional => 1,
1202 },
1203 }),
1204 },
1205 returns => {
1206 type => 'string',
1207 optional => 1,
1208 },
1209 code => $update_vm_api,
1210});
1211
1212__PACKAGE__->register_method({
1213 name => 'update_vm',
1214 path => '{vmid}/config',
1215 method => 'PUT',
1216 protected => 1,
1217 proxyto => 'node',
1218 description => "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1219 permissions => {
1220 check => ['perm', '/vms/{vmid}', $vm_config_perm_list, any => 1],
1221 },
1222 parameters => {
1223 additionalProperties => 0,
1224 properties => PVE::QemuServer::json_config_properties(
1225 {
1226 node => get_standard_option('pve-node'),
335af808 1227 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
5555edea
DM
1228 skiplock => get_standard_option('skiplock'),
1229 delete => {
1230 type => 'string', format => 'pve-configid-list',
1231 description => "A list of settings you want to delete.",
1232 optional => 1,
1233 },
4c8365fa
DM
1234 revert => {
1235 type => 'string', format => 'pve-configid-list',
1236 description => "Revert a pending change.",
1237 optional => 1,
1238 },
5555edea
DM
1239 force => {
1240 type => 'boolean',
1241 description => $opt_force_description,
1242 optional => 1,
1243 requires => 'delete',
1244 },
1245 digest => {
1246 type => 'string',
1247 description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1248 maxLength => 40,
1249 optional => 1,
1250 },
1251 }),
1252 },
1253 returns => { type => 'null' },
1254 code => sub {
1255 my ($param) = @_;
1256 &$update_vm_api($param, 1);
1e3baf05 1257 return undef;
5555edea
DM
1258 }
1259});
1e3baf05
DM
1260
1261
1262__PACKAGE__->register_method({
afdb31d5
DM
1263 name => 'destroy_vm',
1264 path => '{vmid}',
1e3baf05
DM
1265 method => 'DELETE',
1266 protected => 1,
1267 proxyto => 'node',
1268 description => "Destroy the vm (also delete all used/owned volumes).",
a0d1b1a2
DM
1269 permissions => {
1270 check => [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1271 },
1e3baf05
DM
1272 parameters => {
1273 additionalProperties => 0,
1274 properties => {
1275 node => get_standard_option('pve-node'),
335af808 1276 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid_stopped }),
3ea94c60 1277 skiplock => get_standard_option('skiplock'),
1e3baf05
DM
1278 },
1279 },
afdb31d5 1280 returns => {
5fdbe4f0
DM
1281 type => 'string',
1282 },
1e3baf05
DM
1283 code => sub {
1284 my ($param) = @_;
1285
1286 my $rpcenv = PVE::RPCEnvironment::get();
1287
a0d1b1a2 1288 my $authuser = $rpcenv->get_user();
1e3baf05
DM
1289
1290 my $vmid = $param->{vmid};
1291
1292 my $skiplock = $param->{skiplock};
afdb31d5 1293 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 1294 if $skiplock && $authuser ne 'root@pam';
1e3baf05 1295
5fdbe4f0 1296 # test if VM exists
ffda963f 1297 my $conf = PVE::QemuConfig->load_config($vmid);
5fdbe4f0 1298
afdb31d5 1299 my $storecfg = PVE::Storage::config();
1e3baf05 1300
ffda963f 1301 PVE::QemuConfig->check_protection($conf, "can't remove VM $vmid");
cb0e4540 1302
952e3ac3
DM
1303 die "unable to remove VM $vmid - used in HA resources\n"
1304 if PVE::HA::Config::vm_is_ha_managed($vmid);
e9f2f8e5 1305
628bb7f2
DM
1306 # do not allow destroy if there are replication jobs
1307 my $repl_conf = PVE::ReplicationConfig->new();
1308 $repl_conf->check_for_existing_jobs($vmid);
1309
db593da2
DM
1310 # early tests (repeat after locking)
1311 die "VM $vmid is running - destroy failed\n"
1312 if PVE::QemuServer::check_running($vmid);
1313
5fdbe4f0 1314 my $realcmd = sub {
ff1a2432
DM
1315 my $upid = shift;
1316
1317 syslog('info', "destroy VM $vmid: $upid\n");
1318
5fdbe4f0 1319 PVE::QemuServer::vm_destroy($storecfg, $vmid, $skiplock);
502d18a2 1320
37f43805 1321 PVE::AccessControl::remove_vm_access($vmid);
e9abcde6
AG
1322
1323 PVE::Firewall::remove_vmfw_conf($vmid);
5fdbe4f0 1324 };
1e3baf05 1325
a0d1b1a2 1326 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1e3baf05
DM
1327 }});
1328
1329__PACKAGE__->register_method({
afdb31d5
DM
1330 name => 'unlink',
1331 path => '{vmid}/unlink',
1e3baf05
DM
1332 method => 'PUT',
1333 protected => 1,
1334 proxyto => 'node',
1335 description => "Unlink/delete disk images.",
a0d1b1a2
DM
1336 permissions => {
1337 check => [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1338 },
1e3baf05
DM
1339 parameters => {
1340 additionalProperties => 0,
1341 properties => {
1342 node => get_standard_option('pve-node'),
335af808 1343 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
1e3baf05
DM
1344 idlist => {
1345 type => 'string', format => 'pve-configid-list',
1346 description => "A list of disk IDs you want to delete.",
1347 },
1348 force => {
1349 type => 'boolean',
1350 description => $opt_force_description,
1351 optional => 1,
1352 },
1353 },
1354 },
1355 returns => { type => 'null'},
1356 code => sub {
1357 my ($param) = @_;
1358
1359 $param->{delete} = extract_param($param, 'idlist');
1360
1361 __PACKAGE__->update_vm($param);
1362
1363 return undef;
1364 }});
1365
1366my $sslcert;
1367
1368__PACKAGE__->register_method({
afdb31d5
DM
1369 name => 'vncproxy',
1370 path => '{vmid}/vncproxy',
1e3baf05
DM
1371 method => 'POST',
1372 protected => 1,
1373 permissions => {
378b359e 1374 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1e3baf05
DM
1375 },
1376 description => "Creates a TCP VNC proxy connections.",
1377 parameters => {
1378 additionalProperties => 0,
1379 properties => {
1380 node => get_standard_option('pve-node'),
1381 vmid => get_standard_option('pve-vmid'),
b4d5c000
SP
1382 websocket => {
1383 optional => 1,
1384 type => 'boolean',
1385 description => "starts websockify instead of vncproxy",
1386 },
1e3baf05
DM
1387 },
1388 },
afdb31d5 1389 returns => {
1e3baf05
DM
1390 additionalProperties => 0,
1391 properties => {
1392 user => { type => 'string' },
1393 ticket => { type => 'string' },
1394 cert => { type => 'string' },
1395 port => { type => 'integer' },
1396 upid => { type => 'string' },
1397 },
1398 },
1399 code => sub {
1400 my ($param) = @_;
1401
1402 my $rpcenv = PVE::RPCEnvironment::get();
1403
a0d1b1a2 1404 my $authuser = $rpcenv->get_user();
1e3baf05
DM
1405
1406 my $vmid = $param->{vmid};
1407 my $node = $param->{node};
983d4582 1408 my $websocket = $param->{websocket};
1e3baf05 1409
ffda963f 1410 my $conf = PVE::QemuConfig->load_config($vmid, $node); # check if VM exists
ef5e2be2 1411
b6f39da2
DM
1412 my $authpath = "/vms/$vmid";
1413
a0d1b1a2 1414 my $ticket = PVE::AccessControl::assemble_vnc_ticket($authuser, $authpath);
b6f39da2 1415
1e3baf05
DM
1416 $sslcert = PVE::Tools::file_get_contents("/etc/pve/pve-root-ca.pem", 8192)
1417 if !$sslcert;
1418
af0eba7e 1419 my ($remip, $family);
ef5e2be2 1420 my $remcmd = [];
afdb31d5 1421
4f1be36c 1422 if ($node ne 'localhost' && $node ne PVE::INotify::nodename()) {
af0eba7e 1423 ($remip, $family) = PVE::Cluster::remote_node_ip($node);
b4d5c000 1424 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
ef5e2be2 1425 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
af0eba7e
WB
1426 } else {
1427 $family = PVE::Tools::get_host_address_family($node);
1e3baf05
DM
1428 }
1429
af0eba7e
WB
1430 my $port = PVE::Tools::next_vnc_port($family);
1431
afdb31d5 1432 my $timeout = 10;
1e3baf05
DM
1433
1434 my $realcmd = sub {
1435 my $upid = shift;
1436
1437 syslog('info', "starting vnc proxy $upid\n");
1438
ef5e2be2 1439 my $cmd;
1e3baf05 1440
2dc23d72 1441 if ($conf->{vga} && ($conf->{vga} =~ m/^serial\d+$/)) {
ef5e2be2 1442
983d4582 1443 die "Websocket mode is not supported in vga serial mode!" if $websocket;
b4d5c000 1444
ef5e2be2
DM
1445 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga} ];
1446 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1447 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
fa8ea931 1448 '-timeout', $timeout, '-authpath', $authpath,
ef5e2be2 1449 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
655d7462 1450 PVE::Tools::run_command($cmd);
ef5e2be2 1451 } else {
1e3baf05 1452
3e7567e0
DM
1453 $ENV{LC_PVE_TICKET} = $ticket if $websocket; # set ticket with "qm vncproxy"
1454
655d7462
WB
1455 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1456
1457 my $sock = IO::Socket::IP->new(
dd32a466 1458 ReuseAddr => 1,
655d7462
WB
1459 Listen => 1,
1460 LocalPort => $port,
1461 Proto => 'tcp',
1462 GetAddrInfoFlags => 0,
1463 ) or die "failed to create socket: $!\n";
1464 # Inside the worker we shouldn't have any previous alarms
1465 # running anyway...:
1466 alarm(0);
1467 local $SIG{ALRM} = sub { die "connection timed out\n" };
1468 alarm $timeout;
1469 accept(my $cli, $sock) or die "connection failed: $!\n";
058ff55b 1470 alarm(0);
655d7462
WB
1471 close($sock);
1472 if (PVE::Tools::run_command($cmd,
1473 output => '>&'.fileno($cli),
1474 input => '<&'.fileno($cli),
1475 noerr => 1) != 0)
1476 {
1477 die "Failed to run vncproxy.\n";
1478 }
ef5e2be2 1479 }
1e3baf05 1480
1e3baf05
DM
1481 return;
1482 };
1483
2c7fc947 1484 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1e3baf05 1485
3da85107
DM
1486 PVE::Tools::wait_for_vnc_port($port);
1487
1e3baf05 1488 return {
a0d1b1a2 1489 user => $authuser,
1e3baf05 1490 ticket => $ticket,
afdb31d5
DM
1491 port => $port,
1492 upid => $upid,
1493 cert => $sslcert,
1e3baf05
DM
1494 };
1495 }});
1496
3e7567e0
DM
1497__PACKAGE__->register_method({
1498 name => 'vncwebsocket',
1499 path => '{vmid}/vncwebsocket',
1500 method => 'GET',
3e7567e0 1501 permissions => {
c422ce93 1502 description => "You also need to pass a valid ticket (vncticket).",
3e7567e0
DM
1503 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1504 },
4d00f52f 1505 description => "Opens a weksocket for VNC traffic.",
3e7567e0
DM
1506 parameters => {
1507 additionalProperties => 0,
1508 properties => {
1509 node => get_standard_option('pve-node'),
1510 vmid => get_standard_option('pve-vmid'),
c422ce93
DM
1511 vncticket => {
1512 description => "Ticket from previous call to vncproxy.",
1513 type => 'string',
1514 maxLength => 512,
1515 },
3e7567e0
DM
1516 port => {
1517 description => "Port number returned by previous vncproxy call.",
1518 type => 'integer',
1519 minimum => 5900,
1520 maximum => 5999,
1521 },
1522 },
1523 },
1524 returns => {
1525 type => "object",
1526 properties => {
1527 port => { type => 'string' },
1528 },
1529 },
1530 code => sub {
1531 my ($param) = @_;
1532
1533 my $rpcenv = PVE::RPCEnvironment::get();
1534
1535 my $authuser = $rpcenv->get_user();
1536
1537 my $vmid = $param->{vmid};
1538 my $node = $param->{node};
1539
c422ce93
DM
1540 my $authpath = "/vms/$vmid";
1541
1542 PVE::AccessControl::verify_vnc_ticket($param->{vncticket}, $authuser, $authpath);
1543
ffda963f 1544 my $conf = PVE::QemuConfig->load_config($vmid, $node); # VM exists ?
3e7567e0
DM
1545
1546 # Note: VNC ports are acessible from outside, so we do not gain any
1547 # security if we verify that $param->{port} belongs to VM $vmid. This
1548 # check is done by verifying the VNC ticket (inside VNC protocol).
1549
1550 my $port = $param->{port};
f34ebd52 1551
3e7567e0
DM
1552 return { port => $port };
1553 }});
1554
288eeea8
DM
1555__PACKAGE__->register_method({
1556 name => 'spiceproxy',
1557 path => '{vmid}/spiceproxy',
78252ce7 1558 method => 'POST',
288eeea8 1559 protected => 1,
78252ce7 1560 proxyto => 'node',
288eeea8
DM
1561 permissions => {
1562 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1563 },
1564 description => "Returns a SPICE configuration to connect to the VM.",
1565 parameters => {
1566 additionalProperties => 0,
1567 properties => {
1568 node => get_standard_option('pve-node'),
1569 vmid => get_standard_option('pve-vmid'),
dd25eecf 1570 proxy => get_standard_option('spice-proxy', { optional => 1 }),
288eeea8
DM
1571 },
1572 },
dd25eecf 1573 returns => get_standard_option('remote-viewer-config'),
288eeea8
DM
1574 code => sub {
1575 my ($param) = @_;
1576
1577 my $rpcenv = PVE::RPCEnvironment::get();
1578
1579 my $authuser = $rpcenv->get_user();
1580
1581 my $vmid = $param->{vmid};
1582 my $node = $param->{node};
fb6c7260 1583 my $proxy = $param->{proxy};
288eeea8 1584
ffda963f 1585 my $conf = PVE::QemuConfig->load_config($vmid, $node);
7f9e28e4
TL
1586 my $title = "VM $vmid";
1587 $title .= " - ". $conf->{name} if $conf->{name};
288eeea8 1588
943340a6 1589 my $port = PVE::QemuServer::spice_port($vmid);
dd25eecf 1590
f34ebd52 1591 my ($ticket, undef, $remote_viewer_config) =
dd25eecf 1592 PVE::AccessControl::remote_viewer_config($authuser, $vmid, $node, $proxy, $title, $port);
f34ebd52 1593
288eeea8
DM
1594 PVE::QemuServer::vm_mon_cmd($vmid, "set_password", protocol => 'spice', password => $ticket);
1595 PVE::QemuServer::vm_mon_cmd($vmid, "expire_password", protocol => 'spice', time => "+30");
f34ebd52 1596
dd25eecf 1597 return $remote_viewer_config;
288eeea8
DM
1598 }});
1599
5fdbe4f0
DM
1600__PACKAGE__->register_method({
1601 name => 'vmcmdidx',
afdb31d5 1602 path => '{vmid}/status',
5fdbe4f0
DM
1603 method => 'GET',
1604 proxyto => 'node',
1605 description => "Directory index",
a0d1b1a2
DM
1606 permissions => {
1607 user => 'all',
1608 },
5fdbe4f0
DM
1609 parameters => {
1610 additionalProperties => 0,
1611 properties => {
1612 node => get_standard_option('pve-node'),
1613 vmid => get_standard_option('pve-vmid'),
1614 },
1615 },
1616 returns => {
1617 type => 'array',
1618 items => {
1619 type => "object",
1620 properties => {
1621 subdir => { type => 'string' },
1622 },
1623 },
1624 links => [ { rel => 'child', href => "{subdir}" } ],
1625 },
1626 code => sub {
1627 my ($param) = @_;
1628
1629 # test if VM exists
ffda963f 1630 my $conf = PVE::QemuConfig->load_config($param->{vmid});
5fdbe4f0
DM
1631
1632 my $res = [
1633 { subdir => 'current' },
1634 { subdir => 'start' },
1635 { subdir => 'stop' },
1636 ];
afdb31d5 1637
5fdbe4f0
DM
1638 return $res;
1639 }});
1640
1e3baf05 1641__PACKAGE__->register_method({
afdb31d5 1642 name => 'vm_status',
5fdbe4f0 1643 path => '{vmid}/status/current',
1e3baf05
DM
1644 method => 'GET',
1645 proxyto => 'node',
1646 protected => 1, # qemu pid files are only readable by root
1647 description => "Get virtual machine status.",
a0d1b1a2
DM
1648 permissions => {
1649 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1650 },
1e3baf05
DM
1651 parameters => {
1652 additionalProperties => 0,
1653 properties => {
1654 node => get_standard_option('pve-node'),
1655 vmid => get_standard_option('pve-vmid'),
1656 },
1657 },
1658 returns => { type => 'object' },
1659 code => sub {
1660 my ($param) = @_;
1661
1662 # test if VM exists
ffda963f 1663 my $conf = PVE::QemuConfig->load_config($param->{vmid});
1e3baf05 1664
03a33f30 1665 my $vmstatus = PVE::QemuServer::vmstatus($param->{vmid}, 1);
8610701a 1666 my $status = $vmstatus->{$param->{vmid}};
1e3baf05 1667
4d2a734e 1668 $status->{ha} = PVE::HA::Config::get_service_status("vm:$param->{vmid}");
8610701a 1669
86b8228b 1670 $status->{spice} = 1 if PVE::QemuServer::vga_conf_has_spice($conf->{vga});
46246f04 1671
8610701a 1672 return $status;
1e3baf05
DM
1673 }});
1674
1675__PACKAGE__->register_method({
afdb31d5 1676 name => 'vm_start',
5fdbe4f0
DM
1677 path => '{vmid}/status/start',
1678 method => 'POST',
1e3baf05
DM
1679 protected => 1,
1680 proxyto => 'node',
5fdbe4f0 1681 description => "Start virtual machine.",
a0d1b1a2
DM
1682 permissions => {
1683 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1684 },
1e3baf05
DM
1685 parameters => {
1686 additionalProperties => 0,
1687 properties => {
1688 node => get_standard_option('pve-node'),
ab5904f7
TL
1689 vmid => get_standard_option('pve-vmid',
1690 { completion => \&PVE::QemuServer::complete_vmid_stopped }),
3ea94c60
DM
1691 skiplock => get_standard_option('skiplock'),
1692 stateuri => get_standard_option('pve-qm-stateuri'),
7e8dcf2c 1693 migratedfrom => get_standard_option('pve-node',{ optional => 1 }),
2de2d6f7
TL
1694 migration_type => {
1695 type => 'string',
1696 enum => ['secure', 'insecure'],
1697 description => "Migration traffic is encrypted using an SSH " .
1698 "tunnel by default. On secure, completely private networks " .
1699 "this can be disabled to increase performance.",
1700 optional => 1,
1701 },
1702 migration_network => {
29ddbe70 1703 type => 'string', format => 'CIDR',
2de2d6f7
TL
1704 description => "CIDR of the (sub) network that is used for migration.",
1705 optional => 1,
1706 },
952958bc 1707 machine => get_standard_option('pve-qm-machine'),
2189246c 1708 targetstorage => {
bd2d5fe6 1709 description => "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
2189246c
AD
1710 type => 'string',
1711 optional => 1
1712 }
1e3baf05
DM
1713 },
1714 },
afdb31d5 1715 returns => {
5fdbe4f0
DM
1716 type => 'string',
1717 },
1e3baf05
DM
1718 code => sub {
1719 my ($param) = @_;
1720
1721 my $rpcenv = PVE::RPCEnvironment::get();
1722
a0d1b1a2 1723 my $authuser = $rpcenv->get_user();
1e3baf05
DM
1724
1725 my $node = extract_param($param, 'node');
1726
1e3baf05
DM
1727 my $vmid = extract_param($param, 'vmid');
1728
952958bc
DM
1729 my $machine = extract_param($param, 'machine');
1730
3ea94c60 1731 my $stateuri = extract_param($param, 'stateuri');
afdb31d5 1732 raise_param_exc({ stateuri => "Only root may use this option." })
a0d1b1a2 1733 if $stateuri && $authuser ne 'root@pam';
3ea94c60 1734
1e3baf05 1735 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 1736 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 1737 if $skiplock && $authuser ne 'root@pam';
1e3baf05 1738
7e8dcf2c
AD
1739 my $migratedfrom = extract_param($param, 'migratedfrom');
1740 raise_param_exc({ migratedfrom => "Only root may use this option." })
1741 if $migratedfrom && $authuser ne 'root@pam';
1742
2de2d6f7
TL
1743 my $migration_type = extract_param($param, 'migration_type');
1744 raise_param_exc({ migration_type => "Only root may use this option." })
1745 if $migration_type && $authuser ne 'root@pam';
1746
1747 my $migration_network = extract_param($param, 'migration_network');
1748 raise_param_exc({ migration_network => "Only root may use this option." })
1749 if $migration_network && $authuser ne 'root@pam';
1750
2189246c
AD
1751 my $targetstorage = extract_param($param, 'targetstorage');
1752 raise_param_exc({ targetstorage => "Only root may use this option." })
1753 if $targetstorage && $authuser ne 'root@pam';
1754
1755 raise_param_exc({ targetstorage => "targetstorage can only by used with migratedfrom." })
1756 if $targetstorage && !$migratedfrom;
1757
7c14dcae
DM
1758 # read spice ticket from STDIN
1759 my $spice_ticket;
1760 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type} eq 'cli')) {
a64d6146 1761 if (defined(my $line = <>)) {
760fb3c8
DM
1762 chomp $line;
1763 $spice_ticket = $line;
1764 }
7c14dcae
DM
1765 }
1766
98cbd0f4
WB
1767 PVE::Cluster::check_cfs_quorum();
1768
afdb31d5 1769 my $storecfg = PVE::Storage::config();
5fdbe4f0 1770
2003f0f8 1771 if (PVE::HA::Config::vm_is_ha_managed($vmid) && !$stateuri &&
cce37749 1772 $rpcenv->{type} ne 'ha') {
5fdbe4f0 1773
88fc87b4
DM
1774 my $hacmd = sub {
1775 my $upid = shift;
5fdbe4f0 1776
c44291cd 1777 my $service = "vm:$vmid";
5fdbe4f0 1778
2a7e2b82 1779 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
88fc87b4 1780
02765844 1781 print "Requesting HA start for VM $vmid\n";
88fc87b4
DM
1782
1783 PVE::Tools::run_command($cmd);
1784
1785 return;
1786 };
1787
1788 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1789
1790 } else {
1791
1792 my $realcmd = sub {
1793 my $upid = shift;
1794
1795 syslog('info', "start VM $vmid: $upid\n");
1796
fa8ea931 1797 PVE::QemuServer::vm_start($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
2189246c 1798 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
88fc87b4
DM
1799
1800 return;
1801 };
5fdbe4f0 1802
88fc87b4
DM
1803 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1804 }
5fdbe4f0
DM
1805 }});
1806
1807__PACKAGE__->register_method({
afdb31d5 1808 name => 'vm_stop',
5fdbe4f0
DM
1809 path => '{vmid}/status/stop',
1810 method => 'POST',
1811 protected => 1,
1812 proxyto => 'node',
346130b2 1813 description => "Stop virtual machine. The qemu process will exit immediately. This" .
d6c747ff 1814 "is akin to pulling the power plug of a running computer and may damage the VM data",
a0d1b1a2
DM
1815 permissions => {
1816 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1817 },
5fdbe4f0
DM
1818 parameters => {
1819 additionalProperties => 0,
1820 properties => {
1821 node => get_standard_option('pve-node'),
ab5904f7
TL
1822 vmid => get_standard_option('pve-vmid',
1823 { completion => \&PVE::QemuServer::complete_vmid_running }),
5fdbe4f0 1824 skiplock => get_standard_option('skiplock'),
debe8882 1825 migratedfrom => get_standard_option('pve-node', { optional => 1 }),
c6bb9502
DM
1826 timeout => {
1827 description => "Wait maximal timeout seconds.",
1828 type => 'integer',
1829 minimum => 0,
1830 optional => 1,
254575e9
DM
1831 },
1832 keepActive => {
94a17e1d 1833 description => "Do not deactivate storage volumes.",
254575e9
DM
1834 type => 'boolean',
1835 optional => 1,
1836 default => 0,
c6bb9502 1837 }
5fdbe4f0
DM
1838 },
1839 },
afdb31d5 1840 returns => {
5fdbe4f0
DM
1841 type => 'string',
1842 },
1843 code => sub {
1844 my ($param) = @_;
1845
1846 my $rpcenv = PVE::RPCEnvironment::get();
1847
a0d1b1a2 1848 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
1849
1850 my $node = extract_param($param, 'node');
1851
1852 my $vmid = extract_param($param, 'vmid');
1853
1854 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 1855 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 1856 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 1857
254575e9 1858 my $keepActive = extract_param($param, 'keepActive');
afdb31d5 1859 raise_param_exc({ keepActive => "Only root may use this option." })
a0d1b1a2 1860 if $keepActive && $authuser ne 'root@pam';
254575e9 1861
af30308f
DM
1862 my $migratedfrom = extract_param($param, 'migratedfrom');
1863 raise_param_exc({ migratedfrom => "Only root may use this option." })
1864 if $migratedfrom && $authuser ne 'root@pam';
1865
1866
ff1a2432
DM
1867 my $storecfg = PVE::Storage::config();
1868
2003f0f8 1869 if (PVE::HA::Config::vm_is_ha_managed($vmid) && ($rpcenv->{type} ne 'ha') && !defined($migratedfrom)) {
5fdbe4f0 1870
88fc87b4
DM
1871 my $hacmd = sub {
1872 my $upid = shift;
5fdbe4f0 1873
c44291cd 1874 my $service = "vm:$vmid";
c6bb9502 1875
e0feef86 1876 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
88fc87b4 1877
02765844 1878 print "Requesting HA stop for VM $vmid\n";
88fc87b4
DM
1879
1880 PVE::Tools::run_command($cmd);
5fdbe4f0 1881
88fc87b4
DM
1882 return;
1883 };
1884
1885 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1886
1887 } else {
1888 my $realcmd = sub {
1889 my $upid = shift;
1890
1891 syslog('info', "stop VM $vmid: $upid\n");
1892
1893 PVE::QemuServer::vm_stop($storecfg, $vmid, $skiplock, 0,
af30308f 1894 $param->{timeout}, 0, 1, $keepActive, $migratedfrom);
88fc87b4
DM
1895
1896 return;
1897 };
1898
1899 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1900 }
5fdbe4f0
DM
1901 }});
1902
1903__PACKAGE__->register_method({
afdb31d5 1904 name => 'vm_reset',
5fdbe4f0
DM
1905 path => '{vmid}/status/reset',
1906 method => 'POST',
1907 protected => 1,
1908 proxyto => 'node',
1909 description => "Reset virtual machine.",
a0d1b1a2
DM
1910 permissions => {
1911 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1912 },
5fdbe4f0
DM
1913 parameters => {
1914 additionalProperties => 0,
1915 properties => {
1916 node => get_standard_option('pve-node'),
ab5904f7
TL
1917 vmid => get_standard_option('pve-vmid',
1918 { completion => \&PVE::QemuServer::complete_vmid_running }),
5fdbe4f0
DM
1919 skiplock => get_standard_option('skiplock'),
1920 },
1921 },
afdb31d5 1922 returns => {
5fdbe4f0
DM
1923 type => 'string',
1924 },
1925 code => sub {
1926 my ($param) = @_;
1927
1928 my $rpcenv = PVE::RPCEnvironment::get();
1929
a0d1b1a2 1930 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
1931
1932 my $node = extract_param($param, 'node');
1933
1934 my $vmid = extract_param($param, 'vmid');
1935
1936 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 1937 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 1938 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 1939
ff1a2432
DM
1940 die "VM $vmid not running\n" if !PVE::QemuServer::check_running($vmid);
1941
5fdbe4f0
DM
1942 my $realcmd = sub {
1943 my $upid = shift;
1944
1e3baf05 1945 PVE::QemuServer::vm_reset($vmid, $skiplock);
5fdbe4f0
DM
1946
1947 return;
1948 };
1949
a0d1b1a2 1950 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
5fdbe4f0
DM
1951 }});
1952
1953__PACKAGE__->register_method({
afdb31d5 1954 name => 'vm_shutdown',
5fdbe4f0
DM
1955 path => '{vmid}/status/shutdown',
1956 method => 'POST',
1957 protected => 1,
1958 proxyto => 'node',
d6c747ff
EK
1959 description => "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
1960 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
a0d1b1a2
DM
1961 permissions => {
1962 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1963 },
5fdbe4f0
DM
1964 parameters => {
1965 additionalProperties => 0,
1966 properties => {
1967 node => get_standard_option('pve-node'),
ab5904f7
TL
1968 vmid => get_standard_option('pve-vmid',
1969 { completion => \&PVE::QemuServer::complete_vmid_running }),
5fdbe4f0 1970 skiplock => get_standard_option('skiplock'),
c6bb9502
DM
1971 timeout => {
1972 description => "Wait maximal timeout seconds.",
1973 type => 'integer',
1974 minimum => 0,
1975 optional => 1,
9269013a
DM
1976 },
1977 forceStop => {
1978 description => "Make sure the VM stops.",
1979 type => 'boolean',
1980 optional => 1,
1981 default => 0,
254575e9
DM
1982 },
1983 keepActive => {
94a17e1d 1984 description => "Do not deactivate storage volumes.",
254575e9
DM
1985 type => 'boolean',
1986 optional => 1,
1987 default => 0,
c6bb9502 1988 }
5fdbe4f0
DM
1989 },
1990 },
afdb31d5 1991 returns => {
5fdbe4f0
DM
1992 type => 'string',
1993 },
1994 code => sub {
1995 my ($param) = @_;
1996
1997 my $rpcenv = PVE::RPCEnvironment::get();
1998
a0d1b1a2 1999 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
2000
2001 my $node = extract_param($param, 'node');
2002
2003 my $vmid = extract_param($param, 'vmid');
2004
2005 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 2006 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 2007 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 2008
254575e9 2009 my $keepActive = extract_param($param, 'keepActive');
afdb31d5 2010 raise_param_exc({ keepActive => "Only root may use this option." })
a0d1b1a2 2011 if $keepActive && $authuser ne 'root@pam';
254575e9 2012
02d07cf5
DM
2013 my $storecfg = PVE::Storage::config();
2014
89897367
DC
2015 my $shutdown = 1;
2016
2017 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2018 # otherwise, we will infer a shutdown command, but run into the timeout,
2019 # then when the vm is resumed, it will instantly shutdown
2020 #
2021 # checking the qmp status here to get feedback to the gui/cli/api
2022 # and the status query should not take too long
2023 my $qmpstatus;
2024 eval {
2025 $qmpstatus = PVE::QemuServer::vm_qmp_command($vmid, { execute => "query-status" }, 0);
2026 };
2027 my $err = $@ if $@;
2028
2029 if (!$err && $qmpstatus->{status} eq "paused") {
2030 if ($param->{forceStop}) {
2031 warn "VM is paused - stop instead of shutdown\n";
2032 $shutdown = 0;
2033 } else {
2034 die "VM is paused - cannot shutdown\n";
2035 }
2036 }
2037
ae849692
DM
2038 if (PVE::HA::Config::vm_is_ha_managed($vmid) &&
2039 ($rpcenv->{type} ne 'ha')) {
5fdbe4f0 2040
ae849692
DM
2041 my $hacmd = sub {
2042 my $upid = shift;
5fdbe4f0 2043
ae849692 2044 my $service = "vm:$vmid";
c6bb9502 2045
ae849692
DM
2046 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2047
02765844 2048 print "Requesting HA stop for VM $vmid\n";
ae849692
DM
2049
2050 PVE::Tools::run_command($cmd);
2051
2052 return;
2053 };
2054
2055 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2056
2057 } else {
2058
2059 my $realcmd = sub {
2060 my $upid = shift;
2061
2062 syslog('info', "shutdown VM $vmid: $upid\n");
5fdbe4f0 2063
ae849692
DM
2064 PVE::QemuServer::vm_stop($storecfg, $vmid, $skiplock, 0, $param->{timeout},
2065 $shutdown, $param->{forceStop}, $keepActive);
2066
2067 return;
2068 };
2069
2070 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2071 }
5fdbe4f0
DM
2072 }});
2073
2074__PACKAGE__->register_method({
afdb31d5 2075 name => 'vm_suspend',
5fdbe4f0
DM
2076 path => '{vmid}/status/suspend',
2077 method => 'POST',
2078 protected => 1,
2079 proxyto => 'node',
2080 description => "Suspend virtual machine.",
a0d1b1a2
DM
2081 permissions => {
2082 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2083 },
5fdbe4f0
DM
2084 parameters => {
2085 additionalProperties => 0,
2086 properties => {
2087 node => get_standard_option('pve-node'),
ab5904f7
TL
2088 vmid => get_standard_option('pve-vmid',
2089 { completion => \&PVE::QemuServer::complete_vmid_running }),
5fdbe4f0
DM
2090 skiplock => get_standard_option('skiplock'),
2091 },
2092 },
afdb31d5 2093 returns => {
5fdbe4f0
DM
2094 type => 'string',
2095 },
2096 code => sub {
2097 my ($param) = @_;
2098
2099 my $rpcenv = PVE::RPCEnvironment::get();
2100
a0d1b1a2 2101 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
2102
2103 my $node = extract_param($param, 'node');
2104
2105 my $vmid = extract_param($param, 'vmid');
2106
2107 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 2108 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 2109 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 2110
ff1a2432
DM
2111 die "VM $vmid not running\n" if !PVE::QemuServer::check_running($vmid);
2112
5fdbe4f0
DM
2113 my $realcmd = sub {
2114 my $upid = shift;
2115
2116 syslog('info', "suspend VM $vmid: $upid\n");
2117
1e3baf05 2118 PVE::QemuServer::vm_suspend($vmid, $skiplock);
5fdbe4f0
DM
2119
2120 return;
2121 };
2122
a0d1b1a2 2123 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
5fdbe4f0
DM
2124 }});
2125
2126__PACKAGE__->register_method({
afdb31d5 2127 name => 'vm_resume',
5fdbe4f0
DM
2128 path => '{vmid}/status/resume',
2129 method => 'POST',
2130 protected => 1,
2131 proxyto => 'node',
2132 description => "Resume virtual machine.",
a0d1b1a2
DM
2133 permissions => {
2134 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2135 },
5fdbe4f0
DM
2136 parameters => {
2137 additionalProperties => 0,
2138 properties => {
2139 node => get_standard_option('pve-node'),
ab5904f7
TL
2140 vmid => get_standard_option('pve-vmid',
2141 { completion => \&PVE::QemuServer::complete_vmid_running }),
5fdbe4f0 2142 skiplock => get_standard_option('skiplock'),
289e0b85
AD
2143 nocheck => { type => 'boolean', optional => 1 },
2144
5fdbe4f0
DM
2145 },
2146 },
afdb31d5 2147 returns => {
5fdbe4f0
DM
2148 type => 'string',
2149 },
2150 code => sub {
2151 my ($param) = @_;
2152
2153 my $rpcenv = PVE::RPCEnvironment::get();
2154
a0d1b1a2 2155 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
2156
2157 my $node = extract_param($param, 'node');
2158
2159 my $vmid = extract_param($param, 'vmid');
2160
2161 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 2162 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 2163 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 2164
289e0b85
AD
2165 my $nocheck = extract_param($param, 'nocheck');
2166
2167 die "VM $vmid not running\n" if !PVE::QemuServer::check_running($vmid, $nocheck);
ff1a2432 2168
5fdbe4f0
DM
2169 my $realcmd = sub {
2170 my $upid = shift;
2171
2172 syslog('info', "resume VM $vmid: $upid\n");
2173
289e0b85 2174 PVE::QemuServer::vm_resume($vmid, $skiplock, $nocheck);
1e3baf05 2175
5fdbe4f0
DM
2176 return;
2177 };
2178
a0d1b1a2 2179 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
5fdbe4f0
DM
2180 }});
2181
2182__PACKAGE__->register_method({
afdb31d5 2183 name => 'vm_sendkey',
5fdbe4f0
DM
2184 path => '{vmid}/sendkey',
2185 method => 'PUT',
2186 protected => 1,
2187 proxyto => 'node',
2188 description => "Send key event to virtual machine.",
a0d1b1a2
DM
2189 permissions => {
2190 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2191 },
5fdbe4f0
DM
2192 parameters => {
2193 additionalProperties => 0,
2194 properties => {
2195 node => get_standard_option('pve-node'),
ab5904f7
TL
2196 vmid => get_standard_option('pve-vmid',
2197 { completion => \&PVE::QemuServer::complete_vmid_running }),
5fdbe4f0
DM
2198 skiplock => get_standard_option('skiplock'),
2199 key => {
2200 description => "The key (qemu monitor encoding).",
2201 type => 'string'
2202 }
2203 },
2204 },
2205 returns => { type => 'null'},
2206 code => sub {
2207 my ($param) = @_;
2208
2209 my $rpcenv = PVE::RPCEnvironment::get();
2210
a0d1b1a2 2211 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
2212
2213 my $node = extract_param($param, 'node');
2214
2215 my $vmid = extract_param($param, 'vmid');
2216
2217 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 2218 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 2219 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0
DM
2220
2221 PVE::QemuServer::vm_sendkey($vmid, $skiplock, $param->{key});
2222
2223 return;
1e3baf05
DM
2224 }});
2225
1ac0d2ee
AD
2226__PACKAGE__->register_method({
2227 name => 'vm_feature',
2228 path => '{vmid}/feature',
2229 method => 'GET',
2230 proxyto => 'node',
75466c4f 2231 protected => 1,
1ac0d2ee
AD
2232 description => "Check if feature for virtual machine is available.",
2233 permissions => {
2234 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2235 },
2236 parameters => {
2237 additionalProperties => 0,
2238 properties => {
2239 node => get_standard_option('pve-node'),
2240 vmid => get_standard_option('pve-vmid'),
2241 feature => {
2242 description => "Feature to check.",
2243 type => 'string',
7758ce86 2244 enum => [ 'snapshot', 'clone', 'copy' ],
1ac0d2ee
AD
2245 },
2246 snapname => get_standard_option('pve-snapshot-name', {
2247 optional => 1,
2248 }),
2249 },
1ac0d2ee
AD
2250 },
2251 returns => {
719893a9
DM
2252 type => "object",
2253 properties => {
2254 hasFeature => { type => 'boolean' },
7043d946 2255 nodes => {
719893a9
DM
2256 type => 'array',
2257 items => { type => 'string' },
2258 }
2259 },
1ac0d2ee
AD
2260 },
2261 code => sub {
2262 my ($param) = @_;
2263
2264 my $node = extract_param($param, 'node');
2265
2266 my $vmid = extract_param($param, 'vmid');
2267
2268 my $snapname = extract_param($param, 'snapname');
2269
2270 my $feature = extract_param($param, 'feature');
2271
2272 my $running = PVE::QemuServer::check_running($vmid);
2273
ffda963f 2274 my $conf = PVE::QemuConfig->load_config($vmid);
1ac0d2ee
AD
2275
2276 if($snapname){
2277 my $snap = $conf->{snapshots}->{$snapname};
2278 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2279 $conf = $snap;
2280 }
2281 my $storecfg = PVE::Storage::config();
2282
719893a9 2283 my $nodelist = PVE::QemuServer::shared_nodes($conf, $storecfg);
b2c9558d 2284 my $hasFeature = PVE::QemuConfig->has_feature($feature, $conf, $storecfg, $snapname, $running);
7043d946 2285
719893a9
DM
2286 return {
2287 hasFeature => $hasFeature,
2288 nodes => [ keys %$nodelist ],
7043d946 2289 };
1ac0d2ee
AD
2290 }});
2291
6116f729 2292__PACKAGE__->register_method({
9418baad
DM
2293 name => 'clone_vm',
2294 path => '{vmid}/clone',
6116f729
DM
2295 method => 'POST',
2296 protected => 1,
2297 proxyto => 'node',
37329185 2298 description => "Create a copy of virtual machine/template.",
6116f729 2299 permissions => {
9418baad 2300 description => "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
6116f729
DM
2301 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2302 "'Datastore.AllocateSpace' on any used storage.",
75466c4f
DM
2303 check =>
2304 [ 'and',
9418baad 2305 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
75466c4f 2306 [ 'or',
6116f729
DM
2307 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2308 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param => 'pool'],
2309 ],
2310 ]
2311 },
2312 parameters => {
2313 additionalProperties => 0,
2314 properties => {
6116f729 2315 node => get_standard_option('pve-node'),
335af808 2316 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
9418baad 2317 newid => get_standard_option('pve-vmid', { description => 'VMID for the clone.' }),
a60ab1a6
DM
2318 name => {
2319 optional => 1,
2320 type => 'string', format => 'dns-name',
2321 description => "Set a name for the new VM.",
2322 },
2323 description => {
2324 optional => 1,
2325 type => 'string',
2326 description => "Description for the new VM.",
2327 },
75466c4f 2328 pool => {
6116f729
DM
2329 optional => 1,
2330 type => 'string', format => 'pve-poolid',
2331 description => "Add the new VM to the specified pool.",
2332 },
9076d880 2333 snapname => get_standard_option('pve-snapshot-name', {
9076d880
DM
2334 optional => 1,
2335 }),
81f043eb 2336 storage => get_standard_option('pve-storage-id', {
9418baad 2337 description => "Target storage for full clone.",
4e4f83fe 2338 requires => 'full',
81f043eb
AD
2339 optional => 1,
2340 }),
55173c6b 2341 'format' => {
42a19c87
AD
2342 description => "Target format for file storage.",
2343 requires => 'full',
2344 type => 'string',
2345 optional => 1,
55173c6b 2346 enum => [ 'raw', 'qcow2', 'vmdk'],
42a19c87 2347 },
6116f729
DM
2348 full => {
2349 optional => 1,
55173c6b
DM
2350 type => 'boolean',
2351 description => "Create a full copy of all disk. This is always done when " .
9418baad 2352 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
6116f729
DM
2353 default => 0,
2354 },
75466c4f 2355 target => get_standard_option('pve-node', {
55173c6b
DM
2356 description => "Target node. Only allowed if the original VM is on shared storage.",
2357 optional => 1,
2358 }),
2359 },
6116f729
DM
2360 },
2361 returns => {
2362 type => 'string',
2363 },
2364 code => sub {
2365 my ($param) = @_;
2366
2367 my $rpcenv = PVE::RPCEnvironment::get();
2368
55173c6b 2369 my $authuser = $rpcenv->get_user();
6116f729
DM
2370
2371 my $node = extract_param($param, 'node');
2372
2373 my $vmid = extract_param($param, 'vmid');
2374
2375 my $newid = extract_param($param, 'newid');
2376
6116f729
DM
2377 my $pool = extract_param($param, 'pool');
2378
2379 if (defined($pool)) {
2380 $rpcenv->check_pool_exist($pool);
2381 }
2382
55173c6b 2383 my $snapname = extract_param($param, 'snapname');
9076d880 2384
81f043eb
AD
2385 my $storage = extract_param($param, 'storage');
2386
42a19c87
AD
2387 my $format = extract_param($param, 'format');
2388
55173c6b
DM
2389 my $target = extract_param($param, 'target');
2390
2391 my $localnode = PVE::INotify::nodename();
2392
751cc556 2393 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
55173c6b
DM
2394
2395 PVE::Cluster::check_node_exists($target) if $target;
2396
6116f729
DM
2397 my $storecfg = PVE::Storage::config();
2398
4a5a2590
DM
2399 if ($storage) {
2400 # check if storage is enabled on local node
2401 PVE::Storage::storage_check_enabled($storecfg, $storage);
2402 if ($target) {
2403 # check if storage is available on target node
2404 PVE::Storage::storage_check_node($storecfg, $storage, $target);
2405 # clone only works if target storage is shared
2406 my $scfg = PVE::Storage::storage_config($storecfg, $storage);
2407 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared};
2408 }
2409 }
2410
55173c6b 2411 PVE::Cluster::check_cfs_quorum();
6116f729 2412
4e4f83fe
DM
2413 my $running = PVE::QemuServer::check_running($vmid) || 0;
2414
4e4f83fe
DM
2415 # exclusive lock if VM is running - else shared lock is enough;
2416 my $shared_lock = $running ? 0 : 1;
2417
9418baad 2418 my $clonefn = sub {
6116f729 2419
829967a9
DM
2420 # do all tests after lock
2421 # we also try to do all tests before we fork the worker
2422
ffda963f 2423 my $conf = PVE::QemuConfig->load_config($vmid);
6116f729 2424
ffda963f 2425 PVE::QemuConfig->check_lock($conf);
6116f729 2426
4e4f83fe 2427 my $verify_running = PVE::QemuServer::check_running($vmid) || 0;
6116f729 2428
4e4f83fe 2429 die "unexpected state change\n" if $verify_running != $running;
6116f729 2430
75466c4f
DM
2431 die "snapshot '$snapname' does not exist\n"
2432 if $snapname && !defined( $conf->{snapshots}->{$snapname});
6116f729 2433
75466c4f 2434 my $oldconf = $snapname ? $conf->{snapshots}->{$snapname} : $conf;
9076d880 2435
9418baad 2436 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
6116f729 2437
9418baad 2438 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
75466c4f 2439
ffda963f 2440 my $conffile = PVE::QemuConfig->config_file($newid);
6116f729
DM
2441
2442 die "unable to create VM $newid: config file already exists\n"
2443 if -f $conffile;
2444
9418baad 2445 my $newconf = { lock => 'clone' };
829967a9 2446 my $drives = {};
34456bf0 2447 my $fullclone = {};
829967a9
DM
2448 my $vollist = [];
2449
2450 foreach my $opt (keys %$oldconf) {
2451 my $value = $oldconf->{$opt};
2452
2453 # do not copy snapshot related info
2454 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2455 $opt eq 'vmstate' || $opt eq 'snapstate';
2456
a78ea5df
WL
2457 # no need to copy unused images, because VMID(owner) changes anyways
2458 next if $opt =~ m/^unused\d+$/;
2459
829967a9
DM
2460 # always change MAC! address
2461 if ($opt =~ m/^net(\d+)$/) {
2462 my $net = PVE::QemuServer::parse_net($value);
b5b99790
WB
2463 my $dc = PVE::Cluster::cfs_read_file('datacenter.cfg');
2464 $net->{macaddr} = PVE::Tools::random_ether_addr($dc->{mac_prefix});
829967a9 2465 $newconf->{$opt} = PVE::QemuServer::print_net($net);
74479ee9 2466 } elsif (PVE::QemuServer::is_valid_drivename($opt)) {
1f1412d1
DM
2467 my $drive = PVE::QemuServer::parse_drive($opt, $value);
2468 die "unable to parse drive options for '$opt'\n" if !$drive;
829967a9
DM
2469 if (PVE::QemuServer::drive_is_cdrom($drive)) {
2470 $newconf->{$opt} = $value; # simply copy configuration
2471 } else {
64ff6fe4 2472 if ($param->{full}) {
6318daca 2473 die "Full clone feature is not supported for drive '$opt'\n"
dba198b0 2474 if !PVE::Storage::volume_has_feature($storecfg, 'copy', $drive->{file}, $snapname, $running);
34456bf0 2475 $fullclone->{$opt} = 1;
64ff6fe4
SP
2476 } else {
2477 # not full means clone instead of copy
6318daca 2478 die "Linked clone feature is not supported for drive '$opt'\n"
64ff6fe4 2479 if !PVE::Storage::volume_has_feature($storecfg, 'clone', $drive->{file}, $snapname, $running);
dba198b0 2480 }
829967a9
DM
2481 $drives->{$opt} = $drive;
2482 push @$vollist, $drive->{file};
2483 }
2484 } else {
2485 # copy everything else
2486 $newconf->{$opt} = $value;
2487 }
2488 }
2489
cd11416f
DM
2490 # auto generate a new uuid
2491 my ($uuid, $uuid_str);
2492 UUID::generate($uuid);
2493 UUID::unparse($uuid, $uuid_str);
2494 my $smbios1 = PVE::QemuServer::parse_smbios1($newconf->{smbios1} || '');
f34ebd52 2495 $smbios1->{uuid} = $uuid_str;
cd11416f
DM
2496 $newconf->{smbios1} = PVE::QemuServer::print_smbios1($smbios1);
2497
829967a9
DM
2498 delete $newconf->{template};
2499
2500 if ($param->{name}) {
2501 $newconf->{name} = $param->{name};
2502 } else {
c55fee03
DM
2503 if ($oldconf->{name}) {
2504 $newconf->{name} = "Copy-of-$oldconf->{name}";
2505 } else {
2506 $newconf->{name} = "Copy-of-VM-$vmid";
2507 }
829967a9 2508 }
2dd53043 2509
829967a9
DM
2510 if ($param->{description}) {
2511 $newconf->{description} = $param->{description};
2512 }
2513
6116f729 2514 # create empty/temp config - this fails if VM already exists on other node
9418baad 2515 PVE::Tools::file_set_contents($conffile, "# qmclone temporary file\nlock: clone\n");
6116f729
DM
2516
2517 my $realcmd = sub {
2518 my $upid = shift;
2519
b83e0181 2520 my $newvollist = [];
c6fdd002 2521 my $jobs = {};
6116f729 2522
b83e0181 2523 eval {
eaae66be
TL
2524 local $SIG{INT} =
2525 local $SIG{TERM} =
2526 local $SIG{QUIT} =
2527 local $SIG{HUP} = sub { die "interrupted by signal\n"; };
75466c4f 2528
eb15b9f0 2529 PVE::Storage::activate_volumes($storecfg, $vollist, $snapname);
6116f729 2530
c6fdd002
AD
2531 my $total_jobs = scalar(keys %{$drives});
2532 my $i = 1;
c6fdd002 2533
829967a9
DM
2534 foreach my $opt (keys %$drives) {
2535 my $drive = $drives->{$opt};
3b4cf0f0 2536 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2dd53043 2537
152fe752 2538 my $newdrive = PVE::QemuServer::clone_disk($storecfg, $vmid, $running, $opt, $drive, $snapname,
3b4cf0f0
WB
2539 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2540 $jobs, $skipcomplete, $oldconf->{agent});
00b095ca 2541
152fe752 2542 $newconf->{$opt} = PVE::QemuServer::print_drive($vmid, $newdrive);
2dd53043 2543
ffda963f 2544 PVE::QemuConfig->write_config($newid, $newconf);
c6fdd002 2545 $i++;
829967a9 2546 }
b83e0181
DM
2547
2548 delete $newconf->{lock};
ffda963f 2549 PVE::QemuConfig->write_config($newid, $newconf);
55173c6b
DM
2550
2551 if ($target) {
baca276d 2552 # always deactivate volumes - avoid lvm LVs to be active on several nodes
51eefb7e 2553 PVE::Storage::deactivate_volumes($storecfg, $vollist, $snapname) if !$running;
32acc380 2554 PVE::Storage::deactivate_volumes($storecfg, $newvollist);
baca276d 2555
ffda963f 2556 my $newconffile = PVE::QemuConfig->config_file($newid, $target);
55173c6b
DM
2557 die "Failed to move config to node '$target' - rename failed: $!\n"
2558 if !rename($conffile, $newconffile);
2559 }
d703d4c0 2560
be517049 2561 PVE::AccessControl::add_vm_to_pool($newid, $pool) if $pool;
6116f729 2562 };
75466c4f 2563 if (my $err = $@) {
6116f729
DM
2564 unlink $conffile;
2565
c6fdd002
AD
2566 eval { PVE::QemuServer::qemu_blockjobs_cancel($vmid, $jobs) };
2567
b83e0181
DM
2568 sleep 1; # some storage like rbd need to wait before release volume - really?
2569
2570 foreach my $volid (@$newvollist) {
2571 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
2572 warn $@ if $@;
2573 }
9418baad 2574 die "clone failed: $err";
6116f729
DM
2575 }
2576
2577 return;
2578 };
2579
457010cc
AG
2580 PVE::Firewall::clone_vmfw_conf($vmid, $newid);
2581
9418baad 2582 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
6116f729
DM
2583 };
2584
ffda963f 2585 return PVE::QemuConfig->lock_config_mode($vmid, 1, $shared_lock, sub {
6116f729 2586 # Aquire exclusive lock lock for $newid
ffda963f 2587 return PVE::QemuConfig->lock_config_full($newid, 1, $clonefn);
6116f729
DM
2588 });
2589
2590 }});
2591
586bfa78 2592__PACKAGE__->register_method({
43bc02a9
DM
2593 name => 'move_vm_disk',
2594 path => '{vmid}/move_disk',
e2cd75fa 2595 method => 'POST',
586bfa78
AD
2596 protected => 1,
2597 proxyto => 'node',
2598 description => "Move volume to different storage.",
2599 permissions => {
c07a9e3d
DM
2600 description => "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2601 check => [ 'and',
2602 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2603 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2604 ],
586bfa78
AD
2605 },
2606 parameters => {
2607 additionalProperties => 0,
c07a9e3d 2608 properties => {
586bfa78 2609 node => get_standard_option('pve-node'),
335af808 2610 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
586bfa78
AD
2611 disk => {
2612 type => 'string',
2613 description => "The disk you want to move.",
74479ee9 2614 enum => [ PVE::QemuServer::valid_drive_names() ],
586bfa78 2615 },
335af808
DM
2616 storage => get_standard_option('pve-storage-id', {
2617 description => "Target storage.",
2618 completion => \&PVE::QemuServer::complete_storage,
2619 }),
635c3c44 2620 'format' => {
586bfa78
AD
2621 type => 'string',
2622 description => "Target Format.",
2623 enum => [ 'raw', 'qcow2', 'vmdk' ],
2624 optional => 1,
2625 },
70d45e33
DM
2626 delete => {
2627 type => 'boolean',
2628 description => "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2629 optional => 1,
2630 default => 0,
2631 },
586bfa78
AD
2632 digest => {
2633 type => 'string',
2634 description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2635 maxLength => 40,
2636 optional => 1,
2637 },
2638 },
2639 },
e2cd75fa
DM
2640 returns => {
2641 type => 'string',
2642 description => "the task ID.",
2643 },
586bfa78
AD
2644 code => sub {
2645 my ($param) = @_;
2646
2647 my $rpcenv = PVE::RPCEnvironment::get();
2648
2649 my $authuser = $rpcenv->get_user();
2650
2651 my $node = extract_param($param, 'node');
2652
2653 my $vmid = extract_param($param, 'vmid');
2654
2655 my $digest = extract_param($param, 'digest');
2656
2657 my $disk = extract_param($param, 'disk');
2658
2659 my $storeid = extract_param($param, 'storage');
2660
2661 my $format = extract_param($param, 'format');
2662
586bfa78
AD
2663 my $storecfg = PVE::Storage::config();
2664
2665 my $updatefn = sub {
2666
ffda963f 2667 my $conf = PVE::QemuConfig->load_config($vmid);
586bfa78 2668
dcce9b46
FG
2669 PVE::QemuConfig->check_lock($conf);
2670
586bfa78
AD
2671 die "checksum missmatch (file change by other user?)\n"
2672 if $digest && $digest ne $conf->{digest};
586bfa78
AD
2673
2674 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2675
2676 my $drive = PVE::QemuServer::parse_drive($disk, $conf->{$disk});
2677
70d45e33 2678 my $old_volid = $drive->{file} || die "disk '$disk' has no associated volume\n";
586bfa78
AD
2679
2680 die "you can't move a cdrom\n" if PVE::QemuServer::drive_is_cdrom($drive);
2681
e2cd75fa 2682 my $oldfmt;
70d45e33 2683 my ($oldstoreid, $oldvolname) = PVE::Storage::parse_volume_id($old_volid);
586bfa78
AD
2684 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2685 $oldfmt = $1;
2686 }
2687
7043d946 2688 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
e2cd75fa 2689 (!$format || !$oldfmt || $oldfmt eq $format);
586bfa78 2690
9dbf9b54
FG
2691 # this only checks snapshots because $disk is passed!
2692 my $snapshotted = PVE::QemuServer::is_volume_in_use($storecfg, $conf, $disk, $old_volid);
2693 die "you can't move a disk with snapshots and delete the source\n"
2694 if $snapshotted && $param->{delete};
2695
586bfa78
AD
2696 PVE::Cluster::log_msg('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2697
2698 my $running = PVE::QemuServer::check_running($vmid);
e2cd75fa
DM
2699
2700 PVE::Storage::activate_volumes($storecfg, [ $drive->{file} ]);
2701
586bfa78
AD
2702 my $realcmd = sub {
2703
2704 my $newvollist = [];
2705
2706 eval {
6cb0144a
EK
2707 local $SIG{INT} =
2708 local $SIG{TERM} =
2709 local $SIG{QUIT} =
2710 local $SIG{HUP} = sub { die "interrupted by signal\n"; };
586bfa78 2711
9dbf9b54
FG
2712 warn "moving disk with snapshots, snapshots will not be moved!\n"
2713 if $snapshotted;
2714
e2cd75fa
DM
2715 my $newdrive = PVE::QemuServer::clone_disk($storecfg, $vmid, $running, $disk, $drive, undef,
2716 $vmid, $storeid, $format, 1, $newvollist);
2717
2718 $conf->{$disk} = PVE::QemuServer::print_drive($vmid, $newdrive);
2719
8793d495 2720 PVE::QemuConfig->add_unused_volume($conf, $old_volid) if !$param->{delete};
7043d946 2721
fbd7dcce
FG
2722 # convert moved disk to base if part of template
2723 PVE::QemuServer::template_create($vmid, $conf, $disk)
2724 if PVE::QemuConfig->is_template($conf);
2725
ffda963f 2726 PVE::QemuConfig->write_config($vmid, $conf);
73272365 2727
f34ebd52 2728 eval {
73272365 2729 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
f34ebd52 2730 PVE::Storage::deactivate_volumes($storecfg, [ $newdrive->{file} ])
73272365
DM
2731 if !$running;
2732 };
2733 warn $@ if $@;
586bfa78
AD
2734 };
2735 if (my $err = $@) {
2736
2737 foreach my $volid (@$newvollist) {
2738 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
2739 warn $@ if $@;
2740 }
2741 die "storage migration failed: $err";
2742 }
70d45e33
DM
2743
2744 if ($param->{delete}) {
a3d0bafb
FG
2745 eval {
2746 PVE::Storage::deactivate_volumes($storecfg, [$old_volid]);
2747 PVE::Storage::vdisk_free($storecfg, $old_volid);
2748 };
2749 warn $@ if $@;
70d45e33 2750 }
586bfa78
AD
2751 };
2752
2753 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2754 };
e2cd75fa 2755
ffda963f 2756 return PVE::QemuConfig->lock_config($vmid, $updatefn);
586bfa78
AD
2757 }});
2758
3ea94c60 2759__PACKAGE__->register_method({
afdb31d5 2760 name => 'migrate_vm',
3ea94c60
DM
2761 path => '{vmid}/migrate',
2762 method => 'POST',
2763 protected => 1,
2764 proxyto => 'node',
2765 description => "Migrate virtual machine. Creates a new migration task.",
a0d1b1a2
DM
2766 permissions => {
2767 check => ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2768 },
3ea94c60
DM
2769 parameters => {
2770 additionalProperties => 0,
2771 properties => {
2772 node => get_standard_option('pve-node'),
335af808
DM
2773 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
2774 target => get_standard_option('pve-node', {
2775 description => "Target node.",
2776 completion => \&PVE::Cluster::complete_migration_target,
2777 }),
3ea94c60
DM
2778 online => {
2779 type => 'boolean',
2780 description => "Use online/live migration.",
2781 optional => 1,
2782 },
2783 force => {
2784 type => 'boolean',
2785 description => "Allow to migrate VMs which use local devices. Only root may use this option.",
2786 optional => 1,
2787 },
2de2d6f7
TL
2788 migration_type => {
2789 type => 'string',
2790 enum => ['secure', 'insecure'],
c07a9e3d 2791 description => "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
2de2d6f7
TL
2792 optional => 1,
2793 },
2794 migration_network => {
c07a9e3d 2795 type => 'string', format => 'CIDR',
2de2d6f7
TL
2796 description => "CIDR of the (sub) network that is used for migration.",
2797 optional => 1,
2798 },
56af7146
AD
2799 "with-local-disks" => {
2800 type => 'boolean',
2801 description => "Enable live storage migration for local disk",
b74cad8a 2802 optional => 1,
56af7146
AD
2803 },
2804 targetstorage => get_standard_option('pve-storage-id', {
2805 description => "Default target storage.",
2806 optional => 1,
2807 completion => \&PVE::QemuServer::complete_storage,
2808 }),
3ea94c60
DM
2809 },
2810 },
afdb31d5 2811 returns => {
3ea94c60
DM
2812 type => 'string',
2813 description => "the task ID.",
2814 },
2815 code => sub {
2816 my ($param) = @_;
2817
2818 my $rpcenv = PVE::RPCEnvironment::get();
2819
a0d1b1a2 2820 my $authuser = $rpcenv->get_user();
3ea94c60
DM
2821
2822 my $target = extract_param($param, 'target');
2823
2824 my $localnode = PVE::INotify::nodename();
2825 raise_param_exc({ target => "target is local node."}) if $target eq $localnode;
2826
2827 PVE::Cluster::check_cfs_quorum();
2828
2829 PVE::Cluster::check_node_exists($target);
2830
2831 my $targetip = PVE::Cluster::remote_node_ip($target);
2832
2833 my $vmid = extract_param($param, 'vmid');
2834
bd2d5fe6 2835 raise_param_exc({ targetstorage => "Live storage migration can only be done online." })
b74cad8a
AD
2836 if !$param->{online} && $param->{targetstorage};
2837
afdb31d5 2838 raise_param_exc({ force => "Only root may use this option." })
a0d1b1a2 2839 if $param->{force} && $authuser ne 'root@pam';
3ea94c60 2840
2de2d6f7
TL
2841 raise_param_exc({ migration_type => "Only root may use this option." })
2842 if $param->{migration_type} && $authuser ne 'root@pam';
2843
2844 # allow root only until better network permissions are available
2845 raise_param_exc({ migration_network => "Only root may use this option." })
2846 if $param->{migration_network} && $authuser ne 'root@pam';
2847
3ea94c60 2848 # test if VM exists
ffda963f 2849 my $conf = PVE::QemuConfig->load_config($vmid);
3ea94c60
DM
2850
2851 # try to detect errors early
a5ed42d3 2852
ffda963f 2853 PVE::QemuConfig->check_lock($conf);
a5ed42d3 2854
3ea94c60 2855 if (PVE::QemuServer::check_running($vmid)) {
afdb31d5 2856 die "cant migrate running VM without --online\n"
3ea94c60
DM
2857 if !$param->{online};
2858 }
2859
47152e2e 2860 my $storecfg = PVE::Storage::config();
d80ad67f
AD
2861
2862 if( $param->{targetstorage}) {
2863 PVE::Storage::storage_check_node($storecfg, $param->{targetstorage}, $target);
2864 } else {
2865 PVE::QemuServer::check_storage_availability($storecfg, $conf, $target);
2866 }
47152e2e 2867
2003f0f8 2868 if (PVE::HA::Config::vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') {
3ea94c60 2869
88fc87b4
DM
2870 my $hacmd = sub {
2871 my $upid = shift;
3ea94c60 2872
c44291cd 2873 my $service = "vm:$vmid";
88fc87b4 2874
2003f0f8 2875 my $cmd = ['ha-manager', 'migrate', $service, $target];
88fc87b4 2876
02765844 2877 print "Requesting HA migration for VM $vmid to node $target\n";
88fc87b4
DM
2878
2879 PVE::Tools::run_command($cmd);
2880
2881 return;
2882 };
2883
2884 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2885
2886 } else {
2887
f53c6ad8 2888 my $realcmd = sub {
f53c6ad8
DM
2889 PVE::QemuMigrate->migrate($target, $targetip, $vmid, $param);
2890 };
88fc87b4 2891
f53c6ad8
DM
2892 my $worker = sub {
2893 return PVE::GuestHelpers::guest_migration_lock($vmid, 10, $realcmd);
88fc87b4
DM
2894 };
2895
f53c6ad8 2896 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
88fc87b4 2897 }
3ea94c60 2898
3ea94c60 2899 }});
1e3baf05 2900
91c94f0a 2901__PACKAGE__->register_method({
afdb31d5
DM
2902 name => 'monitor',
2903 path => '{vmid}/monitor',
91c94f0a
DM
2904 method => 'POST',
2905 protected => 1,
2906 proxyto => 'node',
2907 description => "Execute Qemu monitor commands.",
a0d1b1a2 2908 permissions => {
a8f2f427 2909 description => "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
c07a9e3d 2910 check => ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
a0d1b1a2 2911 },
91c94f0a
DM
2912 parameters => {
2913 additionalProperties => 0,
2914 properties => {
2915 node => get_standard_option('pve-node'),
2916 vmid => get_standard_option('pve-vmid'),
2917 command => {
2918 type => 'string',
2919 description => "The monitor command.",
2920 }
2921 },
2922 },
2923 returns => { type => 'string'},
2924 code => sub {
2925 my ($param) = @_;
2926
a8f2f427
FG
2927 my $rpcenv = PVE::RPCEnvironment::get();
2928 my $authuser = $rpcenv->get_user();
2929
2930 my $is_ro = sub {
2931 my $command = shift;
2932 return $command =~ m/^\s*info(\s+|$)/
2933 || $command =~ m/^\s*help\s*$/;
2934 };
2935
2936 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
2937 if !&$is_ro($param->{command});
2938
91c94f0a
DM
2939 my $vmid = $param->{vmid};
2940
ffda963f 2941 my $conf = PVE::QemuConfig->load_config ($vmid); # check if VM exists
91c94f0a
DM
2942
2943 my $res = '';
2944 eval {
7b7c6d1b 2945 $res = PVE::QemuServer::vm_human_monitor_command($vmid, $param->{command});
91c94f0a
DM
2946 };
2947 $res = "ERROR: $@" if $@;
2948
2949 return $res;
2950 }});
2951
a5d5341c 2952my $guest_agent_commands = [
249d8fed
DM
2953 'ping',
2954 'get-time',
2955 'info',
2956 'fsfreeze-status',
2957 'fsfreeze-freeze',
2958 'fsfreeze-thaw',
2959 'fstrim',
2960 'network-get-interfaces',
2961 'get-vcpus',
2962 'get-fsinfo',
2963 'get-memory-blocks',
2964 'get-memory-block-info',
2965 'suspend-hybrid',
2966 'suspend-ram',
2967 'suspend-disk',
2968 'shutdown',
a5d5341c
DM
2969 ];
2970
d1a47427
WL
2971__PACKAGE__->register_method({
2972 name => 'agent',
2973 path => '{vmid}/agent',
2974 method => 'POST',
2975 protected => 1,
2976 proxyto => 'node',
2977 description => "Execute Qemu Guest Agent commands.",
2978 permissions => {
2979 check => ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2980 },
2981 parameters => {
2982 additionalProperties => 0,
2983 properties => {
2984 node => get_standard_option('pve-node'),
f38c5e27
DM
2985 vmid => get_standard_option('pve-vmid', {
2986 completion => \&PVE::QemuServer::complete_vmid_running }),
d1a47427
WL
2987 command => {
2988 type => 'string',
2989 description => "The QGA command.",
a5d5341c 2990 enum => $guest_agent_commands,
c07a9e3d 2991 },
d1a47427
WL
2992 },
2993 },
57bdd459
DM
2994 returns => {
2995 type => 'object',
2996 description => "Returns an object with a single `result` property. The type of that
2997property depends on the executed command.",
2998 },
d1a47427
WL
2999 code => sub {
3000 my ($param) = @_;
3001
3002 my $vmid = $param->{vmid};
3003
3004 my $conf = PVE::QemuConfig->load_config ($vmid); # check if VM exists
3005
d1a47427
WL
3006 die "No Qemu Guest Agent\n" if !defined($conf->{agent});
3007 die "VM $vmid is not running\n" if !PVE::QemuServer::check_running($vmid);
3008
249d8fed
DM
3009 my $cmd = $param->{command};
3010
3011 my $res = PVE::QemuServer::vm_mon_cmd($vmid, "guest-$cmd");
d1a47427 3012
57bdd459 3013 return { result => $res };
d1a47427
WL
3014 }});
3015
0d02881c
AD
3016__PACKAGE__->register_method({
3017 name => 'resize_vm',
614e3941 3018 path => '{vmid}/resize',
0d02881c
AD
3019 method => 'PUT',
3020 protected => 1,
3021 proxyto => 'node',
2f48a4f5 3022 description => "Extend volume size.",
0d02881c 3023 permissions => {
3b2773f6 3024 check => ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
0d02881c
AD
3025 },
3026 parameters => {
3027 additionalProperties => 0,
2f48a4f5
DM
3028 properties => {
3029 node => get_standard_option('pve-node'),
335af808 3030 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
2f48a4f5
DM
3031 skiplock => get_standard_option('skiplock'),
3032 disk => {
3033 type => 'string',
3034 description => "The disk you want to resize.",
74479ee9 3035 enum => [PVE::QemuServer::valid_drive_names()],
2f48a4f5
DM
3036 },
3037 size => {
3038 type => 'string',
f91b2e45 3039 pattern => '\+?\d+(\.\d+)?[KMGT]?',
e248477e 3040 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.",
2f48a4f5
DM
3041 },
3042 digest => {
3043 type => 'string',
3044 description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3045 maxLength => 40,
3046 optional => 1,
3047 },
3048 },
0d02881c
AD
3049 },
3050 returns => { type => 'null'},
3051 code => sub {
3052 my ($param) = @_;
3053
3054 my $rpcenv = PVE::RPCEnvironment::get();
3055
3056 my $authuser = $rpcenv->get_user();
3057
3058 my $node = extract_param($param, 'node');
3059
3060 my $vmid = extract_param($param, 'vmid');
3061
3062 my $digest = extract_param($param, 'digest');
3063
2f48a4f5 3064 my $disk = extract_param($param, 'disk');
75466c4f 3065
2f48a4f5 3066 my $sizestr = extract_param($param, 'size');
0d02881c 3067
f91b2e45 3068 my $skiplock = extract_param($param, 'skiplock');
0d02881c
AD
3069 raise_param_exc({ skiplock => "Only root may use this option." })
3070 if $skiplock && $authuser ne 'root@pam';
3071
0d02881c
AD
3072 my $storecfg = PVE::Storage::config();
3073
0d02881c
AD
3074 my $updatefn = sub {
3075
ffda963f 3076 my $conf = PVE::QemuConfig->load_config($vmid);
0d02881c
AD
3077
3078 die "checksum missmatch (file change by other user?)\n"
3079 if $digest && $digest ne $conf->{digest};
ffda963f 3080 PVE::QemuConfig->check_lock($conf) if !$skiplock;
0d02881c 3081
f91b2e45
DM
3082 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3083
3084 my $drive = PVE::QemuServer::parse_drive($disk, $conf->{$disk});
3085
d662790a
WL
3086 my (undef, undef, undef, undef, undef, undef, $format) =
3087 PVE::Storage::parse_volname($storecfg, $drive->{file});
3088
3089 die "can't resize volume: $disk if snapshot exists\n"
3090 if %{$conf->{snapshots}} && $format eq 'qcow2';
3091
f91b2e45
DM
3092 my $volid = $drive->{file};
3093
3094 die "disk '$disk' has no associated volume\n" if !$volid;
3095
3096 die "you can't resize a cdrom\n" if PVE::QemuServer::drive_is_cdrom($drive);
3097
3098 my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid);
3099
3100 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3101
b572a606 3102 PVE::Storage::activate_volumes($storecfg, [$volid]);
f91b2e45
DM
3103 my $size = PVE::Storage::volume_size_info($storecfg, $volid, 5);
3104
3105 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3106 my ($ext, $newsize, $unit) = ($1, $2, $4);
3107 if ($unit) {
3108 if ($unit eq 'K') {
3109 $newsize = $newsize * 1024;
3110 } elsif ($unit eq 'M') {
3111 $newsize = $newsize * 1024 * 1024;
3112 } elsif ($unit eq 'G') {
3113 $newsize = $newsize * 1024 * 1024 * 1024;
3114 } elsif ($unit eq 'T') {
3115 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3116 }
3117 }
3118 $newsize += $size if $ext;
3119 $newsize = int($newsize);
3120
9a478b17 3121 die "shrinking disks is not supported\n" if $newsize < $size;
f91b2e45
DM
3122
3123 return if $size == $newsize;
3124
2f48a4f5 3125 PVE::Cluster::log_msg('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
0d02881c 3126
f91b2e45 3127 PVE::QemuServer::qemu_block_resize($vmid, "drive-$disk", $storecfg, $volid, $newsize);
75466c4f 3128
f91b2e45
DM
3129 $drive->{size} = $newsize;
3130 $conf->{$disk} = PVE::QemuServer::print_drive($vmid, $drive);
3131
ffda963f 3132 PVE::QemuConfig->write_config($vmid, $conf);
f91b2e45 3133 };
0d02881c 3134
ffda963f 3135 PVE::QemuConfig->lock_config($vmid, $updatefn);
0d02881c
AD
3136 return undef;
3137 }});
3138
9dbd1ee4 3139__PACKAGE__->register_method({
7e7d7b61 3140 name => 'snapshot_list',
9dbd1ee4 3141 path => '{vmid}/snapshot',
7e7d7b61
DM
3142 method => 'GET',
3143 description => "List all snapshots.",
3144 permissions => {
3145 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3146 },
3147 proxyto => 'node',
3148 protected => 1, # qemu pid files are only readable by root
3149 parameters => {
3150 additionalProperties => 0,
3151 properties => {
e261de40 3152 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
7e7d7b61
DM
3153 node => get_standard_option('pve-node'),
3154 },
3155 },
3156 returns => {
3157 type => 'array',
3158 items => {
3159 type => "object",
3160 properties => {},
3161 },
3162 links => [ { rel => 'child', href => "{name}" } ],
3163 },
3164 code => sub {
3165 my ($param) = @_;
3166
6aa4651b
DM
3167 my $vmid = $param->{vmid};
3168
ffda963f 3169 my $conf = PVE::QemuConfig->load_config($vmid);
7e7d7b61
DM
3170 my $snaphash = $conf->{snapshots} || {};
3171
3172 my $res = [];
3173
3174 foreach my $name (keys %$snaphash) {
0ea6bc69 3175 my $d = $snaphash->{$name};
75466c4f
DM
3176 my $item = {
3177 name => $name,
3178 snaptime => $d->{snaptime} || 0,
6aa4651b 3179 vmstate => $d->{vmstate} ? 1 : 0,
982c7f12
DM
3180 description => $d->{description} || '',
3181 };
0ea6bc69 3182 $item->{parent} = $d->{parent} if $d->{parent};
3ee28e38 3183 $item->{snapstate} = $d->{snapstate} if $d->{snapstate};
0ea6bc69
DM
3184 push @$res, $item;
3185 }
3186
6aa4651b
DM
3187 my $running = PVE::QemuServer::check_running($vmid, 1) ? 1 : 0;
3188 my $current = { name => 'current', digest => $conf->{digest}, running => $running };
d1914468
DM
3189 $current->{parent} = $conf->{parent} if $conf->{parent};
3190
3191 push @$res, $current;
7e7d7b61
DM
3192
3193 return $res;
3194 }});
3195
3196__PACKAGE__->register_method({
3197 name => 'snapshot',
3198 path => '{vmid}/snapshot',
3199 method => 'POST',
9dbd1ee4
AD
3200 protected => 1,
3201 proxyto => 'node',
3202 description => "Snapshot a VM.",
3203 permissions => {
f1baf1df 3204 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
9dbd1ee4
AD
3205 },
3206 parameters => {
3207 additionalProperties => 0,
3208 properties => {
3209 node => get_standard_option('pve-node'),
335af808 3210 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
8abd398b 3211 snapname => get_standard_option('pve-snapshot-name'),
9dbd1ee4
AD
3212 vmstate => {
3213 optional => 1,
3214 type => 'boolean',
3215 description => "Save the vmstate",
3216 },
782f4f75
DM
3217 description => {
3218 optional => 1,
3219 type => 'string',
3220 description => "A textual description or comment.",
3221 },
9dbd1ee4
AD
3222 },
3223 },
7e7d7b61
DM
3224 returns => {
3225 type => 'string',
3226 description => "the task ID.",
3227 },
9dbd1ee4
AD
3228 code => sub {
3229 my ($param) = @_;
3230
3231 my $rpcenv = PVE::RPCEnvironment::get();
3232
3233 my $authuser = $rpcenv->get_user();
3234
3235 my $node = extract_param($param, 'node');
3236
3237 my $vmid = extract_param($param, 'vmid');
3238
9dbd1ee4
AD
3239 my $snapname = extract_param($param, 'snapname');
3240
d1914468
DM
3241 die "unable to use snapshot name 'current' (reserved name)\n"
3242 if $snapname eq 'current';
3243
7e7d7b61 3244 my $realcmd = sub {
22c377f0 3245 PVE::Cluster::log_msg('info', $authuser, "snapshot VM $vmid: $snapname");
b2c9558d 3246 PVE::QemuConfig->snapshot_create($vmid, $snapname, $param->{vmstate},
af9110dd 3247 $param->{description});
7e7d7b61
DM
3248 };
3249
3250 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3251 }});
3252
154ccdcd
DM
3253__PACKAGE__->register_method({
3254 name => 'snapshot_cmd_idx',
3255 path => '{vmid}/snapshot/{snapname}',
3256 description => '',
3257 method => 'GET',
3258 permissions => {
3259 user => 'all',
3260 },
3261 parameters => {
3262 additionalProperties => 0,
3263 properties => {
3264 vmid => get_standard_option('pve-vmid'),
3265 node => get_standard_option('pve-node'),
8abd398b 3266 snapname => get_standard_option('pve-snapshot-name'),
154ccdcd
DM
3267 },
3268 },
3269 returns => {
3270 type => 'array',
3271 items => {
3272 type => "object",
3273 properties => {},
3274 },
3275 links => [ { rel => 'child', href => "{cmd}" } ],
3276 },
3277 code => sub {
3278 my ($param) = @_;
3279
3280 my $res = [];
3281
3282 push @$res, { cmd => 'rollback' };
d788cea6 3283 push @$res, { cmd => 'config' };
154ccdcd
DM
3284
3285 return $res;
3286 }});
3287
d788cea6
DM
3288__PACKAGE__->register_method({
3289 name => 'update_snapshot_config',
3290 path => '{vmid}/snapshot/{snapname}/config',
3291 method => 'PUT',
3292 protected => 1,
3293 proxyto => 'node',
3294 description => "Update snapshot metadata.",
3295 permissions => {
3296 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3297 },
3298 parameters => {
3299 additionalProperties => 0,
3300 properties => {
3301 node => get_standard_option('pve-node'),
3302 vmid => get_standard_option('pve-vmid'),
3303 snapname => get_standard_option('pve-snapshot-name'),
3304 description => {
3305 optional => 1,
3306 type => 'string',
3307 description => "A textual description or comment.",
3308 },
3309 },
3310 },
3311 returns => { type => 'null' },
3312 code => sub {
3313 my ($param) = @_;
3314
3315 my $rpcenv = PVE::RPCEnvironment::get();
3316
3317 my $authuser = $rpcenv->get_user();
3318
3319 my $vmid = extract_param($param, 'vmid');
3320
3321 my $snapname = extract_param($param, 'snapname');
3322
3323 return undef if !defined($param->{description});
3324
3325 my $updatefn = sub {
3326
ffda963f 3327 my $conf = PVE::QemuConfig->load_config($vmid);
d788cea6 3328
ffda963f 3329 PVE::QemuConfig->check_lock($conf);
d788cea6
DM
3330
3331 my $snap = $conf->{snapshots}->{$snapname};
3332
75466c4f
DM
3333 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3334
d788cea6
DM
3335 $snap->{description} = $param->{description} if defined($param->{description});
3336
ffda963f 3337 PVE::QemuConfig->write_config($vmid, $conf);
d788cea6
DM
3338 };
3339
ffda963f 3340 PVE::QemuConfig->lock_config($vmid, $updatefn);
d788cea6
DM
3341
3342 return undef;
3343 }});
3344
3345__PACKAGE__->register_method({
3346 name => 'get_snapshot_config',
3347 path => '{vmid}/snapshot/{snapname}/config',
3348 method => 'GET',
3349 proxyto => 'node',
3350 description => "Get snapshot configuration",
3351 permissions => {
c268337d 3352 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any => 1],
d788cea6
DM
3353 },
3354 parameters => {
3355 additionalProperties => 0,
3356 properties => {
3357 node => get_standard_option('pve-node'),
3358 vmid => get_standard_option('pve-vmid'),
3359 snapname => get_standard_option('pve-snapshot-name'),
3360 },
3361 },
3362 returns => { type => "object" },
3363 code => sub {
3364 my ($param) = @_;
3365
3366 my $rpcenv = PVE::RPCEnvironment::get();
3367
3368 my $authuser = $rpcenv->get_user();
3369
3370 my $vmid = extract_param($param, 'vmid');
3371
3372 my $snapname = extract_param($param, 'snapname');
3373
ffda963f 3374 my $conf = PVE::QemuConfig->load_config($vmid);
d788cea6
DM
3375
3376 my $snap = $conf->{snapshots}->{$snapname};
3377
75466c4f
DM
3378 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3379
d788cea6
DM
3380 return $snap;
3381 }});
3382
7e7d7b61
DM
3383__PACKAGE__->register_method({
3384 name => 'rollback',
154ccdcd 3385 path => '{vmid}/snapshot/{snapname}/rollback',
7e7d7b61
DM
3386 method => 'POST',
3387 protected => 1,
3388 proxyto => 'node',
3389 description => "Rollback VM state to specified snapshot.",
3390 permissions => {
c268337d 3391 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any => 1],
7e7d7b61
DM
3392 },
3393 parameters => {
3394 additionalProperties => 0,
3395 properties => {
3396 node => get_standard_option('pve-node'),
335af808 3397 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
8abd398b 3398 snapname => get_standard_option('pve-snapshot-name'),
7e7d7b61
DM
3399 },
3400 },
3401 returns => {
3402 type => 'string',
3403 description => "the task ID.",
3404 },
3405 code => sub {
3406 my ($param) = @_;
3407
3408 my $rpcenv = PVE::RPCEnvironment::get();
3409
3410 my $authuser = $rpcenv->get_user();
3411
3412 my $node = extract_param($param, 'node');
3413
3414 my $vmid = extract_param($param, 'vmid');
3415
3416 my $snapname = extract_param($param, 'snapname');
3417
7e7d7b61 3418 my $realcmd = sub {
22c377f0 3419 PVE::Cluster::log_msg('info', $authuser, "rollback snapshot VM $vmid: $snapname");
b2c9558d 3420 PVE::QemuConfig->snapshot_rollback($vmid, $snapname);
7e7d7b61
DM
3421 };
3422
c068c1c3
WL
3423 my $worker = sub {
3424 # hold migration lock, this makes sure that nobody create replication snapshots
3425 return PVE::GuestHelpers::guest_migration_lock($vmid, 10, $realcmd);
3426 };
3427
3428 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
7e7d7b61
DM
3429 }});
3430
3431__PACKAGE__->register_method({
3432 name => 'delsnapshot',
3433 path => '{vmid}/snapshot/{snapname}',
3434 method => 'DELETE',
3435 protected => 1,
3436 proxyto => 'node',
3437 description => "Delete a VM snapshot.",
3438 permissions => {
f1baf1df 3439 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
7e7d7b61
DM
3440 },
3441 parameters => {
3442 additionalProperties => 0,
3443 properties => {
3444 node => get_standard_option('pve-node'),
335af808 3445 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
8abd398b 3446 snapname => get_standard_option('pve-snapshot-name'),
3ee28e38
DM
3447 force => {
3448 optional => 1,
3449 type => 'boolean',
3450 description => "For removal from config file, even if removing disk snapshots fails.",
3451 },
7e7d7b61
DM
3452 },
3453 },
3454 returns => {
3455 type => 'string',
3456 description => "the task ID.",
3457 },
3458 code => sub {
3459 my ($param) = @_;
3460
3461 my $rpcenv = PVE::RPCEnvironment::get();
3462
3463 my $authuser = $rpcenv->get_user();
3464
3465 my $node = extract_param($param, 'node');
3466
3467 my $vmid = extract_param($param, 'vmid');
3468
3469 my $snapname = extract_param($param, 'snapname');
3470
7e7d7b61 3471 my $realcmd = sub {
22c377f0 3472 PVE::Cluster::log_msg('info', $authuser, "delete snapshot VM $vmid: $snapname");
b2c9558d 3473 PVE::QemuConfig->snapshot_delete($vmid, $snapname, $param->{force});
7e7d7b61 3474 };
9dbd1ee4 3475
7b2257a8 3476 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
9dbd1ee4
AD
3477 }});
3478
04a69bb4
AD
3479__PACKAGE__->register_method({
3480 name => 'template',
3481 path => '{vmid}/template',
3482 method => 'POST',
3483 protected => 1,
3484 proxyto => 'node',
3485 description => "Create a Template.",
b02691d8 3486 permissions => {
7af0a6c8
DM
3487 description => "You need 'VM.Allocate' permissions on /vms/{vmid}",
3488 check => [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
b02691d8 3489 },
04a69bb4
AD
3490 parameters => {
3491 additionalProperties => 0,
3492 properties => {
3493 node => get_standard_option('pve-node'),
335af808 3494 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid_stopped }),
04a69bb4
AD
3495 disk => {
3496 optional => 1,
3497 type => 'string',
3498 description => "If you want to convert only 1 disk to base image.",
74479ee9 3499 enum => [PVE::QemuServer::valid_drive_names()],
04a69bb4
AD
3500 },
3501
3502 },
3503 },
3504 returns => { type => 'null'},
3505 code => sub {
3506 my ($param) = @_;
3507
3508 my $rpcenv = PVE::RPCEnvironment::get();
3509
3510 my $authuser = $rpcenv->get_user();
3511
3512 my $node = extract_param($param, 'node');
3513
3514 my $vmid = extract_param($param, 'vmid');
3515
3516 my $disk = extract_param($param, 'disk');
3517
3518 my $updatefn = sub {
3519
ffda963f 3520 my $conf = PVE::QemuConfig->load_config($vmid);
04a69bb4 3521
ffda963f 3522 PVE::QemuConfig->check_lock($conf);
04a69bb4 3523
75466c4f 3524 die "unable to create template, because VM contains snapshots\n"
b91c2aae 3525 if $conf->{snapshots} && scalar(keys %{$conf->{snapshots}});
0402a80b 3526
75466c4f 3527 die "you can't convert a template to a template\n"
ffda963f 3528 if PVE::QemuConfig->is_template($conf) && !$disk;
0402a80b 3529
75466c4f 3530 die "you can't convert a VM to template if VM is running\n"
218cab9a 3531 if PVE::QemuServer::check_running($vmid);
35c5fdef 3532
04a69bb4
AD
3533 my $realcmd = sub {
3534 PVE::QemuServer::template_create($vmid, $conf, $disk);
3535 };
04a69bb4 3536
75e7e997 3537 $conf->{template} = 1;
ffda963f 3538 PVE::QemuConfig->write_config($vmid, $conf);
75e7e997
DM
3539
3540 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
04a69bb4
AD
3541 };
3542
ffda963f 3543 PVE::QemuConfig->lock_config($vmid, $updatefn);
04a69bb4
AD
3544 return undef;
3545 }});
3546
1e3baf05 35471;