]> git.proxmox.com Git - qemu-server.git/blame - PVE/API2/Qemu.pm
cleanup update_vm - move param checks to start of function
[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 616
0532bc63
DM
617 my $delete_str = extract_param($param, 'delete');
618
1e3baf05
DM
619 my $force = extract_param($param, 'force');
620
0532bc63
DM
621 die "no options specified\n" if !$delete_str && !scalar(keys %$param);
622
623 my @delete = ();
624 foreach my $opt (PVE::Tools::split_list($delete_str)) {
625 $opt = 'ide2' if $opt eq 'cdrom';
626 raise_param_exc({ delete => "you can't use '-$opt' and " .
627 "-delete $opt' at the same time" })
628 if defined($param->{$opt});
629
630 if (!PVE::QemuServer::option_exists($opt)) {
631 raise_param_exc({ delete => "unknown option '$opt'" });
632 }
633 push @delete, $opt;
634 }
1e3baf05 635
afdb31d5 636 my $storecfg = PVE::Storage::config();
1e3baf05
DM
637
638 &$resolve_cdrom_alias($param);
639
5d39a182 640 my $updatefn = sub {
1e3baf05 641
5d39a182 642 my $conf = PVE::QemuServer::load_config($vmid);
1e3baf05 643
5d39a182
DM
644 die "checksum missmatch (file change by other user?)\n"
645 if $digest && $digest ne $conf->{digest};
1e3baf05 646
5d39a182 647 PVE::QemuServer::check_lock($conf) if !$skiplock;
1e3baf05 648
a0d1b1a2 649 PVE::Cluster::log_msg('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
c2a64aa7 650
5d39a182 651 #delete
0532bc63 652 foreach my $opt (@delete) {
5d39a182
DM
653
654 next if !defined($conf->{$opt});
655
656 die "error hot-unplug $opt" if !PVE::QemuServer::vm_deviceunplug($vmid, $conf, $opt);
1e3baf05 657
5d39a182
DM
658 #drive
659 if (PVE::QemuServer::valid_drivename($opt)) {
660 my $drive = PVE::QemuServer::parse_drive($opt, $conf->{$opt});
661 #hdd
662 if (!PVE::QemuServer::drive_is_cdrom($drive)) {
663 my $volid = $drive->{file};
664
665 if ($volid !~ m|^/|) {
666 my ($path, $owner);
667 eval { ($path, $owner) = PVE::Storage::path($storecfg, $volid); };
668 if ($owner && ($owner == $vmid)) {
669 if ($force) {
670 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
671 # fixme: log ?
672 warn $@ if $@;
673 } else {
674 PVE::QemuServer::add_unused_volume($conf, $volid, $vmid);
675 }
1e3baf05
DM
676 }
677 }
678 }
5d39a182 679 } elsif ($opt =~ m/^unused/) {
c2a64aa7
DA
680 my $drive = PVE::QemuServer::parse_drive($opt, $conf->{$opt});
681 my $volid = $drive->{file};
682 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
683 # fixme: log ?
684 warn $@ if $@;
5d39a182 685 }
1e3baf05 686
5d39a182
DM
687 PVE::QemuServer::change_config_nolock($vmid, {}, { $opt => 1 }, 1);
688 }
1e3baf05 689
5d39a182
DM
690 #add
691 foreach my $opt (keys %$param) {
1e3baf05 692
5d39a182
DM
693 #drives
694 if (PVE::QemuServer::valid_drivename($opt)) {
695 my $drive = PVE::QemuServer::parse_drive($opt, $param->{$opt});
696 raise_param_exc({ $opt => "unable to parse drive options" }) if !$drive;
1e3baf05 697
5d39a182
DM
698 PVE::QemuServer::cleanup_drive_path($opt, $storecfg, $drive);
699 $param->{$opt} = PVE::QemuServer::print_drive($vmid, $drive);
1e3baf05 700
5d39a182
DM
701 #cdrom
702 if (PVE::QemuServer::drive_is_cdrom($drive) && PVE::QemuServer::check_running($vmid)) {
703 if ($drive->{file} eq 'none') {
704 PVE::QemuServer::vm_monitor_command($vmid, "eject -f drive-$opt", 0);
705 #delete $param->{$opt};
706 }
707 else {
708 my $path = PVE::QemuServer::get_iso_path($storecfg, $vmid, $drive->{file});
709 PVE::QemuServer::vm_monitor_command($vmid, "eject -f drive-$opt", 0); #force eject if locked
710 PVE::QemuServer::vm_monitor_command($vmid, "change drive-$opt \"$path\"", 0) if $path;
711 }
c2a64aa7 712 }
5d39a182 713 #hdd
c2a64aa7 714 else {
5d39a182
DM
715 #swap drive
716 if ($conf->{$opt}){
717 my $old_drive = PVE::QemuServer::parse_drive($opt, $conf->{$opt});
718 if ($drive->{file} ne $old_drive->{file} && !PVE::QemuServer::drive_is_cdrom($old_drive)) {
719
720 my ($path, $owner);
721 eval { ($path, $owner) = PVE::Storage::path($storecfg, $old_drive->{file}); };
722 if ($owner && ($owner == $vmid)) {
723 die "error hot-unplug $opt" if !PVE::QemuServer::vm_deviceunplug($vmid, $conf, $opt);
724 PVE::QemuServer::add_unused_volume($conf, $old_drive->{file}, $vmid);
725 }
726 }
727 }
728 my $settings = { $opt => $param->{$opt} };
a0d1b1a2 729 &$create_disks($rpcenv, $authuser, $storecfg, $vmid, undef, $settings, $conf);
5d39a182
DM
730 $param->{$opt} = $settings->{$opt};
731 #hotplug disks
732 if(!PVE::QemuServer::vm_deviceplug($storecfg, $conf, $vmid, $opt, $drive)) {
733 PVE::QemuServer::add_unused_volume($conf,$drive->{file},$vmid);
734 PVE::QemuServer::change_config_nolock($vmid, {}, { $opt => 1 }, 1);
735 die "error hotplug $opt - put disk in unused";
736 }
737 }
c2a64aa7 738 }
5d39a182 739 #nics
3a1e36bb 740 my $net = undef;
5d39a182 741 if ($opt =~ m/^net(\d+)$/) {
3a1e36bb 742 $net = PVE::QemuServer::parse_net($param->{$opt});
5d39a182 743 $param->{$opt} = PVE::QemuServer::print_net($net);
3a1e36bb
DA
744 #if online update, then unplug first
745 die "error hot-unplug $opt for update" if $conf->{$opt} && !PVE::QemuServer::vm_deviceunplug($vmid, $conf, $opt);
5d39a182
DM
746 }
747
748 PVE::QemuServer::change_config_nolock($vmid, { $opt => $param->{$opt} }, {}, 1);
3a1e36bb
DA
749
750 #nic hotplug after config write as we need it for pve-bridge script
751 if (defined ($net)) {
752 if(!PVE::QemuServer::vm_deviceplug($storecfg, $conf, $vmid, $opt, $net)) {
753 #rewrite conf to remove nic if hotplug fail
754 PVE::QemuServer::change_config_nolock($vmid, {}, { $opt => 1 }, 1);
755 die "error hotplug $opt";
756 }
757 }
5d39a182
DM
758 }
759 };
760
761 PVE::QemuServer::lock_config($vmid, $updatefn);
fcdb0117 762
1e3baf05
DM
763 return undef;
764 }});
765
766
767__PACKAGE__->register_method({
afdb31d5
DM
768 name => 'destroy_vm',
769 path => '{vmid}',
1e3baf05
DM
770 method => 'DELETE',
771 protected => 1,
772 proxyto => 'node',
773 description => "Destroy the vm (also delete all used/owned volumes).",
a0d1b1a2
DM
774 permissions => {
775 check => [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
776 },
1e3baf05
DM
777 parameters => {
778 additionalProperties => 0,
779 properties => {
780 node => get_standard_option('pve-node'),
781 vmid => get_standard_option('pve-vmid'),
3ea94c60 782 skiplock => get_standard_option('skiplock'),
1e3baf05
DM
783 },
784 },
afdb31d5 785 returns => {
5fdbe4f0
DM
786 type => 'string',
787 },
1e3baf05
DM
788 code => sub {
789 my ($param) = @_;
790
791 my $rpcenv = PVE::RPCEnvironment::get();
792
a0d1b1a2 793 my $authuser = $rpcenv->get_user();
1e3baf05
DM
794
795 my $vmid = $param->{vmid};
796
797 my $skiplock = $param->{skiplock};
afdb31d5 798 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 799 if $skiplock && $authuser ne 'root@pam';
1e3baf05 800
5fdbe4f0
DM
801 # test if VM exists
802 my $conf = PVE::QemuServer::load_config($vmid);
803
afdb31d5 804 my $storecfg = PVE::Storage::config();
1e3baf05 805
5fdbe4f0 806 my $realcmd = sub {
ff1a2432
DM
807 my $upid = shift;
808
809 syslog('info', "destroy VM $vmid: $upid\n");
810
5fdbe4f0
DM
811 PVE::QemuServer::vm_destroy($storecfg, $vmid, $skiplock);
812 };
1e3baf05 813
a0d1b1a2 814 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1e3baf05
DM
815 }});
816
817__PACKAGE__->register_method({
afdb31d5
DM
818 name => 'unlink',
819 path => '{vmid}/unlink',
1e3baf05
DM
820 method => 'PUT',
821 protected => 1,
822 proxyto => 'node',
823 description => "Unlink/delete disk images.",
a0d1b1a2
DM
824 permissions => {
825 check => [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
826 },
1e3baf05
DM
827 parameters => {
828 additionalProperties => 0,
829 properties => {
830 node => get_standard_option('pve-node'),
831 vmid => get_standard_option('pve-vmid'),
832 idlist => {
833 type => 'string', format => 'pve-configid-list',
834 description => "A list of disk IDs you want to delete.",
835 },
836 force => {
837 type => 'boolean',
838 description => $opt_force_description,
839 optional => 1,
840 },
841 },
842 },
843 returns => { type => 'null'},
844 code => sub {
845 my ($param) = @_;
846
847 $param->{delete} = extract_param($param, 'idlist');
848
849 __PACKAGE__->update_vm($param);
850
851 return undef;
852 }});
853
854my $sslcert;
855
856__PACKAGE__->register_method({
afdb31d5
DM
857 name => 'vncproxy',
858 path => '{vmid}/vncproxy',
1e3baf05
DM
859 method => 'POST',
860 protected => 1,
861 permissions => {
378b359e 862 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1e3baf05
DM
863 },
864 description => "Creates a TCP VNC proxy connections.",
865 parameters => {
866 additionalProperties => 0,
867 properties => {
868 node => get_standard_option('pve-node'),
869 vmid => get_standard_option('pve-vmid'),
870 },
871 },
afdb31d5 872 returns => {
1e3baf05
DM
873 additionalProperties => 0,
874 properties => {
875 user => { type => 'string' },
876 ticket => { type => 'string' },
877 cert => { type => 'string' },
878 port => { type => 'integer' },
879 upid => { type => 'string' },
880 },
881 },
882 code => sub {
883 my ($param) = @_;
884
885 my $rpcenv = PVE::RPCEnvironment::get();
886
a0d1b1a2 887 my $authuser = $rpcenv->get_user();
1e3baf05
DM
888
889 my $vmid = $param->{vmid};
890 my $node = $param->{node};
891
b6f39da2
DM
892 my $authpath = "/vms/$vmid";
893
a0d1b1a2 894 my $ticket = PVE::AccessControl::assemble_vnc_ticket($authuser, $authpath);
b6f39da2 895
1e3baf05
DM
896 $sslcert = PVE::Tools::file_get_contents("/etc/pve/pve-root-ca.pem", 8192)
897 if !$sslcert;
898
899 my $port = PVE::Tools::next_vnc_port();
900
901 my $remip;
afdb31d5 902
4f1be36c 903 if ($node ne 'localhost' && $node ne PVE::INotify::nodename()) {
1e3baf05
DM
904 $remip = PVE::Cluster::remote_node_ip($node);
905 }
906
907 # NOTE: kvm VNC traffic is already TLS encrypted,
908 # so we select the fastest chipher here (or 'none'?)
909 my $remcmd = $remip ? ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes',
910 '-c', 'blowfish-cbc', $remip] : [];
911
afdb31d5 912 my $timeout = 10;
1e3baf05
DM
913
914 my $realcmd = sub {
915 my $upid = shift;
916
917 syslog('info', "starting vnc proxy $upid\n");
918
919 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
920
921 my $qmstr = join(' ', @$qmcmd);
922
923 # also redirect stderr (else we get RFB protocol errors)
be62c45c 924 my $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1e3baf05 925
be62c45c 926 PVE::Tools::run_command($cmd);
1e3baf05
DM
927
928 return;
929 };
930
a0d1b1a2 931 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1e3baf05
DM
932
933 return {
a0d1b1a2 934 user => $authuser,
1e3baf05 935 ticket => $ticket,
afdb31d5
DM
936 port => $port,
937 upid => $upid,
938 cert => $sslcert,
1e3baf05
DM
939 };
940 }});
941
5fdbe4f0
DM
942__PACKAGE__->register_method({
943 name => 'vmcmdidx',
afdb31d5 944 path => '{vmid}/status',
5fdbe4f0
DM
945 method => 'GET',
946 proxyto => 'node',
947 description => "Directory index",
a0d1b1a2
DM
948 permissions => {
949 user => 'all',
950 },
5fdbe4f0
DM
951 parameters => {
952 additionalProperties => 0,
953 properties => {
954 node => get_standard_option('pve-node'),
955 vmid => get_standard_option('pve-vmid'),
956 },
957 },
958 returns => {
959 type => 'array',
960 items => {
961 type => "object",
962 properties => {
963 subdir => { type => 'string' },
964 },
965 },
966 links => [ { rel => 'child', href => "{subdir}" } ],
967 },
968 code => sub {
969 my ($param) = @_;
970
971 # test if VM exists
972 my $conf = PVE::QemuServer::load_config($param->{vmid});
973
974 my $res = [
975 { subdir => 'current' },
976 { subdir => 'start' },
977 { subdir => 'stop' },
978 ];
afdb31d5 979
5fdbe4f0
DM
980 return $res;
981 }});
982
1e3baf05 983__PACKAGE__->register_method({
afdb31d5 984 name => 'vm_status',
5fdbe4f0 985 path => '{vmid}/status/current',
1e3baf05
DM
986 method => 'GET',
987 proxyto => 'node',
988 protected => 1, # qemu pid files are only readable by root
989 description => "Get virtual machine status.",
a0d1b1a2
DM
990 permissions => {
991 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
992 },
1e3baf05
DM
993 parameters => {
994 additionalProperties => 0,
995 properties => {
996 node => get_standard_option('pve-node'),
997 vmid => get_standard_option('pve-vmid'),
998 },
999 },
1000 returns => { type => 'object' },
1001 code => sub {
1002 my ($param) = @_;
1003
1004 # test if VM exists
1005 my $conf = PVE::QemuServer::load_config($param->{vmid});
1006
ff1a2432 1007 my $vmstatus = PVE::QemuServer::vmstatus($param->{vmid});
8610701a 1008 my $status = $vmstatus->{$param->{vmid}};
1e3baf05 1009
8610701a
DM
1010 my $cc = PVE::Cluster::cfs_read_file('cluster.conf');
1011 if (PVE::Cluster::cluster_conf_lookup_pvevm($cc, 0, $param->{vmid}, 1)) {
1012 $status->{ha} = 1;
1013 } else {
1014 $status->{ha} = 0;
1015 }
1016
1017 return $status;
1e3baf05
DM
1018 }});
1019
1020__PACKAGE__->register_method({
afdb31d5 1021 name => 'vm_start',
5fdbe4f0
DM
1022 path => '{vmid}/status/start',
1023 method => 'POST',
1e3baf05
DM
1024 protected => 1,
1025 proxyto => 'node',
5fdbe4f0 1026 description => "Start virtual machine.",
a0d1b1a2
DM
1027 permissions => {
1028 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1029 },
1e3baf05
DM
1030 parameters => {
1031 additionalProperties => 0,
1032 properties => {
1033 node => get_standard_option('pve-node'),
1034 vmid => get_standard_option('pve-vmid'),
3ea94c60
DM
1035 skiplock => get_standard_option('skiplock'),
1036 stateuri => get_standard_option('pve-qm-stateuri'),
1e3baf05
DM
1037 },
1038 },
afdb31d5 1039 returns => {
5fdbe4f0
DM
1040 type => 'string',
1041 },
1e3baf05
DM
1042 code => sub {
1043 my ($param) = @_;
1044
1045 my $rpcenv = PVE::RPCEnvironment::get();
1046
a0d1b1a2 1047 my $authuser = $rpcenv->get_user();
1e3baf05
DM
1048
1049 my $node = extract_param($param, 'node');
1050
1e3baf05
DM
1051 my $vmid = extract_param($param, 'vmid');
1052
3ea94c60 1053 my $stateuri = extract_param($param, 'stateuri');
afdb31d5 1054 raise_param_exc({ stateuri => "Only root may use this option." })
a0d1b1a2 1055 if $stateuri && $authuser ne 'root@pam';
3ea94c60 1056
1e3baf05 1057 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 1058 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 1059 if $skiplock && $authuser ne 'root@pam';
1e3baf05 1060
afdb31d5 1061 my $storecfg = PVE::Storage::config();
5fdbe4f0
DM
1062
1063 my $realcmd = sub {
1064 my $upid = shift;
1065
1066 syslog('info', "start VM $vmid: $upid\n");
1067
3ea94c60 1068 PVE::QemuServer::vm_start($storecfg, $vmid, $stateuri, $skiplock);
5fdbe4f0
DM
1069
1070 return;
1071 };
1072
a0d1b1a2 1073 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
5fdbe4f0
DM
1074 }});
1075
1076__PACKAGE__->register_method({
afdb31d5 1077 name => 'vm_stop',
5fdbe4f0
DM
1078 path => '{vmid}/status/stop',
1079 method => 'POST',
1080 protected => 1,
1081 proxyto => 'node',
1082 description => "Stop virtual machine.",
a0d1b1a2
DM
1083 permissions => {
1084 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1085 },
5fdbe4f0
DM
1086 parameters => {
1087 additionalProperties => 0,
1088 properties => {
1089 node => get_standard_option('pve-node'),
1090 vmid => get_standard_option('pve-vmid'),
1091 skiplock => get_standard_option('skiplock'),
c6bb9502
DM
1092 timeout => {
1093 description => "Wait maximal timeout seconds.",
1094 type => 'integer',
1095 minimum => 0,
1096 optional => 1,
254575e9
DM
1097 },
1098 keepActive => {
1099 description => "Do not decativate storage volumes.",
1100 type => 'boolean',
1101 optional => 1,
1102 default => 0,
c6bb9502 1103 }
5fdbe4f0
DM
1104 },
1105 },
afdb31d5 1106 returns => {
5fdbe4f0
DM
1107 type => 'string',
1108 },
1109 code => sub {
1110 my ($param) = @_;
1111
1112 my $rpcenv = PVE::RPCEnvironment::get();
1113
a0d1b1a2 1114 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
1115
1116 my $node = extract_param($param, 'node');
1117
1118 my $vmid = extract_param($param, 'vmid');
1119
1120 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 1121 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 1122 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 1123
254575e9 1124 my $keepActive = extract_param($param, 'keepActive');
afdb31d5 1125 raise_param_exc({ keepActive => "Only root may use this option." })
a0d1b1a2 1126 if $keepActive && $authuser ne 'root@pam';
254575e9 1127
ff1a2432
DM
1128 my $storecfg = PVE::Storage::config();
1129
5fdbe4f0
DM
1130 my $realcmd = sub {
1131 my $upid = shift;
1132
1133 syslog('info', "stop VM $vmid: $upid\n");
1134
afdb31d5 1135 PVE::QemuServer::vm_stop($storecfg, $vmid, $skiplock, 0,
254575e9 1136 $param->{timeout}, 0, 1, $keepActive);
c6bb9502 1137
5fdbe4f0
DM
1138 return;
1139 };
1140
a0d1b1a2 1141 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
5fdbe4f0
DM
1142 }});
1143
1144__PACKAGE__->register_method({
afdb31d5 1145 name => 'vm_reset',
5fdbe4f0
DM
1146 path => '{vmid}/status/reset',
1147 method => 'POST',
1148 protected => 1,
1149 proxyto => 'node',
1150 description => "Reset virtual machine.",
a0d1b1a2
DM
1151 permissions => {
1152 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1153 },
5fdbe4f0
DM
1154 parameters => {
1155 additionalProperties => 0,
1156 properties => {
1157 node => get_standard_option('pve-node'),
1158 vmid => get_standard_option('pve-vmid'),
1159 skiplock => get_standard_option('skiplock'),
1160 },
1161 },
afdb31d5 1162 returns => {
5fdbe4f0
DM
1163 type => 'string',
1164 },
1165 code => sub {
1166 my ($param) = @_;
1167
1168 my $rpcenv = PVE::RPCEnvironment::get();
1169
a0d1b1a2 1170 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
1171
1172 my $node = extract_param($param, 'node');
1173
1174 my $vmid = extract_param($param, 'vmid');
1175
1176 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 1177 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 1178 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 1179
ff1a2432
DM
1180 die "VM $vmid not running\n" if !PVE::QemuServer::check_running($vmid);
1181
5fdbe4f0
DM
1182 my $realcmd = sub {
1183 my $upid = shift;
1184
1e3baf05 1185 PVE::QemuServer::vm_reset($vmid, $skiplock);
5fdbe4f0
DM
1186
1187 return;
1188 };
1189
a0d1b1a2 1190 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
5fdbe4f0
DM
1191 }});
1192
1193__PACKAGE__->register_method({
afdb31d5 1194 name => 'vm_shutdown',
5fdbe4f0
DM
1195 path => '{vmid}/status/shutdown',
1196 method => 'POST',
1197 protected => 1,
1198 proxyto => 'node',
1199 description => "Shutdown virtual machine.",
a0d1b1a2
DM
1200 permissions => {
1201 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1202 },
5fdbe4f0
DM
1203 parameters => {
1204 additionalProperties => 0,
1205 properties => {
1206 node => get_standard_option('pve-node'),
1207 vmid => get_standard_option('pve-vmid'),
1208 skiplock => get_standard_option('skiplock'),
c6bb9502
DM
1209 timeout => {
1210 description => "Wait maximal timeout seconds.",
1211 type => 'integer',
1212 minimum => 0,
1213 optional => 1,
9269013a
DM
1214 },
1215 forceStop => {
1216 description => "Make sure the VM stops.",
1217 type => 'boolean',
1218 optional => 1,
1219 default => 0,
254575e9
DM
1220 },
1221 keepActive => {
1222 description => "Do not decativate storage volumes.",
1223 type => 'boolean',
1224 optional => 1,
1225 default => 0,
c6bb9502 1226 }
5fdbe4f0
DM
1227 },
1228 },
afdb31d5 1229 returns => {
5fdbe4f0
DM
1230 type => 'string',
1231 },
1232 code => sub {
1233 my ($param) = @_;
1234
1235 my $rpcenv = PVE::RPCEnvironment::get();
1236
a0d1b1a2 1237 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
1238
1239 my $node = extract_param($param, 'node');
1240
1241 my $vmid = extract_param($param, 'vmid');
1242
1243 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 1244 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 1245 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 1246
254575e9 1247 my $keepActive = extract_param($param, 'keepActive');
afdb31d5 1248 raise_param_exc({ keepActive => "Only root may use this option." })
a0d1b1a2 1249 if $keepActive && $authuser ne 'root@pam';
254575e9 1250
02d07cf5
DM
1251 my $storecfg = PVE::Storage::config();
1252
5fdbe4f0
DM
1253 my $realcmd = sub {
1254 my $upid = shift;
1255
1256 syslog('info', "shutdown VM $vmid: $upid\n");
1257
afdb31d5 1258 PVE::QemuServer::vm_stop($storecfg, $vmid, $skiplock, 0, $param->{timeout},
254575e9 1259 1, $param->{forceStop}, $keepActive);
c6bb9502 1260
5fdbe4f0
DM
1261 return;
1262 };
1263
a0d1b1a2 1264 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
5fdbe4f0
DM
1265 }});
1266
1267__PACKAGE__->register_method({
afdb31d5 1268 name => 'vm_suspend',
5fdbe4f0
DM
1269 path => '{vmid}/status/suspend',
1270 method => 'POST',
1271 protected => 1,
1272 proxyto => 'node',
1273 description => "Suspend virtual machine.",
a0d1b1a2
DM
1274 permissions => {
1275 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1276 },
5fdbe4f0
DM
1277 parameters => {
1278 additionalProperties => 0,
1279 properties => {
1280 node => get_standard_option('pve-node'),
1281 vmid => get_standard_option('pve-vmid'),
1282 skiplock => get_standard_option('skiplock'),
1283 },
1284 },
afdb31d5 1285 returns => {
5fdbe4f0
DM
1286 type => 'string',
1287 },
1288 code => sub {
1289 my ($param) = @_;
1290
1291 my $rpcenv = PVE::RPCEnvironment::get();
1292
a0d1b1a2 1293 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
1294
1295 my $node = extract_param($param, 'node');
1296
1297 my $vmid = extract_param($param, 'vmid');
1298
1299 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 1300 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 1301 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 1302
ff1a2432
DM
1303 die "VM $vmid not running\n" if !PVE::QemuServer::check_running($vmid);
1304
5fdbe4f0
DM
1305 my $realcmd = sub {
1306 my $upid = shift;
1307
1308 syslog('info', "suspend VM $vmid: $upid\n");
1309
1e3baf05 1310 PVE::QemuServer::vm_suspend($vmid, $skiplock);
5fdbe4f0
DM
1311
1312 return;
1313 };
1314
a0d1b1a2 1315 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
5fdbe4f0
DM
1316 }});
1317
1318__PACKAGE__->register_method({
afdb31d5 1319 name => 'vm_resume',
5fdbe4f0
DM
1320 path => '{vmid}/status/resume',
1321 method => 'POST',
1322 protected => 1,
1323 proxyto => 'node',
1324 description => "Resume virtual machine.",
a0d1b1a2
DM
1325 permissions => {
1326 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1327 },
5fdbe4f0
DM
1328 parameters => {
1329 additionalProperties => 0,
1330 properties => {
1331 node => get_standard_option('pve-node'),
1332 vmid => get_standard_option('pve-vmid'),
1333 skiplock => get_standard_option('skiplock'),
1334 },
1335 },
afdb31d5 1336 returns => {
5fdbe4f0
DM
1337 type => 'string',
1338 },
1339 code => sub {
1340 my ($param) = @_;
1341
1342 my $rpcenv = PVE::RPCEnvironment::get();
1343
a0d1b1a2 1344 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
1345
1346 my $node = extract_param($param, 'node');
1347
1348 my $vmid = extract_param($param, 'vmid');
1349
1350 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 1351 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 1352 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 1353
b7eeab21 1354 die "VM $vmid not running\n" if !PVE::QemuServer::check_running($vmid);
ff1a2432 1355
5fdbe4f0
DM
1356 my $realcmd = sub {
1357 my $upid = shift;
1358
1359 syslog('info', "resume VM $vmid: $upid\n");
1360
1e3baf05 1361 PVE::QemuServer::vm_resume($vmid, $skiplock);
1e3baf05 1362
5fdbe4f0
DM
1363 return;
1364 };
1365
a0d1b1a2 1366 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
5fdbe4f0
DM
1367 }});
1368
1369__PACKAGE__->register_method({
afdb31d5 1370 name => 'vm_sendkey',
5fdbe4f0
DM
1371 path => '{vmid}/sendkey',
1372 method => 'PUT',
1373 protected => 1,
1374 proxyto => 'node',
1375 description => "Send key event to virtual machine.",
a0d1b1a2
DM
1376 permissions => {
1377 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1378 },
5fdbe4f0
DM
1379 parameters => {
1380 additionalProperties => 0,
1381 properties => {
1382 node => get_standard_option('pve-node'),
1383 vmid => get_standard_option('pve-vmid'),
1384 skiplock => get_standard_option('skiplock'),
1385 key => {
1386 description => "The key (qemu monitor encoding).",
1387 type => 'string'
1388 }
1389 },
1390 },
1391 returns => { type => 'null'},
1392 code => sub {
1393 my ($param) = @_;
1394
1395 my $rpcenv = PVE::RPCEnvironment::get();
1396
a0d1b1a2 1397 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
1398
1399 my $node = extract_param($param, 'node');
1400
1401 my $vmid = extract_param($param, 'vmid');
1402
1403 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 1404 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 1405 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0
DM
1406
1407 PVE::QemuServer::vm_sendkey($vmid, $skiplock, $param->{key});
1408
1409 return;
1e3baf05
DM
1410 }});
1411
3ea94c60 1412__PACKAGE__->register_method({
afdb31d5 1413 name => 'migrate_vm',
3ea94c60
DM
1414 path => '{vmid}/migrate',
1415 method => 'POST',
1416 protected => 1,
1417 proxyto => 'node',
1418 description => "Migrate virtual machine. Creates a new migration task.",
a0d1b1a2
DM
1419 permissions => {
1420 check => ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
1421 },
3ea94c60
DM
1422 parameters => {
1423 additionalProperties => 0,
1424 properties => {
1425 node => get_standard_option('pve-node'),
1426 vmid => get_standard_option('pve-vmid'),
1427 target => get_standard_option('pve-node', { description => "Target node." }),
1428 online => {
1429 type => 'boolean',
1430 description => "Use online/live migration.",
1431 optional => 1,
1432 },
1433 force => {
1434 type => 'boolean',
1435 description => "Allow to migrate VMs which use local devices. Only root may use this option.",
1436 optional => 1,
1437 },
1438 },
1439 },
afdb31d5 1440 returns => {
3ea94c60
DM
1441 type => 'string',
1442 description => "the task ID.",
1443 },
1444 code => sub {
1445 my ($param) = @_;
1446
1447 my $rpcenv = PVE::RPCEnvironment::get();
1448
a0d1b1a2 1449 my $authuser = $rpcenv->get_user();
3ea94c60
DM
1450
1451 my $target = extract_param($param, 'target');
1452
1453 my $localnode = PVE::INotify::nodename();
1454 raise_param_exc({ target => "target is local node."}) if $target eq $localnode;
1455
1456 PVE::Cluster::check_cfs_quorum();
1457
1458 PVE::Cluster::check_node_exists($target);
1459
1460 my $targetip = PVE::Cluster::remote_node_ip($target);
1461
1462 my $vmid = extract_param($param, 'vmid');
1463
afdb31d5 1464 raise_param_exc({ force => "Only root may use this option." })
a0d1b1a2 1465 if $param->{force} && $authuser ne 'root@pam';
3ea94c60
DM
1466
1467 # test if VM exists
a5ed42d3 1468 my $conf = PVE::QemuServer::load_config($vmid);
3ea94c60
DM
1469
1470 # try to detect errors early
a5ed42d3
DM
1471
1472 PVE::QemuServer::check_lock($conf);
1473
3ea94c60 1474 if (PVE::QemuServer::check_running($vmid)) {
afdb31d5 1475 die "cant migrate running VM without --online\n"
3ea94c60
DM
1476 if !$param->{online};
1477 }
1478
1479 my $realcmd = sub {
1480 my $upid = shift;
1481
16e903f2 1482 PVE::QemuMigrate->migrate($target, $targetip, $vmid, $param);
3ea94c60
DM
1483 };
1484
a0d1b1a2 1485 my $upid = $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
3ea94c60
DM
1486
1487 return $upid;
1488 }});
1e3baf05 1489
91c94f0a 1490__PACKAGE__->register_method({
afdb31d5
DM
1491 name => 'monitor',
1492 path => '{vmid}/monitor',
91c94f0a
DM
1493 method => 'POST',
1494 protected => 1,
1495 proxyto => 'node',
1496 description => "Execute Qemu monitor commands.",
a0d1b1a2
DM
1497 permissions => {
1498 check => ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
1499 },
91c94f0a
DM
1500 parameters => {
1501 additionalProperties => 0,
1502 properties => {
1503 node => get_standard_option('pve-node'),
1504 vmid => get_standard_option('pve-vmid'),
1505 command => {
1506 type => 'string',
1507 description => "The monitor command.",
1508 }
1509 },
1510 },
1511 returns => { type => 'string'},
1512 code => sub {
1513 my ($param) = @_;
1514
1515 my $vmid = $param->{vmid};
1516
1517 my $conf = PVE::QemuServer::load_config ($vmid); # check if VM exists
1518
1519 my $res = '';
1520 eval {
1521 $res = PVE::QemuServer::vm_monitor_command($vmid, $param->{command});
1522 };
1523 $res = "ERROR: $@" if $@;
1524
1525 return $res;
1526 }});
1527
1e3baf05 15281;