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'),
1197 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1207 my $rpcenv = PVE
::RPCEnvironment
::get
();
1209 my $authuser = $rpcenv->get_user();
1211 my $node = extract_param
($param, 'node');
1213 my $vmid = extract_param
($param, 'vmid');
1215 my $stateuri = extract_param
($param, 'stateuri');
1216 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1217 if $stateuri && $authuser ne 'root@pam';
1219 my $skiplock = extract_param
($param, 'skiplock');
1220 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1221 if $skiplock && $authuser ne 'root@pam';
1223 my $migratedfrom = extract_param
($param, 'migratedfrom');
1224 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1225 if $migratedfrom && $authuser ne 'root@pam';
1227 my $storecfg = PVE
::Storage
::config
();
1229 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1230 $rpcenv->{type
} ne 'ha') {
1235 my $service = "pvevm:$vmid";
1237 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1239 print "Executing HA start for VM $vmid\n";
1241 PVE
::Tools
::run_command
($cmd);
1246 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1253 syslog
('info', "start VM $vmid: $upid\n");
1255 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom);
1260 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1264 __PACKAGE__-
>register_method({
1266 path
=> '{vmid}/status/stop',
1270 description
=> "Stop virtual machine.",
1272 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1275 additionalProperties
=> 0,
1277 node
=> get_standard_option
('pve-node'),
1278 vmid
=> get_standard_option
('pve-vmid'),
1279 skiplock
=> get_standard_option
('skiplock'),
1280 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1282 description
=> "Wait maximal timeout seconds.",
1288 description
=> "Do not decativate storage volumes.",
1301 my $rpcenv = PVE
::RPCEnvironment
::get
();
1303 my $authuser = $rpcenv->get_user();
1305 my $node = extract_param
($param, 'node');
1307 my $vmid = extract_param
($param, 'vmid');
1309 my $skiplock = extract_param
($param, 'skiplock');
1310 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1311 if $skiplock && $authuser ne 'root@pam';
1313 my $keepActive = extract_param
($param, 'keepActive');
1314 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1315 if $keepActive && $authuser ne 'root@pam';
1317 my $migratedfrom = extract_param
($param, 'migratedfrom');
1318 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1319 if $migratedfrom && $authuser ne 'root@pam';
1322 my $storecfg = PVE
::Storage
::config
();
1324 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1329 my $service = "pvevm:$vmid";
1331 my $cmd = ['clusvcadm', '-d', $service];
1333 print "Executing HA stop for VM $vmid\n";
1335 PVE
::Tools
::run_command
($cmd);
1340 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1346 syslog
('info', "stop VM $vmid: $upid\n");
1348 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1349 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1354 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1358 __PACKAGE__-
>register_method({
1360 path
=> '{vmid}/status/reset',
1364 description
=> "Reset virtual machine.",
1366 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1369 additionalProperties
=> 0,
1371 node
=> get_standard_option
('pve-node'),
1372 vmid
=> get_standard_option
('pve-vmid'),
1373 skiplock
=> get_standard_option
('skiplock'),
1382 my $rpcenv = PVE
::RPCEnvironment
::get
();
1384 my $authuser = $rpcenv->get_user();
1386 my $node = extract_param
($param, 'node');
1388 my $vmid = extract_param
($param, 'vmid');
1390 my $skiplock = extract_param
($param, 'skiplock');
1391 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1392 if $skiplock && $authuser ne 'root@pam';
1394 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1399 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1404 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1407 __PACKAGE__-
>register_method({
1408 name
=> 'vm_shutdown',
1409 path
=> '{vmid}/status/shutdown',
1413 description
=> "Shutdown virtual machine.",
1415 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1418 additionalProperties
=> 0,
1420 node
=> get_standard_option
('pve-node'),
1421 vmid
=> get_standard_option
('pve-vmid'),
1422 skiplock
=> get_standard_option
('skiplock'),
1424 description
=> "Wait maximal timeout seconds.",
1430 description
=> "Make sure the VM stops.",
1436 description
=> "Do not decativate storage volumes.",
1449 my $rpcenv = PVE
::RPCEnvironment
::get
();
1451 my $authuser = $rpcenv->get_user();
1453 my $node = extract_param
($param, 'node');
1455 my $vmid = extract_param
($param, 'vmid');
1457 my $skiplock = extract_param
($param, 'skiplock');
1458 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1459 if $skiplock && $authuser ne 'root@pam';
1461 my $keepActive = extract_param
($param, 'keepActive');
1462 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1463 if $keepActive && $authuser ne 'root@pam';
1465 my $storecfg = PVE
::Storage
::config
();
1470 syslog
('info', "shutdown VM $vmid: $upid\n");
1472 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1473 1, $param->{forceStop
}, $keepActive);
1478 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1481 __PACKAGE__-
>register_method({
1482 name
=> 'vm_suspend',
1483 path
=> '{vmid}/status/suspend',
1487 description
=> "Suspend virtual machine.",
1489 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1492 additionalProperties
=> 0,
1494 node
=> get_standard_option
('pve-node'),
1495 vmid
=> get_standard_option
('pve-vmid'),
1496 skiplock
=> get_standard_option
('skiplock'),
1505 my $rpcenv = PVE
::RPCEnvironment
::get
();
1507 my $authuser = $rpcenv->get_user();
1509 my $node = extract_param
($param, 'node');
1511 my $vmid = extract_param
($param, 'vmid');
1513 my $skiplock = extract_param
($param, 'skiplock');
1514 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1515 if $skiplock && $authuser ne 'root@pam';
1517 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1522 syslog
('info', "suspend VM $vmid: $upid\n");
1524 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1529 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1532 __PACKAGE__-
>register_method({
1533 name
=> 'vm_resume',
1534 path
=> '{vmid}/status/resume',
1538 description
=> "Resume virtual machine.",
1540 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1543 additionalProperties
=> 0,
1545 node
=> get_standard_option
('pve-node'),
1546 vmid
=> get_standard_option
('pve-vmid'),
1547 skiplock
=> get_standard_option
('skiplock'),
1556 my $rpcenv = PVE
::RPCEnvironment
::get
();
1558 my $authuser = $rpcenv->get_user();
1560 my $node = extract_param
($param, 'node');
1562 my $vmid = extract_param
($param, 'vmid');
1564 my $skiplock = extract_param
($param, 'skiplock');
1565 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1566 if $skiplock && $authuser ne 'root@pam';
1568 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1573 syslog
('info', "resume VM $vmid: $upid\n");
1575 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1580 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1583 __PACKAGE__-
>register_method({
1584 name
=> 'vm_sendkey',
1585 path
=> '{vmid}/sendkey',
1589 description
=> "Send key event to virtual machine.",
1591 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1594 additionalProperties
=> 0,
1596 node
=> get_standard_option
('pve-node'),
1597 vmid
=> get_standard_option
('pve-vmid'),
1598 skiplock
=> get_standard_option
('skiplock'),
1600 description
=> "The key (qemu monitor encoding).",
1605 returns
=> { type
=> 'null'},
1609 my $rpcenv = PVE
::RPCEnvironment
::get
();
1611 my $authuser = $rpcenv->get_user();
1613 my $node = extract_param
($param, 'node');
1615 my $vmid = extract_param
($param, 'vmid');
1617 my $skiplock = extract_param
($param, 'skiplock');
1618 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1619 if $skiplock && $authuser ne 'root@pam';
1621 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1626 __PACKAGE__-
>register_method({
1627 name
=> 'migrate_vm',
1628 path
=> '{vmid}/migrate',
1632 description
=> "Migrate virtual machine. Creates a new migration task.",
1634 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
1637 additionalProperties
=> 0,
1639 node
=> get_standard_option
('pve-node'),
1640 vmid
=> get_standard_option
('pve-vmid'),
1641 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
1644 description
=> "Use online/live migration.",
1649 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
1656 description
=> "the task ID.",
1661 my $rpcenv = PVE
::RPCEnvironment
::get
();
1663 my $authuser = $rpcenv->get_user();
1665 my $target = extract_param
($param, 'target');
1667 my $localnode = PVE
::INotify
::nodename
();
1668 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
1670 PVE
::Cluster
::check_cfs_quorum
();
1672 PVE
::Cluster
::check_node_exists
($target);
1674 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
1676 my $vmid = extract_param
($param, 'vmid');
1678 raise_param_exc
({ force
=> "Only root may use this option." })
1679 if $param->{force
} && $authuser ne 'root@pam';
1682 my $conf = PVE
::QemuServer
::load_config
($vmid);
1684 # try to detect errors early
1686 PVE
::QemuServer
::check_lock
($conf);
1688 if (PVE
::QemuServer
::check_running
($vmid)) {
1689 die "cant migrate running VM without --online\n"
1690 if !$param->{online
};
1693 my $storecfg = PVE
::Storage
::config
();
1694 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
1696 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1701 my $service = "pvevm:$vmid";
1703 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
1705 print "Executing HA migrate for VM $vmid to node $target\n";
1707 PVE
::Tools
::run_command
($cmd);
1712 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
1719 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
1722 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
1727 __PACKAGE__-
>register_method({
1729 path
=> '{vmid}/monitor',
1733 description
=> "Execute Qemu monitor commands.",
1735 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
1738 additionalProperties
=> 0,
1740 node
=> get_standard_option
('pve-node'),
1741 vmid
=> get_standard_option
('pve-vmid'),
1744 description
=> "The monitor command.",
1748 returns
=> { type
=> 'string'},
1752 my $vmid = $param->{vmid
};
1754 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
1758 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
1760 $res = "ERROR: $@" if $@;
1765 __PACKAGE__-
>register_method({
1766 name
=> 'resize_vm',
1767 path
=> '{vmid}/resize',
1771 description
=> "Extend volume size.",
1773 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
1776 additionalProperties
=> 0,
1778 node
=> get_standard_option
('pve-node'),
1779 vmid
=> get_standard_option
('pve-vmid'),
1780 skiplock
=> get_standard_option
('skiplock'),
1783 description
=> "The disk you want to resize.",
1784 enum
=> [PVE
::QemuServer
::disknames
()],
1788 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
1789 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.",
1793 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1799 returns
=> { type
=> 'null'},
1803 my $rpcenv = PVE
::RPCEnvironment
::get
();
1805 my $authuser = $rpcenv->get_user();
1807 my $node = extract_param
($param, 'node');
1809 my $vmid = extract_param
($param, 'vmid');
1811 my $digest = extract_param
($param, 'digest');
1813 my $disk = extract_param
($param, 'disk');
1815 my $sizestr = extract_param
($param, 'size');
1817 my $skiplock = extract_param
($param, 'skiplock');
1818 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1819 if $skiplock && $authuser ne 'root@pam';
1821 my $storecfg = PVE
::Storage
::config
();
1823 my $updatefn = sub {
1825 my $conf = PVE
::QemuServer
::load_config
($vmid);
1827 die "checksum missmatch (file change by other user?)\n"
1828 if $digest && $digest ne $conf->{digest
};
1829 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
1831 die "disk '$disk' does not exist\n" if !$conf->{$disk};
1833 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
1835 my $volid = $drive->{file
};
1837 die "disk '$disk' has no associated volume\n" if !$volid;
1839 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
1841 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1843 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1845 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
1847 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
1848 my ($ext, $newsize, $unit) = ($1, $2, $4);
1851 $newsize = $newsize * 1024;
1852 } elsif ($unit eq 'M') {
1853 $newsize = $newsize * 1024 * 1024;
1854 } elsif ($unit eq 'G') {
1855 $newsize = $newsize * 1024 * 1024 * 1024;
1856 } elsif ($unit eq 'T') {
1857 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
1860 $newsize += $size if $ext;
1861 $newsize = int($newsize);
1863 die "unable to skrink disk size\n" if $newsize < $size;
1865 return if $size == $newsize;
1867 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
1869 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
1871 $drive->{size
} = $newsize;
1872 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
1874 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
1877 PVE
::QemuServer
::lock_config
($vmid, $updatefn);