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