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