]> git.proxmox.com Git - qemu-server.git/blame - PVE/API2/Qemu.pm
fix CD eject
[qemu-server.git] / PVE / API2 / Qemu.pm
CommitLineData
1e3baf05
DM
1package PVE::API2::Qemu;
2
3use strict;
4use warnings;
5b9d692a 5use Cwd 'abs_path';
1e3baf05
DM
6
7use PVE::Cluster;
8use PVE::SafeSyslog;
9use PVE::Tools qw(extract_param);
10use PVE::Exception qw(raise raise_param_exc);
11use PVE::Storage;
12use PVE::JSONSchema qw(get_standard_option);
13use PVE::RESTHandler;
14use PVE::QemuServer;
3ea94c60 15use PVE::QemuMigrate;
1e3baf05
DM
16use PVE::RPCEnvironment;
17use PVE::AccessControl;
18use PVE::INotify;
19
20use Data::Dumper; # fixme: remove
21
22use base qw(PVE::RESTHandler);
23
24my $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.";
25
26my $resolve_cdrom_alias = sub {
27 my $param = shift;
28
29 if (my $value = $param->{cdrom}) {
30 $value .= ",media=cdrom" if $value !~ m/media=/;
31 $param->{ide2} = $value;
32 delete $param->{cdrom};
33 }
34};
35
a0d1b1a2
DM
36my $check_volume_access = sub {
37 my ($rpcenv, $authuser, $storecfg, $vmid, $volid, $pool) = @_;
38
39 my $path;
40 if (my ($sid, $volname) = PVE::Storage::parse_volume_id($volid, 1)) {
41 my ($ownervm, $vtype);
42 ($path, $ownervm, $vtype) = PVE::Storage::path($storecfg, $volid);
43 if ($vtype eq 'iso' || $vtype eq 'vztmpl') {
44 # we simply allow access
45 } elsif (!$ownervm || ($ownervm != $vmid)) {
46 # allow if we are Datastore administrator
47 $rpcenv->check_storage_perm($authuser, $vmid, $pool, $sid, [ 'Datastore.Allocate' ]);
48 }
49 } else {
50 die "Only root can pass arbitrary filesystem paths."
51 if $authuser ne 'root@pam';
52
53 $path = abs_path($volid);
54 }
55 return $path;
56};
57
ae57f6b3
DM
58my $check_storage_access = sub {
59 my ($rpcenv, $authuser, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
a0d1b1a2 60
ae57f6b3
DM
61 PVE::QemuServer::foreach_drive($settings, sub {
62 my ($ds, $drive) = @_;
a0d1b1a2 63
ae57f6b3 64 my $isCDROM = PVE::QemuServer::drive_is_cdrom($drive);
a0d1b1a2 65
ae57f6b3 66 my $volid = $drive->{file};
a0d1b1a2 67
09d0ee64
DM
68 if (!$volid || $volid eq 'none') {
69 # nothing to check
70 } elsif (!$isCDROM && ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/)) {
a0d1b1a2
DM
71 my ($storeid, $size) = ($2 || $default_storage, $3);
72 die "no storage ID specified (and no default storage)\n" if !$storeid;
73 $rpcenv->check_storage_perm($authuser, $vmid, $pool, $storeid, [ 'Datastore.AllocateSpace' ]);
a0d1b1a2
DM
74 } else {
75 my $path = &$check_volume_access($rpcenv, $authuser, $storecfg, $vmid, $volid, $pool);
76 die "image '$path' does not exists\n" if (!(-f $path || -b $path));
77 }
78 });
ae57f6b3 79};
a0d1b1a2 80
ae57f6b3
DM
81# Note: $pool is only needed when creating a VM, because pool permissions
82# are automatically inherited if VM already exists inside a pool.
83my $create_disks = sub {
84 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
a0d1b1a2
DM
85
86 my $vollist = [];
a0d1b1a2 87
ae57f6b3
DM
88 my $res = {};
89 PVE::QemuServer::foreach_drive($settings, sub {
90 my ($ds, $disk) = @_;
91
92 my $volid = $disk->{file};
93
09d0ee64
DM
94 if (!$volid || $volid eq 'none') {
95 $res->{$ds} = $settings->{$ds};
96 } elsif ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/) {
ae57f6b3
DM
97 my ($storeid, $size) = ($2 || $default_storage, $3);
98 die "no storage ID specified (and no default storage)\n" if !$storeid;
99 my $defformat = PVE::Storage::storage_default_format($storecfg, $storeid);
100 my $fmt = $disk->{format} || $defformat;
a0d1b1a2
DM
101 my $volid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $vmid,
102 $fmt, undef, $size*1024*1024);
a0d1b1a2
DM
103 $disk->{file} = $volid;
104 push @$vollist, $volid;
ae57f6b3
DM
105 delete $disk->{format}; # no longer needed
106 $res->{$ds} = PVE::QemuServer::print_drive($vmid, $disk);
107 } else {
108 my $path = &$check_volume_access($rpcenv, $authuser, $storecfg, $vmid, $volid, $pool);
109 die "image '$path' does not exists\n" if (!(-f $path || -b $path));
09d0ee64 110 $res->{$ds} = $settings->{$ds};
a0d1b1a2 111 }
ae57f6b3 112 });
a0d1b1a2
DM
113
114 # free allocated images on error
115 if (my $err = $@) {
116 syslog('err', "VM $vmid creating disks failed");
117 foreach my $volid (@$vollist) {
118 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
119 warn $@ if $@;
120 }
121 die $err;
122 }
123
124 # modify vm config if everything went well
ae57f6b3
DM
125 foreach my $ds (keys %$res) {
126 $conf->{$ds} = $res->{$ds};
a0d1b1a2
DM
127 }
128
129 return $vollist;
130};
131
132my $check_vm_modify_config_perm = sub {
ae57f6b3 133 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
a0d1b1a2
DM
134
135 return 1 if $authuser ne 'root@pam';
136
ae57f6b3 137 foreach my $opt (@$key_list) {
a0d1b1a2
DM
138 # disk checks need to be done somewhere else
139 next if PVE::QemuServer::valid_drivename($opt);
140
141 if ($opt eq 'sockets' || $opt eq 'cores' ||
142 $opt eq 'cpu' || $opt eq 'smp' ||
143 $opt eq 'cpuimit' || $opt eq 'cpuunits') {
144 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
145 } elsif ($opt eq 'boot' || $opt eq 'bootdisk') {
146 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
147 } elsif ($opt eq 'memory' || $opt eq 'balloon') {
148 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
149 } elsif ($opt eq 'args' || $opt eq 'lock') {
150 die "only root can set '$opt' config\n";
151 } elsif ($opt eq 'cpu' || $opt eq 'kvm' || $opt eq 'acpi' ||
152 $opt eq 'vga' || $opt eq 'watchdog' || $opt eq 'tablet') {
153 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
154 } elsif ($opt =~ m/^net\d+$/) {
155 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
156 } else {
157 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
158 }
159 }
160
161 return 1;
162};
163
1e3baf05 164__PACKAGE__->register_method({
afdb31d5
DM
165 name => 'vmlist',
166 path => '',
1e3baf05
DM
167 method => 'GET',
168 description => "Virtual machine index (per node).",
a0d1b1a2
DM
169 permissions => {
170 description => "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
171 user => 'all',
172 },
1e3baf05
DM
173 proxyto => 'node',
174 protected => 1, # qemu pid files are only readable by root
175 parameters => {
176 additionalProperties => 0,
177 properties => {
178 node => get_standard_option('pve-node'),
179 },
180 },
181 returns => {
182 type => 'array',
183 items => {
184 type => "object",
185 properties => {},
186 },
187 links => [ { rel => 'child', href => "{vmid}" } ],
188 },
189 code => sub {
190 my ($param) = @_;
191
a0d1b1a2
DM
192 my $rpcenv = PVE::RPCEnvironment::get();
193 my $authuser = $rpcenv->get_user();
194
1e3baf05
DM
195 my $vmstatus = PVE::QemuServer::vmstatus();
196
a0d1b1a2
DM
197 my $res = [];
198 foreach my $vmid (keys %$vmstatus) {
199 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
200
201 my $data = $vmstatus->{$vmid};
202 $data->{vmid} = $vmid;
203 push @$res, $data;
204 }
1e3baf05 205
a0d1b1a2 206 return $res;
1e3baf05
DM
207 }});
208
209__PACKAGE__->register_method({
afdb31d5
DM
210 name => 'create_vm',
211 path => '',
1e3baf05 212 method => 'POST',
3e16d5fc 213 description => "Create or restore a virtual machine.",
a0d1b1a2
DM
214 permissions => {
215 description => "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
216 check => [ 'or',
217 [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
218 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param => 'pool'],
219 ],
220 },
1e3baf05
DM
221 protected => 1,
222 proxyto => 'node',
223 parameters => {
224 additionalProperties => 0,
225 properties => PVE::QemuServer::json_config_properties(
226 {
227 node => get_standard_option('pve-node'),
228 vmid => get_standard_option('pve-vmid'),
3e16d5fc
DM
229 archive => {
230 description => "The backup file.",
231 type => 'string',
232 optional => 1,
233 maxLength => 255,
234 },
235 storage => get_standard_option('pve-storage-id', {
236 description => "Default storage.",
237 optional => 1,
238 }),
239 force => {
afdb31d5 240 optional => 1,
3e16d5fc
DM
241 type => 'boolean',
242 description => "Allow to overwrite existing VM.",
51586c3a
DM
243 requires => 'archive',
244 },
245 unique => {
afdb31d5 246 optional => 1,
51586c3a
DM
247 type => 'boolean',
248 description => "Assign a unique random ethernet address.",
249 requires => 'archive',
3e16d5fc 250 },
a0d1b1a2
DM
251 pool => {
252 optional => 1,
253 type => 'string', format => 'pve-poolid',
254 description => "Add the VM to the specified pool.",
255 },
1e3baf05
DM
256 }),
257 },
afdb31d5 258 returns => {
5fdbe4f0
DM
259 type => 'string',
260 },
1e3baf05
DM
261 code => sub {
262 my ($param) = @_;
263
5fdbe4f0
DM
264 my $rpcenv = PVE::RPCEnvironment::get();
265
a0d1b1a2 266 my $authuser = $rpcenv->get_user();
5fdbe4f0 267
1e3baf05
DM
268 my $node = extract_param($param, 'node');
269
1e3baf05
DM
270 my $vmid = extract_param($param, 'vmid');
271
3e16d5fc
DM
272 my $archive = extract_param($param, 'archive');
273
274 my $storage = extract_param($param, 'storage');
275
51586c3a
DM
276 my $force = extract_param($param, 'force');
277
278 my $unique = extract_param($param, 'unique');
a0d1b1a2
DM
279
280 my $pool = extract_param($param, 'pool');
51586c3a 281
1e3baf05 282 my $filename = PVE::QemuServer::config_file($vmid);
afdb31d5
DM
283
284 my $storecfg = PVE::Storage::config();
1e3baf05 285
3e16d5fc 286 PVE::Cluster::check_cfs_quorum();
1e3baf05 287
a0d1b1a2
DM
288 if (defined($pool)) {
289 $rpcenv->check_pool_exist($pool);
290 $rpcenv->check_perm_modify($authuser, "/pool/$pool");
291 }
292
293 $rpcenv->check_storage_perm($authuser, $vmid, $pool, $storage, [ 'Datastore.AllocateSpace' ])
294 if defined($storage);
295
afdb31d5 296 if (!$archive) {
3e16d5fc 297 &$resolve_cdrom_alias($param);
1e3baf05 298
ae57f6b3
DM
299 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $pool, $param, $storage);
300
301 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
302
3e16d5fc
DM
303 foreach my $opt (keys %$param) {
304 if (PVE::QemuServer::valid_drivename($opt)) {
305 my $drive = PVE::QemuServer::parse_drive($opt, $param->{$opt});
306 raise_param_exc({ $opt => "unable to parse drive options" }) if !$drive;
afdb31d5 307
3e16d5fc
DM
308 PVE::QemuServer::cleanup_drive_path($opt, $storecfg, $drive);
309 $param->{$opt} = PVE::QemuServer::print_drive($vmid, $drive);
310 }
1e3baf05 311 }
3e16d5fc
DM
312
313 PVE::QemuServer::add_random_macs($param);
51586c3a
DM
314 } else {
315 my $keystr = join(' ', keys %$param);
bc4dcb99
DM
316 raise_param_exc({ archive => "option conflicts with other options ($keystr)"}) if $keystr;
317
5b9d692a 318 if ($archive eq '-') {
afdb31d5 319 die "pipe requires cli environment\n"
5b9d692a
DM
320 && $rpcenv->{type} ne 'cli';
321 } else {
a0d1b1a2 322 my $path = &$check_volume_access($rpcenv, $authuser, $storecfg, $vmid, $archive, $pool);
971f27c4
DM
323 die "can't find archive file '$archive'\n" if !($path && -f $path);
324 $archive = $path;
325 }
1e3baf05
DM
326 }
327
3e16d5fc
DM
328 my $restorefn = sub {
329
330 if (-f $filename) {
afdb31d5 331 die "unable to restore vm $vmid: config file already exists\n"
51586c3a 332 if !$force;
3e16d5fc 333
afdb31d5 334 die "unable to restore vm $vmid: vm is running\n"
3e16d5fc 335 if PVE::QemuServer::check_running($vmid);
a6af7b3e
DM
336
337 # destroy existing data - keep empty config
338 PVE::QemuServer::destroy_vm($storecfg, $vmid, 1);
3e16d5fc
DM
339 }
340
341 my $realcmd = sub {
a0d1b1a2 342 PVE::QemuServer::restore_archive($archive, $vmid, $authuser, {
51586c3a 343 storage => $storage,
a0d1b1a2 344 pool => $pool,
51586c3a 345 unique => $unique });
3e16d5fc
DM
346 };
347
a0d1b1a2 348 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
3e16d5fc 349 };
1e3baf05 350
1e3baf05
DM
351 my $createfn = sub {
352
353 # second test (after locking test is accurate)
afdb31d5 354 die "unable to create vm $vmid: config file already exists\n"
1e3baf05
DM
355 if -f $filename;
356
5fdbe4f0 357 my $realcmd = sub {
1e3baf05 358
5fdbe4f0 359 my $vollist = [];
1e3baf05 360
1858638f
DM
361 my $conf = $param;
362
5fdbe4f0 363 eval {
ae57f6b3 364
1858638f 365 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
1e3baf05 366
5fdbe4f0
DM
367 # try to be smart about bootdisk
368 my @disks = PVE::QemuServer::disknames();
369 my $firstdisk;
370 foreach my $ds (reverse @disks) {
1858638f
DM
371 next if !$conf->{$ds};
372 my $disk = PVE::QemuServer::parse_drive($ds, $conf->{$ds});
5fdbe4f0
DM
373 next if PVE::QemuServer::drive_is_cdrom($disk);
374 $firstdisk = $ds;
375 }
1e3baf05 376
1858638f
DM
377 if (!$conf->{bootdisk} && $firstdisk) {
378 $conf->{bootdisk} = $firstdisk;
5fdbe4f0 379 }
1e3baf05 380
1858638f 381 PVE::QemuServer::update_conf_nolock($vmid, $conf);
5fdbe4f0
DM
382 };
383 my $err = $@;
1e3baf05 384
5fdbe4f0
DM
385 if ($err) {
386 foreach my $volid (@$vollist) {
387 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
388 warn $@ if $@;
389 }
390 die "create failed - $err";
391 }
392 };
393
a0d1b1a2 394 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
5fdbe4f0
DM
395 };
396
3e16d5fc 397 return PVE::QemuServer::lock_config($vmid, $archive ? $restorefn : $createfn);
1e3baf05
DM
398 }});
399
400__PACKAGE__->register_method({
401 name => 'vmdiridx',
afdb31d5 402 path => '{vmid}',
1e3baf05
DM
403 method => 'GET',
404 proxyto => 'node',
405 description => "Directory index",
a0d1b1a2
DM
406 permissions => {
407 user => 'all',
408 },
1e3baf05
DM
409 parameters => {
410 additionalProperties => 0,
411 properties => {
412 node => get_standard_option('pve-node'),
413 vmid => get_standard_option('pve-vmid'),
414 },
415 },
416 returns => {
417 type => 'array',
418 items => {
419 type => "object",
420 properties => {
421 subdir => { type => 'string' },
422 },
423 },
424 links => [ { rel => 'child', href => "{subdir}" } ],
425 },
426 code => sub {
427 my ($param) = @_;
428
429 my $res = [
430 { subdir => 'config' },
431 { subdir => 'status' },
432 { subdir => 'unlink' },
433 { subdir => 'vncproxy' },
3ea94c60 434 { subdir => 'migrate' },
1e3baf05
DM
435 { subdir => 'rrd' },
436 { subdir => 'rrddata' },
91c94f0a 437 { subdir => 'monitor' },
1e3baf05 438 ];
afdb31d5 439
1e3baf05
DM
440 return $res;
441 }});
442
443__PACKAGE__->register_method({
afdb31d5
DM
444 name => 'rrd',
445 path => '{vmid}/rrd',
1e3baf05
DM
446 method => 'GET',
447 protected => 1, # fixme: can we avoid that?
448 permissions => {
378b359e 449 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1e3baf05
DM
450 },
451 description => "Read VM RRD statistics (returns PNG)",
452 parameters => {
453 additionalProperties => 0,
454 properties => {
455 node => get_standard_option('pve-node'),
456 vmid => get_standard_option('pve-vmid'),
457 timeframe => {
458 description => "Specify the time frame you are interested in.",
459 type => 'string',
460 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
461 },
462 ds => {
463 description => "The list of datasources you want to display.",
464 type => 'string', format => 'pve-configid-list',
465 },
466 cf => {
467 description => "The RRD consolidation function",
468 type => 'string',
469 enum => [ 'AVERAGE', 'MAX' ],
470 optional => 1,
471 },
472 },
473 },
474 returns => {
475 type => "object",
476 properties => {
477 filename => { type => 'string' },
478 },
479 },
480 code => sub {
481 my ($param) = @_;
482
483 return PVE::Cluster::create_rrd_graph(
afdb31d5 484 "pve2-vm/$param->{vmid}", $param->{timeframe},
1e3baf05 485 $param->{ds}, $param->{cf});
afdb31d5 486
1e3baf05
DM
487 }});
488
489__PACKAGE__->register_method({
afdb31d5
DM
490 name => 'rrddata',
491 path => '{vmid}/rrddata',
1e3baf05
DM
492 method => 'GET',
493 protected => 1, # fixme: can we avoid that?
494 permissions => {
378b359e 495 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1e3baf05
DM
496 },
497 description => "Read VM RRD statistics",
498 parameters => {
499 additionalProperties => 0,
500 properties => {
501 node => get_standard_option('pve-node'),
502 vmid => get_standard_option('pve-vmid'),
503 timeframe => {
504 description => "Specify the time frame you are interested in.",
505 type => 'string',
506 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
507 },
508 cf => {
509 description => "The RRD consolidation function",
510 type => 'string',
511 enum => [ 'AVERAGE', 'MAX' ],
512 optional => 1,
513 },
514 },
515 },
516 returns => {
517 type => "array",
518 items => {
519 type => "object",
520 properties => {},
521 },
522 },
523 code => sub {
524 my ($param) = @_;
525
526 return PVE::Cluster::create_rrd_data(
527 "pve2-vm/$param->{vmid}", $param->{timeframe}, $param->{cf});
528 }});
529
530
531__PACKAGE__->register_method({
afdb31d5
DM
532 name => 'vm_config',
533 path => '{vmid}/config',
1e3baf05
DM
534 method => 'GET',
535 proxyto => 'node',
536 description => "Get virtual machine configuration.",
a0d1b1a2
DM
537 permissions => {
538 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
539 },
1e3baf05
DM
540 parameters => {
541 additionalProperties => 0,
542 properties => {
543 node => get_standard_option('pve-node'),
544 vmid => get_standard_option('pve-vmid'),
545 },
546 },
afdb31d5 547 returns => {
1e3baf05 548 type => "object",
554ac7e7
DM
549 properties => {
550 digest => {
551 type => 'string',
552 description => 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
553 }
554 },
1e3baf05
DM
555 },
556 code => sub {
557 my ($param) = @_;
558
559 my $conf = PVE::QemuServer::load_config($param->{vmid});
560
561 return $conf;
562 }});
563
ae57f6b3
DM
564my $vm_is_volid_owner = sub {
565 my ($storecfg, $vmid, $volid) =@_;
566
567 if ($volid !~ m|^/|) {
568 my ($path, $owner);
569 eval { ($path, $owner) = PVE::Storage::path($storecfg, $volid); };
570 if ($owner && ($owner == $vmid)) {
571 return 1;
572 }
573 }
574
575 return undef;
576};
577
578my $test_deallocate_drive = sub {
579 my ($storecfg, $vmid, $key, $drive, $force) = @_;
580
581 if (!PVE::QemuServer::drive_is_cdrom($drive)) {
582 my $volid = $drive->{file};
583 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
584 if ($force || $key =~ m/^unused/) {
585 my $sid = PVE::Storage::parse_volume_id($volid);
586 return $sid;
587 }
588 }
589 }
590
591 return undef;
592};
593
5d7a6767 594my $delete_drive = sub {
1858638f 595 my ($conf, $storecfg, $vmid, $key, $drive, $force) = @_;
5d7a6767
DM
596
597 if (!PVE::QemuServer::drive_is_cdrom($drive)) {
598 my $volid = $drive->{file};
ae57f6b3
DM
599 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
600 if ($force || $key =~ m/^unused/) {
601 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
602 warn $@ if $@;
603 } else {
604 PVE::QemuServer::add_unused_volume($conf, $volid, $vmid);
5d7a6767 605 }
ae57f6b3
DM
606 delete $conf->{$key};
607 }
608 }
609};
610
611my $vmconfig_delete_option = sub {
612 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force) = @_;
613
614 return if !defined($conf->{$opt});
615
616 my $isDisk = PVE::QemuServer::valid_drivename($opt)|| ($opt =~ m/^unused/);
617
618 if ($isDisk) {
619 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
620
621 my $drive = PVE::QemuServer::parse_drive($opt, $conf->{$opt});
622 if (my $sid = &$test_deallocate_drive($storecfg, $vmid, $opt, $drive, $force)) {
623 $rpcenv->check_storage_perm($authuser, $vmid, undef, $sid, [ 'Datastore.Allocate' ]);
5d7a6767 624 }
ae57f6b3
DM
625 }
626
627 die "error hot-unplug $opt" if !PVE::QemuServer::vm_deviceunplug($vmid, $conf, $opt);
628
629 if ($isDisk) {
630 my $drive = PVE::QemuServer::parse_drive($opt, $conf->{$opt});
631 &$delete_drive($conf, $storecfg, $vmid, $opt, $drive, $force);
5d7a6767 632 } else {
ae57f6b3
DM
633 delete $conf->{$opt};
634 }
635
636 PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
637};
638
639my $vmconfig_update_disk = sub {
640 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value, $force) = @_;
5d7a6767 641
ae57f6b3
DM
642 my $drive = PVE::QemuServer::parse_drive($opt, $value);
643
644 if (PVE::QemuServer::drive_is_cdrom($drive)) { #cdrom
645 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
646 } else {
647 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
648 }
649
650 if ($conf->{$opt}) {
651
652 if (my $old_drive = PVE::QemuServer::parse_drive($opt, $conf->{$opt})) {
653
654 my $media = $drive->{media} || 'disk';
655 my $oldmedia = $old_drive->{media} || 'disk';
656 die "unable to change media type\n" if $media ne $oldmedia;
657
658 if (!PVE::QemuServer::drive_is_cdrom($old_drive) &&
659 ($drive->{file} ne $old_drive->{file})) { # delete old disks
660
661 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
662 $conf = PVE::QemuServer::load_config($vmid); # update/reload
663 }
664 }
665 }
666
667 &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, undef, {$opt => $value});
668 PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
669
670 $conf = PVE::QemuServer::load_config($vmid); # update/reload
671 $drive = PVE::QemuServer::parse_drive($opt, $conf->{$opt});
672
673 if (PVE::QemuServer::drive_is_cdrom($drive)) { # cdrom
674
675 if (PVE::QemuServer::check_running($vmid)) {
676 if ($drive->{file} eq 'none') {
677 PVE::QemuServer::vm_monitor_command($vmid, "eject -f drive-$opt", 0);
678 } else {
679 my $path = PVE::QemuServer::get_iso_path($storecfg, $vmid, $drive->{file});
680 PVE::QemuServer::vm_monitor_command($vmid, "eject -f drive-$opt", 0); #force eject if locked
681 PVE::QemuServer::vm_monitor_command($vmid, "change drive-$opt \"$path\"", 0) if $path;
682 }
683 }
684
685 } else { # hotplug new disks
686
687 die "error hotplug $opt" if !PVE::QemuServer::vm_deviceplug($storecfg, $conf, $vmid, $opt, $drive);
5d7a6767 688 }
5d7a6767
DM
689};
690
ae57f6b3
DM
691my $vmconfig_update_net = sub {
692 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value) = @_;
693
694 if ($conf->{$opt}) {
695 #if online update, then unplug first
696 die "error hot-unplug $opt for update" if !PVE::QemuServer::vm_deviceunplug($vmid, $conf, $opt);
697 }
698
699 $conf->{$opt} = $value;
700 PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
701 $conf = PVE::QemuServer::load_config($vmid); # update/reload
702
703 my $net = PVE::QemuServer::parse_net($conf->{$opt});
704
705 die "error hotplug $opt" if !PVE::QemuServer::vm_deviceplug($storecfg, $conf, $vmid, $opt, $net);
706};
707
a0d1b1a2
DM
708my $vm_config_perm_list = [
709 'VM.Config.Disk',
710 'VM.Config.CDROM',
711 'VM.Config.CPU',
712 'VM.Config.Memory',
713 'VM.Config.Network',
714 'VM.Config.HWType',
715 'VM.Config.Options',
716 ];
717
1e3baf05 718__PACKAGE__->register_method({
afdb31d5
DM
719 name => 'update_vm',
720 path => '{vmid}/config',
1e3baf05
DM
721 method => 'PUT',
722 protected => 1,
723 proxyto => 'node',
724 description => "Set virtual machine options.",
a0d1b1a2
DM
725 permissions => {
726 check => ['perm', '/vms/{vmid}', $vm_config_perm_list, any => 1],
727 },
1e3baf05
DM
728 parameters => {
729 additionalProperties => 0,
730 properties => PVE::QemuServer::json_config_properties(
731 {
732 node => get_standard_option('pve-node'),
733 vmid => get_standard_option('pve-vmid'),
3ea94c60 734 skiplock => get_standard_option('skiplock'),
1e3baf05
DM
735 delete => {
736 type => 'string', format => 'pve-configid-list',
737 description => "A list of settings you want to delete.",
738 optional => 1,
739 },
740 force => {
741 type => 'boolean',
742 description => $opt_force_description,
743 optional => 1,
744 requires => 'delete',
745 },
554ac7e7
DM
746 digest => {
747 type => 'string',
748 description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
749 maxLength => 40,
afdb31d5 750 optional => 1,
554ac7e7 751 }
1e3baf05
DM
752 }),
753 },
754 returns => { type => 'null'},
755 code => sub {
756 my ($param) = @_;
757
758 my $rpcenv = PVE::RPCEnvironment::get();
759
a0d1b1a2 760 my $authuser = $rpcenv->get_user();
1e3baf05
DM
761
762 my $node = extract_param($param, 'node');
763
1e3baf05
DM
764 my $vmid = extract_param($param, 'vmid');
765
5fdbe4f0
DM
766 my $digest = extract_param($param, 'digest');
767
768 my @paramarr = (); # used for log message
769 foreach my $key (keys %$param) {
770 push @paramarr, "-$key", $param->{$key};
771 }
772
1e3baf05 773 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 774 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 775 if $skiplock && $authuser ne 'root@pam';
1e3baf05 776
0532bc63
DM
777 my $delete_str = extract_param($param, 'delete');
778
1e3baf05
DM
779 my $force = extract_param($param, 'force');
780
0532bc63
DM
781 die "no options specified\n" if !$delete_str && !scalar(keys %$param);
782
1e68cb19
DM
783 my $storecfg = PVE::Storage::config();
784
785 &$resolve_cdrom_alias($param);
786
787 # now try to verify all parameters
788
0532bc63
DM
789 my @delete = ();
790 foreach my $opt (PVE::Tools::split_list($delete_str)) {
791 $opt = 'ide2' if $opt eq 'cdrom';
792 raise_param_exc({ delete => "you can't use '-$opt' and " .
793 "-delete $opt' at the same time" })
794 if defined($param->{$opt});
795
796 if (!PVE::QemuServer::option_exists($opt)) {
797 raise_param_exc({ delete => "unknown option '$opt'" });
798 }
ae57f6b3 799
0532bc63
DM
800 push @delete, $opt;
801 }
1e3baf05 802
1e68cb19
DM
803 foreach my $opt (keys %$param) {
804 if (PVE::QemuServer::valid_drivename($opt)) {
805 # cleanup drive path
806 my $drive = PVE::QemuServer::parse_drive($opt, $param->{$opt});
807 PVE::QemuServer::cleanup_drive_path($opt, $storecfg, $drive);
808 $param->{$opt} = PVE::QemuServer::print_drive($vmid, $drive);
1e68cb19
DM
809 } elsif ($opt =~ m/^net(\d+)$/) {
810 # add macaddr
811 my $net = PVE::QemuServer::parse_net($param->{$opt});
812 $param->{$opt} = PVE::QemuServer::print_net($net);
1e68cb19
DM
813 }
814 }
1e3baf05 815
ae57f6b3
DM
816 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
817
818 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
819
820 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, undef, $param);
1e3baf05 821
5d39a182 822 my $updatefn = sub {
1e3baf05 823
5d39a182 824 my $conf = PVE::QemuServer::load_config($vmid);
1e3baf05 825
5d39a182
DM
826 die "checksum missmatch (file change by other user?)\n"
827 if $digest && $digest ne $conf->{digest};
1e3baf05 828
5d39a182 829 PVE::QemuServer::check_lock($conf) if !$skiplock;
1e3baf05 830
a0d1b1a2 831 PVE::Cluster::log_msg('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
c2a64aa7 832
5d7a6767 833 foreach my $opt (@delete) { # delete
1e68cb19 834 $conf = PVE::QemuServer::load_config($vmid); # update/reload
ae57f6b3 835 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
5d39a182 836 }
1e3baf05 837
5d7a6767 838 foreach my $opt (keys %$param) { # add/change
1e3baf05 839
1e68cb19 840 $conf = PVE::QemuServer::load_config($vmid); # update/reload
1e3baf05 841
5d7a6767
DM
842 next if $conf->{$opt} && ($param->{$opt} eq $conf->{$opt}); # skip if nothing changed
843
ae57f6b3 844 if (PVE::QemuServer::valid_drivename($opt)) {
5d7a6767 845
ae57f6b3
DM
846 &$vmconfig_update_disk($rpcenv, $authuser, $conf, $storecfg, $vmid,
847 $opt, $param->{$opt}, $force);
1e68cb19 848
ae57f6b3 849 } elsif ($opt =~ m/^net(\d+)$/) { #nics
1858638f 850
ae57f6b3
DM
851 &$vmconfig_update_net($rpcenv, $authuser, $conf, $storecfg, $vmid,
852 $opt, $param->{$opt});
1e68cb19
DM
853
854 } else {
855
1858638f
DM
856 $conf->{$opt} = $param->{$opt};
857 PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
3a1e36bb 858 }
5d39a182
DM
859 }
860 };
861
862 PVE::QemuServer::lock_config($vmid, $updatefn);
fcdb0117 863
1e3baf05
DM
864 return undef;
865 }});
866
867
868__PACKAGE__->register_method({
afdb31d5
DM
869 name => 'destroy_vm',
870 path => '{vmid}',
1e3baf05
DM
871 method => 'DELETE',
872 protected => 1,
873 proxyto => 'node',
874 description => "Destroy the vm (also delete all used/owned volumes).",
a0d1b1a2
DM
875 permissions => {
876 check => [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
877 },
1e3baf05
DM
878 parameters => {
879 additionalProperties => 0,
880 properties => {
881 node => get_standard_option('pve-node'),
882 vmid => get_standard_option('pve-vmid'),
3ea94c60 883 skiplock => get_standard_option('skiplock'),
1e3baf05
DM
884 },
885 },
afdb31d5 886 returns => {
5fdbe4f0
DM
887 type => 'string',
888 },
1e3baf05
DM
889 code => sub {
890 my ($param) = @_;
891
892 my $rpcenv = PVE::RPCEnvironment::get();
893
a0d1b1a2 894 my $authuser = $rpcenv->get_user();
1e3baf05
DM
895
896 my $vmid = $param->{vmid};
897
898 my $skiplock = $param->{skiplock};
afdb31d5 899 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 900 if $skiplock && $authuser ne 'root@pam';
1e3baf05 901
5fdbe4f0
DM
902 # test if VM exists
903 my $conf = PVE::QemuServer::load_config($vmid);
904
afdb31d5 905 my $storecfg = PVE::Storage::config();
1e3baf05 906
5fdbe4f0 907 my $realcmd = sub {
ff1a2432
DM
908 my $upid = shift;
909
910 syslog('info', "destroy VM $vmid: $upid\n");
911
5fdbe4f0
DM
912 PVE::QemuServer::vm_destroy($storecfg, $vmid, $skiplock);
913 };
1e3baf05 914
a0d1b1a2 915 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1e3baf05
DM
916 }});
917
918__PACKAGE__->register_method({
afdb31d5
DM
919 name => 'unlink',
920 path => '{vmid}/unlink',
1e3baf05
DM
921 method => 'PUT',
922 protected => 1,
923 proxyto => 'node',
924 description => "Unlink/delete disk images.",
a0d1b1a2
DM
925 permissions => {
926 check => [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
927 },
1e3baf05
DM
928 parameters => {
929 additionalProperties => 0,
930 properties => {
931 node => get_standard_option('pve-node'),
932 vmid => get_standard_option('pve-vmid'),
933 idlist => {
934 type => 'string', format => 'pve-configid-list',
935 description => "A list of disk IDs you want to delete.",
936 },
937 force => {
938 type => 'boolean',
939 description => $opt_force_description,
940 optional => 1,
941 },
942 },
943 },
944 returns => { type => 'null'},
945 code => sub {
946 my ($param) = @_;
947
948 $param->{delete} = extract_param($param, 'idlist');
949
950 __PACKAGE__->update_vm($param);
951
952 return undef;
953 }});
954
955my $sslcert;
956
957__PACKAGE__->register_method({
afdb31d5
DM
958 name => 'vncproxy',
959 path => '{vmid}/vncproxy',
1e3baf05
DM
960 method => 'POST',
961 protected => 1,
962 permissions => {
378b359e 963 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1e3baf05
DM
964 },
965 description => "Creates a TCP VNC proxy connections.",
966 parameters => {
967 additionalProperties => 0,
968 properties => {
969 node => get_standard_option('pve-node'),
970 vmid => get_standard_option('pve-vmid'),
971 },
972 },
afdb31d5 973 returns => {
1e3baf05
DM
974 additionalProperties => 0,
975 properties => {
976 user => { type => 'string' },
977 ticket => { type => 'string' },
978 cert => { type => 'string' },
979 port => { type => 'integer' },
980 upid => { type => 'string' },
981 },
982 },
983 code => sub {
984 my ($param) = @_;
985
986 my $rpcenv = PVE::RPCEnvironment::get();
987
a0d1b1a2 988 my $authuser = $rpcenv->get_user();
1e3baf05
DM
989
990 my $vmid = $param->{vmid};
991 my $node = $param->{node};
992
b6f39da2
DM
993 my $authpath = "/vms/$vmid";
994
a0d1b1a2 995 my $ticket = PVE::AccessControl::assemble_vnc_ticket($authuser, $authpath);
b6f39da2 996
1e3baf05
DM
997 $sslcert = PVE::Tools::file_get_contents("/etc/pve/pve-root-ca.pem", 8192)
998 if !$sslcert;
999
1000 my $port = PVE::Tools::next_vnc_port();
1001
1002 my $remip;
afdb31d5 1003
4f1be36c 1004 if ($node ne 'localhost' && $node ne PVE::INotify::nodename()) {
1e3baf05
DM
1005 $remip = PVE::Cluster::remote_node_ip($node);
1006 }
1007
1008 # NOTE: kvm VNC traffic is already TLS encrypted,
1009 # so we select the fastest chipher here (or 'none'?)
1010 my $remcmd = $remip ? ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes',
1011 '-c', 'blowfish-cbc', $remip] : [];
1012
afdb31d5 1013 my $timeout = 10;
1e3baf05
DM
1014
1015 my $realcmd = sub {
1016 my $upid = shift;
1017
1018 syslog('info', "starting vnc proxy $upid\n");
1019
1020 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1021
1022 my $qmstr = join(' ', @$qmcmd);
1023
1024 # also redirect stderr (else we get RFB protocol errors)
be62c45c 1025 my $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1e3baf05 1026
be62c45c 1027 PVE::Tools::run_command($cmd);
1e3baf05
DM
1028
1029 return;
1030 };
1031
a0d1b1a2 1032 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1e3baf05
DM
1033
1034 return {
a0d1b1a2 1035 user => $authuser,
1e3baf05 1036 ticket => $ticket,
afdb31d5
DM
1037 port => $port,
1038 upid => $upid,
1039 cert => $sslcert,
1e3baf05
DM
1040 };
1041 }});
1042
5fdbe4f0
DM
1043__PACKAGE__->register_method({
1044 name => 'vmcmdidx',
afdb31d5 1045 path => '{vmid}/status',
5fdbe4f0
DM
1046 method => 'GET',
1047 proxyto => 'node',
1048 description => "Directory index",
a0d1b1a2
DM
1049 permissions => {
1050 user => 'all',
1051 },
5fdbe4f0
DM
1052 parameters => {
1053 additionalProperties => 0,
1054 properties => {
1055 node => get_standard_option('pve-node'),
1056 vmid => get_standard_option('pve-vmid'),
1057 },
1058 },
1059 returns => {
1060 type => 'array',
1061 items => {
1062 type => "object",
1063 properties => {
1064 subdir => { type => 'string' },
1065 },
1066 },
1067 links => [ { rel => 'child', href => "{subdir}" } ],
1068 },
1069 code => sub {
1070 my ($param) = @_;
1071
1072 # test if VM exists
1073 my $conf = PVE::QemuServer::load_config($param->{vmid});
1074
1075 my $res = [
1076 { subdir => 'current' },
1077 { subdir => 'start' },
1078 { subdir => 'stop' },
1079 ];
afdb31d5 1080
5fdbe4f0
DM
1081 return $res;
1082 }});
1083
1e3baf05 1084__PACKAGE__->register_method({
afdb31d5 1085 name => 'vm_status',
5fdbe4f0 1086 path => '{vmid}/status/current',
1e3baf05
DM
1087 method => 'GET',
1088 proxyto => 'node',
1089 protected => 1, # qemu pid files are only readable by root
1090 description => "Get virtual machine status.",
a0d1b1a2
DM
1091 permissions => {
1092 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1093 },
1e3baf05
DM
1094 parameters => {
1095 additionalProperties => 0,
1096 properties => {
1097 node => get_standard_option('pve-node'),
1098 vmid => get_standard_option('pve-vmid'),
1099 },
1100 },
1101 returns => { type => 'object' },
1102 code => sub {
1103 my ($param) = @_;
1104
1105 # test if VM exists
1106 my $conf = PVE::QemuServer::load_config($param->{vmid});
1107
ff1a2432 1108 my $vmstatus = PVE::QemuServer::vmstatus($param->{vmid});
8610701a 1109 my $status = $vmstatus->{$param->{vmid}};
1e3baf05 1110
8610701a
DM
1111 my $cc = PVE::Cluster::cfs_read_file('cluster.conf');
1112 if (PVE::Cluster::cluster_conf_lookup_pvevm($cc, 0, $param->{vmid}, 1)) {
1113 $status->{ha} = 1;
1114 } else {
1115 $status->{ha} = 0;
1116 }
1117
1118 return $status;
1e3baf05
DM
1119 }});
1120
1121__PACKAGE__->register_method({
afdb31d5 1122 name => 'vm_start',
5fdbe4f0
DM
1123 path => '{vmid}/status/start',
1124 method => 'POST',
1e3baf05
DM
1125 protected => 1,
1126 proxyto => 'node',
5fdbe4f0 1127 description => "Start virtual machine.",
a0d1b1a2
DM
1128 permissions => {
1129 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1130 },
1e3baf05
DM
1131 parameters => {
1132 additionalProperties => 0,
1133 properties => {
1134 node => get_standard_option('pve-node'),
1135 vmid => get_standard_option('pve-vmid'),
3ea94c60
DM
1136 skiplock => get_standard_option('skiplock'),
1137 stateuri => get_standard_option('pve-qm-stateuri'),
1e3baf05
DM
1138 },
1139 },
afdb31d5 1140 returns => {
5fdbe4f0
DM
1141 type => 'string',
1142 },
1e3baf05
DM
1143 code => sub {
1144 my ($param) = @_;
1145
1146 my $rpcenv = PVE::RPCEnvironment::get();
1147
a0d1b1a2 1148 my $authuser = $rpcenv->get_user();
1e3baf05
DM
1149
1150 my $node = extract_param($param, 'node');
1151
1e3baf05
DM
1152 my $vmid = extract_param($param, 'vmid');
1153
3ea94c60 1154 my $stateuri = extract_param($param, 'stateuri');
afdb31d5 1155 raise_param_exc({ stateuri => "Only root may use this option." })
a0d1b1a2 1156 if $stateuri && $authuser ne 'root@pam';
3ea94c60 1157
1e3baf05 1158 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 1159 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 1160 if $skiplock && $authuser ne 'root@pam';
1e3baf05 1161
afdb31d5 1162 my $storecfg = PVE::Storage::config();
5fdbe4f0
DM
1163
1164 my $realcmd = sub {
1165 my $upid = shift;
1166
1167 syslog('info', "start VM $vmid: $upid\n");
1168
3ea94c60 1169 PVE::QemuServer::vm_start($storecfg, $vmid, $stateuri, $skiplock);
5fdbe4f0
DM
1170
1171 return;
1172 };
1173
a0d1b1a2 1174 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
5fdbe4f0
DM
1175 }});
1176
1177__PACKAGE__->register_method({
afdb31d5 1178 name => 'vm_stop',
5fdbe4f0
DM
1179 path => '{vmid}/status/stop',
1180 method => 'POST',
1181 protected => 1,
1182 proxyto => 'node',
1183 description => "Stop virtual machine.",
a0d1b1a2
DM
1184 permissions => {
1185 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1186 },
5fdbe4f0
DM
1187 parameters => {
1188 additionalProperties => 0,
1189 properties => {
1190 node => get_standard_option('pve-node'),
1191 vmid => get_standard_option('pve-vmid'),
1192 skiplock => get_standard_option('skiplock'),
c6bb9502
DM
1193 timeout => {
1194 description => "Wait maximal timeout seconds.",
1195 type => 'integer',
1196 minimum => 0,
1197 optional => 1,
254575e9
DM
1198 },
1199 keepActive => {
1200 description => "Do not decativate storage volumes.",
1201 type => 'boolean',
1202 optional => 1,
1203 default => 0,
c6bb9502 1204 }
5fdbe4f0
DM
1205 },
1206 },
afdb31d5 1207 returns => {
5fdbe4f0
DM
1208 type => 'string',
1209 },
1210 code => sub {
1211 my ($param) = @_;
1212
1213 my $rpcenv = PVE::RPCEnvironment::get();
1214
a0d1b1a2 1215 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
1216
1217 my $node = extract_param($param, 'node');
1218
1219 my $vmid = extract_param($param, 'vmid');
1220
1221 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 1222 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 1223 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 1224
254575e9 1225 my $keepActive = extract_param($param, 'keepActive');
afdb31d5 1226 raise_param_exc({ keepActive => "Only root may use this option." })
a0d1b1a2 1227 if $keepActive && $authuser ne 'root@pam';
254575e9 1228
ff1a2432
DM
1229 my $storecfg = PVE::Storage::config();
1230
5fdbe4f0
DM
1231 my $realcmd = sub {
1232 my $upid = shift;
1233
1234 syslog('info', "stop VM $vmid: $upid\n");
1235
afdb31d5 1236 PVE::QemuServer::vm_stop($storecfg, $vmid, $skiplock, 0,
254575e9 1237 $param->{timeout}, 0, 1, $keepActive);
c6bb9502 1238
5fdbe4f0
DM
1239 return;
1240 };
1241
a0d1b1a2 1242 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
5fdbe4f0
DM
1243 }});
1244
1245__PACKAGE__->register_method({
afdb31d5 1246 name => 'vm_reset',
5fdbe4f0
DM
1247 path => '{vmid}/status/reset',
1248 method => 'POST',
1249 protected => 1,
1250 proxyto => 'node',
1251 description => "Reset virtual machine.",
a0d1b1a2
DM
1252 permissions => {
1253 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1254 },
5fdbe4f0
DM
1255 parameters => {
1256 additionalProperties => 0,
1257 properties => {
1258 node => get_standard_option('pve-node'),
1259 vmid => get_standard_option('pve-vmid'),
1260 skiplock => get_standard_option('skiplock'),
1261 },
1262 },
afdb31d5 1263 returns => {
5fdbe4f0
DM
1264 type => 'string',
1265 },
1266 code => sub {
1267 my ($param) = @_;
1268
1269 my $rpcenv = PVE::RPCEnvironment::get();
1270
a0d1b1a2 1271 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
1272
1273 my $node = extract_param($param, 'node');
1274
1275 my $vmid = extract_param($param, 'vmid');
1276
1277 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 1278 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 1279 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 1280
ff1a2432
DM
1281 die "VM $vmid not running\n" if !PVE::QemuServer::check_running($vmid);
1282
5fdbe4f0
DM
1283 my $realcmd = sub {
1284 my $upid = shift;
1285
1e3baf05 1286 PVE::QemuServer::vm_reset($vmid, $skiplock);
5fdbe4f0
DM
1287
1288 return;
1289 };
1290
a0d1b1a2 1291 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
5fdbe4f0
DM
1292 }});
1293
1294__PACKAGE__->register_method({
afdb31d5 1295 name => 'vm_shutdown',
5fdbe4f0
DM
1296 path => '{vmid}/status/shutdown',
1297 method => 'POST',
1298 protected => 1,
1299 proxyto => 'node',
1300 description => "Shutdown virtual machine.",
a0d1b1a2
DM
1301 permissions => {
1302 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1303 },
5fdbe4f0
DM
1304 parameters => {
1305 additionalProperties => 0,
1306 properties => {
1307 node => get_standard_option('pve-node'),
1308 vmid => get_standard_option('pve-vmid'),
1309 skiplock => get_standard_option('skiplock'),
c6bb9502
DM
1310 timeout => {
1311 description => "Wait maximal timeout seconds.",
1312 type => 'integer',
1313 minimum => 0,
1314 optional => 1,
9269013a
DM
1315 },
1316 forceStop => {
1317 description => "Make sure the VM stops.",
1318 type => 'boolean',
1319 optional => 1,
1320 default => 0,
254575e9
DM
1321 },
1322 keepActive => {
1323 description => "Do not decativate storage volumes.",
1324 type => 'boolean',
1325 optional => 1,
1326 default => 0,
c6bb9502 1327 }
5fdbe4f0
DM
1328 },
1329 },
afdb31d5 1330 returns => {
5fdbe4f0
DM
1331 type => 'string',
1332 },
1333 code => sub {
1334 my ($param) = @_;
1335
1336 my $rpcenv = PVE::RPCEnvironment::get();
1337
a0d1b1a2 1338 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
1339
1340 my $node = extract_param($param, 'node');
1341
1342 my $vmid = extract_param($param, 'vmid');
1343
1344 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 1345 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 1346 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 1347
254575e9 1348 my $keepActive = extract_param($param, 'keepActive');
afdb31d5 1349 raise_param_exc({ keepActive => "Only root may use this option." })
a0d1b1a2 1350 if $keepActive && $authuser ne 'root@pam';
254575e9 1351
02d07cf5
DM
1352 my $storecfg = PVE::Storage::config();
1353
5fdbe4f0
DM
1354 my $realcmd = sub {
1355 my $upid = shift;
1356
1357 syslog('info', "shutdown VM $vmid: $upid\n");
1358
afdb31d5 1359 PVE::QemuServer::vm_stop($storecfg, $vmid, $skiplock, 0, $param->{timeout},
254575e9 1360 1, $param->{forceStop}, $keepActive);
c6bb9502 1361
5fdbe4f0
DM
1362 return;
1363 };
1364
a0d1b1a2 1365 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
5fdbe4f0
DM
1366 }});
1367
1368__PACKAGE__->register_method({
afdb31d5 1369 name => 'vm_suspend',
5fdbe4f0
DM
1370 path => '{vmid}/status/suspend',
1371 method => 'POST',
1372 protected => 1,
1373 proxyto => 'node',
1374 description => "Suspend virtual machine.",
a0d1b1a2
DM
1375 permissions => {
1376 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1377 },
5fdbe4f0
DM
1378 parameters => {
1379 additionalProperties => 0,
1380 properties => {
1381 node => get_standard_option('pve-node'),
1382 vmid => get_standard_option('pve-vmid'),
1383 skiplock => get_standard_option('skiplock'),
1384 },
1385 },
afdb31d5 1386 returns => {
5fdbe4f0
DM
1387 type => 'string',
1388 },
1389 code => sub {
1390 my ($param) = @_;
1391
1392 my $rpcenv = PVE::RPCEnvironment::get();
1393
a0d1b1a2 1394 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
1395
1396 my $node = extract_param($param, 'node');
1397
1398 my $vmid = extract_param($param, 'vmid');
1399
1400 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 1401 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 1402 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 1403
ff1a2432
DM
1404 die "VM $vmid not running\n" if !PVE::QemuServer::check_running($vmid);
1405
5fdbe4f0
DM
1406 my $realcmd = sub {
1407 my $upid = shift;
1408
1409 syslog('info', "suspend VM $vmid: $upid\n");
1410
1e3baf05 1411 PVE::QemuServer::vm_suspend($vmid, $skiplock);
5fdbe4f0
DM
1412
1413 return;
1414 };
1415
a0d1b1a2 1416 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
5fdbe4f0
DM
1417 }});
1418
1419__PACKAGE__->register_method({
afdb31d5 1420 name => 'vm_resume',
5fdbe4f0
DM
1421 path => '{vmid}/status/resume',
1422 method => 'POST',
1423 protected => 1,
1424 proxyto => 'node',
1425 description => "Resume virtual machine.",
a0d1b1a2
DM
1426 permissions => {
1427 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1428 },
5fdbe4f0
DM
1429 parameters => {
1430 additionalProperties => 0,
1431 properties => {
1432 node => get_standard_option('pve-node'),
1433 vmid => get_standard_option('pve-vmid'),
1434 skiplock => get_standard_option('skiplock'),
1435 },
1436 },
afdb31d5 1437 returns => {
5fdbe4f0
DM
1438 type => 'string',
1439 },
1440 code => sub {
1441 my ($param) = @_;
1442
1443 my $rpcenv = PVE::RPCEnvironment::get();
1444
a0d1b1a2 1445 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
1446
1447 my $node = extract_param($param, 'node');
1448
1449 my $vmid = extract_param($param, 'vmid');
1450
1451 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 1452 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 1453 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 1454
b7eeab21 1455 die "VM $vmid not running\n" if !PVE::QemuServer::check_running($vmid);
ff1a2432 1456
5fdbe4f0
DM
1457 my $realcmd = sub {
1458 my $upid = shift;
1459
1460 syslog('info', "resume VM $vmid: $upid\n");
1461
1e3baf05 1462 PVE::QemuServer::vm_resume($vmid, $skiplock);
1e3baf05 1463
5fdbe4f0
DM
1464 return;
1465 };
1466
a0d1b1a2 1467 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
5fdbe4f0
DM
1468 }});
1469
1470__PACKAGE__->register_method({
afdb31d5 1471 name => 'vm_sendkey',
5fdbe4f0
DM
1472 path => '{vmid}/sendkey',
1473 method => 'PUT',
1474 protected => 1,
1475 proxyto => 'node',
1476 description => "Send key event to virtual machine.",
a0d1b1a2
DM
1477 permissions => {
1478 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1479 },
5fdbe4f0
DM
1480 parameters => {
1481 additionalProperties => 0,
1482 properties => {
1483 node => get_standard_option('pve-node'),
1484 vmid => get_standard_option('pve-vmid'),
1485 skiplock => get_standard_option('skiplock'),
1486 key => {
1487 description => "The key (qemu monitor encoding).",
1488 type => 'string'
1489 }
1490 },
1491 },
1492 returns => { type => 'null'},
1493 code => sub {
1494 my ($param) = @_;
1495
1496 my $rpcenv = PVE::RPCEnvironment::get();
1497
a0d1b1a2 1498 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
1499
1500 my $node = extract_param($param, 'node');
1501
1502 my $vmid = extract_param($param, 'vmid');
1503
1504 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 1505 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 1506 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0
DM
1507
1508 PVE::QemuServer::vm_sendkey($vmid, $skiplock, $param->{key});
1509
1510 return;
1e3baf05
DM
1511 }});
1512
3ea94c60 1513__PACKAGE__->register_method({
afdb31d5 1514 name => 'migrate_vm',
3ea94c60
DM
1515 path => '{vmid}/migrate',
1516 method => 'POST',
1517 protected => 1,
1518 proxyto => 'node',
1519 description => "Migrate virtual machine. Creates a new migration task.",
a0d1b1a2
DM
1520 permissions => {
1521 check => ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
1522 },
3ea94c60
DM
1523 parameters => {
1524 additionalProperties => 0,
1525 properties => {
1526 node => get_standard_option('pve-node'),
1527 vmid => get_standard_option('pve-vmid'),
1528 target => get_standard_option('pve-node', { description => "Target node." }),
1529 online => {
1530 type => 'boolean',
1531 description => "Use online/live migration.",
1532 optional => 1,
1533 },
1534 force => {
1535 type => 'boolean',
1536 description => "Allow to migrate VMs which use local devices. Only root may use this option.",
1537 optional => 1,
1538 },
1539 },
1540 },
afdb31d5 1541 returns => {
3ea94c60
DM
1542 type => 'string',
1543 description => "the task ID.",
1544 },
1545 code => sub {
1546 my ($param) = @_;
1547
1548 my $rpcenv = PVE::RPCEnvironment::get();
1549
a0d1b1a2 1550 my $authuser = $rpcenv->get_user();
3ea94c60
DM
1551
1552 my $target = extract_param($param, 'target');
1553
1554 my $localnode = PVE::INotify::nodename();
1555 raise_param_exc({ target => "target is local node."}) if $target eq $localnode;
1556
1557 PVE::Cluster::check_cfs_quorum();
1558
1559 PVE::Cluster::check_node_exists($target);
1560
1561 my $targetip = PVE::Cluster::remote_node_ip($target);
1562
1563 my $vmid = extract_param($param, 'vmid');
1564
afdb31d5 1565 raise_param_exc({ force => "Only root may use this option." })
a0d1b1a2 1566 if $param->{force} && $authuser ne 'root@pam';
3ea94c60
DM
1567
1568 # test if VM exists
a5ed42d3 1569 my $conf = PVE::QemuServer::load_config($vmid);
3ea94c60
DM
1570
1571 # try to detect errors early
a5ed42d3
DM
1572
1573 PVE::QemuServer::check_lock($conf);
1574
3ea94c60 1575 if (PVE::QemuServer::check_running($vmid)) {
afdb31d5 1576 die "cant migrate running VM without --online\n"
3ea94c60
DM
1577 if !$param->{online};
1578 }
1579
1580 my $realcmd = sub {
1581 my $upid = shift;
1582
16e903f2 1583 PVE::QemuMigrate->migrate($target, $targetip, $vmid, $param);
3ea94c60
DM
1584 };
1585
a0d1b1a2 1586 my $upid = $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
3ea94c60
DM
1587
1588 return $upid;
1589 }});
1e3baf05 1590
91c94f0a 1591__PACKAGE__->register_method({
afdb31d5
DM
1592 name => 'monitor',
1593 path => '{vmid}/monitor',
91c94f0a
DM
1594 method => 'POST',
1595 protected => 1,
1596 proxyto => 'node',
1597 description => "Execute Qemu monitor commands.",
a0d1b1a2
DM
1598 permissions => {
1599 check => ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
1600 },
91c94f0a
DM
1601 parameters => {
1602 additionalProperties => 0,
1603 properties => {
1604 node => get_standard_option('pve-node'),
1605 vmid => get_standard_option('pve-vmid'),
1606 command => {
1607 type => 'string',
1608 description => "The monitor command.",
1609 }
1610 },
1611 },
1612 returns => { type => 'string'},
1613 code => sub {
1614 my ($param) = @_;
1615
1616 my $vmid = $param->{vmid};
1617
1618 my $conf = PVE::QemuServer::load_config ($vmid); # check if VM exists
1619
1620 my $res = '';
1621 eval {
1622 $res = PVE::QemuServer::vm_monitor_command($vmid, $param->{command});
1623 };
1624 $res = "ERROR: $@" if $@;
1625
1626 return $res;
1627 }});
1628
1e3baf05 16291;