]> git.proxmox.com Git - qemu-server.git/blame - PVE/API2/Qemu.pm
cleanups
[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',
69 description => "Create new virtual machine.",
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'),
78 }),
79 },
80 returns => { type => 'null'},
81 code => sub {
82 my ($param) = @_;
83
84 my $node = extract_param($param, 'node');
85
86 # fixme: fork worker?
87
88 my $vmid = extract_param($param, 'vmid');
89
90 my $filename = PVE::QemuServer::config_file($vmid);
91 # first test (befor locking)
92 die "unable to create vm $vmid: config file already exists\n"
93 if -f $filename;
94
95 my $storecfg = PVE::Storage::config();
96
97 &$resolve_cdrom_alias($param);
98
99 foreach my $opt (keys %$param) {
100 if (PVE::QemuServer::valid_drivename($opt)) {
101 my $drive = PVE::QemuServer::parse_drive($opt, $param->{$opt});
102 raise_param_exc({ $opt => "unable to parse drive options" }) if !$drive;
103
104 PVE::QemuServer::cleanup_drive_path($opt, $storecfg, $drive);
105 $param->{$opt} = PVE::QemuServer::print_drive($vmid, $drive);
106 }
107 }
108
109 PVE::QemuServer::add_random_macs($param);
110
111 #fixme: ? syslog ('info', "VM $vmid creating new virtual machine");
112
113 my $vollist = [];
114
115 my $createfn = sub {
116
117 # second test (after locking test is accurate)
118 die "unable to create vm $vmid: config file already exists\n"
119 if -f $filename;
120
121 $vollist = PVE::QemuServer::create_disks($storecfg, $vmid, $param);
122
123 # try to be smart about bootdisk
124 my @disks = PVE::QemuServer::disknames();
125 my $firstdisk;
126 foreach my $ds (reverse @disks) {
127 next if !$param->{$ds};
128 my $disk = PVE::QemuServer::parse_drive($ds, $param->{$ds});
129 next if PVE::QemuServer::drive_is_cdrom($disk);
130 $firstdisk = $ds;
131 }
132
133 if (!$param->{bootdisk} && $firstdisk) {
134 $param->{bootdisk} = $firstdisk;
135 }
136
137 PVE::QemuServer::create_conf_nolock($vmid, $param);
138 };
139
140 eval { PVE::QemuServer::lock_config($vmid, $createfn); };
141 my $err = $@;
142
143 if ($err) {
144 foreach my $volid (@$vollist) {
145 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
146 warn $@ if $@;
147 }
148 die "create failed - $err";
149 }
150
151 return undef;
152 }});
153
154__PACKAGE__->register_method({
155 name => 'vmdiridx',
156 path => '{vmid}',
157 method => 'GET',
158 proxyto => 'node',
159 description => "Directory index",
160 parameters => {
161 additionalProperties => 0,
162 properties => {
163 node => get_standard_option('pve-node'),
164 vmid => get_standard_option('pve-vmid'),
165 },
166 },
167 returns => {
168 type => 'array',
169 items => {
170 type => "object",
171 properties => {
172 subdir => { type => 'string' },
173 },
174 },
175 links => [ { rel => 'child', href => "{subdir}" } ],
176 },
177 code => sub {
178 my ($param) = @_;
179
180 my $res = [
181 { subdir => 'config' },
182 { subdir => 'status' },
183 { subdir => 'unlink' },
184 { subdir => 'vncproxy' },
3ea94c60 185 { subdir => 'migrate' },
1e3baf05
DM
186 { subdir => 'rrd' },
187 { subdir => 'rrddata' },
188 ];
189
190 return $res;
191 }});
192
193__PACKAGE__->register_method({
194 name => 'rrd',
195 path => '{vmid}/rrd',
196 method => 'GET',
197 protected => 1, # fixme: can we avoid that?
198 permissions => {
199 path => '/vms/{vmid}',
200 privs => [ 'VM.Audit' ],
201 },
202 description => "Read VM RRD statistics (returns PNG)",
203 parameters => {
204 additionalProperties => 0,
205 properties => {
206 node => get_standard_option('pve-node'),
207 vmid => get_standard_option('pve-vmid'),
208 timeframe => {
209 description => "Specify the time frame you are interested in.",
210 type => 'string',
211 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
212 },
213 ds => {
214 description => "The list of datasources you want to display.",
215 type => 'string', format => 'pve-configid-list',
216 },
217 cf => {
218 description => "The RRD consolidation function",
219 type => 'string',
220 enum => [ 'AVERAGE', 'MAX' ],
221 optional => 1,
222 },
223 },
224 },
225 returns => {
226 type => "object",
227 properties => {
228 filename => { type => 'string' },
229 },
230 },
231 code => sub {
232 my ($param) = @_;
233
234 return PVE::Cluster::create_rrd_graph(
235 "pve2-vm/$param->{vmid}", $param->{timeframe},
236 $param->{ds}, $param->{cf});
237
238 }});
239
240__PACKAGE__->register_method({
241 name => 'rrddata',
242 path => '{vmid}/rrddata',
243 method => 'GET',
244 protected => 1, # fixme: can we avoid that?
245 permissions => {
246 path => '/vms/{vmid}',
247 privs => [ 'VM.Audit' ],
248 },
249 description => "Read VM RRD statistics",
250 parameters => {
251 additionalProperties => 0,
252 properties => {
253 node => get_standard_option('pve-node'),
254 vmid => get_standard_option('pve-vmid'),
255 timeframe => {
256 description => "Specify the time frame you are interested in.",
257 type => 'string',
258 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
259 },
260 cf => {
261 description => "The RRD consolidation function",
262 type => 'string',
263 enum => [ 'AVERAGE', 'MAX' ],
264 optional => 1,
265 },
266 },
267 },
268 returns => {
269 type => "array",
270 items => {
271 type => "object",
272 properties => {},
273 },
274 },
275 code => sub {
276 my ($param) = @_;
277
278 return PVE::Cluster::create_rrd_data(
279 "pve2-vm/$param->{vmid}", $param->{timeframe}, $param->{cf});
280 }});
281
282
283__PACKAGE__->register_method({
284 name => 'vm_config',
285 path => '{vmid}/config',
286 method => 'GET',
287 proxyto => 'node',
288 description => "Get virtual machine configuration.",
289 parameters => {
290 additionalProperties => 0,
291 properties => {
292 node => get_standard_option('pve-node'),
293 vmid => get_standard_option('pve-vmid'),
294 },
295 },
296 returns => {
297 type => "object",
554ac7e7
DM
298 properties => {
299 digest => {
300 type => 'string',
301 description => 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
302 }
303 },
1e3baf05
DM
304 },
305 code => sub {
306 my ($param) = @_;
307
308 my $conf = PVE::QemuServer::load_config($param->{vmid});
309
310 return $conf;
311 }});
312
313__PACKAGE__->register_method({
314 name => 'update_vm',
315 path => '{vmid}/config',
316 method => 'PUT',
317 protected => 1,
318 proxyto => 'node',
319 description => "Set virtual machine options.",
320 parameters => {
321 additionalProperties => 0,
322 properties => PVE::QemuServer::json_config_properties(
323 {
324 node => get_standard_option('pve-node'),
325 vmid => get_standard_option('pve-vmid'),
3ea94c60 326 skiplock => get_standard_option('skiplock'),
1e3baf05
DM
327 delete => {
328 type => 'string', format => 'pve-configid-list',
329 description => "A list of settings you want to delete.",
330 optional => 1,
331 },
332 force => {
333 type => 'boolean',
334 description => $opt_force_description,
335 optional => 1,
336 requires => 'delete',
337 },
554ac7e7
DM
338 digest => {
339 type => 'string',
340 description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
341 maxLength => 40,
342 optional => 1,
343 }
1e3baf05
DM
344 }),
345 },
346 returns => { type => 'null'},
347 code => sub {
348 my ($param) = @_;
349
350 my $rpcenv = PVE::RPCEnvironment::get();
351
352 my $user = $rpcenv->get_user();
353
354 my $node = extract_param($param, 'node');
355
1e3baf05
DM
356 my $vmid = extract_param($param, 'vmid');
357
358 my $skiplock = extract_param($param, 'skiplock');
3ea94c60
DM
359 raise_param_exc({ skiplock => "Only root may use this option." })
360 if $skiplock && $user ne 'root@pam';
1e3baf05
DM
361
362 my $delete = extract_param($param, 'delete');
363 my $force = extract_param($param, 'force');
364
365 die "no options specified\n" if !$delete && !scalar(keys %$param);
366
554ac7e7
DM
367 my $digest = extract_param($param, 'digest');
368
1e3baf05
DM
369 my $storecfg = PVE::Storage::config();
370
371 &$resolve_cdrom_alias($param);
372
373 my $eject = {};
374 my $cdchange = {};
375
376 foreach my $opt (keys %$param) {
377 if (PVE::QemuServer::valid_drivename($opt)) {
378 my $drive = PVE::QemuServer::parse_drive($opt, $param->{$opt});
379 raise_param_exc({ $opt => "unable to parse drive options" }) if !$drive;
380 if ($drive->{file} eq 'eject') {
381 $eject->{$opt} = 1;
382 delete $param->{$opt};
383 next;
384 }
385
386 PVE::QemuServer::cleanup_drive_path($opt, $storecfg, $drive);
387 $param->{$opt} = PVE::QemuServer::print_drive($vmid, $drive);
388
389 if (PVE::QemuServer::drive_is_cdrom($drive)) {
390 $cdchange->{$opt} = PVE::QemuServer::get_iso_path($storecfg, $vmid, $drive->{file});
391 }
392 }
393 }
394
395 foreach my $opt (PVE::Tools::split_list($delete)) {
396 $opt = 'ide2' if $opt eq 'cdrom';
397 die "you can't use '-$opt' and '-delete $opt' at the same time\n"
398 if defined($param->{$opt});
399 }
400
401 PVE::QemuServer::add_random_macs($param);
402
403 my $vollist = [];
404
405 my $updatefn = sub {
406
407 my $conf = PVE::QemuServer::load_config($vmid);
408
554ac7e7
DM
409 die "checksum missmatch (file change by other user?)\n"
410 if $digest && $digest ne $conf->{digest};
411
1e3baf05
DM
412 PVE::QemuServer::check_lock($conf) if !$skiplock;
413
414 foreach my $opt (keys %$eject) {
415 if ($conf->{$opt}) {
416 my $drive = PVE::QemuServer::parse_drive($opt, $conf->{$opt});
417 $cdchange->{$opt} = undef if PVE::QemuServer::drive_is_cdrom($drive);
418 } else {
419 raise_param_exc({ $opt => "eject failed - drive does not exist." });
420 }
421 }
422
423 foreach my $opt (keys %$param) {
424 next if !PVE::QemuServer::valid_drivename($opt);
425 next if !$conf->{$opt};
426 my $old_drive = PVE::QemuServer::parse_drive($opt, $conf->{$opt});
427 next if PVE::QemuServer::drive_is_cdrom($old_drive);
428 my $new_drive = PVE::QemuServer::parse_drive($opt, $param->{$opt});
429 if ($new_drive->{file} ne $old_drive->{file}) {
430 my ($path, $owner);
431 eval { ($path, $owner) = PVE::Storage::path($storecfg, $old_drive->{file}); };
432 if ($owner && ($owner == $vmid)) {
433 PVE::QemuServer::add_unused_volume($conf, $param, $old_drive->{file});
434 }
435 }
436 }
437
438 my $unset = {};
439
440 foreach my $opt (PVE::Tools::split_list($delete)) {
441 $opt = 'ide2' if $opt eq 'cdrom';
442 if (!PVE::QemuServer::option_exists($opt)) {
443 raise_param_exc({ delete => "unknown option '$opt'" });
444 }
445 next if !defined($conf->{$opt});
446 if (PVE::QemuServer::valid_drivename($opt)) {
447 my $drive = PVE::QemuServer::parse_drive($opt, $conf->{$opt});
448 if (PVE::QemuServer::drive_is_cdrom($drive)) {
449 $cdchange->{$opt} = undef;
450 } else {
451 my $volid = $drive->{file};
452
453 if ($volid !~ m|^/|) {
454 my ($path, $owner);
455 eval { ($path, $owner) = PVE::Storage::path($storecfg, $volid); };
456 if ($owner && ($owner == $vmid)) {
457 if ($force) {
458 push @$vollist, $volid;
459 } else {
460 PVE::QemuServer::add_unused_volume($conf, $param, $volid);
461 }
462 }
463 }
464 }
465 } elsif ($opt =~ m/^unused/) {
466 push @$vollist, $conf->{$opt};
467 }
468
469 $unset->{$opt} = 1;
470 }
471
472 PVE::QemuServer::create_disks($storecfg, $vmid, $param);
473
474 PVE::QemuServer::change_config_nolock($vmid, $param, $unset, 1);
475
476 return if !PVE::QemuServer::check_running($vmid);
477
478 foreach my $opt (keys %$cdchange) {
479 my $qdn = PVE::QemuServer::qemu_drive_name($opt, 'cdrom');
480 my $path = $cdchange->{$opt};
481 PVE::QemuServer::vm_monitor_command($vmid, "eject $qdn", 0);
482 PVE::QemuServer::vm_monitor_command($vmid, "change $qdn \"$path\"", 0) if $path;
483 }
484 };
485
486 PVE::QemuServer::lock_config($vmid, $updatefn);
487
488 foreach my $volid (@$vollist) {
489 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
490 # fixme: log ?
491 warn $@ if $@;
492 }
493
494 return undef;
495 }});
496
497
498__PACKAGE__->register_method({
499 name => 'destroy_vm',
500 path => '{vmid}',
501 method => 'DELETE',
502 protected => 1,
503 proxyto => 'node',
504 description => "Destroy the vm (also delete all used/owned volumes).",
505 parameters => {
506 additionalProperties => 0,
507 properties => {
508 node => get_standard_option('pve-node'),
509 vmid => get_standard_option('pve-vmid'),
3ea94c60 510 skiplock => get_standard_option('skiplock'),
1e3baf05
DM
511 },
512 },
513 returns => { type => 'null' },
514 code => sub {
515 my ($param) = @_;
516
517 my $rpcenv = PVE::RPCEnvironment::get();
518
519 my $user = $rpcenv->get_user();
520
521 my $vmid = $param->{vmid};
522
523 my $skiplock = $param->{skiplock};
524 raise_param_exc({ skiplock => "Only root may use this option." })
3ea94c60 525 if $skiplock && $user ne 'root@pam';
1e3baf05
DM
526
527 my $storecfg = PVE::Storage::config();
528
529 PVE::QemuServer::vm_destroy($storecfg, $vmid, $skiplock);
530
531 return undef;
532 }});
533
534__PACKAGE__->register_method({
535 name => 'unlink',
536 path => '{vmid}/unlink',
537 method => 'PUT',
538 protected => 1,
539 proxyto => 'node',
540 description => "Unlink/delete disk images.",
541 parameters => {
542 additionalProperties => 0,
543 properties => {
544 node => get_standard_option('pve-node'),
545 vmid => get_standard_option('pve-vmid'),
546 idlist => {
547 type => 'string', format => 'pve-configid-list',
548 description => "A list of disk IDs you want to delete.",
549 },
550 force => {
551 type => 'boolean',
552 description => $opt_force_description,
553 optional => 1,
554 },
555 },
556 },
557 returns => { type => 'null'},
558 code => sub {
559 my ($param) = @_;
560
561 $param->{delete} = extract_param($param, 'idlist');
562
563 __PACKAGE__->update_vm($param);
564
565 return undef;
566 }});
567
568my $sslcert;
569
570__PACKAGE__->register_method({
571 name => 'vncproxy',
572 path => '{vmid}/vncproxy',
573 method => 'POST',
574 protected => 1,
575 permissions => {
576 path => '/vms/{vmid}',
577 privs => [ 'VM.Console' ],
578 },
579 description => "Creates a TCP VNC proxy connections.",
580 parameters => {
581 additionalProperties => 0,
582 properties => {
583 node => get_standard_option('pve-node'),
584 vmid => get_standard_option('pve-vmid'),
585 },
586 },
587 returns => {
588 additionalProperties => 0,
589 properties => {
590 user => { type => 'string' },
591 ticket => { type => 'string' },
592 cert => { type => 'string' },
593 port => { type => 'integer' },
594 upid => { type => 'string' },
595 },
596 },
597 code => sub {
598 my ($param) = @_;
599
600 my $rpcenv = PVE::RPCEnvironment::get();
601
602 my $user = $rpcenv->get_user();
603 my $ticket = PVE::AccessControl::assemble_ticket($user);
604
605 my $vmid = $param->{vmid};
606 my $node = $param->{node};
607
608 $sslcert = PVE::Tools::file_get_contents("/etc/pve/pve-root-ca.pem", 8192)
609 if !$sslcert;
610
611 my $port = PVE::Tools::next_vnc_port();
612
613 my $remip;
614
615 if ($node ne PVE::INotify::nodename()) {
616 $remip = PVE::Cluster::remote_node_ip($node);
617 }
618
619 # NOTE: kvm VNC traffic is already TLS encrypted,
620 # so we select the fastest chipher here (or 'none'?)
621 my $remcmd = $remip ? ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes',
622 '-c', 'blowfish-cbc', $remip] : [];
623
624 my $timeout = 10;
625
626 my $realcmd = sub {
627 my $upid = shift;
628
629 syslog('info', "starting vnc proxy $upid\n");
630
631 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
632
633 my $qmstr = join(' ', @$qmcmd);
634
635 # also redirect stderr (else we get RFB protocol errors)
be62c45c 636 my $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1e3baf05 637
be62c45c 638 PVE::Tools::run_command($cmd);
1e3baf05
DM
639
640 return;
641 };
642
643 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $user, $realcmd);
644
645 return {
646 user => $user,
647 ticket => $ticket,
648 port => $port,
649 upid => $upid,
650 cert => $sslcert,
651 };
652 }});
653
654__PACKAGE__->register_method({
655 name => 'vm_status',
656 path => '{vmid}/status',
657 method => 'GET',
658 proxyto => 'node',
659 protected => 1, # qemu pid files are only readable by root
660 description => "Get virtual machine status.",
661 parameters => {
662 additionalProperties => 0,
663 properties => {
664 node => get_standard_option('pve-node'),
665 vmid => get_standard_option('pve-vmid'),
666 },
667 },
668 returns => { type => 'object' },
669 code => sub {
670 my ($param) = @_;
671
672 # test if VM exists
673 my $conf = PVE::QemuServer::load_config($param->{vmid});
674
675 my $vmstatus = PVE::QemuServer::vmstatus($param->{vmid});
676
677 return $vmstatus->{$param->{vmid}};
678 }});
679
680__PACKAGE__->register_method({
681 name => 'vm_command',
682 path => '{vmid}/status',
683 method => 'PUT',
684 protected => 1,
685 proxyto => 'node',
3ea94c60 686 description => "Set virtual machine status (execute vm commands).",
1e3baf05
DM
687 parameters => {
688 additionalProperties => 0,
689 properties => {
690 node => get_standard_option('pve-node'),
691 vmid => get_standard_option('pve-vmid'),
3ea94c60
DM
692 skiplock => get_standard_option('skiplock'),
693 stateuri => get_standard_option('pve-qm-stateuri'),
1e3baf05 694 command => {
3ea94c60 695 description => "The command to execute.",
1e3baf05
DM
696 type => 'string',
697 enum => [qw(start stop reset shutdown cad suspend resume) ],
698 },
699 },
700 },
701 returns => { type => 'null'},
702 code => sub {
703 my ($param) = @_;
704
705 my $rpcenv = PVE::RPCEnvironment::get();
706
707 my $user = $rpcenv->get_user();
708
709 my $node = extract_param($param, 'node');
710
1e3baf05
DM
711 my $vmid = extract_param($param, 'vmid');
712
3ea94c60
DM
713 my $stateuri = extract_param($param, 'stateuri');
714 raise_param_exc({ stateuri => "Only root may use this option." })
715 if $stateuri && $user ne 'root@pam';
716
1e3baf05
DM
717 my $skiplock = extract_param($param, 'skiplock');
718 raise_param_exc({ skiplock => "Only root may use this option." })
3ea94c60 719 if $skiplock && $user ne 'root@pam';
1e3baf05
DM
720
721 my $command = $param->{command};
722
723 my $storecfg = PVE::Storage::config();
724
725 if ($command eq 'start') {
3ea94c60 726 PVE::QemuServer::vm_start($storecfg, $vmid, $stateuri, $skiplock);
1e3baf05
DM
727 } elsif ($command eq 'stop') {
728 PVE::QemuServer::vm_stop($vmid, $skiplock);
729 } elsif ($command eq 'reset') {
730 PVE::QemuServer::vm_reset($vmid, $skiplock);
731 } elsif ($command eq 'shutdown') {
732 PVE::QemuServer::vm_shutdown($vmid, $skiplock);
733 } elsif ($command eq 'suspend') {
734 PVE::QemuServer::vm_suspend($vmid, $skiplock);
735 } elsif ($command eq 'resume') {
736 PVE::QemuServer::vm_resume($vmid, $skiplock);
737 } elsif ($command eq 'cad') {
738 PVE::QemuServer::vm_cad($vmid, $skiplock);
739 } else {
740 raise_param_exc({ command => "unknown command '$command'" })
741 }
742
743 return undef;
744 }});
745
3ea94c60
DM
746__PACKAGE__->register_method({
747 name => 'migrate_vm',
748 path => '{vmid}/migrate',
749 method => 'POST',
750 protected => 1,
751 proxyto => 'node',
752 description => "Migrate virtual machine. Creates a new migration task.",
753 parameters => {
754 additionalProperties => 0,
755 properties => {
756 node => get_standard_option('pve-node'),
757 vmid => get_standard_option('pve-vmid'),
758 target => get_standard_option('pve-node', { description => "Target node." }),
759 online => {
760 type => 'boolean',
761 description => "Use online/live migration.",
762 optional => 1,
763 },
764 force => {
765 type => 'boolean',
766 description => "Allow to migrate VMs which use local devices. Only root may use this option.",
767 optional => 1,
768 },
769 },
770 },
771 returns => {
772 type => 'string',
773 description => "the task ID.",
774 },
775 code => sub {
776 my ($param) = @_;
777
778 my $rpcenv = PVE::RPCEnvironment::get();
779
780 my $user = $rpcenv->get_user();
781
782 my $target = extract_param($param, 'target');
783
784 my $localnode = PVE::INotify::nodename();
785 raise_param_exc({ target => "target is local node."}) if $target eq $localnode;
786
787 PVE::Cluster::check_cfs_quorum();
788
789 PVE::Cluster::check_node_exists($target);
790
791 my $targetip = PVE::Cluster::remote_node_ip($target);
792
793 my $vmid = extract_param($param, 'vmid');
794
795 raise_param_exc({ force => "Only root may use this option." }) if $user ne 'root@pam';
796
797 # test if VM exists
798 PVE::QemuServer::load_config($vmid);
799
800 # try to detect errors early
801 if (PVE::QemuServer::check_running($vmid)) {
802 die "cant migrate running VM without --online\n"
803 if !$param->{online};
804 }
805
806 my $realcmd = sub {
807 my $upid = shift;
808
809 PVE::QemuMigrate::migrate($target, $targetip, $vmid, $param->{online}, $param->{force});
810 };
811
812 my $upid = $rpcenv->fork_worker('qmigrate', $vmid, $user, $realcmd);
813
814 return $upid;
815 }});
1e3baf05
DM
816
8171;