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