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