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