1 package PVE
::API2
::Qemu
;
7 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
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
};
37 my $check_storage_access = sub {
38 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
40 PVE
::QemuServer
::foreach_drive
($settings, sub {
41 my ($ds, $drive) = @_;
43 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
45 my $volid = $drive->{file
};
47 if (!$volid || $volid eq 'none') {
49 } elsif ($isCDROM && ($volid eq 'cdrom')) {
50 $rpcenv->check($authuser, "/", ['Sys.Console']);
51 } elsif (!$isCDROM && ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/)) {
52 my ($storeid, $size) = ($2 || $default_storage, $3);
53 die "no storage ID specified (and no default storage)\n" if !$storeid;
54 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
56 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
61 # Note: $pool is only needed when creating a VM, because pool permissions
62 # are automatically inherited if VM already exists inside a pool.
63 my $create_disks = sub {
64 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
69 PVE
::QemuServer
::foreach_drive
($settings, sub {
72 my $volid = $disk->{file
};
74 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
75 $res->{$ds} = $settings->{$ds};
76 } elsif ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/) {
77 my ($storeid, $size) = ($2 || $default_storage, $3);
78 die "no storage ID specified (and no default storage)\n" if !$storeid;
79 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
80 my $fmt = $disk->{format
} || $defformat;
81 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
82 $fmt, undef, $size*1024*1024);
83 $disk->{file
} = $volid;
84 $disk->{size
} = $size*1024*1024*1024;
85 push @$vollist, $volid;
86 delete $disk->{format
}; # no longer needed
87 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
90 my $path = $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
92 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
94 my $foundvolid = undef;
97 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]);
98 my $dl = PVE
::Storage
::vdisk_list
($storecfg, $storeid, undef);
100 PVE
::Storage
::foreach_volid
($dl, sub {
102 if($volumeid eq $volid) {
109 die "image '$path' does not exists\n" if (!(-f
$path || -b
$path || $foundvolid));
111 my ($size) = PVE
::Storage
::volume_size_info
($storecfg, $volid, 1);
112 $disk->{size
} = $size;
113 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
117 # free allocated images on error
119 syslog
('err', "VM $vmid creating disks failed");
120 foreach my $volid (@$vollist) {
121 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
127 # modify vm config if everything went well
128 foreach my $ds (keys %$res) {
129 $conf->{$ds} = $res->{$ds};
135 my $check_vm_modify_config_perm = sub {
136 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
138 return 1 if $authuser eq 'root@pam';
140 foreach my $opt (@$key_list) {
141 # disk checks need to be done somewhere else
142 next if PVE
::QemuServer
::valid_drivename
($opt);
144 if ($opt eq 'sockets' || $opt eq 'cores' ||
145 $opt eq 'cpu' || $opt eq 'smp' ||
146 $opt eq 'cpulimit' || $opt eq 'cpuunits') {
147 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
148 } elsif ($opt eq 'boot' || $opt eq 'bootdisk') {
149 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
150 } elsif ($opt eq 'memory' || $opt eq 'balloon') {
151 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
152 } elsif ($opt eq 'args' || $opt eq 'lock') {
153 die "only root can set '$opt' config\n";
154 } elsif ($opt eq 'cpu' || $opt eq 'kvm' || $opt eq 'acpi' ||
155 $opt eq 'vga' || $opt eq 'watchdog' || $opt eq 'tablet') {
156 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
157 } elsif ($opt =~ m/^net\d+$/) {
158 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
160 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
167 __PACKAGE__-
>register_method({
171 description
=> "Virtual machine index (per node).",
173 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
177 protected
=> 1, # qemu pid files are only readable by root
179 additionalProperties
=> 0,
181 node
=> get_standard_option
('pve-node'),
190 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
195 my $rpcenv = PVE
::RPCEnvironment
::get
();
196 my $authuser = $rpcenv->get_user();
198 my $vmstatus = PVE
::QemuServer
::vmstatus
();
201 foreach my $vmid (keys %$vmstatus) {
202 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
204 my $data = $vmstatus->{$vmid};
205 $data->{vmid
} = $vmid;
212 __PACKAGE__-
>register_method({
216 description
=> "Create or restore a virtual machine.",
218 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.",
220 [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
221 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
227 additionalProperties
=> 0,
228 properties
=> PVE
::QemuServer
::json_config_properties
(
230 node
=> get_standard_option
('pve-node'),
231 vmid
=> get_standard_option
('pve-vmid'),
233 description
=> "The backup file.",
238 storage
=> get_standard_option
('pve-storage-id', {
239 description
=> "Default storage.",
245 description
=> "Allow to overwrite existing VM.",
246 requires
=> 'archive',
251 description
=> "Assign a unique random ethernet address.",
252 requires
=> 'archive',
256 type
=> 'string', format
=> 'pve-poolid',
257 description
=> "Add the VM to the specified pool.",
267 my $rpcenv = PVE
::RPCEnvironment
::get
();
269 my $authuser = $rpcenv->get_user();
271 my $node = extract_param
($param, 'node');
273 my $vmid = extract_param
($param, 'vmid');
275 my $archive = extract_param
($param, 'archive');
277 my $storage = extract_param
($param, 'storage');
279 my $force = extract_param
($param, 'force');
281 my $unique = extract_param
($param, 'unique');
283 my $pool = extract_param
($param, 'pool');
285 my $filename = PVE
::QemuServer
::config_file
($vmid);
287 my $storecfg = PVE
::Storage
::config
();
289 PVE
::Cluster
::check_cfs_quorum
();
291 if (defined($pool)) {
292 $rpcenv->check_pool_exist($pool);
295 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
296 if defined($storage);
299 &$resolve_cdrom_alias($param);
301 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
303 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
305 foreach my $opt (keys %$param) {
306 if (PVE
::QemuServer
::valid_drivename
($opt)) {
307 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
308 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
310 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
311 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
315 PVE
::QemuServer
::add_random_macs
($param);
317 my $keystr = join(' ', keys %$param);
318 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
320 if ($archive eq '-') {
321 die "pipe requires cli environment\n"
322 if $rpcenv->{type
} ne 'cli';
324 my $path = $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $archive);
326 PVE
::Storage
::activate_volumes
($storecfg, [ $archive ])
327 if PVE
::Storage
::parse_volume_id
($archive, 1);
329 die "can't find archive file '$archive'\n" if !($path && -f
$path);
334 my $addVMtoPoolFn = sub {
335 my $usercfg = cfs_read_file
("user.cfg");
336 if (my $data = $usercfg->{pools
}->{$pool}) {
337 $data->{vms
}->{$vmid} = 1;
338 $usercfg->{vms
}->{$vmid} = $pool;
339 cfs_write_file
("user.cfg", $usercfg);
343 my $restorefn = sub {
346 die "unable to restore vm $vmid: config file already exists\n"
349 die "unable to restore vm $vmid: vm is running\n"
350 if PVE
::QemuServer
::check_running
($vmid);
352 # destroy existing data - keep empty config
353 PVE
::QemuServer
::destroy_vm
($storecfg, $vmid, 1);
357 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
360 unique
=> $unique });
362 PVE
::AccessControl
::lock_user_config
($addVMtoPoolFn, "can't add VM to pool") if $pool;
365 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
371 die "unable to create vm $vmid: config file already exists\n"
382 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
384 # try to be smart about bootdisk
385 my @disks = PVE
::QemuServer
::disknames
();
387 foreach my $ds (reverse @disks) {
388 next if !$conf->{$ds};
389 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
390 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
394 if (!$conf->{bootdisk
} && $firstdisk) {
395 $conf->{bootdisk
} = $firstdisk;
398 PVE
::QemuServer
::update_config_nolock
($vmid, $conf);
404 foreach my $volid (@$vollist) {
405 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
408 die "create failed - $err";
411 PVE
::AccessControl
::lock_user_config
($addVMtoPoolFn, "can't add VM to pool") if $pool;
414 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
417 return PVE
::QemuServer
::lock_config_full
($vmid, 1, $archive ?
$restorefn : $createfn);
420 __PACKAGE__-
>register_method({
425 description
=> "Directory index",
430 additionalProperties
=> 0,
432 node
=> get_standard_option
('pve-node'),
433 vmid
=> get_standard_option
('pve-vmid'),
441 subdir
=> { type
=> 'string' },
444 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
450 { subdir
=> 'config' },
451 { subdir
=> 'status' },
452 { subdir
=> 'unlink' },
453 { subdir
=> 'vncproxy' },
454 { subdir
=> 'migrate' },
455 { subdir
=> 'resize' },
457 { subdir
=> 'rrddata' },
458 { subdir
=> 'monitor' },
464 __PACKAGE__-
>register_method({
466 path
=> '{vmid}/rrd',
468 protected
=> 1, # fixme: can we avoid that?
470 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
472 description
=> "Read VM RRD statistics (returns PNG)",
474 additionalProperties
=> 0,
476 node
=> get_standard_option
('pve-node'),
477 vmid
=> get_standard_option
('pve-vmid'),
479 description
=> "Specify the time frame you are interested in.",
481 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
484 description
=> "The list of datasources you want to display.",
485 type
=> 'string', format
=> 'pve-configid-list',
488 description
=> "The RRD consolidation function",
490 enum
=> [ 'AVERAGE', 'MAX' ],
498 filename
=> { type
=> 'string' },
504 return PVE
::Cluster
::create_rrd_graph
(
505 "pve2-vm/$param->{vmid}", $param->{timeframe
},
506 $param->{ds
}, $param->{cf
});
510 __PACKAGE__-
>register_method({
512 path
=> '{vmid}/rrddata',
514 protected
=> 1, # fixme: can we avoid that?
516 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
518 description
=> "Read VM RRD statistics",
520 additionalProperties
=> 0,
522 node
=> get_standard_option
('pve-node'),
523 vmid
=> get_standard_option
('pve-vmid'),
525 description
=> "Specify the time frame you are interested in.",
527 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
530 description
=> "The RRD consolidation function",
532 enum
=> [ 'AVERAGE', 'MAX' ],
547 return PVE
::Cluster
::create_rrd_data
(
548 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
552 __PACKAGE__-
>register_method({
554 path
=> '{vmid}/config',
557 description
=> "Get virtual machine configuration.",
559 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
562 additionalProperties
=> 0,
564 node
=> get_standard_option
('pve-node'),
565 vmid
=> get_standard_option
('pve-vmid'),
573 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
580 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
585 my $vm_is_volid_owner = sub {
586 my ($storecfg, $vmid, $volid) =@_;
588 if ($volid !~ m
|^/|) {
590 eval { ($path, $owner) = PVE
::Storage
::path
($storecfg, $volid); };
591 if ($owner && ($owner == $vmid)) {
599 my $test_deallocate_drive = sub {
600 my ($storecfg, $vmid, $key, $drive, $force) = @_;
602 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
603 my $volid = $drive->{file
};
604 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
605 if ($force || $key =~ m/^unused/) {
606 my $sid = PVE
::Storage
::parse_volume_id
($volid);
615 my $delete_drive = sub {
616 my ($conf, $storecfg, $vmid, $key, $drive, $force) = @_;
618 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
619 my $volid = $drive->{file
};
620 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
621 if ($force || $key =~ m/^unused/) {
622 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
625 PVE
::QemuServer
::add_unused_volume
($conf, $volid, $vmid);
630 delete $conf->{$key};
633 my $vmconfig_delete_option = sub {
634 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force) = @_;
636 return if !defined($conf->{$opt});
638 my $isDisk = PVE
::QemuServer
::valid_drivename
($opt)|| ($opt =~ m/^unused/);
641 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
643 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
644 if (my $sid = &$test_deallocate_drive($storecfg, $vmid, $opt, $drive, $force)) {
645 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.Allocate']);
649 die "error hot-unplug $opt" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
652 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
653 &$delete_drive($conf, $storecfg, $vmid, $opt, $drive, $force);
655 delete $conf->{$opt};
658 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
661 my $safe_int_ne = sub {
664 return 0 if !defined($a) && !defined($b);
665 return 1 if !defined($a);
666 return 1 if !defined($b);
671 my $vmconfig_update_disk = sub {
672 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value, $force) = @_;
674 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
676 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { #cdrom
677 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
679 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
684 if (my $old_drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt})) {
686 my $media = $drive->{media
} || 'disk';
687 my $oldmedia = $old_drive->{media
} || 'disk';
688 die "unable to change media type\n" if $media ne $oldmedia;
690 if (!PVE
::QemuServer
::drive_is_cdrom
($old_drive) &&
691 ($drive->{file
} ne $old_drive->{file
})) { # delete old disks
693 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
694 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
697 if(&$safe_int_ne($drive->{bps
}, $old_drive->{bps
}) ||
698 &$safe_int_ne($drive->{bps_rd
}, $old_drive->{bps_rd
}) ||
699 &$safe_int_ne($drive->{bps_wr
}, $old_drive->{bps_wr
}) ||
700 &$safe_int_ne($drive->{iops
}, $old_drive->{iops
}) ||
701 &$safe_int_ne($drive->{iops_rd
}, $old_drive->{iops_rd
}) ||
702 &$safe_int_ne($drive->{iops_wr
}, $old_drive->{iops_wr
})) {
703 PVE
::QemuServer
::qemu_block_set_io_throttle
($vmid,"drive-$opt",$drive->{bps
}, $drive->{bps_rd
}, $drive->{bps_wr
}, $drive->{iops
}, $drive->{iops_rd
}, $drive->{iops_wr
}) if !PVE
::QemuServer
::drive_is_cdrom
($drive);
708 &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, undef, {$opt => $value});
709 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
711 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
712 $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
714 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # cdrom
716 if (PVE
::QemuServer
::check_running
($vmid)) {
717 if ($drive->{file
} eq 'none') {
718 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt");
720 my $path = PVE
::QemuServer
::get_iso_path
($storecfg, $vmid, $drive->{file
});
721 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt"); #force eject if locked
722 PVE
::QemuServer
::vm_mon_cmd
($vmid, "change",device
=> "drive-$opt",target
=> "$path") if $path;
726 } else { # hotplug new disks
728 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $drive);
732 my $vmconfig_update_net = sub {
733 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value) = @_;
736 #if online update, then unplug first
737 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
740 $conf->{$opt} = $value;
741 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
742 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
744 my $net = PVE
::QemuServer
::parse_net
($conf->{$opt});
746 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $net);
749 my $vm_config_perm_list = [
759 __PACKAGE__-
>register_method({
761 path
=> '{vmid}/config',
765 description
=> "Set virtual machine options.",
767 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
770 additionalProperties
=> 0,
771 properties
=> PVE
::QemuServer
::json_config_properties
(
773 node
=> get_standard_option
('pve-node'),
774 vmid
=> get_standard_option
('pve-vmid'),
775 skiplock
=> get_standard_option
('skiplock'),
777 type
=> 'string', format
=> 'pve-configid-list',
778 description
=> "A list of settings you want to delete.",
783 description
=> $opt_force_description,
785 requires
=> 'delete',
789 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
795 returns
=> { type
=> 'null'},
799 my $rpcenv = PVE
::RPCEnvironment
::get
();
801 my $authuser = $rpcenv->get_user();
803 my $node = extract_param
($param, 'node');
805 my $vmid = extract_param
($param, 'vmid');
807 my $digest = extract_param
($param, 'digest');
809 my @paramarr = (); # used for log message
810 foreach my $key (keys %$param) {
811 push @paramarr, "-$key", $param->{$key};
814 my $skiplock = extract_param
($param, 'skiplock');
815 raise_param_exc
({ skiplock
=> "Only root may use this option." })
816 if $skiplock && $authuser ne 'root@pam';
818 my $delete_str = extract_param
($param, 'delete');
820 my $force = extract_param
($param, 'force');
822 die "no options specified\n" if !$delete_str && !scalar(keys %$param);
824 my $storecfg = PVE
::Storage
::config
();
826 &$resolve_cdrom_alias($param);
828 # now try to verify all parameters
831 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
832 $opt = 'ide2' if $opt eq 'cdrom';
833 raise_param_exc
({ delete => "you can't use '-$opt' and " .
834 "-delete $opt' at the same time" })
835 if defined($param->{$opt});
837 if (!PVE
::QemuServer
::option_exists
($opt)) {
838 raise_param_exc
({ delete => "unknown option '$opt'" });
844 foreach my $opt (keys %$param) {
845 if (PVE
::QemuServer
::valid_drivename
($opt)) {
847 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
848 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
849 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
850 } elsif ($opt =~ m/^net(\d+)$/) {
852 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
853 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
857 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
859 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
861 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
865 my $conf = PVE
::QemuServer
::load_config
($vmid);
867 die "checksum missmatch (file change by other user?)\n"
868 if $digest && $digest ne $conf->{digest
};
870 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
872 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
874 foreach my $opt (@delete) { # delete
875 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
876 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
879 foreach my $opt (keys %$param) { # add/change
881 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
883 next if $conf->{$opt} && ($param->{$opt} eq $conf->{$opt}); # skip if nothing changed
885 if (PVE
::QemuServer
::valid_drivename
($opt)) {
887 &$vmconfig_update_disk($rpcenv, $authuser, $conf, $storecfg, $vmid,
888 $opt, $param->{$opt}, $force);
890 } elsif ($opt =~ m/^net(\d+)$/) { #nics
892 &$vmconfig_update_net($rpcenv, $authuser, $conf, $storecfg, $vmid,
893 $opt, $param->{$opt});
897 $conf->{$opt} = $param->{$opt};
898 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
903 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
909 __PACKAGE__-
>register_method({
910 name
=> 'destroy_vm',
915 description
=> "Destroy the vm (also delete all used/owned volumes).",
917 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
920 additionalProperties
=> 0,
922 node
=> get_standard_option
('pve-node'),
923 vmid
=> get_standard_option
('pve-vmid'),
924 skiplock
=> get_standard_option
('skiplock'),
933 my $rpcenv = PVE
::RPCEnvironment
::get
();
935 my $authuser = $rpcenv->get_user();
937 my $vmid = $param->{vmid
};
939 my $skiplock = $param->{skiplock
};
940 raise_param_exc
({ skiplock
=> "Only root may use this option." })
941 if $skiplock && $authuser ne 'root@pam';
944 my $conf = PVE
::QemuServer
::load_config
($vmid);
946 my $storecfg = PVE
::Storage
::config
();
948 my $delVMfromPoolFn = sub {
949 my $usercfg = cfs_read_file
("user.cfg");
950 if (my $pool = $usercfg->{vms
}->{$vmid}) {
951 if (my $data = $usercfg->{pools
}->{$pool}) {
952 delete $data->{vms
}->{$vmid};
953 delete $usercfg->{vms
}->{$vmid};
954 cfs_write_file
("user.cfg", $usercfg);
962 syslog
('info', "destroy VM $vmid: $upid\n");
964 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
966 PVE
::AccessControl
::lock_user_config
($delVMfromPoolFn, "pool cleanup failed");
969 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
972 __PACKAGE__-
>register_method({
974 path
=> '{vmid}/unlink',
978 description
=> "Unlink/delete disk images.",
980 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
983 additionalProperties
=> 0,
985 node
=> get_standard_option
('pve-node'),
986 vmid
=> get_standard_option
('pve-vmid'),
988 type
=> 'string', format
=> 'pve-configid-list',
989 description
=> "A list of disk IDs you want to delete.",
993 description
=> $opt_force_description,
998 returns
=> { type
=> 'null'},
1002 $param->{delete} = extract_param
($param, 'idlist');
1004 __PACKAGE__-
>update_vm($param);
1011 __PACKAGE__-
>register_method({
1013 path
=> '{vmid}/vncproxy',
1017 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1019 description
=> "Creates a TCP VNC proxy connections.",
1021 additionalProperties
=> 0,
1023 node
=> get_standard_option
('pve-node'),
1024 vmid
=> get_standard_option
('pve-vmid'),
1028 additionalProperties
=> 0,
1030 user
=> { type
=> 'string' },
1031 ticket
=> { type
=> 'string' },
1032 cert
=> { type
=> 'string' },
1033 port
=> { type
=> 'integer' },
1034 upid
=> { type
=> 'string' },
1040 my $rpcenv = PVE
::RPCEnvironment
::get
();
1042 my $authuser = $rpcenv->get_user();
1044 my $vmid = $param->{vmid
};
1045 my $node = $param->{node
};
1047 my $authpath = "/vms/$vmid";
1049 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1051 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1054 my $port = PVE
::Tools
::next_vnc_port
();
1058 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1059 $remip = PVE
::Cluster
::remote_node_ip
($node);
1062 # NOTE: kvm VNC traffic is already TLS encrypted,
1063 # so we select the fastest chipher here (or 'none'?)
1064 my $remcmd = $remip ?
['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes',
1065 '-c', 'blowfish-cbc', $remip] : [];
1072 syslog
('info', "starting vnc proxy $upid\n");
1074 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1076 my $qmstr = join(' ', @$qmcmd);
1078 # also redirect stderr (else we get RFB protocol errors)
1079 my $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1081 PVE
::Tools
::run_command
($cmd);
1086 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1097 __PACKAGE__-
>register_method({
1099 path
=> '{vmid}/status',
1102 description
=> "Directory index",
1107 additionalProperties
=> 0,
1109 node
=> get_standard_option
('pve-node'),
1110 vmid
=> get_standard_option
('pve-vmid'),
1118 subdir
=> { type
=> 'string' },
1121 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1127 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1130 { subdir
=> 'current' },
1131 { subdir
=> 'start' },
1132 { subdir
=> 'stop' },
1138 my $vm_is_ha_managed = sub {
1141 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
1142 if (PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1)) {
1148 __PACKAGE__-
>register_method({
1149 name
=> 'vm_status',
1150 path
=> '{vmid}/status/current',
1153 protected
=> 1, # qemu pid files are only readable by root
1154 description
=> "Get virtual machine status.",
1156 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1159 additionalProperties
=> 0,
1161 node
=> get_standard_option
('pve-node'),
1162 vmid
=> get_standard_option
('pve-vmid'),
1165 returns
=> { type
=> 'object' },
1170 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1172 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1173 my $status = $vmstatus->{$param->{vmid
}};
1175 $status->{ha
} = &$vm_is_ha_managed($param->{vmid
});
1180 __PACKAGE__-
>register_method({
1182 path
=> '{vmid}/status/start',
1186 description
=> "Start virtual machine.",
1188 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1191 additionalProperties
=> 0,
1193 node
=> get_standard_option
('pve-node'),
1194 vmid
=> get_standard_option
('pve-vmid'),
1195 skiplock
=> get_standard_option
('skiplock'),
1196 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1205 my $rpcenv = PVE
::RPCEnvironment
::get
();
1207 my $authuser = $rpcenv->get_user();
1209 my $node = extract_param
($param, 'node');
1211 my $vmid = extract_param
($param, 'vmid');
1213 my $stateuri = extract_param
($param, 'stateuri');
1214 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1215 if $stateuri && $authuser ne 'root@pam';
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 $storecfg = PVE
::Storage
::config
();
1223 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1224 $rpcenv->{type
} ne 'ha') {
1229 my $service = "pvevm:$vmid";
1231 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1233 print "Executing HA start for VM $vmid\n";
1235 PVE
::Tools
::run_command
($cmd);
1240 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1247 syslog
('info', "start VM $vmid: $upid\n");
1249 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock);
1254 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1258 __PACKAGE__-
>register_method({
1260 path
=> '{vmid}/status/stop',
1264 description
=> "Stop virtual machine.",
1266 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1269 additionalProperties
=> 0,
1271 node
=> get_standard_option
('pve-node'),
1272 vmid
=> get_standard_option
('pve-vmid'),
1273 skiplock
=> get_standard_option
('skiplock'),
1275 description
=> "Wait maximal timeout seconds.",
1281 description
=> "Do not decativate storage volumes.",
1294 my $rpcenv = PVE
::RPCEnvironment
::get
();
1296 my $authuser = $rpcenv->get_user();
1298 my $node = extract_param
($param, 'node');
1300 my $vmid = extract_param
($param, 'vmid');
1302 my $skiplock = extract_param
($param, 'skiplock');
1303 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1304 if $skiplock && $authuser ne 'root@pam';
1306 my $keepActive = extract_param
($param, 'keepActive');
1307 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1308 if $keepActive && $authuser ne 'root@pam';
1310 my $storecfg = PVE
::Storage
::config
();
1312 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1317 my $service = "pvevm:$vmid";
1319 my $cmd = ['clusvcadm', '-d', $service];
1321 print "Executing HA stop for VM $vmid\n";
1323 PVE
::Tools
::run_command
($cmd);
1328 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1334 syslog
('info', "stop VM $vmid: $upid\n");
1336 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1337 $param->{timeout
}, 0, 1, $keepActive);
1342 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1346 __PACKAGE__-
>register_method({
1348 path
=> '{vmid}/status/reset',
1352 description
=> "Reset virtual machine.",
1354 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1357 additionalProperties
=> 0,
1359 node
=> get_standard_option
('pve-node'),
1360 vmid
=> get_standard_option
('pve-vmid'),
1361 skiplock
=> get_standard_option
('skiplock'),
1370 my $rpcenv = PVE
::RPCEnvironment
::get
();
1372 my $authuser = $rpcenv->get_user();
1374 my $node = extract_param
($param, 'node');
1376 my $vmid = extract_param
($param, 'vmid');
1378 my $skiplock = extract_param
($param, 'skiplock');
1379 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1380 if $skiplock && $authuser ne 'root@pam';
1382 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1387 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1392 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1395 __PACKAGE__-
>register_method({
1396 name
=> 'vm_shutdown',
1397 path
=> '{vmid}/status/shutdown',
1401 description
=> "Shutdown virtual machine.",
1403 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1406 additionalProperties
=> 0,
1408 node
=> get_standard_option
('pve-node'),
1409 vmid
=> get_standard_option
('pve-vmid'),
1410 skiplock
=> get_standard_option
('skiplock'),
1412 description
=> "Wait maximal timeout seconds.",
1418 description
=> "Make sure the VM stops.",
1424 description
=> "Do not decativate storage volumes.",
1437 my $rpcenv = PVE
::RPCEnvironment
::get
();
1439 my $authuser = $rpcenv->get_user();
1441 my $node = extract_param
($param, 'node');
1443 my $vmid = extract_param
($param, 'vmid');
1445 my $skiplock = extract_param
($param, 'skiplock');
1446 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1447 if $skiplock && $authuser ne 'root@pam';
1449 my $keepActive = extract_param
($param, 'keepActive');
1450 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1451 if $keepActive && $authuser ne 'root@pam';
1453 my $storecfg = PVE
::Storage
::config
();
1458 syslog
('info', "shutdown VM $vmid: $upid\n");
1460 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1461 1, $param->{forceStop
}, $keepActive);
1466 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1469 __PACKAGE__-
>register_method({
1470 name
=> 'vm_suspend',
1471 path
=> '{vmid}/status/suspend',
1475 description
=> "Suspend virtual machine.",
1477 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1480 additionalProperties
=> 0,
1482 node
=> get_standard_option
('pve-node'),
1483 vmid
=> get_standard_option
('pve-vmid'),
1484 skiplock
=> get_standard_option
('skiplock'),
1493 my $rpcenv = PVE
::RPCEnvironment
::get
();
1495 my $authuser = $rpcenv->get_user();
1497 my $node = extract_param
($param, 'node');
1499 my $vmid = extract_param
($param, 'vmid');
1501 my $skiplock = extract_param
($param, 'skiplock');
1502 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1503 if $skiplock && $authuser ne 'root@pam';
1505 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1510 syslog
('info', "suspend VM $vmid: $upid\n");
1512 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1517 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1520 __PACKAGE__-
>register_method({
1521 name
=> 'vm_resume',
1522 path
=> '{vmid}/status/resume',
1526 description
=> "Resume virtual machine.",
1528 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1531 additionalProperties
=> 0,
1533 node
=> get_standard_option
('pve-node'),
1534 vmid
=> get_standard_option
('pve-vmid'),
1535 skiplock
=> get_standard_option
('skiplock'),
1544 my $rpcenv = PVE
::RPCEnvironment
::get
();
1546 my $authuser = $rpcenv->get_user();
1548 my $node = extract_param
($param, 'node');
1550 my $vmid = extract_param
($param, 'vmid');
1552 my $skiplock = extract_param
($param, 'skiplock');
1553 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1554 if $skiplock && $authuser ne 'root@pam';
1556 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1561 syslog
('info', "resume VM $vmid: $upid\n");
1563 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1568 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1571 __PACKAGE__-
>register_method({
1572 name
=> 'vm_sendkey',
1573 path
=> '{vmid}/sendkey',
1577 description
=> "Send key event to virtual machine.",
1579 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1582 additionalProperties
=> 0,
1584 node
=> get_standard_option
('pve-node'),
1585 vmid
=> get_standard_option
('pve-vmid'),
1586 skiplock
=> get_standard_option
('skiplock'),
1588 description
=> "The key (qemu monitor encoding).",
1593 returns
=> { type
=> 'null'},
1597 my $rpcenv = PVE
::RPCEnvironment
::get
();
1599 my $authuser = $rpcenv->get_user();
1601 my $node = extract_param
($param, 'node');
1603 my $vmid = extract_param
($param, 'vmid');
1605 my $skiplock = extract_param
($param, 'skiplock');
1606 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1607 if $skiplock && $authuser ne 'root@pam';
1609 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1614 __PACKAGE__-
>register_method({
1615 name
=> 'migrate_vm',
1616 path
=> '{vmid}/migrate',
1620 description
=> "Migrate virtual machine. Creates a new migration task.",
1622 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
1625 additionalProperties
=> 0,
1627 node
=> get_standard_option
('pve-node'),
1628 vmid
=> get_standard_option
('pve-vmid'),
1629 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
1632 description
=> "Use online/live migration.",
1637 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
1644 description
=> "the task ID.",
1649 my $rpcenv = PVE
::RPCEnvironment
::get
();
1651 my $authuser = $rpcenv->get_user();
1653 my $target = extract_param
($param, 'target');
1655 my $localnode = PVE
::INotify
::nodename
();
1656 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
1658 PVE
::Cluster
::check_cfs_quorum
();
1660 PVE
::Cluster
::check_node_exists
($target);
1662 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
1664 my $vmid = extract_param
($param, 'vmid');
1666 raise_param_exc
({ force
=> "Only root may use this option." })
1667 if $param->{force
} && $authuser ne 'root@pam';
1670 my $conf = PVE
::QemuServer
::load_config
($vmid);
1672 # try to detect errors early
1674 PVE
::QemuServer
::check_lock
($conf);
1676 if (PVE
::QemuServer
::check_running
($vmid)) {
1677 die "cant migrate running VM without --online\n"
1678 if !$param->{online
};
1681 my $storecfg = PVE
::Storage
::config
();
1682 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
1684 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1689 my $service = "pvevm:$vmid";
1691 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
1693 print "Executing HA migrate for VM $vmid to node $target\n";
1695 PVE
::Tools
::run_command
($cmd);
1700 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
1707 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
1710 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
1715 __PACKAGE__-
>register_method({
1717 path
=> '{vmid}/monitor',
1721 description
=> "Execute Qemu monitor commands.",
1723 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
1726 additionalProperties
=> 0,
1728 node
=> get_standard_option
('pve-node'),
1729 vmid
=> get_standard_option
('pve-vmid'),
1732 description
=> "The monitor command.",
1736 returns
=> { type
=> 'string'},
1740 my $vmid = $param->{vmid
};
1742 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
1746 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
1748 $res = "ERROR: $@" if $@;
1753 __PACKAGE__-
>register_method({
1754 name
=> 'resize_vm',
1755 path
=> '{vmid}/resize',
1759 description
=> "Extend volume size.",
1761 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
1764 additionalProperties
=> 0,
1766 node
=> get_standard_option
('pve-node'),
1767 vmid
=> get_standard_option
('pve-vmid'),
1768 skiplock
=> get_standard_option
('skiplock'),
1771 description
=> "The disk you want to resize.",
1772 enum
=> [PVE
::QemuServer
::disknames
()],
1776 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
1777 description
=> "The new size. With the '+' sign the value is added to the actual size of the volume and without it, the value is taken as an absolute one. Shrinking disk size is not supported.",
1781 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1787 returns
=> { type
=> 'null'},
1791 my $rpcenv = PVE
::RPCEnvironment
::get
();
1793 my $authuser = $rpcenv->get_user();
1795 my $node = extract_param
($param, 'node');
1797 my $vmid = extract_param
($param, 'vmid');
1799 my $digest = extract_param
($param, 'digest');
1801 my $disk = extract_param
($param, 'disk');
1803 my $sizestr = extract_param
($param, 'size');
1805 my $skiplock = extract_param
($param, 'skiplock');
1806 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1807 if $skiplock && $authuser ne 'root@pam';
1809 my $storecfg = PVE
::Storage
::config
();
1811 my $updatefn = sub {
1813 my $conf = PVE
::QemuServer
::load_config
($vmid);
1815 die "checksum missmatch (file change by other user?)\n"
1816 if $digest && $digest ne $conf->{digest
};
1817 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
1819 die "disk '$disk' does not exist\n" if !$conf->{$disk};
1821 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
1823 my $volid = $drive->{file
};
1825 die "disk '$disk' has no associated volume\n" if !$volid;
1827 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
1829 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1831 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1833 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
1835 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
1836 my ($ext, $newsize, $unit) = ($1, $2, $4);
1839 $newsize = $newsize * 1024;
1840 } elsif ($unit eq 'M') {
1841 $newsize = $newsize * 1024 * 1024;
1842 } elsif ($unit eq 'G') {
1843 $newsize = $newsize * 1024 * 1024 * 1024;
1844 } elsif ($unit eq 'T') {
1845 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
1848 $newsize += $size if $ext;
1849 $newsize = int($newsize);
1851 die "unable to skrink disk size\n" if $newsize < $size;
1853 return if $size == $newsize;
1855 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
1857 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
1859 $drive->{size
} = $newsize;
1860 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
1862 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
1865 PVE
::QemuServer
::lock_config
($vmid, $updatefn);