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