]> git.proxmox.com Git - qemu-server.git/blame - PVE/API2/Qemu.pm
replace change_config_nolock with update_config_nolock
[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 {
1858638f 61 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
a0d1b1a2
DM
62
63 # check permissions first
64
65 my $alloc = [];
1858638f 66 PVE::QemuServer::foreach_drive($settings, sub {
a0d1b1a2
DM
67 my ($ds, $disk) = @_;
68
1858638f 69 return if PVE::QemuServer::drive_is_cdrom($disk);
a0d1b1a2
DM
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
1858638f 115 $conf->{$ds} = $settings->{$ds} = PVE::QemuServer::print_drive($vmid, $disk);
a0d1b1a2
DM
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
1858638f
DM
348 my $conf = $param;
349
5fdbe4f0 350 eval {
1858638f 351 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
1e3baf05 352
5fdbe4f0
DM
353 # try to be smart about bootdisk
354 my @disks = PVE::QemuServer::disknames();
355 my $firstdisk;
356 foreach my $ds (reverse @disks) {
1858638f
DM
357 next if !$conf->{$ds};
358 my $disk = PVE::QemuServer::parse_drive($ds, $conf->{$ds});
5fdbe4f0
DM
359 next if PVE::QemuServer::drive_is_cdrom($disk);
360 $firstdisk = $ds;
361 }
1e3baf05 362
1858638f
DM
363 if (!$conf->{bootdisk} && $firstdisk) {
364 $conf->{bootdisk} = $firstdisk;
5fdbe4f0 365 }
1e3baf05 366
1858638f 367 PVE::QemuServer::update_conf_nolock($vmid, $conf);
5fdbe4f0
DM
368 };
369 my $err = $@;
1e3baf05 370
5fdbe4f0
DM
371 if ($err) {
372 foreach my $volid (@$vollist) {
373 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
374 warn $@ if $@;
375 }
376 die "create failed - $err";
377 }
378 };
379
a0d1b1a2 380 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
5fdbe4f0
DM
381 };
382
3e16d5fc 383 return PVE::QemuServer::lock_config($vmid, $archive ? $restorefn : $createfn);
1e3baf05
DM
384 }});
385
386__PACKAGE__->register_method({
387 name => 'vmdiridx',
afdb31d5 388 path => '{vmid}',
1e3baf05
DM
389 method => 'GET',
390 proxyto => 'node',
391 description => "Directory index",
a0d1b1a2
DM
392 permissions => {
393 user => 'all',
394 },
1e3baf05
DM
395 parameters => {
396 additionalProperties => 0,
397 properties => {
398 node => get_standard_option('pve-node'),
399 vmid => get_standard_option('pve-vmid'),
400 },
401 },
402 returns => {
403 type => 'array',
404 items => {
405 type => "object",
406 properties => {
407 subdir => { type => 'string' },
408 },
409 },
410 links => [ { rel => 'child', href => "{subdir}" } ],
411 },
412 code => sub {
413 my ($param) = @_;
414
415 my $res = [
416 { subdir => 'config' },
417 { subdir => 'status' },
418 { subdir => 'unlink' },
419 { subdir => 'vncproxy' },
3ea94c60 420 { subdir => 'migrate' },
1e3baf05
DM
421 { subdir => 'rrd' },
422 { subdir => 'rrddata' },
91c94f0a 423 { subdir => 'monitor' },
1e3baf05 424 ];
afdb31d5 425
1e3baf05
DM
426 return $res;
427 }});
428
429__PACKAGE__->register_method({
afdb31d5
DM
430 name => 'rrd',
431 path => '{vmid}/rrd',
1e3baf05
DM
432 method => 'GET',
433 protected => 1, # fixme: can we avoid that?
434 permissions => {
378b359e 435 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1e3baf05
DM
436 },
437 description => "Read VM RRD statistics (returns PNG)",
438 parameters => {
439 additionalProperties => 0,
440 properties => {
441 node => get_standard_option('pve-node'),
442 vmid => get_standard_option('pve-vmid'),
443 timeframe => {
444 description => "Specify the time frame you are interested in.",
445 type => 'string',
446 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
447 },
448 ds => {
449 description => "The list of datasources you want to display.",
450 type => 'string', format => 'pve-configid-list',
451 },
452 cf => {
453 description => "The RRD consolidation function",
454 type => 'string',
455 enum => [ 'AVERAGE', 'MAX' ],
456 optional => 1,
457 },
458 },
459 },
460 returns => {
461 type => "object",
462 properties => {
463 filename => { type => 'string' },
464 },
465 },
466 code => sub {
467 my ($param) = @_;
468
469 return PVE::Cluster::create_rrd_graph(
afdb31d5 470 "pve2-vm/$param->{vmid}", $param->{timeframe},
1e3baf05 471 $param->{ds}, $param->{cf});
afdb31d5 472
1e3baf05
DM
473 }});
474
475__PACKAGE__->register_method({
afdb31d5
DM
476 name => 'rrddata',
477 path => '{vmid}/rrddata',
1e3baf05
DM
478 method => 'GET',
479 protected => 1, # fixme: can we avoid that?
480 permissions => {
378b359e 481 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1e3baf05
DM
482 },
483 description => "Read VM RRD statistics",
484 parameters => {
485 additionalProperties => 0,
486 properties => {
487 node => get_standard_option('pve-node'),
488 vmid => get_standard_option('pve-vmid'),
489 timeframe => {
490 description => "Specify the time frame you are interested in.",
491 type => 'string',
492 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
493 },
494 cf => {
495 description => "The RRD consolidation function",
496 type => 'string',
497 enum => [ 'AVERAGE', 'MAX' ],
498 optional => 1,
499 },
500 },
501 },
502 returns => {
503 type => "array",
504 items => {
505 type => "object",
506 properties => {},
507 },
508 },
509 code => sub {
510 my ($param) = @_;
511
512 return PVE::Cluster::create_rrd_data(
513 "pve2-vm/$param->{vmid}", $param->{timeframe}, $param->{cf});
514 }});
515
516
517__PACKAGE__->register_method({
afdb31d5
DM
518 name => 'vm_config',
519 path => '{vmid}/config',
1e3baf05
DM
520 method => 'GET',
521 proxyto => 'node',
522 description => "Get virtual machine configuration.",
a0d1b1a2
DM
523 permissions => {
524 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
525 },
1e3baf05
DM
526 parameters => {
527 additionalProperties => 0,
528 properties => {
529 node => get_standard_option('pve-node'),
530 vmid => get_standard_option('pve-vmid'),
531 },
532 },
afdb31d5 533 returns => {
1e3baf05 534 type => "object",
554ac7e7
DM
535 properties => {
536 digest => {
537 type => 'string',
538 description => 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
539 }
540 },
1e3baf05
DM
541 },
542 code => sub {
543 my ($param) = @_;
544
545 my $conf = PVE::QemuServer::load_config($param->{vmid});
546
547 return $conf;
548 }});
549
5d7a6767 550my $delete_drive = sub {
1858638f 551 my ($conf, $storecfg, $vmid, $key, $drive, $force) = @_;
5d7a6767
DM
552
553 if (!PVE::QemuServer::drive_is_cdrom($drive)) {
554 my $volid = $drive->{file};
555
556 if ($volid !~ m|^/|) {
557 my ($path, $owner);
558 eval { ($path, $owner) = PVE::Storage::path($storecfg, $volid); };
559 if ($owner && ($owner == $vmid)) {
560 if ($force) {
561 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
562 warn $@ if $@;
563 } else {
1858638f 564 PVE::QemuServer::add_unused_volume($conf, $volid, $vmid);
5d7a6767 565 }
1858638f 566 delete $conf->{$key};
5d7a6767
DM
567 }
568 }
569 } else {
570
571 }
5d7a6767
DM
572};
573
a0d1b1a2
DM
574my $vm_config_perm_list = [
575 'VM.Config.Disk',
576 'VM.Config.CDROM',
577 'VM.Config.CPU',
578 'VM.Config.Memory',
579 'VM.Config.Network',
580 'VM.Config.HWType',
581 'VM.Config.Options',
582 ];
583
1e3baf05 584__PACKAGE__->register_method({
afdb31d5
DM
585 name => 'update_vm',
586 path => '{vmid}/config',
1e3baf05
DM
587 method => 'PUT',
588 protected => 1,
589 proxyto => 'node',
590 description => "Set virtual machine options.",
a0d1b1a2
DM
591 permissions => {
592 check => ['perm', '/vms/{vmid}', $vm_config_perm_list, any => 1],
593 },
1e3baf05
DM
594 parameters => {
595 additionalProperties => 0,
596 properties => PVE::QemuServer::json_config_properties(
597 {
598 node => get_standard_option('pve-node'),
599 vmid => get_standard_option('pve-vmid'),
3ea94c60 600 skiplock => get_standard_option('skiplock'),
1e3baf05
DM
601 delete => {
602 type => 'string', format => 'pve-configid-list',
603 description => "A list of settings you want to delete.",
604 optional => 1,
605 },
606 force => {
607 type => 'boolean',
608 description => $opt_force_description,
609 optional => 1,
610 requires => 'delete',
611 },
554ac7e7
DM
612 digest => {
613 type => 'string',
614 description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
615 maxLength => 40,
afdb31d5 616 optional => 1,
554ac7e7 617 }
1e3baf05
DM
618 }),
619 },
620 returns => { type => 'null'},
621 code => sub {
622 my ($param) = @_;
623
624 my $rpcenv = PVE::RPCEnvironment::get();
625
a0d1b1a2 626 my $authuser = $rpcenv->get_user();
1e3baf05
DM
627
628 my $node = extract_param($param, 'node');
629
1e3baf05
DM
630 my $vmid = extract_param($param, 'vmid');
631
5fdbe4f0
DM
632 my $digest = extract_param($param, 'digest');
633
634 my @paramarr = (); # used for log message
635 foreach my $key (keys %$param) {
636 push @paramarr, "-$key", $param->{$key};
637 }
638
1e3baf05 639 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 640 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 641 if $skiplock && $authuser ne 'root@pam';
1e3baf05 642
0532bc63
DM
643 my $delete_str = extract_param($param, 'delete');
644
1e3baf05
DM
645 my $force = extract_param($param, 'force');
646
0532bc63
DM
647 die "no options specified\n" if !$delete_str && !scalar(keys %$param);
648
1e68cb19
DM
649 my $storecfg = PVE::Storage::config();
650
651 &$resolve_cdrom_alias($param);
652
653 # now try to verify all parameters
654
0532bc63
DM
655 my @delete = ();
656 foreach my $opt (PVE::Tools::split_list($delete_str)) {
657 $opt = 'ide2' if $opt eq 'cdrom';
658 raise_param_exc({ delete => "you can't use '-$opt' and " .
659 "-delete $opt' at the same time" })
660 if defined($param->{$opt});
661
662 if (!PVE::QemuServer::option_exists($opt)) {
663 raise_param_exc({ delete => "unknown option '$opt'" });
664 }
665 push @delete, $opt;
666 }
1e3baf05 667
1e68cb19
DM
668 my $drive_hash = {};
669 my $net_hash = {};
670 foreach my $opt (keys %$param) {
671 if (PVE::QemuServer::valid_drivename($opt)) {
672 # cleanup drive path
673 my $drive = PVE::QemuServer::parse_drive($opt, $param->{$opt});
674 PVE::QemuServer::cleanup_drive_path($opt, $storecfg, $drive);
675 $param->{$opt} = PVE::QemuServer::print_drive($vmid, $drive);
676 $drive_hash->{$opt} = $drive;
677 } elsif ($opt =~ m/^net(\d+)$/) {
678 # add macaddr
679 my $net = PVE::QemuServer::parse_net($param->{$opt});
680 $param->{$opt} = PVE::QemuServer::print_net($net);
681 $net_hash->{$opt} = $net;
682 }
683 }
1e3baf05 684
1e3baf05 685
5d39a182 686 my $updatefn = sub {
1e3baf05 687
5d39a182 688 my $conf = PVE::QemuServer::load_config($vmid);
1e3baf05 689
5d39a182
DM
690 die "checksum missmatch (file change by other user?)\n"
691 if $digest && $digest ne $conf->{digest};
1e3baf05 692
5d39a182 693 PVE::QemuServer::check_lock($conf) if !$skiplock;
1e3baf05 694
a0d1b1a2 695 PVE::Cluster::log_msg('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
c2a64aa7 696
5d7a6767 697 foreach my $opt (@delete) { # delete
5d39a182 698
1e68cb19
DM
699 $conf = PVE::QemuServer::load_config($vmid); # update/reload
700
5d39a182 701 next if !defined($conf->{$opt});
1858638f 702
5d39a182 703 die "error hot-unplug $opt" if !PVE::QemuServer::vm_deviceunplug($vmid, $conf, $opt);
1e3baf05 704
1858638f
DM
705 if (PVE::QemuServer::valid_drivename($opt)) {
706 my $drive = PVE::QemuServer::parse_drive($opt, $conf->{$opt});
707 &$delete_drive($conf, $storecfg, $vmid, $opt, $drive, $force);
5d39a182 708 } elsif ($opt =~ m/^unused/) {
1e68cb19 709 my $drive = PVE::QemuServer::parse_drive($opt, $conf->{$opt});
c2a64aa7
DA
710 my $volid = $drive->{file};
711 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
5d7a6767 712 warn $@ if $@;
1858638f 713 delete $conf->{$opt};
5d39a182 714 }
1858638f 715 PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
5d39a182 716 }
1e3baf05 717
5d7a6767 718 foreach my $opt (keys %$param) { # add/change
1e3baf05 719
1e68cb19 720 $conf = PVE::QemuServer::load_config($vmid); # update/reload
1e3baf05 721
5d7a6767
DM
722 next if $conf->{$opt} && ($param->{$opt} eq $conf->{$opt}); # skip if nothing changed
723
724 if (my $drive = $drive_hash->{$opt}) { # drives
725
726 my $old_drive = PVE::QemuServer::parse_drive($opt, $conf->{$opt}) if $conf->{$opt};
727
728 if ($old_drive && ($drive->{file} ne $old_drive->{file}) &&
729 !PVE::QemuServer::drive_is_cdrom($old_drive)) { # delete old disks
730
731 die "error hot-unplug $opt" if !PVE::QemuServer::vm_deviceunplug($vmid, $conf, $opt);
1858638f
DM
732
733 &$delete_drive($conf, $storecfg, $vmid, $opt, $old_drive, 0);
734 PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
5d7a6767
DM
735 $conf = PVE::QemuServer::load_config($vmid); # update/reload
736 }
737
738 if (PVE::QemuServer::drive_is_cdrom($drive)) { #cdrom
739
740 if (PVE::QemuServer::check_running($vmid)) {
741 if ($drive->{file} eq 'none') {
742 PVE::QemuServer::vm_monitor_command($vmid, "eject -f drive-$opt", 0);
743 } else {
744 my $path = PVE::QemuServer::get_iso_path($storecfg, $vmid, $drive->{file});
745 PVE::QemuServer::vm_monitor_command($vmid, "eject -f drive-$opt", 0); #force eject if locked
746 PVE::QemuServer::vm_monitor_command($vmid, "change drive-$opt \"$path\"", 0) if $path;
5d39a182
DM
747 }
748 }
5d7a6767 749
1858638f
DM
750 $conf->{$opt} = $param->{$opt};
751 PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
5d7a6767
DM
752
753 } else { #hdd
754
5d39a182 755 my $settings = { $opt => $param->{$opt} };
1858638f
DM
756 &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, undef, $settings);
757 PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
5d7a6767 758 $conf = PVE::QemuServer::load_config($vmid); # update/reload
1e68cb19 759
5d7a6767
DM
760 my $newdrive = PVE::QemuServer::parse_drive($opt, $conf->{$opt});
761
762 #hotplug new disks
763 die "error hotplug $opt" if !PVE::QemuServer::vm_deviceplug($storecfg, $conf, $vmid, $opt, $newdrive);
764 }
1e68cb19
DM
765
766 } elsif (my $net = $net_hash->{$opt}) { #nics
767
3a1e36bb 768 #if online update, then unplug first
1e68cb19
DM
769 die "error hot-unplug $opt for update" if $conf->{$opt} &&
770 !PVE::QemuServer::vm_deviceunplug($vmid, $conf, $opt);
5d39a182 771
1858638f
DM
772 $conf->{$opt} = $param->{$opt};
773 PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
5d7a6767 774 $conf = PVE::QemuServer::load_config($vmid); # update/reload
3a1e36bb 775
5d7a6767 776 my $newnet = PVE::QemuServer::parse_net($conf->{$opt});
1858638f 777
5d7a6767 778 die "error hotplug $opt" if !PVE::QemuServer::vm_deviceplug($storecfg, $conf, $vmid, $opt, $newnet);
1e68cb19
DM
779
780 } else {
781
1858638f
DM
782 $conf->{$opt} = $param->{$opt};
783 PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
3a1e36bb 784 }
5d39a182
DM
785 }
786 };
787
788 PVE::QemuServer::lock_config($vmid, $updatefn);
fcdb0117 789
1e3baf05
DM
790 return undef;
791 }});
792
793
794__PACKAGE__->register_method({
afdb31d5
DM
795 name => 'destroy_vm',
796 path => '{vmid}',
1e3baf05
DM
797 method => 'DELETE',
798 protected => 1,
799 proxyto => 'node',
800 description => "Destroy the vm (also delete all used/owned volumes).",
a0d1b1a2
DM
801 permissions => {
802 check => [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
803 },
1e3baf05
DM
804 parameters => {
805 additionalProperties => 0,
806 properties => {
807 node => get_standard_option('pve-node'),
808 vmid => get_standard_option('pve-vmid'),
3ea94c60 809 skiplock => get_standard_option('skiplock'),
1e3baf05
DM
810 },
811 },
afdb31d5 812 returns => {
5fdbe4f0
DM
813 type => 'string',
814 },
1e3baf05
DM
815 code => sub {
816 my ($param) = @_;
817
818 my $rpcenv = PVE::RPCEnvironment::get();
819
a0d1b1a2 820 my $authuser = $rpcenv->get_user();
1e3baf05
DM
821
822 my $vmid = $param->{vmid};
823
824 my $skiplock = $param->{skiplock};
afdb31d5 825 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 826 if $skiplock && $authuser ne 'root@pam';
1e3baf05 827
5fdbe4f0
DM
828 # test if VM exists
829 my $conf = PVE::QemuServer::load_config($vmid);
830
afdb31d5 831 my $storecfg = PVE::Storage::config();
1e3baf05 832
5fdbe4f0 833 my $realcmd = sub {
ff1a2432
DM
834 my $upid = shift;
835
836 syslog('info', "destroy VM $vmid: $upid\n");
837
5fdbe4f0
DM
838 PVE::QemuServer::vm_destroy($storecfg, $vmid, $skiplock);
839 };
1e3baf05 840
a0d1b1a2 841 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1e3baf05
DM
842 }});
843
844__PACKAGE__->register_method({
afdb31d5
DM
845 name => 'unlink',
846 path => '{vmid}/unlink',
1e3baf05
DM
847 method => 'PUT',
848 protected => 1,
849 proxyto => 'node',
850 description => "Unlink/delete disk images.",
a0d1b1a2
DM
851 permissions => {
852 check => [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
853 },
1e3baf05
DM
854 parameters => {
855 additionalProperties => 0,
856 properties => {
857 node => get_standard_option('pve-node'),
858 vmid => get_standard_option('pve-vmid'),
859 idlist => {
860 type => 'string', format => 'pve-configid-list',
861 description => "A list of disk IDs you want to delete.",
862 },
863 force => {
864 type => 'boolean',
865 description => $opt_force_description,
866 optional => 1,
867 },
868 },
869 },
870 returns => { type => 'null'},
871 code => sub {
872 my ($param) = @_;
873
874 $param->{delete} = extract_param($param, 'idlist');
875
876 __PACKAGE__->update_vm($param);
877
878 return undef;
879 }});
880
881my $sslcert;
882
883__PACKAGE__->register_method({
afdb31d5
DM
884 name => 'vncproxy',
885 path => '{vmid}/vncproxy',
1e3baf05
DM
886 method => 'POST',
887 protected => 1,
888 permissions => {
378b359e 889 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1e3baf05
DM
890 },
891 description => "Creates a TCP VNC proxy connections.",
892 parameters => {
893 additionalProperties => 0,
894 properties => {
895 node => get_standard_option('pve-node'),
896 vmid => get_standard_option('pve-vmid'),
897 },
898 },
afdb31d5 899 returns => {
1e3baf05
DM
900 additionalProperties => 0,
901 properties => {
902 user => { type => 'string' },
903 ticket => { type => 'string' },
904 cert => { type => 'string' },
905 port => { type => 'integer' },
906 upid => { type => 'string' },
907 },
908 },
909 code => sub {
910 my ($param) = @_;
911
912 my $rpcenv = PVE::RPCEnvironment::get();
913
a0d1b1a2 914 my $authuser = $rpcenv->get_user();
1e3baf05
DM
915
916 my $vmid = $param->{vmid};
917 my $node = $param->{node};
918
b6f39da2
DM
919 my $authpath = "/vms/$vmid";
920
a0d1b1a2 921 my $ticket = PVE::AccessControl::assemble_vnc_ticket($authuser, $authpath);
b6f39da2 922
1e3baf05
DM
923 $sslcert = PVE::Tools::file_get_contents("/etc/pve/pve-root-ca.pem", 8192)
924 if !$sslcert;
925
926 my $port = PVE::Tools::next_vnc_port();
927
928 my $remip;
afdb31d5 929
4f1be36c 930 if ($node ne 'localhost' && $node ne PVE::INotify::nodename()) {
1e3baf05
DM
931 $remip = PVE::Cluster::remote_node_ip($node);
932 }
933
934 # NOTE: kvm VNC traffic is already TLS encrypted,
935 # so we select the fastest chipher here (or 'none'?)
936 my $remcmd = $remip ? ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes',
937 '-c', 'blowfish-cbc', $remip] : [];
938
afdb31d5 939 my $timeout = 10;
1e3baf05
DM
940
941 my $realcmd = sub {
942 my $upid = shift;
943
944 syslog('info', "starting vnc proxy $upid\n");
945
946 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
947
948 my $qmstr = join(' ', @$qmcmd);
949
950 # also redirect stderr (else we get RFB protocol errors)
be62c45c 951 my $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1e3baf05 952
be62c45c 953 PVE::Tools::run_command($cmd);
1e3baf05
DM
954
955 return;
956 };
957
a0d1b1a2 958 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1e3baf05
DM
959
960 return {
a0d1b1a2 961 user => $authuser,
1e3baf05 962 ticket => $ticket,
afdb31d5
DM
963 port => $port,
964 upid => $upid,
965 cert => $sslcert,
1e3baf05
DM
966 };
967 }});
968
5fdbe4f0
DM
969__PACKAGE__->register_method({
970 name => 'vmcmdidx',
afdb31d5 971 path => '{vmid}/status',
5fdbe4f0
DM
972 method => 'GET',
973 proxyto => 'node',
974 description => "Directory index",
a0d1b1a2
DM
975 permissions => {
976 user => 'all',
977 },
5fdbe4f0
DM
978 parameters => {
979 additionalProperties => 0,
980 properties => {
981 node => get_standard_option('pve-node'),
982 vmid => get_standard_option('pve-vmid'),
983 },
984 },
985 returns => {
986 type => 'array',
987 items => {
988 type => "object",
989 properties => {
990 subdir => { type => 'string' },
991 },
992 },
993 links => [ { rel => 'child', href => "{subdir}" } ],
994 },
995 code => sub {
996 my ($param) = @_;
997
998 # test if VM exists
999 my $conf = PVE::QemuServer::load_config($param->{vmid});
1000
1001 my $res = [
1002 { subdir => 'current' },
1003 { subdir => 'start' },
1004 { subdir => 'stop' },
1005 ];
afdb31d5 1006
5fdbe4f0
DM
1007 return $res;
1008 }});
1009
1e3baf05 1010__PACKAGE__->register_method({
afdb31d5 1011 name => 'vm_status',
5fdbe4f0 1012 path => '{vmid}/status/current',
1e3baf05
DM
1013 method => 'GET',
1014 proxyto => 'node',
1015 protected => 1, # qemu pid files are only readable by root
1016 description => "Get virtual machine status.",
a0d1b1a2
DM
1017 permissions => {
1018 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1019 },
1e3baf05
DM
1020 parameters => {
1021 additionalProperties => 0,
1022 properties => {
1023 node => get_standard_option('pve-node'),
1024 vmid => get_standard_option('pve-vmid'),
1025 },
1026 },
1027 returns => { type => 'object' },
1028 code => sub {
1029 my ($param) = @_;
1030
1031 # test if VM exists
1032 my $conf = PVE::QemuServer::load_config($param->{vmid});
1033
ff1a2432 1034 my $vmstatus = PVE::QemuServer::vmstatus($param->{vmid});
8610701a 1035 my $status = $vmstatus->{$param->{vmid}};
1e3baf05 1036
8610701a
DM
1037 my $cc = PVE::Cluster::cfs_read_file('cluster.conf');
1038 if (PVE::Cluster::cluster_conf_lookup_pvevm($cc, 0, $param->{vmid}, 1)) {
1039 $status->{ha} = 1;
1040 } else {
1041 $status->{ha} = 0;
1042 }
1043
1044 return $status;
1e3baf05
DM
1045 }});
1046
1047__PACKAGE__->register_method({
afdb31d5 1048 name => 'vm_start',
5fdbe4f0
DM
1049 path => '{vmid}/status/start',
1050 method => 'POST',
1e3baf05
DM
1051 protected => 1,
1052 proxyto => 'node',
5fdbe4f0 1053 description => "Start virtual machine.",
a0d1b1a2
DM
1054 permissions => {
1055 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1056 },
1e3baf05
DM
1057 parameters => {
1058 additionalProperties => 0,
1059 properties => {
1060 node => get_standard_option('pve-node'),
1061 vmid => get_standard_option('pve-vmid'),
3ea94c60
DM
1062 skiplock => get_standard_option('skiplock'),
1063 stateuri => get_standard_option('pve-qm-stateuri'),
1e3baf05
DM
1064 },
1065 },
afdb31d5 1066 returns => {
5fdbe4f0
DM
1067 type => 'string',
1068 },
1e3baf05
DM
1069 code => sub {
1070 my ($param) = @_;
1071
1072 my $rpcenv = PVE::RPCEnvironment::get();
1073
a0d1b1a2 1074 my $authuser = $rpcenv->get_user();
1e3baf05
DM
1075
1076 my $node = extract_param($param, 'node');
1077
1e3baf05
DM
1078 my $vmid = extract_param($param, 'vmid');
1079
3ea94c60 1080 my $stateuri = extract_param($param, 'stateuri');
afdb31d5 1081 raise_param_exc({ stateuri => "Only root may use this option." })
a0d1b1a2 1082 if $stateuri && $authuser ne 'root@pam';
3ea94c60 1083
1e3baf05 1084 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 1085 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 1086 if $skiplock && $authuser ne 'root@pam';
1e3baf05 1087
afdb31d5 1088 my $storecfg = PVE::Storage::config();
5fdbe4f0
DM
1089
1090 my $realcmd = sub {
1091 my $upid = shift;
1092
1093 syslog('info', "start VM $vmid: $upid\n");
1094
3ea94c60 1095 PVE::QemuServer::vm_start($storecfg, $vmid, $stateuri, $skiplock);
5fdbe4f0
DM
1096
1097 return;
1098 };
1099
a0d1b1a2 1100 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
5fdbe4f0
DM
1101 }});
1102
1103__PACKAGE__->register_method({
afdb31d5 1104 name => 'vm_stop',
5fdbe4f0
DM
1105 path => '{vmid}/status/stop',
1106 method => 'POST',
1107 protected => 1,
1108 proxyto => 'node',
1109 description => "Stop virtual machine.",
a0d1b1a2
DM
1110 permissions => {
1111 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1112 },
5fdbe4f0
DM
1113 parameters => {
1114 additionalProperties => 0,
1115 properties => {
1116 node => get_standard_option('pve-node'),
1117 vmid => get_standard_option('pve-vmid'),
1118 skiplock => get_standard_option('skiplock'),
c6bb9502
DM
1119 timeout => {
1120 description => "Wait maximal timeout seconds.",
1121 type => 'integer',
1122 minimum => 0,
1123 optional => 1,
254575e9
DM
1124 },
1125 keepActive => {
1126 description => "Do not decativate storage volumes.",
1127 type => 'boolean',
1128 optional => 1,
1129 default => 0,
c6bb9502 1130 }
5fdbe4f0
DM
1131 },
1132 },
afdb31d5 1133 returns => {
5fdbe4f0
DM
1134 type => 'string',
1135 },
1136 code => sub {
1137 my ($param) = @_;
1138
1139 my $rpcenv = PVE::RPCEnvironment::get();
1140
a0d1b1a2 1141 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
1142
1143 my $node = extract_param($param, 'node');
1144
1145 my $vmid = extract_param($param, 'vmid');
1146
1147 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 1148 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 1149 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 1150
254575e9 1151 my $keepActive = extract_param($param, 'keepActive');
afdb31d5 1152 raise_param_exc({ keepActive => "Only root may use this option." })
a0d1b1a2 1153 if $keepActive && $authuser ne 'root@pam';
254575e9 1154
ff1a2432
DM
1155 my $storecfg = PVE::Storage::config();
1156
5fdbe4f0
DM
1157 my $realcmd = sub {
1158 my $upid = shift;
1159
1160 syslog('info', "stop VM $vmid: $upid\n");
1161
afdb31d5 1162 PVE::QemuServer::vm_stop($storecfg, $vmid, $skiplock, 0,
254575e9 1163 $param->{timeout}, 0, 1, $keepActive);
c6bb9502 1164
5fdbe4f0
DM
1165 return;
1166 };
1167
a0d1b1a2 1168 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
5fdbe4f0
DM
1169 }});
1170
1171__PACKAGE__->register_method({
afdb31d5 1172 name => 'vm_reset',
5fdbe4f0
DM
1173 path => '{vmid}/status/reset',
1174 method => 'POST',
1175 protected => 1,
1176 proxyto => 'node',
1177 description => "Reset virtual machine.",
a0d1b1a2
DM
1178 permissions => {
1179 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1180 },
5fdbe4f0
DM
1181 parameters => {
1182 additionalProperties => 0,
1183 properties => {
1184 node => get_standard_option('pve-node'),
1185 vmid => get_standard_option('pve-vmid'),
1186 skiplock => get_standard_option('skiplock'),
1187 },
1188 },
afdb31d5 1189 returns => {
5fdbe4f0
DM
1190 type => 'string',
1191 },
1192 code => sub {
1193 my ($param) = @_;
1194
1195 my $rpcenv = PVE::RPCEnvironment::get();
1196
a0d1b1a2 1197 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
1198
1199 my $node = extract_param($param, 'node');
1200
1201 my $vmid = extract_param($param, 'vmid');
1202
1203 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 1204 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 1205 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 1206
ff1a2432
DM
1207 die "VM $vmid not running\n" if !PVE::QemuServer::check_running($vmid);
1208
5fdbe4f0
DM
1209 my $realcmd = sub {
1210 my $upid = shift;
1211
1e3baf05 1212 PVE::QemuServer::vm_reset($vmid, $skiplock);
5fdbe4f0
DM
1213
1214 return;
1215 };
1216
a0d1b1a2 1217 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
5fdbe4f0
DM
1218 }});
1219
1220__PACKAGE__->register_method({
afdb31d5 1221 name => 'vm_shutdown',
5fdbe4f0
DM
1222 path => '{vmid}/status/shutdown',
1223 method => 'POST',
1224 protected => 1,
1225 proxyto => 'node',
1226 description => "Shutdown virtual machine.",
a0d1b1a2
DM
1227 permissions => {
1228 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1229 },
5fdbe4f0
DM
1230 parameters => {
1231 additionalProperties => 0,
1232 properties => {
1233 node => get_standard_option('pve-node'),
1234 vmid => get_standard_option('pve-vmid'),
1235 skiplock => get_standard_option('skiplock'),
c6bb9502
DM
1236 timeout => {
1237 description => "Wait maximal timeout seconds.",
1238 type => 'integer',
1239 minimum => 0,
1240 optional => 1,
9269013a
DM
1241 },
1242 forceStop => {
1243 description => "Make sure the VM stops.",
1244 type => 'boolean',
1245 optional => 1,
1246 default => 0,
254575e9
DM
1247 },
1248 keepActive => {
1249 description => "Do not decativate storage volumes.",
1250 type => 'boolean',
1251 optional => 1,
1252 default => 0,
c6bb9502 1253 }
5fdbe4f0
DM
1254 },
1255 },
afdb31d5 1256 returns => {
5fdbe4f0
DM
1257 type => 'string',
1258 },
1259 code => sub {
1260 my ($param) = @_;
1261
1262 my $rpcenv = PVE::RPCEnvironment::get();
1263
a0d1b1a2 1264 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
1265
1266 my $node = extract_param($param, 'node');
1267
1268 my $vmid = extract_param($param, 'vmid');
1269
1270 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 1271 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 1272 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 1273
254575e9 1274 my $keepActive = extract_param($param, 'keepActive');
afdb31d5 1275 raise_param_exc({ keepActive => "Only root may use this option." })
a0d1b1a2 1276 if $keepActive && $authuser ne 'root@pam';
254575e9 1277
02d07cf5
DM
1278 my $storecfg = PVE::Storage::config();
1279
5fdbe4f0
DM
1280 my $realcmd = sub {
1281 my $upid = shift;
1282
1283 syslog('info', "shutdown VM $vmid: $upid\n");
1284
afdb31d5 1285 PVE::QemuServer::vm_stop($storecfg, $vmid, $skiplock, 0, $param->{timeout},
254575e9 1286 1, $param->{forceStop}, $keepActive);
c6bb9502 1287
5fdbe4f0
DM
1288 return;
1289 };
1290
a0d1b1a2 1291 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
5fdbe4f0
DM
1292 }});
1293
1294__PACKAGE__->register_method({
afdb31d5 1295 name => 'vm_suspend',
5fdbe4f0
DM
1296 path => '{vmid}/status/suspend',
1297 method => 'POST',
1298 protected => 1,
1299 proxyto => 'node',
1300 description => "Suspend virtual machine.",
a0d1b1a2
DM
1301 permissions => {
1302 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1303 },
5fdbe4f0
DM
1304 parameters => {
1305 additionalProperties => 0,
1306 properties => {
1307 node => get_standard_option('pve-node'),
1308 vmid => get_standard_option('pve-vmid'),
1309 skiplock => get_standard_option('skiplock'),
1310 },
1311 },
afdb31d5 1312 returns => {
5fdbe4f0
DM
1313 type => 'string',
1314 },
1315 code => sub {
1316 my ($param) = @_;
1317
1318 my $rpcenv = PVE::RPCEnvironment::get();
1319
a0d1b1a2 1320 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
1321
1322 my $node = extract_param($param, 'node');
1323
1324 my $vmid = extract_param($param, 'vmid');
1325
1326 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 1327 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 1328 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 1329
ff1a2432
DM
1330 die "VM $vmid not running\n" if !PVE::QemuServer::check_running($vmid);
1331
5fdbe4f0
DM
1332 my $realcmd = sub {
1333 my $upid = shift;
1334
1335 syslog('info', "suspend VM $vmid: $upid\n");
1336
1e3baf05 1337 PVE::QemuServer::vm_suspend($vmid, $skiplock);
5fdbe4f0
DM
1338
1339 return;
1340 };
1341
a0d1b1a2 1342 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
5fdbe4f0
DM
1343 }});
1344
1345__PACKAGE__->register_method({
afdb31d5 1346 name => 'vm_resume',
5fdbe4f0
DM
1347 path => '{vmid}/status/resume',
1348 method => 'POST',
1349 protected => 1,
1350 proxyto => 'node',
1351 description => "Resume virtual machine.",
a0d1b1a2
DM
1352 permissions => {
1353 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1354 },
5fdbe4f0
DM
1355 parameters => {
1356 additionalProperties => 0,
1357 properties => {
1358 node => get_standard_option('pve-node'),
1359 vmid => get_standard_option('pve-vmid'),
1360 skiplock => get_standard_option('skiplock'),
1361 },
1362 },
afdb31d5 1363 returns => {
5fdbe4f0
DM
1364 type => 'string',
1365 },
1366 code => sub {
1367 my ($param) = @_;
1368
1369 my $rpcenv = PVE::RPCEnvironment::get();
1370
a0d1b1a2 1371 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
1372
1373 my $node = extract_param($param, 'node');
1374
1375 my $vmid = extract_param($param, 'vmid');
1376
1377 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 1378 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 1379 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 1380
b7eeab21 1381 die "VM $vmid not running\n" if !PVE::QemuServer::check_running($vmid);
ff1a2432 1382
5fdbe4f0
DM
1383 my $realcmd = sub {
1384 my $upid = shift;
1385
1386 syslog('info', "resume VM $vmid: $upid\n");
1387
1e3baf05 1388 PVE::QemuServer::vm_resume($vmid, $skiplock);
1e3baf05 1389
5fdbe4f0
DM
1390 return;
1391 };
1392
a0d1b1a2 1393 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
5fdbe4f0
DM
1394 }});
1395
1396__PACKAGE__->register_method({
afdb31d5 1397 name => 'vm_sendkey',
5fdbe4f0
DM
1398 path => '{vmid}/sendkey',
1399 method => 'PUT',
1400 protected => 1,
1401 proxyto => 'node',
1402 description => "Send key event to virtual machine.",
a0d1b1a2
DM
1403 permissions => {
1404 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1405 },
5fdbe4f0
DM
1406 parameters => {
1407 additionalProperties => 0,
1408 properties => {
1409 node => get_standard_option('pve-node'),
1410 vmid => get_standard_option('pve-vmid'),
1411 skiplock => get_standard_option('skiplock'),
1412 key => {
1413 description => "The key (qemu monitor encoding).",
1414 type => 'string'
1415 }
1416 },
1417 },
1418 returns => { type => 'null'},
1419 code => sub {
1420 my ($param) = @_;
1421
1422 my $rpcenv = PVE::RPCEnvironment::get();
1423
a0d1b1a2 1424 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
1425
1426 my $node = extract_param($param, 'node');
1427
1428 my $vmid = extract_param($param, 'vmid');
1429
1430 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 1431 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 1432 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0
DM
1433
1434 PVE::QemuServer::vm_sendkey($vmid, $skiplock, $param->{key});
1435
1436 return;
1e3baf05
DM
1437 }});
1438
3ea94c60 1439__PACKAGE__->register_method({
afdb31d5 1440 name => 'migrate_vm',
3ea94c60
DM
1441 path => '{vmid}/migrate',
1442 method => 'POST',
1443 protected => 1,
1444 proxyto => 'node',
1445 description => "Migrate virtual machine. Creates a new migration task.",
a0d1b1a2
DM
1446 permissions => {
1447 check => ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
1448 },
3ea94c60
DM
1449 parameters => {
1450 additionalProperties => 0,
1451 properties => {
1452 node => get_standard_option('pve-node'),
1453 vmid => get_standard_option('pve-vmid'),
1454 target => get_standard_option('pve-node', { description => "Target node." }),
1455 online => {
1456 type => 'boolean',
1457 description => "Use online/live migration.",
1458 optional => 1,
1459 },
1460 force => {
1461 type => 'boolean',
1462 description => "Allow to migrate VMs which use local devices. Only root may use this option.",
1463 optional => 1,
1464 },
1465 },
1466 },
afdb31d5 1467 returns => {
3ea94c60
DM
1468 type => 'string',
1469 description => "the task ID.",
1470 },
1471 code => sub {
1472 my ($param) = @_;
1473
1474 my $rpcenv = PVE::RPCEnvironment::get();
1475
a0d1b1a2 1476 my $authuser = $rpcenv->get_user();
3ea94c60
DM
1477
1478 my $target = extract_param($param, 'target');
1479
1480 my $localnode = PVE::INotify::nodename();
1481 raise_param_exc({ target => "target is local node."}) if $target eq $localnode;
1482
1483 PVE::Cluster::check_cfs_quorum();
1484
1485 PVE::Cluster::check_node_exists($target);
1486
1487 my $targetip = PVE::Cluster::remote_node_ip($target);
1488
1489 my $vmid = extract_param($param, 'vmid');
1490
afdb31d5 1491 raise_param_exc({ force => "Only root may use this option." })
a0d1b1a2 1492 if $param->{force} && $authuser ne 'root@pam';
3ea94c60
DM
1493
1494 # test if VM exists
a5ed42d3 1495 my $conf = PVE::QemuServer::load_config($vmid);
3ea94c60
DM
1496
1497 # try to detect errors early
a5ed42d3
DM
1498
1499 PVE::QemuServer::check_lock($conf);
1500
3ea94c60 1501 if (PVE::QemuServer::check_running($vmid)) {
afdb31d5 1502 die "cant migrate running VM without --online\n"
3ea94c60
DM
1503 if !$param->{online};
1504 }
1505
1506 my $realcmd = sub {
1507 my $upid = shift;
1508
16e903f2 1509 PVE::QemuMigrate->migrate($target, $targetip, $vmid, $param);
3ea94c60
DM
1510 };
1511
a0d1b1a2 1512 my $upid = $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
3ea94c60
DM
1513
1514 return $upid;
1515 }});
1e3baf05 1516
91c94f0a 1517__PACKAGE__->register_method({
afdb31d5
DM
1518 name => 'monitor',
1519 path => '{vmid}/monitor',
91c94f0a
DM
1520 method => 'POST',
1521 protected => 1,
1522 proxyto => 'node',
1523 description => "Execute Qemu monitor commands.",
a0d1b1a2
DM
1524 permissions => {
1525 check => ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
1526 },
91c94f0a
DM
1527 parameters => {
1528 additionalProperties => 0,
1529 properties => {
1530 node => get_standard_option('pve-node'),
1531 vmid => get_standard_option('pve-vmid'),
1532 command => {
1533 type => 'string',
1534 description => "The monitor command.",
1535 }
1536 },
1537 },
1538 returns => { type => 'string'},
1539 code => sub {
1540 my ($param) = @_;
1541
1542 my $vmid = $param->{vmid};
1543
1544 my $conf = PVE::QemuServer::load_config ($vmid); # check if VM exists
1545
1546 my $res = '';
1547 eval {
1548 $res = PVE::QemuServer::vm_monitor_command($vmid, $param->{command});
1549 };
1550 $res = "ERROR: $@" if $@;
1551
1552 return $res;
1553 }});
1554
1e3baf05 15551;