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