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