1 package PVE
::API2
::Qemu
;
9 use PVE
::Tools
qw(extract_param);
10 use PVE
::Exception
qw(raise raise_param_exc);
12 use PVE
::JSONSchema
qw(get_standard_option);
16 use PVE
::RPCEnvironment
;
17 use PVE
::AccessControl
;
20 use Data
::Dumper
; # fixme: remove
22 use base
qw(PVE::RESTHandler);
24 my $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.";
26 my $resolve_cdrom_alias = sub {
29 if (my $value = $param->{cdrom
}) {
30 $value .= ",media=cdrom" if $value !~ m/media=/;
31 $param->{ide2
} = $value;
32 delete $param->{cdrom
};
36 my $check_volume_access = sub {
37 my ($rpcenv, $authuser, $storecfg, $vmid, $volid, $pool) = @_;
40 if (my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1)) {
41 my ($ownervm, $vtype);
42 ($path, $ownervm, $vtype) = PVE
::Storage
::path
($storecfg, $volid);
43 if ($vtype eq 'iso' || $vtype eq 'vztmpl') {
44 # we simply allow access
45 } elsif (!$ownervm || ($ownervm != $vmid)) {
46 # allow if we are Datastore administrator
47 $rpcenv->check_storage_perm($authuser, $vmid, $pool, $sid, [ 'Datastore.Allocate' ]);
50 die "Only root can pass arbitrary filesystem paths."
51 if $authuser ne 'root@pam';
53 $path = abs_path
($volid);
58 my $check_storage_access = sub {
59 my ($rpcenv, $authuser, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
61 PVE
::QemuServer
::foreach_drive
($settings, sub {
62 my ($ds, $drive) = @_;
64 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
66 my $volid = $drive->{file
};
68 if (!$isCDROM && ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/)) {
69 my ($storeid, $size) = ($2 || $default_storage, $3);
70 die "no storage ID specified (and no default storage)\n" if !$storeid;
71 $rpcenv->check_storage_perm($authuser, $vmid, $pool, $storeid, [ 'Datastore.AllocateSpace' ]);
73 my $path = &$check_volume_access($rpcenv, $authuser, $storecfg, $vmid, $volid, $pool);
74 die "image '$path' does not exists\n" if (!(-f
$path || -b
$path));
79 # Note: $pool is only needed when creating a VM, because pool permissions
80 # are automatically inherited if VM already exists inside a pool.
81 my $create_disks = sub {
82 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
87 PVE
::QemuServer
::foreach_drive
($settings, sub {
90 my $volid = $disk->{file
};
92 if ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/) {
93 my ($storeid, $size) = ($2 || $default_storage, $3);
94 die "no storage ID specified (and no default storage)\n" if !$storeid;
95 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
96 my $fmt = $disk->{format
} || $defformat;
97 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
98 $fmt, undef, $size*1024*1024);
99 $disk->{file
} = $volid;
100 push @$vollist, $volid;
101 delete $disk->{format
}; # no longer needed
102 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
104 my $path = &$check_volume_access($rpcenv, $authuser, $storecfg, $vmid, $volid, $pool);
105 die "image '$path' does not exists\n" if (!(-f
$path || -b
$path));
106 $res->{$ds} = $settings->{ds
};
110 # free allocated images on error
112 syslog
('err', "VM $vmid creating disks failed");
113 foreach my $volid (@$vollist) {
114 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
120 # modify vm config if everything went well
121 foreach my $ds (keys %$res) {
122 $conf->{$ds} = $res->{$ds};
128 my $check_vm_modify_config_perm = sub {
129 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
131 return 1 if $authuser ne 'root@pam';
133 foreach my $opt (@$key_list) {
134 # disk checks need to be done somewhere else
135 next if PVE
::QemuServer
::valid_drivename
($opt);
137 if ($opt eq 'sockets' || $opt eq 'cores' ||
138 $opt eq 'cpu' || $opt eq 'smp' ||
139 $opt eq 'cpuimit' || $opt eq 'cpuunits') {
140 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
141 } elsif ($opt eq 'boot' || $opt eq 'bootdisk') {
142 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
143 } elsif ($opt eq 'memory' || $opt eq 'balloon') {
144 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
145 } elsif ($opt eq 'args' || $opt eq 'lock') {
146 die "only root can set '$opt' config\n";
147 } elsif ($opt eq 'cpu' || $opt eq 'kvm' || $opt eq 'acpi' ||
148 $opt eq 'vga' || $opt eq 'watchdog' || $opt eq 'tablet') {
149 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
150 } elsif ($opt =~ m/^net\d+$/) {
151 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
153 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
160 __PACKAGE__-
>register_method({
164 description
=> "Virtual machine index (per node).",
166 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
170 protected
=> 1, # qemu pid files are only readable by root
172 additionalProperties
=> 0,
174 node
=> get_standard_option
('pve-node'),
183 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
188 my $rpcenv = PVE
::RPCEnvironment
::get
();
189 my $authuser = $rpcenv->get_user();
191 my $vmstatus = PVE
::QemuServer
::vmstatus
();
194 foreach my $vmid (keys %$vmstatus) {
195 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
197 my $data = $vmstatus->{$vmid};
198 $data->{vmid
} = $vmid;
205 __PACKAGE__-
>register_method({
209 description
=> "Create or restore a virtual machine.",
211 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
213 [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
214 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
220 additionalProperties
=> 0,
221 properties
=> PVE
::QemuServer
::json_config_properties
(
223 node
=> get_standard_option
('pve-node'),
224 vmid
=> get_standard_option
('pve-vmid'),
226 description
=> "The backup file.",
231 storage
=> get_standard_option
('pve-storage-id', {
232 description
=> "Default storage.",
238 description
=> "Allow to overwrite existing VM.",
239 requires
=> 'archive',
244 description
=> "Assign a unique random ethernet address.",
245 requires
=> 'archive',
249 type
=> 'string', format
=> 'pve-poolid',
250 description
=> "Add the VM to the specified pool.",
260 my $rpcenv = PVE
::RPCEnvironment
::get
();
262 my $authuser = $rpcenv->get_user();
264 my $node = extract_param
($param, 'node');
266 my $vmid = extract_param
($param, 'vmid');
268 my $archive = extract_param
($param, 'archive');
270 my $storage = extract_param
($param, 'storage');
272 my $force = extract_param
($param, 'force');
274 my $unique = extract_param
($param, 'unique');
276 my $pool = extract_param
($param, 'pool');
278 my $filename = PVE
::QemuServer
::config_file
($vmid);
280 my $storecfg = PVE
::Storage
::config
();
282 PVE
::Cluster
::check_cfs_quorum
();
284 if (defined($pool)) {
285 $rpcenv->check_pool_exist($pool);
286 $rpcenv->check_perm_modify($authuser, "/pool/$pool");
289 $rpcenv->check_storage_perm($authuser, $vmid, $pool, $storage, [ 'Datastore.AllocateSpace' ])
290 if defined($storage);
293 &$resolve_cdrom_alias($param);
295 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $pool, $param, $storage);
297 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
299 foreach my $opt (keys %$param) {
300 if (PVE
::QemuServer
::valid_drivename
($opt)) {
301 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
302 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
304 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
305 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
309 PVE
::QemuServer
::add_random_macs
($param);
311 my $keystr = join(' ', keys %$param);
312 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
314 if ($archive eq '-') {
315 die "pipe requires cli environment\n"
316 && $rpcenv->{type
} ne 'cli';
318 my $path = &$check_volume_access($rpcenv, $authuser, $storecfg, $vmid, $archive, $pool);
319 die "can't find archive file '$archive'\n" if !($path && -f
$path);
324 my $restorefn = sub {
327 die "unable to restore vm $vmid: config file already exists\n"
330 die "unable to restore vm $vmid: vm is running\n"
331 if PVE
::QemuServer
::check_running
($vmid);
333 # destroy existing data - keep empty config
334 PVE
::QemuServer
::destroy_vm
($storecfg, $vmid, 1);
338 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
341 unique
=> $unique });
344 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
349 # second test (after locking test is accurate)
350 die "unable to create vm $vmid: config file already exists\n"
361 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
363 # try to be smart about bootdisk
364 my @disks = PVE
::QemuServer
::disknames
();
366 foreach my $ds (reverse @disks) {
367 next if !$conf->{$ds};
368 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
369 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
373 if (!$conf->{bootdisk
} && $firstdisk) {
374 $conf->{bootdisk
} = $firstdisk;
377 PVE
::QemuServer
::update_conf_nolock
($vmid, $conf);
382 foreach my $volid (@$vollist) {
383 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
386 die "create failed - $err";
390 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
393 return PVE
::QemuServer
::lock_config
($vmid, $archive ?
$restorefn : $createfn);
396 __PACKAGE__-
>register_method({
401 description
=> "Directory index",
406 additionalProperties
=> 0,
408 node
=> get_standard_option
('pve-node'),
409 vmid
=> get_standard_option
('pve-vmid'),
417 subdir
=> { type
=> 'string' },
420 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
426 { subdir
=> 'config' },
427 { subdir
=> 'status' },
428 { subdir
=> 'unlink' },
429 { subdir
=> 'vncproxy' },
430 { subdir
=> 'migrate' },
432 { subdir
=> 'rrddata' },
433 { subdir
=> 'monitor' },
439 __PACKAGE__-
>register_method({
441 path
=> '{vmid}/rrd',
443 protected
=> 1, # fixme: can we avoid that?
445 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
447 description
=> "Read VM RRD statistics (returns PNG)",
449 additionalProperties
=> 0,
451 node
=> get_standard_option
('pve-node'),
452 vmid
=> get_standard_option
('pve-vmid'),
454 description
=> "Specify the time frame you are interested in.",
456 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
459 description
=> "The list of datasources you want to display.",
460 type
=> 'string', format
=> 'pve-configid-list',
463 description
=> "The RRD consolidation function",
465 enum
=> [ 'AVERAGE', 'MAX' ],
473 filename
=> { type
=> 'string' },
479 return PVE
::Cluster
::create_rrd_graph
(
480 "pve2-vm/$param->{vmid}", $param->{timeframe
},
481 $param->{ds
}, $param->{cf
});
485 __PACKAGE__-
>register_method({
487 path
=> '{vmid}/rrddata',
489 protected
=> 1, # fixme: can we avoid that?
491 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
493 description
=> "Read VM RRD statistics",
495 additionalProperties
=> 0,
497 node
=> get_standard_option
('pve-node'),
498 vmid
=> get_standard_option
('pve-vmid'),
500 description
=> "Specify the time frame you are interested in.",
502 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
505 description
=> "The RRD consolidation function",
507 enum
=> [ 'AVERAGE', 'MAX' ],
522 return PVE
::Cluster
::create_rrd_data
(
523 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
527 __PACKAGE__-
>register_method({
529 path
=> '{vmid}/config',
532 description
=> "Get virtual machine configuration.",
534 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
537 additionalProperties
=> 0,
539 node
=> get_standard_option
('pve-node'),
540 vmid
=> get_standard_option
('pve-vmid'),
548 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
555 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
560 my $vm_is_volid_owner = sub {
561 my ($storecfg, $vmid, $volid) =@_;
563 if ($volid !~ m
|^/|) {
565 eval { ($path, $owner) = PVE
::Storage
::path
($storecfg, $volid); };
566 if ($owner && ($owner == $vmid)) {
574 my $test_deallocate_drive = sub {
575 my ($storecfg, $vmid, $key, $drive, $force) = @_;
577 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
578 my $volid = $drive->{file
};
579 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
580 if ($force || $key =~ m/^unused/) {
581 my $sid = PVE
::Storage
::parse_volume_id
($volid);
590 my $delete_drive = sub {
591 my ($conf, $storecfg, $vmid, $key, $drive, $force) = @_;
593 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
594 my $volid = $drive->{file
};
595 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
596 if ($force || $key =~ m/^unused/) {
597 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
600 PVE
::QemuServer
::add_unused_volume
($conf, $volid, $vmid);
602 delete $conf->{$key};
607 my $vmconfig_delete_option = sub {
608 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force) = @_;
610 return if !defined($conf->{$opt});
612 my $isDisk = PVE
::QemuServer
::valid_drivename
($opt)|| ($opt =~ m/^unused/);
615 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
617 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
618 if (my $sid = &$test_deallocate_drive($storecfg, $vmid, $opt, $drive, $force)) {
619 $rpcenv->check_storage_perm($authuser, $vmid, undef, $sid, [ 'Datastore.Allocate' ]);
623 die "error hot-unplug $opt" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
626 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
627 &$delete_drive($conf, $storecfg, $vmid, $opt, $drive, $force);
629 delete $conf->{$opt};
632 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
635 my $vmconfig_update_disk = sub {
636 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value, $force) = @_;
638 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
640 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { #cdrom
641 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
643 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
648 if (my $old_drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt})) {
650 my $media = $drive->{media
} || 'disk';
651 my $oldmedia = $old_drive->{media
} || 'disk';
652 die "unable to change media type\n" if $media ne $oldmedia;
654 if (!PVE
::QemuServer
::drive_is_cdrom
($old_drive) &&
655 ($drive->{file
} ne $old_drive->{file
})) { # delete old disks
657 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
658 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
663 &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, undef, {$opt => $value});
664 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
666 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
667 $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
669 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # cdrom
671 if (PVE
::QemuServer
::check_running
($vmid)) {
672 if ($drive->{file
} eq 'none') {
673 PVE
::QemuServer
::vm_monitor_command
($vmid, "eject -f drive-$opt", 0);
675 my $path = PVE
::QemuServer
::get_iso_path
($storecfg, $vmid, $drive->{file
});
676 PVE
::QemuServer
::vm_monitor_command
($vmid, "eject -f drive-$opt", 0); #force eject if locked
677 PVE
::QemuServer
::vm_monitor_command
($vmid, "change drive-$opt \"$path\"", 0) if $path;
681 } else { # hotplug new disks
683 die "error hotplug
$opt" if !PVE::QemuServer::vm_deviceplug($storecfg, $conf, $vmid, $opt, $drive);
687 my $vmconfig_update_net = sub {
688 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value) = @_;
691 #if online update, then unplug first
692 die "error hot-unplug
$opt for update
" if !PVE::QemuServer::vm_deviceunplug($vmid, $conf, $opt);
695 $conf->{$opt} = $value;
696 PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
697 $conf = PVE::QemuServer::load_config($vmid); # update/reload
699 my $net = PVE::QemuServer::parse_net($conf->{$opt});
701 die "error hotplug
$opt" if !PVE::QemuServer::vm_deviceplug($storecfg, $conf, $vmid, $opt, $net);
704 my $vm_config_perm_list = [
714 __PACKAGE__->register_method({
716 path => '{vmid}/config',
720 description => "Set virtual machine options
.",
722 check => ['perm', '/vms/{vmid}', $vm_config_perm_list, any => 1],
725 additionalProperties => 0,
726 properties => PVE::QemuServer::json_config_properties(
728 node => get_standard_option('pve-node'),
729 vmid => get_standard_option('pve-vmid'),
730 skiplock => get_standard_option('skiplock'),
732 type => 'string', format => 'pve-configid-list',
733 description => "A list of settings you want to
delete.",
738 description => $opt_force_description,
740 requires => 'delete',
744 description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
750 returns => { type => 'null'},
754 my $rpcenv = PVE::RPCEnvironment::get();
756 my $authuser = $rpcenv->get_user();
758 my $node = extract_param($param, 'node');
760 my $vmid = extract_param($param, 'vmid');
762 my $digest = extract_param($param, 'digest');
764 my @paramarr = (); # used for log message
765 foreach my $key (keys %$param) {
766 push @paramarr, "-$key", $param->{$key};
769 my $skiplock = extract_param($param, 'skiplock');
770 raise_param_exc({ skiplock => "Only root may
use this option
." })
771 if $skiplock && $authuser ne 'root@pam';
773 my $delete_str = extract_param($param, 'delete');
775 my $force = extract_param($param, 'force');
777 die "no options specified
\n" if !$delete_str && !scalar(keys %$param);
779 my $storecfg = PVE::Storage::config();
781 &$resolve_cdrom_alias($param);
783 # now try to verify all parameters
786 foreach my $opt (PVE::Tools::split_list($delete_str)) {
787 $opt = 'ide2' if $opt eq 'cdrom';
788 raise_param_exc({ delete => "you can
't use '-$opt' and " .
789 "-delete $opt' at the same
time" })
790 if defined($param->{$opt});
792 if (!PVE::QemuServer::option_exists($opt)) {
793 raise_param_exc({ delete => "unknown option
'$opt'" });
799 foreach my $opt (keys %$param) {
800 if (PVE::QemuServer::valid_drivename($opt)) {
802 my $drive = PVE::QemuServer::parse_drive($opt, $param->{$opt});
803 PVE::QemuServer::cleanup_drive_path($opt, $storecfg, $drive);
804 $param->{$opt} = PVE::QemuServer::print_drive($vmid, $drive);
805 } elsif ($opt =~ m/^net(\d+)$/) {
807 my $net = PVE::QemuServer::parse_net($param->{$opt});
808 $param->{$opt} = PVE::QemuServer::print_net($net);
812 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
814 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
816 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, undef, $param);
820 my $conf = PVE::QemuServer::load_config($vmid);
822 die "checksum missmatch
(file change by other user?
)\n"
823 if $digest && $digest ne $conf->{digest};
825 PVE::QemuServer::check_lock($conf) if !$skiplock;
827 PVE::Cluster::log_msg('info', $authuser, "update VM
$vmid: " . join (' ', @paramarr));
829 foreach my $opt (@delete) { # delete
830 $conf = PVE::QemuServer::load_config($vmid); # update/reload
831 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
834 foreach my $opt (keys %$param) { # add/change
836 $conf = PVE::QemuServer::load_config($vmid); # update/reload
838 next if $conf->{$opt} && ($param->{$opt} eq $conf->{$opt}); # skip if nothing changed
840 if (PVE::QemuServer::valid_drivename($opt)) {
842 &$vmconfig_update_disk($rpcenv, $authuser, $conf, $storecfg, $vmid,
843 $opt, $param->{$opt}, $force);
845 } elsif ($opt =~ m/^net(\d+)$/) { #nics
847 &$vmconfig_update_net($rpcenv, $authuser, $conf, $storecfg, $vmid,
848 $opt, $param->{$opt});
852 $conf->{$opt} = $param->{$opt};
853 PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
858 PVE::QemuServer::lock_config($vmid, $updatefn);
864 __PACKAGE__->register_method({
865 name => 'destroy_vm',
870 description => "Destroy the vm
(also
delete all used
/owned volumes
).",
872 check => [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
875 additionalProperties => 0,
877 node => get_standard_option('pve-node'),
878 vmid => get_standard_option('pve-vmid'),
879 skiplock => get_standard_option('skiplock'),
888 my $rpcenv = PVE::RPCEnvironment::get();
890 my $authuser = $rpcenv->get_user();
892 my $vmid = $param->{vmid};
894 my $skiplock = $param->{skiplock};
895 raise_param_exc({ skiplock => "Only root may
use this option
." })
896 if $skiplock && $authuser ne 'root@pam';
899 my $conf = PVE::QemuServer::load_config($vmid);
901 my $storecfg = PVE::Storage::config();
906 syslog('info', "destroy VM
$vmid: $upid\n");
908 PVE::QemuServer::vm_destroy($storecfg, $vmid, $skiplock);
911 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
914 __PACKAGE__->register_method({
916 path => '{vmid}/unlink',
920 description => "Unlink
/delete disk images
.",
922 check => [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
925 additionalProperties => 0,
927 node => get_standard_option('pve-node'),
928 vmid => get_standard_option('pve-vmid'),
930 type => 'string', format => 'pve-configid-list',
931 description => "A list of disk IDs you want to
delete.",
935 description => $opt_force_description,
940 returns => { type => 'null'},
944 $param->{delete} = extract_param($param, 'idlist');
946 __PACKAGE__->update_vm($param);
953 __PACKAGE__->register_method({
955 path => '{vmid}/vncproxy',
959 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
961 description => "Creates a TCP VNC proxy connections
.",
963 additionalProperties => 0,
965 node => get_standard_option('pve-node'),
966 vmid => get_standard_option('pve-vmid'),
970 additionalProperties => 0,
972 user => { type => 'string' },
973 ticket => { type => 'string' },
974 cert => { type => 'string' },
975 port => { type => 'integer' },
976 upid => { type => 'string' },
982 my $rpcenv = PVE::RPCEnvironment::get();
984 my $authuser = $rpcenv->get_user();
986 my $vmid = $param->{vmid};
987 my $node = $param->{node};
989 my $authpath = "/vms/$vmid";
991 my $ticket = PVE::AccessControl::assemble_vnc_ticket($authuser, $authpath);
993 $sslcert = PVE::Tools::file_get_contents("/etc/pve
/pve-root-ca
.pem
", 8192)
996 my $port = PVE::Tools::next_vnc_port();
1000 if ($node ne 'localhost' && $node ne PVE::INotify::nodename()) {
1001 $remip = PVE::Cluster::remote_node_ip($node);
1004 # NOTE: kvm VNC traffic is already TLS encrypted,
1005 # so we select the fastest chipher here (or 'none'?)
1006 my $remcmd = $remip ? ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes',
1007 '-c', 'blowfish-cbc', $remip] : [];
1014 syslog('info', "starting vnc proxy
$upid\n");
1016 my $qmcmd = [@$remcmd, "/usr/sbin
/qm
", 'vncproxy', $vmid];
1018 my $qmstr = join(' ', @$qmcmd);
1020 # also redirect stderr (else we get RFB protocol errors)
1021 my $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null
"];
1023 PVE::Tools::run_command($cmd);
1028 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1039 __PACKAGE__->register_method({
1041 path => '{vmid}/status',
1044 description => "Directory
index",
1049 additionalProperties => 0,
1051 node => get_standard_option('pve-node'),
1052 vmid => get_standard_option('pve-vmid'),
1060 subdir => { type => 'string' },
1063 links => [ { rel => 'child', href => "{subdir
}" } ],
1069 my $conf = PVE::QemuServer::load_config($param->{vmid});
1072 { subdir => 'current' },
1073 { subdir => 'start' },
1074 { subdir => 'stop' },
1080 __PACKAGE__->register_method({
1081 name => 'vm_status',
1082 path => '{vmid}/status/current',
1085 protected => 1, # qemu pid files are only readable by root
1086 description => "Get virtual machine status
.",
1088 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1091 additionalProperties => 0,
1093 node => get_standard_option('pve-node'),
1094 vmid => get_standard_option('pve-vmid'),
1097 returns => { type => 'object' },
1102 my $conf = PVE::QemuServer::load_config($param->{vmid});
1104 my $vmstatus = PVE::QemuServer::vmstatus($param->{vmid});
1105 my $status = $vmstatus->{$param->{vmid}};
1107 my $cc = PVE::Cluster::cfs_read_file('cluster.conf');
1108 if (PVE::Cluster::cluster_conf_lookup_pvevm($cc, 0, $param->{vmid}, 1)) {
1117 __PACKAGE__->register_method({
1119 path => '{vmid}/status/start',
1123 description => "Start virtual machine
.",
1125 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1128 additionalProperties => 0,
1130 node => get_standard_option('pve-node'),
1131 vmid => get_standard_option('pve-vmid'),
1132 skiplock => get_standard_option('skiplock'),
1133 stateuri => get_standard_option('pve-qm-stateuri'),
1142 my $rpcenv = PVE::RPCEnvironment::get();
1144 my $authuser = $rpcenv->get_user();
1146 my $node = extract_param($param, 'node');
1148 my $vmid = extract_param($param, 'vmid');
1150 my $stateuri = extract_param($param, 'stateuri');
1151 raise_param_exc({ stateuri => "Only root may
use this option
." })
1152 if $stateuri && $authuser ne 'root@pam';
1154 my $skiplock = extract_param($param, 'skiplock');
1155 raise_param_exc({ skiplock => "Only root may
use this option
." })
1156 if $skiplock && $authuser ne 'root@pam';
1158 my $storecfg = PVE::Storage::config();
1163 syslog('info', "start VM
$vmid: $upid\n");
1165 PVE::QemuServer::vm_start($storecfg, $vmid, $stateuri, $skiplock);
1170 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1173 __PACKAGE__->register_method({
1175 path => '{vmid}/status/stop',
1179 description => "Stop virtual machine
.",
1181 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1184 additionalProperties => 0,
1186 node => get_standard_option('pve-node'),
1187 vmid => get_standard_option('pve-vmid'),
1188 skiplock => get_standard_option('skiplock'),
1190 description => "Wait maximal timeout seconds
.",
1196 description => "Do
not decativate storage volumes
.",
1209 my $rpcenv = PVE::RPCEnvironment::get();
1211 my $authuser = $rpcenv->get_user();
1213 my $node = extract_param($param, 'node');
1215 my $vmid = extract_param($param, 'vmid');
1217 my $skiplock = extract_param($param, 'skiplock');
1218 raise_param_exc({ skiplock => "Only root may
use this option
." })
1219 if $skiplock && $authuser ne 'root@pam';
1221 my $keepActive = extract_param($param, 'keepActive');
1222 raise_param_exc({ keepActive => "Only root may
use this option
." })
1223 if $keepActive && $authuser ne 'root@pam';
1225 my $storecfg = PVE::Storage::config();
1230 syslog('info', "stop VM
$vmid: $upid\n");
1232 PVE::QemuServer::vm_stop($storecfg, $vmid, $skiplock, 0,
1233 $param->{timeout}, 0, 1, $keepActive);
1238 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1241 __PACKAGE__->register_method({
1243 path => '{vmid}/status/reset',
1247 description => "Reset virtual machine
.",
1249 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1252 additionalProperties => 0,
1254 node => get_standard_option('pve-node'),
1255 vmid => get_standard_option('pve-vmid'),
1256 skiplock => get_standard_option('skiplock'),
1265 my $rpcenv = PVE::RPCEnvironment::get();
1267 my $authuser = $rpcenv->get_user();
1269 my $node = extract_param($param, 'node');
1271 my $vmid = extract_param($param, 'vmid');
1273 my $skiplock = extract_param($param, 'skiplock');
1274 raise_param_exc({ skiplock => "Only root may
use this option
." })
1275 if $skiplock && $authuser ne 'root@pam';
1277 die "VM
$vmid not running
\n" if !PVE::QemuServer::check_running($vmid);
1282 PVE::QemuServer::vm_reset($vmid, $skiplock);
1287 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1290 __PACKAGE__->register_method({
1291 name => 'vm_shutdown',
1292 path => '{vmid}/status/shutdown',
1296 description => "Shutdown virtual machine
.",
1298 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1301 additionalProperties => 0,
1303 node => get_standard_option('pve-node'),
1304 vmid => get_standard_option('pve-vmid'),
1305 skiplock => get_standard_option('skiplock'),
1307 description => "Wait maximal timeout seconds
.",
1313 description => "Make sure the VM stops
.",
1319 description => "Do
not decativate storage volumes
.",
1332 my $rpcenv = PVE::RPCEnvironment::get();
1334 my $authuser = $rpcenv->get_user();
1336 my $node = extract_param($param, 'node');
1338 my $vmid = extract_param($param, 'vmid');
1340 my $skiplock = extract_param($param, 'skiplock');
1341 raise_param_exc({ skiplock => "Only root may
use this option
." })
1342 if $skiplock && $authuser ne 'root@pam';
1344 my $keepActive = extract_param($param, 'keepActive');
1345 raise_param_exc({ keepActive => "Only root may
use this option
." })
1346 if $keepActive && $authuser ne 'root@pam';
1348 my $storecfg = PVE::Storage::config();
1353 syslog('info', "shutdown VM
$vmid: $upid\n");
1355 PVE::QemuServer::vm_stop($storecfg, $vmid, $skiplock, 0, $param->{timeout},
1356 1, $param->{forceStop}, $keepActive);
1361 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1364 __PACKAGE__->register_method({
1365 name => 'vm_suspend',
1366 path => '{vmid}/status/suspend',
1370 description => "Suspend virtual machine
.",
1372 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1375 additionalProperties => 0,
1377 node => get_standard_option('pve-node'),
1378 vmid => get_standard_option('pve-vmid'),
1379 skiplock => get_standard_option('skiplock'),
1388 my $rpcenv = PVE::RPCEnvironment::get();
1390 my $authuser = $rpcenv->get_user();
1392 my $node = extract_param($param, 'node');
1394 my $vmid = extract_param($param, 'vmid');
1396 my $skiplock = extract_param($param, 'skiplock');
1397 raise_param_exc({ skiplock => "Only root may
use this option
." })
1398 if $skiplock && $authuser ne 'root@pam';
1400 die "VM
$vmid not running
\n" if !PVE::QemuServer::check_running($vmid);
1405 syslog('info', "suspend VM
$vmid: $upid\n");
1407 PVE::QemuServer::vm_suspend($vmid, $skiplock);
1412 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1415 __PACKAGE__->register_method({
1416 name => 'vm_resume',
1417 path => '{vmid}/status/resume',
1421 description => "Resume virtual machine
.",
1423 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1426 additionalProperties => 0,
1428 node => get_standard_option('pve-node'),
1429 vmid => get_standard_option('pve-vmid'),
1430 skiplock => get_standard_option('skiplock'),
1439 my $rpcenv = PVE::RPCEnvironment::get();
1441 my $authuser = $rpcenv->get_user();
1443 my $node = extract_param($param, 'node');
1445 my $vmid = extract_param($param, 'vmid');
1447 my $skiplock = extract_param($param, 'skiplock');
1448 raise_param_exc({ skiplock => "Only root may
use this option
." })
1449 if $skiplock && $authuser ne 'root@pam';
1451 die "VM
$vmid not running
\n" if !PVE::QemuServer::check_running($vmid);
1456 syslog('info', "resume VM
$vmid: $upid\n");
1458 PVE::QemuServer::vm_resume($vmid, $skiplock);
1463 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1466 __PACKAGE__->register_method({
1467 name => 'vm_sendkey',
1468 path => '{vmid}/sendkey',
1472 description => "Send key event to virtual machine
.",
1474 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1477 additionalProperties => 0,
1479 node => get_standard_option('pve-node'),
1480 vmid => get_standard_option('pve-vmid'),
1481 skiplock => get_standard_option('skiplock'),
1483 description => "The key
(qemu monitor encoding
).",
1488 returns => { type => 'null'},
1492 my $rpcenv = PVE::RPCEnvironment::get();
1494 my $authuser = $rpcenv->get_user();
1496 my $node = extract_param($param, 'node');
1498 my $vmid = extract_param($param, 'vmid');
1500 my $skiplock = extract_param($param, 'skiplock');
1501 raise_param_exc({ skiplock => "Only root may
use this option
." })
1502 if $skiplock && $authuser ne 'root@pam';
1504 PVE::QemuServer::vm_sendkey($vmid, $skiplock, $param->{key});
1509 __PACKAGE__->register_method({
1510 name => 'migrate_vm',
1511 path => '{vmid}/migrate',
1515 description => "Migrate virtual machine
. Creates a new migration task
.",
1517 check => ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
1520 additionalProperties => 0,
1522 node => get_standard_option('pve-node'),
1523 vmid => get_standard_option('pve-vmid'),
1524 target => get_standard_option('pve-node', { description => "Target node
." }),
1527 description => "Use online
/live migration
.",
1532 description => "Allow to migrate VMs which
use local devices
. Only root may
use this option
.",
1539 description => "the task ID
.",
1544 my $rpcenv = PVE::RPCEnvironment::get();
1546 my $authuser = $rpcenv->get_user();
1548 my $target = extract_param($param, 'target');
1550 my $localnode = PVE::INotify::nodename();
1551 raise_param_exc({ target => "target
is local node
."}) if $target eq $localnode;
1553 PVE::Cluster::check_cfs_quorum();
1555 PVE::Cluster::check_node_exists($target);
1557 my $targetip = PVE::Cluster::remote_node_ip($target);
1559 my $vmid = extract_param($param, 'vmid');
1561 raise_param_exc({ force => "Only root may
use this option
." })
1562 if $param->{force} && $authuser ne 'root@pam';
1565 my $conf = PVE::QemuServer::load_config($vmid);
1567 # try to detect errors early
1569 PVE::QemuServer::check_lock($conf);
1571 if (PVE::QemuServer::check_running($vmid)) {
1572 die "cant migrate running VM without
--online
\n"
1573 if !$param->{online};
1579 PVE::QemuMigrate->migrate($target, $targetip, $vmid, $param);
1582 my $upid = $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
1587 __PACKAGE__->register_method({
1589 path => '{vmid}/monitor',
1593 description => "Execute Qemu monitor commands
.",
1595 check => ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
1598 additionalProperties => 0,
1600 node => get_standard_option('pve-node'),
1601 vmid => get_standard_option('pve-vmid'),
1604 description => "The monitor command
.",
1608 returns => { type => 'string'},
1612 my $vmid = $param->{vmid};
1614 my $conf = PVE::QemuServer::load_config ($vmid); # check if VM exists
1618 $res = PVE::QemuServer::vm_monitor_command($vmid, $param->{command});
1620 $res = "ERROR
: $@" if $@;