]> git.proxmox.com Git - qemu-server.git/blame - PVE/API2/Qemu.pm
test for CLI when using pipe
[qemu-server.git] / PVE / API2 / Qemu.pm
CommitLineData
1e3baf05
DM
1package PVE::API2::Qemu;
2
3use strict;
4use warnings;
5
6use PVE::Cluster;
7use PVE::SafeSyslog;
8use PVE::Tools qw(extract_param);
9use PVE::Exception qw(raise raise_param_exc);
10use PVE::Storage;
11use PVE::JSONSchema qw(get_standard_option);
12use PVE::RESTHandler;
13use PVE::QemuServer;
3ea94c60 14use PVE::QemuMigrate;
1e3baf05
DM
15use PVE::RPCEnvironment;
16use PVE::AccessControl;
17use PVE::INotify;
18
19use Data::Dumper; # fixme: remove
20
21use base qw(PVE::RESTHandler);
22
23my $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.";
24
25my $resolve_cdrom_alias = sub {
26 my $param = shift;
27
28 if (my $value = $param->{cdrom}) {
29 $value .= ",media=cdrom" if $value !~ m/media=/;
30 $param->{ide2} = $value;
31 delete $param->{cdrom};
32 }
33};
34
35__PACKAGE__->register_method({
36 name => 'vmlist',
37 path => '',
38 method => 'GET',
39 description => "Virtual machine index (per node).",
40 proxyto => 'node',
41 protected => 1, # qemu pid files are only readable by root
42 parameters => {
43 additionalProperties => 0,
44 properties => {
45 node => get_standard_option('pve-node'),
46 },
47 },
48 returns => {
49 type => 'array',
50 items => {
51 type => "object",
52 properties => {},
53 },
54 links => [ { rel => 'child', href => "{vmid}" } ],
55 },
56 code => sub {
57 my ($param) = @_;
58
59 my $vmstatus = PVE::QemuServer::vmstatus();
60
61 return PVE::RESTHandler::hash_to_array($vmstatus, 'vmid');
62
63 }});
64
65__PACKAGE__->register_method({
66 name => 'create_vm',
67 path => '',
68 method => 'POST',
3e16d5fc 69 description => "Create or restore a virtual machine.",
1e3baf05
DM
70 protected => 1,
71 proxyto => 'node',
72 parameters => {
73 additionalProperties => 0,
74 properties => PVE::QemuServer::json_config_properties(
75 {
76 node => get_standard_option('pve-node'),
77 vmid => get_standard_option('pve-vmid'),
3e16d5fc
DM
78 archive => {
79 description => "The backup file.",
80 type => 'string',
81 optional => 1,
82 maxLength => 255,
83 },
84 storage => get_standard_option('pve-storage-id', {
85 description => "Default storage.",
86 optional => 1,
87 }),
88 force => {
89 optional => 1,
90 type => 'boolean',
91 description => "Allow to overwrite existing VM.",
51586c3a
DM
92 requires => 'archive',
93 },
94 unique => {
95 optional => 1,
96 type => 'boolean',
97 description => "Assign a unique random ethernet address.",
98 requires => 'archive',
3e16d5fc 99 },
1e3baf05
DM
100 }),
101 },
5fdbe4f0
DM
102 returns => {
103 type => 'string',
104 },
1e3baf05
DM
105 code => sub {
106 my ($param) = @_;
107
5fdbe4f0
DM
108 my $rpcenv = PVE::RPCEnvironment::get();
109
110 my $user = $rpcenv->get_user();
111
1e3baf05
DM
112 my $node = extract_param($param, 'node');
113
1e3baf05
DM
114 my $vmid = extract_param($param, 'vmid');
115
3e16d5fc
DM
116 my $archive = extract_param($param, 'archive');
117
118 my $storage = extract_param($param, 'storage');
119
51586c3a
DM
120 my $force = extract_param($param, 'force');
121
122 my $unique = extract_param($param, 'unique');
123
1e3baf05 124 my $filename = PVE::QemuServer::config_file($vmid);
1e3baf05
DM
125
126 my $storecfg = PVE::Storage::config();
127
3e16d5fc 128 PVE::Cluster::check_cfs_quorum();
1e3baf05 129
3e16d5fc
DM
130 if (!$archive) {
131 &$resolve_cdrom_alias($param);
1e3baf05 132
3e16d5fc
DM
133 foreach my $opt (keys %$param) {
134 if (PVE::QemuServer::valid_drivename($opt)) {
135 my $drive = PVE::QemuServer::parse_drive($opt, $param->{$opt});
136 raise_param_exc({ $opt => "unable to parse drive options" }) if !$drive;
137
138 PVE::QemuServer::cleanup_drive_path($opt, $storecfg, $drive);
139 $param->{$opt} = PVE::QemuServer::print_drive($vmid, $drive);
140 }
1e3baf05 141 }
3e16d5fc
DM
142
143 PVE::QemuServer::add_random_macs($param);
51586c3a
DM
144 } else {
145 my $keystr = join(' ', keys %$param);
bc4dcb99
DM
146 raise_param_exc({ archive => "option conflicts with other options ($keystr)"}) if $keystr;
147
148 die "pipe requires cli environment\n"
149 if $archive eq '-' && $rpcenv->{type} ne 'cli';
1e3baf05
DM
150 }
151
3e16d5fc
DM
152 my $restorefn = sub {
153
154 if (-f $filename) {
155 die "unable to restore vm $vmid: config file already exists\n"
51586c3a 156 if !$force;
3e16d5fc
DM
157
158 die "unable to restore vm $vmid: vm is running\n"
159 if PVE::QemuServer::check_running($vmid);
a6af7b3e
DM
160
161 # destroy existing data - keep empty config
162 PVE::QemuServer::destroy_vm($storecfg, $vmid, 1);
3e16d5fc
DM
163 }
164
165 my $realcmd = sub {
51586c3a
DM
166 PVE::QemuServer::restore_archive($archive, $vmid, {
167 storage => $storage,
168 unique => $unique });
3e16d5fc
DM
169 };
170
171 return $rpcenv->fork_worker('qmrestore', $vmid, $user, $realcmd);
172 };
1e3baf05 173
1e3baf05
DM
174 my $createfn = sub {
175
176 # second test (after locking test is accurate)
177 die "unable to create vm $vmid: config file already exists\n"
178 if -f $filename;
179
5fdbe4f0 180 my $realcmd = sub {
1e3baf05 181
5fdbe4f0 182 my $vollist = [];
1e3baf05 183
5fdbe4f0 184 eval {
3e16d5fc 185 $vollist = PVE::QemuServer::create_disks($storecfg, $vmid, $param, $storage);
1e3baf05 186
5fdbe4f0
DM
187 # try to be smart about bootdisk
188 my @disks = PVE::QemuServer::disknames();
189 my $firstdisk;
190 foreach my $ds (reverse @disks) {
191 next if !$param->{$ds};
192 my $disk = PVE::QemuServer::parse_drive($ds, $param->{$ds});
193 next if PVE::QemuServer::drive_is_cdrom($disk);
194 $firstdisk = $ds;
195 }
1e3baf05 196
5fdbe4f0
DM
197 if (!$param->{bootdisk} && $firstdisk) {
198 $param->{bootdisk} = $firstdisk;
199 }
1e3baf05 200
5fdbe4f0
DM
201 PVE::QemuServer::create_conf_nolock($vmid, $param);
202 };
203 my $err = $@;
1e3baf05 204
5fdbe4f0
DM
205 if ($err) {
206 foreach my $volid (@$vollist) {
207 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
208 warn $@ if $@;
209 }
210 die "create failed - $err";
211 }
212 };
213
214 return $rpcenv->fork_worker('qmcreate', $vmid, $user, $realcmd);
215 };
216
3e16d5fc 217 return PVE::QemuServer::lock_config($vmid, $archive ? $restorefn : $createfn);
1e3baf05
DM
218 }});
219
220__PACKAGE__->register_method({
221 name => 'vmdiridx',
222 path => '{vmid}',
223 method => 'GET',
224 proxyto => 'node',
225 description => "Directory index",
226 parameters => {
227 additionalProperties => 0,
228 properties => {
229 node => get_standard_option('pve-node'),
230 vmid => get_standard_option('pve-vmid'),
231 },
232 },
233 returns => {
234 type => 'array',
235 items => {
236 type => "object",
237 properties => {
238 subdir => { type => 'string' },
239 },
240 },
241 links => [ { rel => 'child', href => "{subdir}" } ],
242 },
243 code => sub {
244 my ($param) = @_;
245
246 my $res = [
247 { subdir => 'config' },
248 { subdir => 'status' },
249 { subdir => 'unlink' },
250 { subdir => 'vncproxy' },
3ea94c60 251 { subdir => 'migrate' },
1e3baf05
DM
252 { subdir => 'rrd' },
253 { subdir => 'rrddata' },
254 ];
255
256 return $res;
257 }});
258
259__PACKAGE__->register_method({
260 name => 'rrd',
261 path => '{vmid}/rrd',
262 method => 'GET',
263 protected => 1, # fixme: can we avoid that?
264 permissions => {
265 path => '/vms/{vmid}',
266 privs => [ 'VM.Audit' ],
267 },
268 description => "Read VM RRD statistics (returns PNG)",
269 parameters => {
270 additionalProperties => 0,
271 properties => {
272 node => get_standard_option('pve-node'),
273 vmid => get_standard_option('pve-vmid'),
274 timeframe => {
275 description => "Specify the time frame you are interested in.",
276 type => 'string',
277 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
278 },
279 ds => {
280 description => "The list of datasources you want to display.",
281 type => 'string', format => 'pve-configid-list',
282 },
283 cf => {
284 description => "The RRD consolidation function",
285 type => 'string',
286 enum => [ 'AVERAGE', 'MAX' ],
287 optional => 1,
288 },
289 },
290 },
291 returns => {
292 type => "object",
293 properties => {
294 filename => { type => 'string' },
295 },
296 },
297 code => sub {
298 my ($param) = @_;
299
300 return PVE::Cluster::create_rrd_graph(
301 "pve2-vm/$param->{vmid}", $param->{timeframe},
302 $param->{ds}, $param->{cf});
303
304 }});
305
306__PACKAGE__->register_method({
307 name => 'rrddata',
308 path => '{vmid}/rrddata',
309 method => 'GET',
310 protected => 1, # fixme: can we avoid that?
311 permissions => {
312 path => '/vms/{vmid}',
313 privs => [ 'VM.Audit' ],
314 },
315 description => "Read VM RRD statistics",
316 parameters => {
317 additionalProperties => 0,
318 properties => {
319 node => get_standard_option('pve-node'),
320 vmid => get_standard_option('pve-vmid'),
321 timeframe => {
322 description => "Specify the time frame you are interested in.",
323 type => 'string',
324 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
325 },
326 cf => {
327 description => "The RRD consolidation function",
328 type => 'string',
329 enum => [ 'AVERAGE', 'MAX' ],
330 optional => 1,
331 },
332 },
333 },
334 returns => {
335 type => "array",
336 items => {
337 type => "object",
338 properties => {},
339 },
340 },
341 code => sub {
342 my ($param) = @_;
343
344 return PVE::Cluster::create_rrd_data(
345 "pve2-vm/$param->{vmid}", $param->{timeframe}, $param->{cf});
346 }});
347
348
349__PACKAGE__->register_method({
350 name => 'vm_config',
351 path => '{vmid}/config',
352 method => 'GET',
353 proxyto => 'node',
354 description => "Get virtual machine configuration.",
355 parameters => {
356 additionalProperties => 0,
357 properties => {
358 node => get_standard_option('pve-node'),
359 vmid => get_standard_option('pve-vmid'),
360 },
361 },
362 returns => {
363 type => "object",
554ac7e7
DM
364 properties => {
365 digest => {
366 type => 'string',
367 description => 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
368 }
369 },
1e3baf05
DM
370 },
371 code => sub {
372 my ($param) = @_;
373
374 my $conf = PVE::QemuServer::load_config($param->{vmid});
375
376 return $conf;
377 }});
378
379__PACKAGE__->register_method({
380 name => 'update_vm',
381 path => '{vmid}/config',
382 method => 'PUT',
383 protected => 1,
384 proxyto => 'node',
385 description => "Set virtual machine options.",
386 parameters => {
387 additionalProperties => 0,
388 properties => PVE::QemuServer::json_config_properties(
389 {
390 node => get_standard_option('pve-node'),
391 vmid => get_standard_option('pve-vmid'),
3ea94c60 392 skiplock => get_standard_option('skiplock'),
1e3baf05
DM
393 delete => {
394 type => 'string', format => 'pve-configid-list',
395 description => "A list of settings you want to delete.",
396 optional => 1,
397 },
398 force => {
399 type => 'boolean',
400 description => $opt_force_description,
401 optional => 1,
402 requires => 'delete',
403 },
554ac7e7
DM
404 digest => {
405 type => 'string',
406 description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
407 maxLength => 40,
408 optional => 1,
409 }
1e3baf05
DM
410 }),
411 },
412 returns => { type => 'null'},
413 code => sub {
414 my ($param) = @_;
415
416 my $rpcenv = PVE::RPCEnvironment::get();
417
418 my $user = $rpcenv->get_user();
419
420 my $node = extract_param($param, 'node');
421
1e3baf05
DM
422 my $vmid = extract_param($param, 'vmid');
423
5fdbe4f0
DM
424 my $digest = extract_param($param, 'digest');
425
426 my @paramarr = (); # used for log message
427 foreach my $key (keys %$param) {
428 push @paramarr, "-$key", $param->{$key};
429 }
430
1e3baf05 431 my $skiplock = extract_param($param, 'skiplock');
3ea94c60
DM
432 raise_param_exc({ skiplock => "Only root may use this option." })
433 if $skiplock && $user ne 'root@pam';
1e3baf05
DM
434
435 my $delete = extract_param($param, 'delete');
436 my $force = extract_param($param, 'force');
437
438 die "no options specified\n" if !$delete && !scalar(keys %$param);
439
440 my $storecfg = PVE::Storage::config();
441
442 &$resolve_cdrom_alias($param);
443
444 my $eject = {};
445 my $cdchange = {};
446
447 foreach my $opt (keys %$param) {
448 if (PVE::QemuServer::valid_drivename($opt)) {
449 my $drive = PVE::QemuServer::parse_drive($opt, $param->{$opt});
450 raise_param_exc({ $opt => "unable to parse drive options" }) if !$drive;
451 if ($drive->{file} eq 'eject') {
452 $eject->{$opt} = 1;
453 delete $param->{$opt};
454 next;
455 }
456
457 PVE::QemuServer::cleanup_drive_path($opt, $storecfg, $drive);
458 $param->{$opt} = PVE::QemuServer::print_drive($vmid, $drive);
459
460 if (PVE::QemuServer::drive_is_cdrom($drive)) {
461 $cdchange->{$opt} = PVE::QemuServer::get_iso_path($storecfg, $vmid, $drive->{file});
462 }
463 }
464 }
465
466 foreach my $opt (PVE::Tools::split_list($delete)) {
467 $opt = 'ide2' if $opt eq 'cdrom';
468 die "you can't use '-$opt' and '-delete $opt' at the same time\n"
469 if defined($param->{$opt});
470 }
471
472 PVE::QemuServer::add_random_macs($param);
473
474 my $vollist = [];
475
476 my $updatefn = sub {
477
478 my $conf = PVE::QemuServer::load_config($vmid);
479
554ac7e7
DM
480 die "checksum missmatch (file change by other user?)\n"
481 if $digest && $digest ne $conf->{digest};
482
1e3baf05
DM
483 PVE::QemuServer::check_lock($conf) if !$skiplock;
484
5fdbe4f0
DM
485 PVE::Cluster::log_msg('info', $user, "update VM $vmid: " . join (' ', @paramarr));
486
1e3baf05
DM
487 foreach my $opt (keys %$eject) {
488 if ($conf->{$opt}) {
489 my $drive = PVE::QemuServer::parse_drive($opt, $conf->{$opt});
490 $cdchange->{$opt} = undef if PVE::QemuServer::drive_is_cdrom($drive);
491 } else {
492 raise_param_exc({ $opt => "eject failed - drive does not exist." });
493 }
494 }
495
496 foreach my $opt (keys %$param) {
497 next if !PVE::QemuServer::valid_drivename($opt);
498 next if !$conf->{$opt};
499 my $old_drive = PVE::QemuServer::parse_drive($opt, $conf->{$opt});
500 next if PVE::QemuServer::drive_is_cdrom($old_drive);
501 my $new_drive = PVE::QemuServer::parse_drive($opt, $param->{$opt});
502 if ($new_drive->{file} ne $old_drive->{file}) {
503 my ($path, $owner);
504 eval { ($path, $owner) = PVE::Storage::path($storecfg, $old_drive->{file}); };
505 if ($owner && ($owner == $vmid)) {
506 PVE::QemuServer::add_unused_volume($conf, $param, $old_drive->{file});
507 }
508 }
509 }
510
511 my $unset = {};
512
513 foreach my $opt (PVE::Tools::split_list($delete)) {
514 $opt = 'ide2' if $opt eq 'cdrom';
515 if (!PVE::QemuServer::option_exists($opt)) {
516 raise_param_exc({ delete => "unknown option '$opt'" });
517 }
518 next if !defined($conf->{$opt});
519 if (PVE::QemuServer::valid_drivename($opt)) {
f19d1c47 520 PVE::QemuServer::vm_devicedel($vmid, $conf, $opt);
1e3baf05
DM
521 my $drive = PVE::QemuServer::parse_drive($opt, $conf->{$opt});
522 if (PVE::QemuServer::drive_is_cdrom($drive)) {
523 $cdchange->{$opt} = undef;
524 } else {
525 my $volid = $drive->{file};
526
527 if ($volid !~ m|^/|) {
528 my ($path, $owner);
529 eval { ($path, $owner) = PVE::Storage::path($storecfg, $volid); };
530 if ($owner && ($owner == $vmid)) {
531 if ($force) {
532 push @$vollist, $volid;
533 } else {
534 PVE::QemuServer::add_unused_volume($conf, $param, $volid);
535 }
536 }
537 }
538 }
539 } elsif ($opt =~ m/^unused/) {
540 push @$vollist, $conf->{$opt};
541 }
542
543 $unset->{$opt} = 1;
544 }
545
f19d1c47 546 PVE::QemuServer::create_disks($storecfg, $vmid, $param, $conf);
1e3baf05
DM
547
548 PVE::QemuServer::change_config_nolock($vmid, $param, $unset, 1);
549
550 return if !PVE::QemuServer::check_running($vmid);
551
552 foreach my $opt (keys %$cdchange) {
553 my $qdn = PVE::QemuServer::qemu_drive_name($opt, 'cdrom');
554 my $path = $cdchange->{$opt};
555 PVE::QemuServer::vm_monitor_command($vmid, "eject $qdn", 0);
556 PVE::QemuServer::vm_monitor_command($vmid, "change $qdn \"$path\"", 0) if $path;
557 }
558 };
559
560 PVE::QemuServer::lock_config($vmid, $updatefn);
561
562 foreach my $volid (@$vollist) {
563 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
564 # fixme: log ?
565 warn $@ if $@;
566 }
567
568 return undef;
569 }});
570
571
572__PACKAGE__->register_method({
573 name => 'destroy_vm',
574 path => '{vmid}',
575 method => 'DELETE',
576 protected => 1,
577 proxyto => 'node',
578 description => "Destroy the vm (also delete all used/owned volumes).",
579 parameters => {
580 additionalProperties => 0,
581 properties => {
582 node => get_standard_option('pve-node'),
583 vmid => get_standard_option('pve-vmid'),
3ea94c60 584 skiplock => get_standard_option('skiplock'),
1e3baf05
DM
585 },
586 },
5fdbe4f0
DM
587 returns => {
588 type => 'string',
589 },
1e3baf05
DM
590 code => sub {
591 my ($param) = @_;
592
593 my $rpcenv = PVE::RPCEnvironment::get();
594
595 my $user = $rpcenv->get_user();
596
597 my $vmid = $param->{vmid};
598
599 my $skiplock = $param->{skiplock};
600 raise_param_exc({ skiplock => "Only root may use this option." })
3ea94c60 601 if $skiplock && $user ne 'root@pam';
1e3baf05 602
5fdbe4f0
DM
603 # test if VM exists
604 my $conf = PVE::QemuServer::load_config($vmid);
605
1e3baf05
DM
606 my $storecfg = PVE::Storage::config();
607
5fdbe4f0
DM
608 my $realcmd = sub {
609 PVE::QemuServer::vm_destroy($storecfg, $vmid, $skiplock);
610 };
1e3baf05 611
5fdbe4f0 612 return $rpcenv->fork_worker('qmdestroy', $vmid, $user, $realcmd);
1e3baf05
DM
613 }});
614
615__PACKAGE__->register_method({
616 name => 'unlink',
617 path => '{vmid}/unlink',
618 method => 'PUT',
619 protected => 1,
620 proxyto => 'node',
621 description => "Unlink/delete disk images.",
622 parameters => {
623 additionalProperties => 0,
624 properties => {
625 node => get_standard_option('pve-node'),
626 vmid => get_standard_option('pve-vmid'),
627 idlist => {
628 type => 'string', format => 'pve-configid-list',
629 description => "A list of disk IDs you want to delete.",
630 },
631 force => {
632 type => 'boolean',
633 description => $opt_force_description,
634 optional => 1,
635 },
636 },
637 },
638 returns => { type => 'null'},
639 code => sub {
640 my ($param) = @_;
641
642 $param->{delete} = extract_param($param, 'idlist');
643
644 __PACKAGE__->update_vm($param);
645
646 return undef;
647 }});
648
649my $sslcert;
650
651__PACKAGE__->register_method({
652 name => 'vncproxy',
653 path => '{vmid}/vncproxy',
654 method => 'POST',
655 protected => 1,
656 permissions => {
657 path => '/vms/{vmid}',
658 privs => [ 'VM.Console' ],
659 },
660 description => "Creates a TCP VNC proxy connections.",
661 parameters => {
662 additionalProperties => 0,
663 properties => {
664 node => get_standard_option('pve-node'),
665 vmid => get_standard_option('pve-vmid'),
666 },
667 },
668 returns => {
669 additionalProperties => 0,
670 properties => {
671 user => { type => 'string' },
672 ticket => { type => 'string' },
673 cert => { type => 'string' },
674 port => { type => 'integer' },
675 upid => { type => 'string' },
676 },
677 },
678 code => sub {
679 my ($param) = @_;
680
681 my $rpcenv = PVE::RPCEnvironment::get();
682
683 my $user = $rpcenv->get_user();
684 my $ticket = PVE::AccessControl::assemble_ticket($user);
685
686 my $vmid = $param->{vmid};
687 my $node = $param->{node};
688
689 $sslcert = PVE::Tools::file_get_contents("/etc/pve/pve-root-ca.pem", 8192)
690 if !$sslcert;
691
692 my $port = PVE::Tools::next_vnc_port();
693
694 my $remip;
695
696 if ($node ne PVE::INotify::nodename()) {
697 $remip = PVE::Cluster::remote_node_ip($node);
698 }
699
700 # NOTE: kvm VNC traffic is already TLS encrypted,
701 # so we select the fastest chipher here (or 'none'?)
702 my $remcmd = $remip ? ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes',
703 '-c', 'blowfish-cbc', $remip] : [];
704
705 my $timeout = 10;
706
707 my $realcmd = sub {
708 my $upid = shift;
709
710 syslog('info', "starting vnc proxy $upid\n");
711
712 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
713
714 my $qmstr = join(' ', @$qmcmd);
715
716 # also redirect stderr (else we get RFB protocol errors)
be62c45c 717 my $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1e3baf05 718
be62c45c 719 PVE::Tools::run_command($cmd);
1e3baf05
DM
720
721 return;
722 };
723
724 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $user, $realcmd);
725
726 return {
727 user => $user,
728 ticket => $ticket,
729 port => $port,
730 upid => $upid,
731 cert => $sslcert,
732 };
733 }});
734
5fdbe4f0
DM
735__PACKAGE__->register_method({
736 name => 'vmcmdidx',
737 path => '{vmid}/status',
738 method => 'GET',
739 proxyto => 'node',
740 description => "Directory index",
741 parameters => {
742 additionalProperties => 0,
743 properties => {
744 node => get_standard_option('pve-node'),
745 vmid => get_standard_option('pve-vmid'),
746 },
747 },
748 returns => {
749 type => 'array',
750 items => {
751 type => "object",
752 properties => {
753 subdir => { type => 'string' },
754 },
755 },
756 links => [ { rel => 'child', href => "{subdir}" } ],
757 },
758 code => sub {
759 my ($param) = @_;
760
761 # test if VM exists
762 my $conf = PVE::QemuServer::load_config($param->{vmid});
763
764 my $res = [
765 { subdir => 'current' },
766 { subdir => 'start' },
767 { subdir => 'stop' },
768 ];
769
770 return $res;
771 }});
772
1e3baf05
DM
773__PACKAGE__->register_method({
774 name => 'vm_status',
5fdbe4f0 775 path => '{vmid}/status/current',
1e3baf05
DM
776 method => 'GET',
777 proxyto => 'node',
778 protected => 1, # qemu pid files are only readable by root
779 description => "Get virtual machine status.",
780 parameters => {
781 additionalProperties => 0,
782 properties => {
783 node => get_standard_option('pve-node'),
784 vmid => get_standard_option('pve-vmid'),
785 },
786 },
787 returns => { type => 'object' },
788 code => sub {
789 my ($param) = @_;
790
791 # test if VM exists
792 my $conf = PVE::QemuServer::load_config($param->{vmid});
793
794 my $vmstatus = PVE::QemuServer::vmstatus($param->{vmid});
795
796 return $vmstatus->{$param->{vmid}};
797 }});
798
799__PACKAGE__->register_method({
5fdbe4f0
DM
800 name => 'vm_start',
801 path => '{vmid}/status/start',
802 method => 'POST',
1e3baf05
DM
803 protected => 1,
804 proxyto => 'node',
5fdbe4f0 805 description => "Start virtual machine.",
1e3baf05
DM
806 parameters => {
807 additionalProperties => 0,
808 properties => {
809 node => get_standard_option('pve-node'),
810 vmid => get_standard_option('pve-vmid'),
3ea94c60
DM
811 skiplock => get_standard_option('skiplock'),
812 stateuri => get_standard_option('pve-qm-stateuri'),
1e3baf05
DM
813 },
814 },
5fdbe4f0
DM
815 returns => {
816 type => 'string',
817 },
1e3baf05
DM
818 code => sub {
819 my ($param) = @_;
820
821 my $rpcenv = PVE::RPCEnvironment::get();
822
823 my $user = $rpcenv->get_user();
824
825 my $node = extract_param($param, 'node');
826
1e3baf05
DM
827 my $vmid = extract_param($param, 'vmid');
828
3ea94c60
DM
829 my $stateuri = extract_param($param, 'stateuri');
830 raise_param_exc({ stateuri => "Only root may use this option." })
831 if $stateuri && $user ne 'root@pam';
832
1e3baf05
DM
833 my $skiplock = extract_param($param, 'skiplock');
834 raise_param_exc({ skiplock => "Only root may use this option." })
3ea94c60 835 if $skiplock && $user ne 'root@pam';
1e3baf05 836
1e3baf05 837 my $storecfg = PVE::Storage::config();
5fdbe4f0
DM
838
839 my $realcmd = sub {
840 my $upid = shift;
841
842 syslog('info', "start VM $vmid: $upid\n");
843
3ea94c60 844 PVE::QemuServer::vm_start($storecfg, $vmid, $stateuri, $skiplock);
5fdbe4f0
DM
845
846 return;
847 };
848
849 return $rpcenv->fork_worker('qmstart', $vmid, $user, $realcmd);
850 }});
851
852__PACKAGE__->register_method({
853 name => 'vm_stop',
854 path => '{vmid}/status/stop',
855 method => 'POST',
856 protected => 1,
857 proxyto => 'node',
858 description => "Stop virtual machine.",
859 parameters => {
860 additionalProperties => 0,
861 properties => {
862 node => get_standard_option('pve-node'),
863 vmid => get_standard_option('pve-vmid'),
864 skiplock => get_standard_option('skiplock'),
c6bb9502
DM
865 timeout => {
866 description => "Wait maximal timeout seconds.",
867 type => 'integer',
868 minimum => 0,
869 optional => 1,
870 }
5fdbe4f0
DM
871 },
872 },
873 returns => {
874 type => 'string',
875 },
876 code => sub {
877 my ($param) = @_;
878
879 my $rpcenv = PVE::RPCEnvironment::get();
880
881 my $user = $rpcenv->get_user();
882
883 my $node = extract_param($param, 'node');
884
885 my $vmid = extract_param($param, 'vmid');
886
887 my $skiplock = extract_param($param, 'skiplock');
888 raise_param_exc({ skiplock => "Only root may use this option." })
889 if $skiplock && $user ne 'root@pam';
890
891 my $realcmd = sub {
892 my $upid = shift;
893
894 syslog('info', "stop VM $vmid: $upid\n");
895
1e3baf05 896 PVE::QemuServer::vm_stop($vmid, $skiplock);
5fdbe4f0 897
c6bb9502
DM
898 my $pid = PVE::QemuServer::check_running ($vmid);
899
900 if ($pid && $param->{timeout}) {
901 print "waiting until VM $vmid stopps (PID $pid)\n";
902
903 my $count = 0;
904 while (($count < $param->{timeout}) &&
905 PVE::QemuServer::check_running($vmid)) {
906 $count++;
907 sleep 1;
908 }
909
910 die "wait failed - got timeout\n" if PVE::QemuServer::check_running($vmid);
911 }
912
5fdbe4f0
DM
913 return;
914 };
915
916 return $rpcenv->fork_worker('qmstop', $vmid, $user, $realcmd);
917 }});
918
919__PACKAGE__->register_method({
920 name => 'vm_reset',
921 path => '{vmid}/status/reset',
922 method => 'POST',
923 protected => 1,
924 proxyto => 'node',
925 description => "Reset virtual machine.",
926 parameters => {
927 additionalProperties => 0,
928 properties => {
929 node => get_standard_option('pve-node'),
930 vmid => get_standard_option('pve-vmid'),
931 skiplock => get_standard_option('skiplock'),
932 },
933 },
934 returns => {
935 type => 'string',
936 },
937 code => sub {
938 my ($param) = @_;
939
940 my $rpcenv = PVE::RPCEnvironment::get();
941
942 my $user = $rpcenv->get_user();
943
944 my $node = extract_param($param, 'node');
945
946 my $vmid = extract_param($param, 'vmid');
947
948 my $skiplock = extract_param($param, 'skiplock');
949 raise_param_exc({ skiplock => "Only root may use this option." })
950 if $skiplock && $user ne 'root@pam';
951
952 my $realcmd = sub {
953 my $upid = shift;
954
955 syslog('info', "reset VM $vmid: $upid\n");
956
1e3baf05 957 PVE::QemuServer::vm_reset($vmid, $skiplock);
5fdbe4f0
DM
958
959 return;
960 };
961
962 return $rpcenv->fork_worker('qmreset', $vmid, $user, $realcmd);
963 }});
964
965__PACKAGE__->register_method({
966 name => 'vm_shutdown',
967 path => '{vmid}/status/shutdown',
968 method => 'POST',
969 protected => 1,
970 proxyto => 'node',
971 description => "Shutdown virtual machine.",
972 parameters => {
973 additionalProperties => 0,
974 properties => {
975 node => get_standard_option('pve-node'),
976 vmid => get_standard_option('pve-vmid'),
977 skiplock => get_standard_option('skiplock'),
c6bb9502
DM
978 timeout => {
979 description => "Wait maximal timeout seconds.",
980 type => 'integer',
981 minimum => 0,
982 optional => 1,
983 }
5fdbe4f0
DM
984 },
985 },
986 returns => {
987 type => 'string',
988 },
989 code => sub {
990 my ($param) = @_;
991
992 my $rpcenv = PVE::RPCEnvironment::get();
993
994 my $user = $rpcenv->get_user();
995
996 my $node = extract_param($param, 'node');
997
998 my $vmid = extract_param($param, 'vmid');
999
1000 my $skiplock = extract_param($param, 'skiplock');
1001 raise_param_exc({ skiplock => "Only root may use this option." })
1002 if $skiplock && $user ne 'root@pam';
1003
1004 my $realcmd = sub {
1005 my $upid = shift;
1006
1007 syslog('info', "shutdown VM $vmid: $upid\n");
1008
1e3baf05 1009 PVE::QemuServer::vm_shutdown($vmid, $skiplock);
5fdbe4f0 1010
c6bb9502
DM
1011 my $pid = PVE::QemuServer::check_running ($vmid);
1012
1013 if ($pid && $param->{timeout}) {
1014 print "waiting until VM $vmid stopps (PID $pid)\n";
1015
1016 my $count = 0;
1017 while (($count < $param->{timeout}) &&
1018 PVE::QemuServer::check_running($vmid)) {
1019 $count++;
1020 sleep 1;
1021 }
1022
1023 die "wait failed - got timeout\n" if PVE::QemuServer::check_running($vmid);
1024 }
1025
5fdbe4f0
DM
1026 return;
1027 };
1028
1029 return $rpcenv->fork_worker('qmshutdown', $vmid, $user, $realcmd);
1030 }});
1031
1032__PACKAGE__->register_method({
1033 name => 'vm_suspend',
1034 path => '{vmid}/status/suspend',
1035 method => 'POST',
1036 protected => 1,
1037 proxyto => 'node',
1038 description => "Suspend virtual machine.",
1039 parameters => {
1040 additionalProperties => 0,
1041 properties => {
1042 node => get_standard_option('pve-node'),
1043 vmid => get_standard_option('pve-vmid'),
1044 skiplock => get_standard_option('skiplock'),
1045 },
1046 },
1047 returns => {
1048 type => 'string',
1049 },
1050 code => sub {
1051 my ($param) = @_;
1052
1053 my $rpcenv = PVE::RPCEnvironment::get();
1054
1055 my $user = $rpcenv->get_user();
1056
1057 my $node = extract_param($param, 'node');
1058
1059 my $vmid = extract_param($param, 'vmid');
1060
1061 my $skiplock = extract_param($param, 'skiplock');
1062 raise_param_exc({ skiplock => "Only root may use this option." })
1063 if $skiplock && $user ne 'root@pam';
1064
1065 my $realcmd = sub {
1066 my $upid = shift;
1067
1068 syslog('info', "suspend VM $vmid: $upid\n");
1069
1e3baf05 1070 PVE::QemuServer::vm_suspend($vmid, $skiplock);
5fdbe4f0
DM
1071
1072 return;
1073 };
1074
1075 return $rpcenv->fork_worker('qmsuspend', $vmid, $user, $realcmd);
1076 }});
1077
1078__PACKAGE__->register_method({
1079 name => 'vm_resume',
1080 path => '{vmid}/status/resume',
1081 method => 'POST',
1082 protected => 1,
1083 proxyto => 'node',
1084 description => "Resume virtual machine.",
1085 parameters => {
1086 additionalProperties => 0,
1087 properties => {
1088 node => get_standard_option('pve-node'),
1089 vmid => get_standard_option('pve-vmid'),
1090 skiplock => get_standard_option('skiplock'),
1091 },
1092 },
1093 returns => {
1094 type => 'string',
1095 },
1096 code => sub {
1097 my ($param) = @_;
1098
1099 my $rpcenv = PVE::RPCEnvironment::get();
1100
1101 my $user = $rpcenv->get_user();
1102
1103 my $node = extract_param($param, 'node');
1104
1105 my $vmid = extract_param($param, 'vmid');
1106
1107 my $skiplock = extract_param($param, 'skiplock');
1108 raise_param_exc({ skiplock => "Only root may use this option." })
1109 if $skiplock && $user ne 'root@pam';
1110
1111 my $realcmd = sub {
1112 my $upid = shift;
1113
1114 syslog('info', "resume VM $vmid: $upid\n");
1115
1e3baf05 1116 PVE::QemuServer::vm_resume($vmid, $skiplock);
1e3baf05 1117
5fdbe4f0
DM
1118 return;
1119 };
1120
1121 return $rpcenv->fork_worker('qmresume', $vmid, $user, $realcmd);
1122 }});
1123
1124__PACKAGE__->register_method({
1125 name => 'vm_sendkey',
1126 path => '{vmid}/sendkey',
1127 method => 'PUT',
1128 protected => 1,
1129 proxyto => 'node',
1130 description => "Send key event to virtual machine.",
1131 parameters => {
1132 additionalProperties => 0,
1133 properties => {
1134 node => get_standard_option('pve-node'),
1135 vmid => get_standard_option('pve-vmid'),
1136 skiplock => get_standard_option('skiplock'),
1137 key => {
1138 description => "The key (qemu monitor encoding).",
1139 type => 'string'
1140 }
1141 },
1142 },
1143 returns => { type => 'null'},
1144 code => sub {
1145 my ($param) = @_;
1146
1147 my $rpcenv = PVE::RPCEnvironment::get();
1148
1149 my $user = $rpcenv->get_user();
1150
1151 my $node = extract_param($param, 'node');
1152
1153 my $vmid = extract_param($param, 'vmid');
1154
1155 my $skiplock = extract_param($param, 'skiplock');
1156 raise_param_exc({ skiplock => "Only root may use this option." })
1157 if $skiplock && $user ne 'root@pam';
1158
1159 PVE::QemuServer::vm_sendkey($vmid, $skiplock, $param->{key});
1160
1161 return;
1e3baf05
DM
1162 }});
1163
3ea94c60
DM
1164__PACKAGE__->register_method({
1165 name => 'migrate_vm',
1166 path => '{vmid}/migrate',
1167 method => 'POST',
1168 protected => 1,
1169 proxyto => 'node',
1170 description => "Migrate virtual machine. Creates a new migration task.",
1171 parameters => {
1172 additionalProperties => 0,
1173 properties => {
1174 node => get_standard_option('pve-node'),
1175 vmid => get_standard_option('pve-vmid'),
1176 target => get_standard_option('pve-node', { description => "Target node." }),
1177 online => {
1178 type => 'boolean',
1179 description => "Use online/live migration.",
1180 optional => 1,
1181 },
1182 force => {
1183 type => 'boolean',
1184 description => "Allow to migrate VMs which use local devices. Only root may use this option.",
1185 optional => 1,
1186 },
1187 },
1188 },
1189 returns => {
1190 type => 'string',
1191 description => "the task ID.",
1192 },
1193 code => sub {
1194 my ($param) = @_;
1195
1196 my $rpcenv = PVE::RPCEnvironment::get();
1197
1198 my $user = $rpcenv->get_user();
1199
1200 my $target = extract_param($param, 'target');
1201
1202 my $localnode = PVE::INotify::nodename();
1203 raise_param_exc({ target => "target is local node."}) if $target eq $localnode;
1204
1205 PVE::Cluster::check_cfs_quorum();
1206
1207 PVE::Cluster::check_node_exists($target);
1208
1209 my $targetip = PVE::Cluster::remote_node_ip($target);
1210
1211 my $vmid = extract_param($param, 'vmid');
1212
1213 raise_param_exc({ force => "Only root may use this option." }) if $user ne 'root@pam';
1214
1215 # test if VM exists
1216 PVE::QemuServer::load_config($vmid);
1217
1218 # try to detect errors early
1219 if (PVE::QemuServer::check_running($vmid)) {
1220 die "cant migrate running VM without --online\n"
1221 if !$param->{online};
1222 }
1223
1224 my $realcmd = sub {
1225 my $upid = shift;
1226
1227 PVE::QemuMigrate::migrate($target, $targetip, $vmid, $param->{online}, $param->{force});
1228 };
1229
1230 my $upid = $rpcenv->fork_worker('qmigrate', $vmid, $user, $realcmd);
1231
1232 return $upid;
1233 }});
1e3baf05
DM
1234
12351;