]> git.proxmox.com Git - qemu-server.git/blame - PVE/API2/Qemu.pm
API/clone: do not overwrite global signal handlers
[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 910 my @paramarr = (); # used for log message
edd48c32 911 foreach my $key (sort 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 {
eaae66be
TL
2535 local $SIG{INT} =
2536 local $SIG{TERM} =
2537 local $SIG{QUIT} =
2538 local $SIG{HUP} = sub { die "interrupted by signal\n"; };
75466c4f 2539
eb15b9f0 2540 PVE::Storage::activate_volumes($storecfg, $vollist, $snapname);
6116f729 2541
c6fdd002
AD
2542 my $total_jobs = scalar(keys %{$drives});
2543 my $i = 1;
c6fdd002 2544
829967a9
DM
2545 foreach my $opt (keys %$drives) {
2546 my $drive = $drives->{$opt};
3b4cf0f0 2547 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2dd53043 2548
152fe752 2549 my $newdrive = PVE::QemuServer::clone_disk($storecfg, $vmid, $running, $opt, $drive, $snapname,
3b4cf0f0
WB
2550 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2551 $jobs, $skipcomplete, $oldconf->{agent});
00b095ca 2552
152fe752 2553 $newconf->{$opt} = PVE::QemuServer::print_drive($vmid, $newdrive);
2dd53043 2554
ffda963f 2555 PVE::QemuConfig->write_config($newid, $newconf);
c6fdd002 2556 $i++;
829967a9 2557 }
b83e0181
DM
2558
2559 delete $newconf->{lock};
ffda963f 2560 PVE::QemuConfig->write_config($newid, $newconf);
55173c6b
DM
2561
2562 if ($target) {
baca276d 2563 # always deactivate volumes - avoid lvm LVs to be active on several nodes
51eefb7e 2564 PVE::Storage::deactivate_volumes($storecfg, $vollist, $snapname) if !$running;
32acc380 2565 PVE::Storage::deactivate_volumes($storecfg, $newvollist);
baca276d 2566
ffda963f 2567 my $newconffile = PVE::QemuConfig->config_file($newid, $target);
55173c6b
DM
2568 die "Failed to move config to node '$target' - rename failed: $!\n"
2569 if !rename($conffile, $newconffile);
2570 }
d703d4c0 2571
be517049 2572 PVE::AccessControl::add_vm_to_pool($newid, $pool) if $pool;
6116f729 2573 };
75466c4f 2574 if (my $err = $@) {
6116f729
DM
2575 unlink $conffile;
2576
c6fdd002
AD
2577 eval { PVE::QemuServer::qemu_blockjobs_cancel($vmid, $jobs) };
2578
b83e0181
DM
2579 sleep 1; # some storage like rbd need to wait before release volume - really?
2580
2581 foreach my $volid (@$newvollist) {
2582 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
2583 warn $@ if $@;
2584 }
9418baad 2585 die "clone failed: $err";
6116f729
DM
2586 }
2587
2588 return;
2589 };
2590
457010cc
AG
2591 PVE::Firewall::clone_vmfw_conf($vmid, $newid);
2592
9418baad 2593 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
6116f729
DM
2594 };
2595
ffda963f 2596 return PVE::QemuConfig->lock_config_mode($vmid, 1, $shared_lock, sub {
6116f729 2597 # Aquire exclusive lock lock for $newid
ffda963f 2598 return PVE::QemuConfig->lock_config_full($newid, 1, $clonefn);
6116f729
DM
2599 });
2600
2601 }});
2602
586bfa78 2603__PACKAGE__->register_method({
43bc02a9
DM
2604 name => 'move_vm_disk',
2605 path => '{vmid}/move_disk',
e2cd75fa 2606 method => 'POST',
586bfa78
AD
2607 protected => 1,
2608 proxyto => 'node',
2609 description => "Move volume to different storage.",
2610 permissions => {
c07a9e3d
DM
2611 description => "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2612 check => [ 'and',
2613 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2614 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2615 ],
586bfa78
AD
2616 },
2617 parameters => {
2618 additionalProperties => 0,
c07a9e3d 2619 properties => {
586bfa78 2620 node => get_standard_option('pve-node'),
335af808 2621 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
586bfa78
AD
2622 disk => {
2623 type => 'string',
2624 description => "The disk you want to move.",
74479ee9 2625 enum => [ PVE::QemuServer::valid_drive_names() ],
586bfa78 2626 },
335af808
DM
2627 storage => get_standard_option('pve-storage-id', {
2628 description => "Target storage.",
2629 completion => \&PVE::QemuServer::complete_storage,
2630 }),
635c3c44 2631 'format' => {
586bfa78
AD
2632 type => 'string',
2633 description => "Target Format.",
2634 enum => [ 'raw', 'qcow2', 'vmdk' ],
2635 optional => 1,
2636 },
70d45e33
DM
2637 delete => {
2638 type => 'boolean',
2639 description => "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2640 optional => 1,
2641 default => 0,
2642 },
586bfa78
AD
2643 digest => {
2644 type => 'string',
2645 description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2646 maxLength => 40,
2647 optional => 1,
2648 },
2649 },
2650 },
e2cd75fa
DM
2651 returns => {
2652 type => 'string',
2653 description => "the task ID.",
2654 },
586bfa78
AD
2655 code => sub {
2656 my ($param) = @_;
2657
2658 my $rpcenv = PVE::RPCEnvironment::get();
2659
2660 my $authuser = $rpcenv->get_user();
2661
2662 my $node = extract_param($param, 'node');
2663
2664 my $vmid = extract_param($param, 'vmid');
2665
2666 my $digest = extract_param($param, 'digest');
2667
2668 my $disk = extract_param($param, 'disk');
2669
2670 my $storeid = extract_param($param, 'storage');
2671
2672 my $format = extract_param($param, 'format');
2673
586bfa78
AD
2674 my $storecfg = PVE::Storage::config();
2675
2676 my $updatefn = sub {
2677
ffda963f 2678 my $conf = PVE::QemuConfig->load_config($vmid);
586bfa78 2679
dcce9b46
FG
2680 PVE::QemuConfig->check_lock($conf);
2681
586bfa78
AD
2682 die "checksum missmatch (file change by other user?)\n"
2683 if $digest && $digest ne $conf->{digest};
586bfa78
AD
2684
2685 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2686
2687 my $drive = PVE::QemuServer::parse_drive($disk, $conf->{$disk});
2688
70d45e33 2689 my $old_volid = $drive->{file} || die "disk '$disk' has no associated volume\n";
586bfa78
AD
2690
2691 die "you can't move a cdrom\n" if PVE::QemuServer::drive_is_cdrom($drive);
2692
e2cd75fa 2693 my $oldfmt;
70d45e33 2694 my ($oldstoreid, $oldvolname) = PVE::Storage::parse_volume_id($old_volid);
586bfa78
AD
2695 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2696 $oldfmt = $1;
2697 }
2698
7043d946 2699 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
e2cd75fa 2700 (!$format || !$oldfmt || $oldfmt eq $format);
586bfa78 2701
9dbf9b54
FG
2702 # this only checks snapshots because $disk is passed!
2703 my $snapshotted = PVE::QemuServer::is_volume_in_use($storecfg, $conf, $disk, $old_volid);
2704 die "you can't move a disk with snapshots and delete the source\n"
2705 if $snapshotted && $param->{delete};
2706
586bfa78
AD
2707 PVE::Cluster::log_msg('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2708
2709 my $running = PVE::QemuServer::check_running($vmid);
e2cd75fa
DM
2710
2711 PVE::Storage::activate_volumes($storecfg, [ $drive->{file} ]);
2712
586bfa78
AD
2713 my $realcmd = sub {
2714
2715 my $newvollist = [];
2716
2717 eval {
2718 local $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = sub { die "interrupted by signal\n"; };
2719
9dbf9b54
FG
2720 warn "moving disk with snapshots, snapshots will not be moved!\n"
2721 if $snapshotted;
2722
e2cd75fa
DM
2723 my $newdrive = PVE::QemuServer::clone_disk($storecfg, $vmid, $running, $disk, $drive, undef,
2724 $vmid, $storeid, $format, 1, $newvollist);
2725
2726 $conf->{$disk} = PVE::QemuServer::print_drive($vmid, $newdrive);
2727
8793d495 2728 PVE::QemuConfig->add_unused_volume($conf, $old_volid) if !$param->{delete};
7043d946 2729
fbd7dcce
FG
2730 # convert moved disk to base if part of template
2731 PVE::QemuServer::template_create($vmid, $conf, $disk)
2732 if PVE::QemuConfig->is_template($conf);
2733
ffda963f 2734 PVE::QemuConfig->write_config($vmid, $conf);
73272365 2735
f34ebd52 2736 eval {
73272365 2737 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
f34ebd52 2738 PVE::Storage::deactivate_volumes($storecfg, [ $newdrive->{file} ])
73272365
DM
2739 if !$running;
2740 };
2741 warn $@ if $@;
586bfa78
AD
2742 };
2743 if (my $err = $@) {
2744
2745 foreach my $volid (@$newvollist) {
2746 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
2747 warn $@ if $@;
2748 }
2749 die "storage migration failed: $err";
2750 }
70d45e33
DM
2751
2752 if ($param->{delete}) {
a3d0bafb
FG
2753 eval {
2754 PVE::Storage::deactivate_volumes($storecfg, [$old_volid]);
2755 PVE::Storage::vdisk_free($storecfg, $old_volid);
2756 };
2757 warn $@ if $@;
70d45e33 2758 }
586bfa78
AD
2759 };
2760
2761 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2762 };
e2cd75fa 2763
ffda963f 2764 return PVE::QemuConfig->lock_config($vmid, $updatefn);
586bfa78
AD
2765 }});
2766
3ea94c60 2767__PACKAGE__->register_method({
afdb31d5 2768 name => 'migrate_vm',
3ea94c60
DM
2769 path => '{vmid}/migrate',
2770 method => 'POST',
2771 protected => 1,
2772 proxyto => 'node',
2773 description => "Migrate virtual machine. Creates a new migration task.",
a0d1b1a2
DM
2774 permissions => {
2775 check => ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2776 },
3ea94c60
DM
2777 parameters => {
2778 additionalProperties => 0,
2779 properties => {
2780 node => get_standard_option('pve-node'),
335af808
DM
2781 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
2782 target => get_standard_option('pve-node', {
2783 description => "Target node.",
2784 completion => \&PVE::Cluster::complete_migration_target,
2785 }),
3ea94c60
DM
2786 online => {
2787 type => 'boolean',
2788 description => "Use online/live migration.",
2789 optional => 1,
2790 },
2791 force => {
2792 type => 'boolean',
2793 description => "Allow to migrate VMs which use local devices. Only root may use this option.",
2794 optional => 1,
2795 },
2de2d6f7
TL
2796 migration_type => {
2797 type => 'string',
2798 enum => ['secure', 'insecure'],
c07a9e3d 2799 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
2800 optional => 1,
2801 },
2802 migration_network => {
c07a9e3d 2803 type => 'string', format => 'CIDR',
2de2d6f7
TL
2804 description => "CIDR of the (sub) network that is used for migration.",
2805 optional => 1,
2806 },
56af7146
AD
2807 "with-local-disks" => {
2808 type => 'boolean',
2809 description => "Enable live storage migration for local disk",
b74cad8a 2810 optional => 1,
56af7146
AD
2811 },
2812 targetstorage => get_standard_option('pve-storage-id', {
2813 description => "Default target storage.",
2814 optional => 1,
2815 completion => \&PVE::QemuServer::complete_storage,
2816 }),
3ea94c60
DM
2817 },
2818 },
afdb31d5 2819 returns => {
3ea94c60
DM
2820 type => 'string',
2821 description => "the task ID.",
2822 },
2823 code => sub {
2824 my ($param) = @_;
2825
2826 my $rpcenv = PVE::RPCEnvironment::get();
2827
a0d1b1a2 2828 my $authuser = $rpcenv->get_user();
3ea94c60
DM
2829
2830 my $target = extract_param($param, 'target');
2831
2832 my $localnode = PVE::INotify::nodename();
2833 raise_param_exc({ target => "target is local node."}) if $target eq $localnode;
2834
2835 PVE::Cluster::check_cfs_quorum();
2836
2837 PVE::Cluster::check_node_exists($target);
2838
2839 my $targetip = PVE::Cluster::remote_node_ip($target);
2840
2841 my $vmid = extract_param($param, 'vmid');
2842
bd2d5fe6 2843 raise_param_exc({ targetstorage => "Live storage migration can only be done online." })
b74cad8a
AD
2844 if !$param->{online} && $param->{targetstorage};
2845
afdb31d5 2846 raise_param_exc({ force => "Only root may use this option." })
a0d1b1a2 2847 if $param->{force} && $authuser ne 'root@pam';
3ea94c60 2848
2de2d6f7
TL
2849 raise_param_exc({ migration_type => "Only root may use this option." })
2850 if $param->{migration_type} && $authuser ne 'root@pam';
2851
2852 # allow root only until better network permissions are available
2853 raise_param_exc({ migration_network => "Only root may use this option." })
2854 if $param->{migration_network} && $authuser ne 'root@pam';
2855
3ea94c60 2856 # test if VM exists
ffda963f 2857 my $conf = PVE::QemuConfig->load_config($vmid);
3ea94c60
DM
2858
2859 # try to detect errors early
a5ed42d3 2860
ffda963f 2861 PVE::QemuConfig->check_lock($conf);
a5ed42d3 2862
3ea94c60 2863 if (PVE::QemuServer::check_running($vmid)) {
afdb31d5 2864 die "cant migrate running VM without --online\n"
3ea94c60
DM
2865 if !$param->{online};
2866 }
2867
47152e2e 2868 my $storecfg = PVE::Storage::config();
d80ad67f
AD
2869
2870 if( $param->{targetstorage}) {
2871 PVE::Storage::storage_check_node($storecfg, $param->{targetstorage}, $target);
2872 } else {
2873 PVE::QemuServer::check_storage_availability($storecfg, $conf, $target);
2874 }
47152e2e 2875
2003f0f8 2876 if (PVE::HA::Config::vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') {
3ea94c60 2877
88fc87b4
DM
2878 my $hacmd = sub {
2879 my $upid = shift;
3ea94c60 2880
c44291cd 2881 my $service = "vm:$vmid";
88fc87b4 2882
2003f0f8 2883 my $cmd = ['ha-manager', 'migrate', $service, $target];
88fc87b4 2884
02765844 2885 print "Requesting HA migration for VM $vmid to node $target\n";
88fc87b4
DM
2886
2887 PVE::Tools::run_command($cmd);
2888
2889 return;
2890 };
2891
2892 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2893
2894 } else {
2895
f53c6ad8 2896 my $realcmd = sub {
f53c6ad8
DM
2897 PVE::QemuMigrate->migrate($target, $targetip, $vmid, $param);
2898 };
88fc87b4 2899
f53c6ad8
DM
2900 my $worker = sub {
2901 return PVE::GuestHelpers::guest_migration_lock($vmid, 10, $realcmd);
88fc87b4
DM
2902 };
2903
f53c6ad8 2904 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
88fc87b4 2905 }
3ea94c60 2906
3ea94c60 2907 }});
1e3baf05 2908
91c94f0a 2909__PACKAGE__->register_method({
afdb31d5
DM
2910 name => 'monitor',
2911 path => '{vmid}/monitor',
91c94f0a
DM
2912 method => 'POST',
2913 protected => 1,
2914 proxyto => 'node',
2915 description => "Execute Qemu monitor commands.",
a0d1b1a2 2916 permissions => {
a8f2f427 2917 description => "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
c07a9e3d 2918 check => ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
a0d1b1a2 2919 },
91c94f0a
DM
2920 parameters => {
2921 additionalProperties => 0,
2922 properties => {
2923 node => get_standard_option('pve-node'),
2924 vmid => get_standard_option('pve-vmid'),
2925 command => {
2926 type => 'string',
2927 description => "The monitor command.",
2928 }
2929 },
2930 },
2931 returns => { type => 'string'},
2932 code => sub {
2933 my ($param) = @_;
2934
a8f2f427
FG
2935 my $rpcenv = PVE::RPCEnvironment::get();
2936 my $authuser = $rpcenv->get_user();
2937
2938 my $is_ro = sub {
2939 my $command = shift;
2940 return $command =~ m/^\s*info(\s+|$)/
2941 || $command =~ m/^\s*help\s*$/;
2942 };
2943
2944 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
2945 if !&$is_ro($param->{command});
2946
91c94f0a
DM
2947 my $vmid = $param->{vmid};
2948
ffda963f 2949 my $conf = PVE::QemuConfig->load_config ($vmid); # check if VM exists
91c94f0a
DM
2950
2951 my $res = '';
2952 eval {
7b7c6d1b 2953 $res = PVE::QemuServer::vm_human_monitor_command($vmid, $param->{command});
91c94f0a
DM
2954 };
2955 $res = "ERROR: $@" if $@;
2956
2957 return $res;
2958 }});
2959
a5d5341c 2960my $guest_agent_commands = [
249d8fed
DM
2961 'ping',
2962 'get-time',
2963 'info',
2964 'fsfreeze-status',
2965 'fsfreeze-freeze',
2966 'fsfreeze-thaw',
2967 'fstrim',
2968 'network-get-interfaces',
2969 'get-vcpus',
2970 'get-fsinfo',
2971 'get-memory-blocks',
2972 'get-memory-block-info',
2973 'suspend-hybrid',
2974 'suspend-ram',
2975 'suspend-disk',
2976 'shutdown',
a5d5341c
DM
2977 ];
2978
d1a47427
WL
2979__PACKAGE__->register_method({
2980 name => 'agent',
2981 path => '{vmid}/agent',
2982 method => 'POST',
2983 protected => 1,
2984 proxyto => 'node',
2985 description => "Execute Qemu Guest Agent commands.",
2986 permissions => {
2987 check => ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2988 },
2989 parameters => {
2990 additionalProperties => 0,
2991 properties => {
2992 node => get_standard_option('pve-node'),
f38c5e27
DM
2993 vmid => get_standard_option('pve-vmid', {
2994 completion => \&PVE::QemuServer::complete_vmid_running }),
d1a47427
WL
2995 command => {
2996 type => 'string',
2997 description => "The QGA command.",
a5d5341c 2998 enum => $guest_agent_commands,
c07a9e3d 2999 },
d1a47427
WL
3000 },
3001 },
57bdd459
DM
3002 returns => {
3003 type => 'object',
3004 description => "Returns an object with a single `result` property. The type of that
3005property depends on the executed command.",
3006 },
d1a47427
WL
3007 code => sub {
3008 my ($param) = @_;
3009
3010 my $vmid = $param->{vmid};
3011
3012 my $conf = PVE::QemuConfig->load_config ($vmid); # check if VM exists
3013
d1a47427
WL
3014 die "No Qemu Guest Agent\n" if !defined($conf->{agent});
3015 die "VM $vmid is not running\n" if !PVE::QemuServer::check_running($vmid);
3016
249d8fed
DM
3017 my $cmd = $param->{command};
3018
3019 my $res = PVE::QemuServer::vm_mon_cmd($vmid, "guest-$cmd");
d1a47427 3020
57bdd459 3021 return { result => $res };
d1a47427
WL
3022 }});
3023
0d02881c
AD
3024__PACKAGE__->register_method({
3025 name => 'resize_vm',
614e3941 3026 path => '{vmid}/resize',
0d02881c
AD
3027 method => 'PUT',
3028 protected => 1,
3029 proxyto => 'node',
2f48a4f5 3030 description => "Extend volume size.",
0d02881c 3031 permissions => {
3b2773f6 3032 check => ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
0d02881c
AD
3033 },
3034 parameters => {
3035 additionalProperties => 0,
2f48a4f5
DM
3036 properties => {
3037 node => get_standard_option('pve-node'),
335af808 3038 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
2f48a4f5
DM
3039 skiplock => get_standard_option('skiplock'),
3040 disk => {
3041 type => 'string',
3042 description => "The disk you want to resize.",
74479ee9 3043 enum => [PVE::QemuServer::valid_drive_names()],
2f48a4f5
DM
3044 },
3045 size => {
3046 type => 'string',
f91b2e45 3047 pattern => '\+?\d+(\.\d+)?[KMGT]?',
e248477e 3048 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
3049 },
3050 digest => {
3051 type => 'string',
3052 description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3053 maxLength => 40,
3054 optional => 1,
3055 },
3056 },
0d02881c
AD
3057 },
3058 returns => { type => 'null'},
3059 code => sub {
3060 my ($param) = @_;
3061
3062 my $rpcenv = PVE::RPCEnvironment::get();
3063
3064 my $authuser = $rpcenv->get_user();
3065
3066 my $node = extract_param($param, 'node');
3067
3068 my $vmid = extract_param($param, 'vmid');
3069
3070 my $digest = extract_param($param, 'digest');
3071
2f48a4f5 3072 my $disk = extract_param($param, 'disk');
75466c4f 3073
2f48a4f5 3074 my $sizestr = extract_param($param, 'size');
0d02881c 3075
f91b2e45 3076 my $skiplock = extract_param($param, 'skiplock');
0d02881c
AD
3077 raise_param_exc({ skiplock => "Only root may use this option." })
3078 if $skiplock && $authuser ne 'root@pam';
3079
0d02881c
AD
3080 my $storecfg = PVE::Storage::config();
3081
0d02881c
AD
3082 my $updatefn = sub {
3083
ffda963f 3084 my $conf = PVE::QemuConfig->load_config($vmid);
0d02881c
AD
3085
3086 die "checksum missmatch (file change by other user?)\n"
3087 if $digest && $digest ne $conf->{digest};
ffda963f 3088 PVE::QemuConfig->check_lock($conf) if !$skiplock;
0d02881c 3089
f91b2e45
DM
3090 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3091
3092 my $drive = PVE::QemuServer::parse_drive($disk, $conf->{$disk});
3093
d662790a
WL
3094 my (undef, undef, undef, undef, undef, undef, $format) =
3095 PVE::Storage::parse_volname($storecfg, $drive->{file});
3096
3097 die "can't resize volume: $disk if snapshot exists\n"
3098 if %{$conf->{snapshots}} && $format eq 'qcow2';
3099
f91b2e45
DM
3100 my $volid = $drive->{file};
3101
3102 die "disk '$disk' has no associated volume\n" if !$volid;
3103
3104 die "you can't resize a cdrom\n" if PVE::QemuServer::drive_is_cdrom($drive);
3105
3106 my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid);
3107
3108 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3109
b572a606 3110 PVE::Storage::activate_volumes($storecfg, [$volid]);
f91b2e45
DM
3111 my $size = PVE::Storage::volume_size_info($storecfg, $volid, 5);
3112
3113 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3114 my ($ext, $newsize, $unit) = ($1, $2, $4);
3115 if ($unit) {
3116 if ($unit eq 'K') {
3117 $newsize = $newsize * 1024;
3118 } elsif ($unit eq 'M') {
3119 $newsize = $newsize * 1024 * 1024;
3120 } elsif ($unit eq 'G') {
3121 $newsize = $newsize * 1024 * 1024 * 1024;
3122 } elsif ($unit eq 'T') {
3123 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3124 }
3125 }
3126 $newsize += $size if $ext;
3127 $newsize = int($newsize);
3128
9a478b17 3129 die "shrinking disks is not supported\n" if $newsize < $size;
f91b2e45
DM
3130
3131 return if $size == $newsize;
3132
2f48a4f5 3133 PVE::Cluster::log_msg('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
0d02881c 3134
f91b2e45 3135 PVE::QemuServer::qemu_block_resize($vmid, "drive-$disk", $storecfg, $volid, $newsize);
75466c4f 3136
f91b2e45
DM
3137 $drive->{size} = $newsize;
3138 $conf->{$disk} = PVE::QemuServer::print_drive($vmid, $drive);
3139
ffda963f 3140 PVE::QemuConfig->write_config($vmid, $conf);
f91b2e45 3141 };
0d02881c 3142
ffda963f 3143 PVE::QemuConfig->lock_config($vmid, $updatefn);
0d02881c
AD
3144 return undef;
3145 }});
3146
9dbd1ee4 3147__PACKAGE__->register_method({
7e7d7b61 3148 name => 'snapshot_list',
9dbd1ee4 3149 path => '{vmid}/snapshot',
7e7d7b61
DM
3150 method => 'GET',
3151 description => "List all snapshots.",
3152 permissions => {
3153 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3154 },
3155 proxyto => 'node',
3156 protected => 1, # qemu pid files are only readable by root
3157 parameters => {
3158 additionalProperties => 0,
3159 properties => {
e261de40 3160 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
7e7d7b61
DM
3161 node => get_standard_option('pve-node'),
3162 },
3163 },
3164 returns => {
3165 type => 'array',
3166 items => {
3167 type => "object",
3168 properties => {},
3169 },
3170 links => [ { rel => 'child', href => "{name}" } ],
3171 },
3172 code => sub {
3173 my ($param) = @_;
3174
6aa4651b
DM
3175 my $vmid = $param->{vmid};
3176
ffda963f 3177 my $conf = PVE::QemuConfig->load_config($vmid);
7e7d7b61
DM
3178 my $snaphash = $conf->{snapshots} || {};
3179
3180 my $res = [];
3181
3182 foreach my $name (keys %$snaphash) {
0ea6bc69 3183 my $d = $snaphash->{$name};
75466c4f
DM
3184 my $item = {
3185 name => $name,
3186 snaptime => $d->{snaptime} || 0,
6aa4651b 3187 vmstate => $d->{vmstate} ? 1 : 0,
982c7f12
DM
3188 description => $d->{description} || '',
3189 };
0ea6bc69 3190 $item->{parent} = $d->{parent} if $d->{parent};
3ee28e38 3191 $item->{snapstate} = $d->{snapstate} if $d->{snapstate};
0ea6bc69
DM
3192 push @$res, $item;
3193 }
3194
6aa4651b
DM
3195 my $running = PVE::QemuServer::check_running($vmid, 1) ? 1 : 0;
3196 my $current = { name => 'current', digest => $conf->{digest}, running => $running };
d1914468
DM
3197 $current->{parent} = $conf->{parent} if $conf->{parent};
3198
3199 push @$res, $current;
7e7d7b61
DM
3200
3201 return $res;
3202 }});
3203
3204__PACKAGE__->register_method({
3205 name => 'snapshot',
3206 path => '{vmid}/snapshot',
3207 method => 'POST',
9dbd1ee4
AD
3208 protected => 1,
3209 proxyto => 'node',
3210 description => "Snapshot a VM.",
3211 permissions => {
f1baf1df 3212 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
9dbd1ee4
AD
3213 },
3214 parameters => {
3215 additionalProperties => 0,
3216 properties => {
3217 node => get_standard_option('pve-node'),
335af808 3218 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
8abd398b 3219 snapname => get_standard_option('pve-snapshot-name'),
9dbd1ee4
AD
3220 vmstate => {
3221 optional => 1,
3222 type => 'boolean',
3223 description => "Save the vmstate",
3224 },
782f4f75
DM
3225 description => {
3226 optional => 1,
3227 type => 'string',
3228 description => "A textual description or comment.",
3229 },
9dbd1ee4
AD
3230 },
3231 },
7e7d7b61
DM
3232 returns => {
3233 type => 'string',
3234 description => "the task ID.",
3235 },
9dbd1ee4
AD
3236 code => sub {
3237 my ($param) = @_;
3238
3239 my $rpcenv = PVE::RPCEnvironment::get();
3240
3241 my $authuser = $rpcenv->get_user();
3242
3243 my $node = extract_param($param, 'node');
3244
3245 my $vmid = extract_param($param, 'vmid');
3246
9dbd1ee4
AD
3247 my $snapname = extract_param($param, 'snapname');
3248
d1914468
DM
3249 die "unable to use snapshot name 'current' (reserved name)\n"
3250 if $snapname eq 'current';
3251
7e7d7b61 3252 my $realcmd = sub {
22c377f0 3253 PVE::Cluster::log_msg('info', $authuser, "snapshot VM $vmid: $snapname");
b2c9558d 3254 PVE::QemuConfig->snapshot_create($vmid, $snapname, $param->{vmstate},
af9110dd 3255 $param->{description});
7e7d7b61
DM
3256 };
3257
3258 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3259 }});
3260
154ccdcd
DM
3261__PACKAGE__->register_method({
3262 name => 'snapshot_cmd_idx',
3263 path => '{vmid}/snapshot/{snapname}',
3264 description => '',
3265 method => 'GET',
3266 permissions => {
3267 user => 'all',
3268 },
3269 parameters => {
3270 additionalProperties => 0,
3271 properties => {
3272 vmid => get_standard_option('pve-vmid'),
3273 node => get_standard_option('pve-node'),
8abd398b 3274 snapname => get_standard_option('pve-snapshot-name'),
154ccdcd
DM
3275 },
3276 },
3277 returns => {
3278 type => 'array',
3279 items => {
3280 type => "object",
3281 properties => {},
3282 },
3283 links => [ { rel => 'child', href => "{cmd}" } ],
3284 },
3285 code => sub {
3286 my ($param) = @_;
3287
3288 my $res = [];
3289
3290 push @$res, { cmd => 'rollback' };
d788cea6 3291 push @$res, { cmd => 'config' };
154ccdcd
DM
3292
3293 return $res;
3294 }});
3295
d788cea6
DM
3296__PACKAGE__->register_method({
3297 name => 'update_snapshot_config',
3298 path => '{vmid}/snapshot/{snapname}/config',
3299 method => 'PUT',
3300 protected => 1,
3301 proxyto => 'node',
3302 description => "Update snapshot metadata.",
3303 permissions => {
3304 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3305 },
3306 parameters => {
3307 additionalProperties => 0,
3308 properties => {
3309 node => get_standard_option('pve-node'),
3310 vmid => get_standard_option('pve-vmid'),
3311 snapname => get_standard_option('pve-snapshot-name'),
3312 description => {
3313 optional => 1,
3314 type => 'string',
3315 description => "A textual description or comment.",
3316 },
3317 },
3318 },
3319 returns => { type => 'null' },
3320 code => sub {
3321 my ($param) = @_;
3322
3323 my $rpcenv = PVE::RPCEnvironment::get();
3324
3325 my $authuser = $rpcenv->get_user();
3326
3327 my $vmid = extract_param($param, 'vmid');
3328
3329 my $snapname = extract_param($param, 'snapname');
3330
3331 return undef if !defined($param->{description});
3332
3333 my $updatefn = sub {
3334
ffda963f 3335 my $conf = PVE::QemuConfig->load_config($vmid);
d788cea6 3336
ffda963f 3337 PVE::QemuConfig->check_lock($conf);
d788cea6
DM
3338
3339 my $snap = $conf->{snapshots}->{$snapname};
3340
75466c4f
DM
3341 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3342
d788cea6
DM
3343 $snap->{description} = $param->{description} if defined($param->{description});
3344
ffda963f 3345 PVE::QemuConfig->write_config($vmid, $conf);
d788cea6
DM
3346 };
3347
ffda963f 3348 PVE::QemuConfig->lock_config($vmid, $updatefn);
d788cea6
DM
3349
3350 return undef;
3351 }});
3352
3353__PACKAGE__->register_method({
3354 name => 'get_snapshot_config',
3355 path => '{vmid}/snapshot/{snapname}/config',
3356 method => 'GET',
3357 proxyto => 'node',
3358 description => "Get snapshot configuration",
3359 permissions => {
3360 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3361 },
3362 parameters => {
3363 additionalProperties => 0,
3364 properties => {
3365 node => get_standard_option('pve-node'),
3366 vmid => get_standard_option('pve-vmid'),
3367 snapname => get_standard_option('pve-snapshot-name'),
3368 },
3369 },
3370 returns => { type => "object" },
3371 code => sub {
3372 my ($param) = @_;
3373
3374 my $rpcenv = PVE::RPCEnvironment::get();
3375
3376 my $authuser = $rpcenv->get_user();
3377
3378 my $vmid = extract_param($param, 'vmid');
3379
3380 my $snapname = extract_param($param, 'snapname');
3381
ffda963f 3382 my $conf = PVE::QemuConfig->load_config($vmid);
d788cea6
DM
3383
3384 my $snap = $conf->{snapshots}->{$snapname};
3385
75466c4f
DM
3386 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3387
d788cea6
DM
3388 return $snap;
3389 }});
3390
7e7d7b61
DM
3391__PACKAGE__->register_method({
3392 name => 'rollback',
154ccdcd 3393 path => '{vmid}/snapshot/{snapname}/rollback',
7e7d7b61
DM
3394 method => 'POST',
3395 protected => 1,
3396 proxyto => 'node',
3397 description => "Rollback VM state to specified snapshot.",
3398 permissions => {
f1baf1df 3399 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
7e7d7b61
DM
3400 },
3401 parameters => {
3402 additionalProperties => 0,
3403 properties => {
3404 node => get_standard_option('pve-node'),
335af808 3405 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
8abd398b 3406 snapname => get_standard_option('pve-snapshot-name'),
7e7d7b61
DM
3407 },
3408 },
3409 returns => {
3410 type => 'string',
3411 description => "the task ID.",
3412 },
3413 code => sub {
3414 my ($param) = @_;
3415
3416 my $rpcenv = PVE::RPCEnvironment::get();
3417
3418 my $authuser = $rpcenv->get_user();
3419
3420 my $node = extract_param($param, 'node');
3421
3422 my $vmid = extract_param($param, 'vmid');
3423
3424 my $snapname = extract_param($param, 'snapname');
3425
7e7d7b61 3426 my $realcmd = sub {
22c377f0 3427 PVE::Cluster::log_msg('info', $authuser, "rollback snapshot VM $vmid: $snapname");
b2c9558d 3428 PVE::QemuConfig->snapshot_rollback($vmid, $snapname);
7e7d7b61
DM
3429 };
3430
c068c1c3
WL
3431 my $worker = sub {
3432 # hold migration lock, this makes sure that nobody create replication snapshots
3433 return PVE::GuestHelpers::guest_migration_lock($vmid, 10, $realcmd);
3434 };
3435
3436 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
7e7d7b61
DM
3437 }});
3438
3439__PACKAGE__->register_method({
3440 name => 'delsnapshot',
3441 path => '{vmid}/snapshot/{snapname}',
3442 method => 'DELETE',
3443 protected => 1,
3444 proxyto => 'node',
3445 description => "Delete a VM snapshot.",
3446 permissions => {
f1baf1df 3447 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
7e7d7b61
DM
3448 },
3449 parameters => {
3450 additionalProperties => 0,
3451 properties => {
3452 node => get_standard_option('pve-node'),
335af808 3453 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
8abd398b 3454 snapname => get_standard_option('pve-snapshot-name'),
3ee28e38
DM
3455 force => {
3456 optional => 1,
3457 type => 'boolean',
3458 description => "For removal from config file, even if removing disk snapshots fails.",
3459 },
7e7d7b61
DM
3460 },
3461 },
3462 returns => {
3463 type => 'string',
3464 description => "the task ID.",
3465 },
3466 code => sub {
3467 my ($param) = @_;
3468
3469 my $rpcenv = PVE::RPCEnvironment::get();
3470
3471 my $authuser = $rpcenv->get_user();
3472
3473 my $node = extract_param($param, 'node');
3474
3475 my $vmid = extract_param($param, 'vmid');
3476
3477 my $snapname = extract_param($param, 'snapname');
3478
7e7d7b61 3479 my $realcmd = sub {
22c377f0 3480 PVE::Cluster::log_msg('info', $authuser, "delete snapshot VM $vmid: $snapname");
b2c9558d 3481 PVE::QemuConfig->snapshot_delete($vmid, $snapname, $param->{force});
7e7d7b61 3482 };
9dbd1ee4 3483
7b2257a8 3484 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
9dbd1ee4
AD
3485 }});
3486
04a69bb4
AD
3487__PACKAGE__->register_method({
3488 name => 'template',
3489 path => '{vmid}/template',
3490 method => 'POST',
3491 protected => 1,
3492 proxyto => 'node',
3493 description => "Create a Template.",
b02691d8 3494 permissions => {
7af0a6c8
DM
3495 description => "You need 'VM.Allocate' permissions on /vms/{vmid}",
3496 check => [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
b02691d8 3497 },
04a69bb4
AD
3498 parameters => {
3499 additionalProperties => 0,
3500 properties => {
3501 node => get_standard_option('pve-node'),
335af808 3502 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid_stopped }),
04a69bb4
AD
3503 disk => {
3504 optional => 1,
3505 type => 'string',
3506 description => "If you want to convert only 1 disk to base image.",
74479ee9 3507 enum => [PVE::QemuServer::valid_drive_names()],
04a69bb4
AD
3508 },
3509
3510 },
3511 },
3512 returns => { type => 'null'},
3513 code => sub {
3514 my ($param) = @_;
3515
3516 my $rpcenv = PVE::RPCEnvironment::get();
3517
3518 my $authuser = $rpcenv->get_user();
3519
3520 my $node = extract_param($param, 'node');
3521
3522 my $vmid = extract_param($param, 'vmid');
3523
3524 my $disk = extract_param($param, 'disk');
3525
3526 my $updatefn = sub {
3527
ffda963f 3528 my $conf = PVE::QemuConfig->load_config($vmid);
04a69bb4 3529
ffda963f 3530 PVE::QemuConfig->check_lock($conf);
04a69bb4 3531
75466c4f 3532 die "unable to create template, because VM contains snapshots\n"
b91c2aae 3533 if $conf->{snapshots} && scalar(keys %{$conf->{snapshots}});
0402a80b 3534
75466c4f 3535 die "you can't convert a template to a template\n"
ffda963f 3536 if PVE::QemuConfig->is_template($conf) && !$disk;
0402a80b 3537
75466c4f 3538 die "you can't convert a VM to template if VM is running\n"
218cab9a 3539 if PVE::QemuServer::check_running($vmid);
35c5fdef 3540
04a69bb4
AD
3541 my $realcmd = sub {
3542 PVE::QemuServer::template_create($vmid, $conf, $disk);
3543 };
04a69bb4 3544
75e7e997 3545 $conf->{template} = 1;
ffda963f 3546 PVE::QemuConfig->write_config($vmid, $conf);
75e7e997
DM
3547
3548 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
04a69bb4
AD
3549 };
3550
ffda963f 3551 PVE::QemuConfig->lock_config($vmid, $updatefn);
04a69bb4
AD
3552 return undef;
3553 }});
3554
1e3baf05 35551;