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