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_resize_disk = sub {
733 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value) = @_;
735 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
737 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { #cdrom
738 die "you can't resize a cdrom";
743 if (my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt})) {
744 my $volid = $drive->{file
};
745 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 1);
746 if ($value =~ m/^\+([1-9]\d*(\.\d+)?)([KMG])?$/){
747 my ($sizeextent, $unit) = ($1, $3);
750 $sizeextent = $sizeextent * 1024;
751 } elsif ($unit eq 'M') {
752 $sizeextent = $sizeextent * 1024 * 1024;
753 } elsif ($unit eq 'G') {
754 $sizeextent = $sizeextent * 1024 * 1024 * 1024;
758 my $targetsize = $size + int($sizeextent);
759 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$opt", $storecfg, $drive->{file
}, $targetsize);
760 my $newsize = PVE
::Storage
::volume_size_info
($storecfg, $volid, 1);
761 $drive->{size
} = $newsize;
762 $conf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
764 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
771 my $vmconfig_update_net = sub {
772 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value) = @_;
775 #if online update, then unplug first
776 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
779 $conf->{$opt} = $value;
780 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
781 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
783 my $net = PVE
::QemuServer
::parse_net
($conf->{$opt});
785 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $net);
788 my $vm_config_perm_list = [
798 __PACKAGE__-
>register_method({
800 path
=> '{vmid}/config',
804 description
=> "Set virtual machine options.",
806 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
809 additionalProperties
=> 0,
810 properties
=> PVE
::QemuServer
::json_config_properties
(
812 node
=> get_standard_option
('pve-node'),
813 vmid
=> get_standard_option
('pve-vmid'),
814 skiplock
=> get_standard_option
('skiplock'),
816 type
=> 'string', format
=> 'pve-configid-list',
817 description
=> "A list of settings you want to delete.",
822 description
=> $opt_force_description,
824 requires
=> 'delete',
828 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
834 returns
=> { type
=> 'null'},
838 my $rpcenv = PVE
::RPCEnvironment
::get
();
840 my $authuser = $rpcenv->get_user();
842 my $node = extract_param
($param, 'node');
844 my $vmid = extract_param
($param, 'vmid');
846 my $digest = extract_param
($param, 'digest');
848 my @paramarr = (); # used for log message
849 foreach my $key (keys %$param) {
850 push @paramarr, "-$key", $param->{$key};
853 my $skiplock = extract_param
($param, 'skiplock');
854 raise_param_exc
({ skiplock
=> "Only root may use this option." })
855 if $skiplock && $authuser ne 'root@pam';
857 my $delete_str = extract_param
($param, 'delete');
859 my $force = extract_param
($param, 'force');
861 die "no options specified\n" if !$delete_str && !scalar(keys %$param);
863 my $storecfg = PVE
::Storage
::config
();
865 &$resolve_cdrom_alias($param);
867 # now try to verify all parameters
870 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
871 $opt = 'ide2' if $opt eq 'cdrom';
872 raise_param_exc
({ delete => "you can't use '-$opt' and " .
873 "-delete $opt' at the same time" })
874 if defined($param->{$opt});
876 if (!PVE
::QemuServer
::option_exists
($opt)) {
877 raise_param_exc
({ delete => "unknown option '$opt'" });
883 foreach my $opt (keys %$param) {
884 if (PVE
::QemuServer
::valid_drivename
($opt)) {
886 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
887 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
888 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
889 } elsif ($opt =~ m/^net(\d+)$/) {
891 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
892 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
896 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
898 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
900 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
904 my $conf = PVE
::QemuServer
::load_config
($vmid);
906 die "checksum missmatch (file change by other user?)\n"
907 if $digest && $digest ne $conf->{digest
};
909 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
911 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
913 foreach my $opt (@delete) { # delete
914 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
915 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
918 foreach my $opt (keys %$param) { # add/change
920 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
922 next if $conf->{$opt} && ($param->{$opt} eq $conf->{$opt}); # skip if nothing changed
924 if (PVE
::QemuServer
::valid_drivename
($opt)) {
926 &$vmconfig_update_disk($rpcenv, $authuser, $conf, $storecfg, $vmid,
927 $opt, $param->{$opt}, $force);
929 } elsif ($opt =~ m/^net(\d+)$/) { #nics
931 &$vmconfig_update_net($rpcenv, $authuser, $conf, $storecfg, $vmid,
932 $opt, $param->{$opt});
936 $conf->{$opt} = $param->{$opt};
937 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
942 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
948 __PACKAGE__-
>register_method({
949 name
=> 'destroy_vm',
954 description
=> "Destroy the vm (also delete all used/owned volumes).",
956 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
959 additionalProperties
=> 0,
961 node
=> get_standard_option
('pve-node'),
962 vmid
=> get_standard_option
('pve-vmid'),
963 skiplock
=> get_standard_option
('skiplock'),
972 my $rpcenv = PVE
::RPCEnvironment
::get
();
974 my $authuser = $rpcenv->get_user();
976 my $vmid = $param->{vmid
};
978 my $skiplock = $param->{skiplock
};
979 raise_param_exc
({ skiplock
=> "Only root may use this option." })
980 if $skiplock && $authuser ne 'root@pam';
983 my $conf = PVE
::QemuServer
::load_config
($vmid);
985 my $storecfg = PVE
::Storage
::config
();
987 my $delVMfromPoolFn = sub {
988 my $usercfg = cfs_read_file
("user.cfg");
989 if (my $pool = $usercfg->{vms
}->{$vmid}) {
990 if (my $data = $usercfg->{pools
}->{$pool}) {
991 delete $data->{vms
}->{$vmid};
992 delete $usercfg->{vms
}->{$vmid};
993 cfs_write_file
("user.cfg", $usercfg);
1001 syslog
('info', "destroy VM $vmid: $upid\n");
1003 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1005 PVE
::AccessControl
::lock_user_config
($delVMfromPoolFn, "pool cleanup failed");
1008 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1011 __PACKAGE__-
>register_method({
1013 path
=> '{vmid}/unlink',
1017 description
=> "Unlink/delete disk images.",
1019 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1022 additionalProperties
=> 0,
1024 node
=> get_standard_option
('pve-node'),
1025 vmid
=> get_standard_option
('pve-vmid'),
1027 type
=> 'string', format
=> 'pve-configid-list',
1028 description
=> "A list of disk IDs you want to delete.",
1032 description
=> $opt_force_description,
1037 returns
=> { type
=> 'null'},
1041 $param->{delete} = extract_param
($param, 'idlist');
1043 __PACKAGE__-
>update_vm($param);
1050 __PACKAGE__-
>register_method({
1052 path
=> '{vmid}/vncproxy',
1056 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1058 description
=> "Creates a TCP VNC proxy connections.",
1060 additionalProperties
=> 0,
1062 node
=> get_standard_option
('pve-node'),
1063 vmid
=> get_standard_option
('pve-vmid'),
1067 additionalProperties
=> 0,
1069 user
=> { type
=> 'string' },
1070 ticket
=> { type
=> 'string' },
1071 cert
=> { type
=> 'string' },
1072 port
=> { type
=> 'integer' },
1073 upid
=> { type
=> 'string' },
1079 my $rpcenv = PVE
::RPCEnvironment
::get
();
1081 my $authuser = $rpcenv->get_user();
1083 my $vmid = $param->{vmid
};
1084 my $node = $param->{node
};
1086 my $authpath = "/vms/$vmid";
1088 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1090 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1093 my $port = PVE
::Tools
::next_vnc_port
();
1097 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1098 $remip = PVE
::Cluster
::remote_node_ip
($node);
1101 # NOTE: kvm VNC traffic is already TLS encrypted,
1102 # so we select the fastest chipher here (or 'none'?)
1103 my $remcmd = $remip ?
['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes',
1104 '-c', 'blowfish-cbc', $remip] : [];
1111 syslog
('info', "starting vnc proxy $upid\n");
1113 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1115 my $qmstr = join(' ', @$qmcmd);
1117 # also redirect stderr (else we get RFB protocol errors)
1118 my $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1120 PVE
::Tools
::run_command
($cmd);
1125 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1136 __PACKAGE__-
>register_method({
1138 path
=> '{vmid}/status',
1141 description
=> "Directory index",
1146 additionalProperties
=> 0,
1148 node
=> get_standard_option
('pve-node'),
1149 vmid
=> get_standard_option
('pve-vmid'),
1157 subdir
=> { type
=> 'string' },
1160 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1166 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1169 { subdir
=> 'current' },
1170 { subdir
=> 'start' },
1171 { subdir
=> 'stop' },
1177 my $vm_is_ha_managed = sub {
1180 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
1181 if (PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1)) {
1187 __PACKAGE__-
>register_method({
1188 name
=> 'vm_status',
1189 path
=> '{vmid}/status/current',
1192 protected
=> 1, # qemu pid files are only readable by root
1193 description
=> "Get virtual machine status.",
1195 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1198 additionalProperties
=> 0,
1200 node
=> get_standard_option
('pve-node'),
1201 vmid
=> get_standard_option
('pve-vmid'),
1204 returns
=> { type
=> 'object' },
1209 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1211 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1212 my $status = $vmstatus->{$param->{vmid
}};
1214 $status->{ha
} = &$vm_is_ha_managed($param->{vmid
});
1219 __PACKAGE__-
>register_method({
1221 path
=> '{vmid}/status/start',
1225 description
=> "Start virtual machine.",
1227 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1230 additionalProperties
=> 0,
1232 node
=> get_standard_option
('pve-node'),
1233 vmid
=> get_standard_option
('pve-vmid'),
1234 skiplock
=> get_standard_option
('skiplock'),
1235 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1244 my $rpcenv = PVE
::RPCEnvironment
::get
();
1246 my $authuser = $rpcenv->get_user();
1248 my $node = extract_param
($param, 'node');
1250 my $vmid = extract_param
($param, 'vmid');
1252 my $stateuri = extract_param
($param, 'stateuri');
1253 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1254 if $stateuri && $authuser ne 'root@pam';
1256 my $skiplock = extract_param
($param, 'skiplock');
1257 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1258 if $skiplock && $authuser ne 'root@pam';
1260 my $storecfg = PVE
::Storage
::config
();
1262 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1263 $rpcenv->{type
} ne 'ha') {
1268 my $service = "pvevm:$vmid";
1270 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1272 print "Executing HA start for VM $vmid\n";
1274 PVE
::Tools
::run_command
($cmd);
1279 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1286 syslog
('info', "start VM $vmid: $upid\n");
1288 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock);
1293 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1297 __PACKAGE__-
>register_method({
1299 path
=> '{vmid}/status/stop',
1303 description
=> "Stop virtual machine.",
1305 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1308 additionalProperties
=> 0,
1310 node
=> get_standard_option
('pve-node'),
1311 vmid
=> get_standard_option
('pve-vmid'),
1312 skiplock
=> get_standard_option
('skiplock'),
1314 description
=> "Wait maximal timeout seconds.",
1320 description
=> "Do not decativate storage volumes.",
1333 my $rpcenv = PVE
::RPCEnvironment
::get
();
1335 my $authuser = $rpcenv->get_user();
1337 my $node = extract_param
($param, 'node');
1339 my $vmid = extract_param
($param, 'vmid');
1341 my $skiplock = extract_param
($param, 'skiplock');
1342 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1343 if $skiplock && $authuser ne 'root@pam';
1345 my $keepActive = extract_param
($param, 'keepActive');
1346 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1347 if $keepActive && $authuser ne 'root@pam';
1349 my $storecfg = PVE
::Storage
::config
();
1351 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1356 my $service = "pvevm:$vmid";
1358 my $cmd = ['clusvcadm', '-d', $service];
1360 print "Executing HA stop for VM $vmid\n";
1362 PVE
::Tools
::run_command
($cmd);
1367 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1373 syslog
('info', "stop VM $vmid: $upid\n");
1375 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1376 $param->{timeout
}, 0, 1, $keepActive);
1381 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1385 __PACKAGE__-
>register_method({
1387 path
=> '{vmid}/status/reset',
1391 description
=> "Reset virtual machine.",
1393 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1396 additionalProperties
=> 0,
1398 node
=> get_standard_option
('pve-node'),
1399 vmid
=> get_standard_option
('pve-vmid'),
1400 skiplock
=> get_standard_option
('skiplock'),
1409 my $rpcenv = PVE
::RPCEnvironment
::get
();
1411 my $authuser = $rpcenv->get_user();
1413 my $node = extract_param
($param, 'node');
1415 my $vmid = extract_param
($param, 'vmid');
1417 my $skiplock = extract_param
($param, 'skiplock');
1418 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1419 if $skiplock && $authuser ne 'root@pam';
1421 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1426 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1431 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1434 __PACKAGE__-
>register_method({
1435 name
=> 'vm_shutdown',
1436 path
=> '{vmid}/status/shutdown',
1440 description
=> "Shutdown virtual machine.",
1442 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1445 additionalProperties
=> 0,
1447 node
=> get_standard_option
('pve-node'),
1448 vmid
=> get_standard_option
('pve-vmid'),
1449 skiplock
=> get_standard_option
('skiplock'),
1451 description
=> "Wait maximal timeout seconds.",
1457 description
=> "Make sure the VM stops.",
1463 description
=> "Do not decativate storage volumes.",
1476 my $rpcenv = PVE
::RPCEnvironment
::get
();
1478 my $authuser = $rpcenv->get_user();
1480 my $node = extract_param
($param, 'node');
1482 my $vmid = extract_param
($param, 'vmid');
1484 my $skiplock = extract_param
($param, 'skiplock');
1485 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1486 if $skiplock && $authuser ne 'root@pam';
1488 my $keepActive = extract_param
($param, 'keepActive');
1489 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1490 if $keepActive && $authuser ne 'root@pam';
1492 my $storecfg = PVE
::Storage
::config
();
1497 syslog
('info', "shutdown VM $vmid: $upid\n");
1499 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1500 1, $param->{forceStop
}, $keepActive);
1505 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1508 __PACKAGE__-
>register_method({
1509 name
=> 'vm_suspend',
1510 path
=> '{vmid}/status/suspend',
1514 description
=> "Suspend virtual machine.",
1516 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1519 additionalProperties
=> 0,
1521 node
=> get_standard_option
('pve-node'),
1522 vmid
=> get_standard_option
('pve-vmid'),
1523 skiplock
=> get_standard_option
('skiplock'),
1532 my $rpcenv = PVE
::RPCEnvironment
::get
();
1534 my $authuser = $rpcenv->get_user();
1536 my $node = extract_param
($param, 'node');
1538 my $vmid = extract_param
($param, 'vmid');
1540 my $skiplock = extract_param
($param, 'skiplock');
1541 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1542 if $skiplock && $authuser ne 'root@pam';
1544 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1549 syslog
('info', "suspend VM $vmid: $upid\n");
1551 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1556 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1559 __PACKAGE__-
>register_method({
1560 name
=> 'vm_resume',
1561 path
=> '{vmid}/status/resume',
1565 description
=> "Resume virtual machine.",
1567 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1570 additionalProperties
=> 0,
1572 node
=> get_standard_option
('pve-node'),
1573 vmid
=> get_standard_option
('pve-vmid'),
1574 skiplock
=> get_standard_option
('skiplock'),
1583 my $rpcenv = PVE
::RPCEnvironment
::get
();
1585 my $authuser = $rpcenv->get_user();
1587 my $node = extract_param
($param, 'node');
1589 my $vmid = extract_param
($param, 'vmid');
1591 my $skiplock = extract_param
($param, 'skiplock');
1592 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1593 if $skiplock && $authuser ne 'root@pam';
1595 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1600 syslog
('info', "resume VM $vmid: $upid\n");
1602 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1607 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1610 __PACKAGE__-
>register_method({
1611 name
=> 'vm_sendkey',
1612 path
=> '{vmid}/sendkey',
1616 description
=> "Send key event to virtual machine.",
1618 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1621 additionalProperties
=> 0,
1623 node
=> get_standard_option
('pve-node'),
1624 vmid
=> get_standard_option
('pve-vmid'),
1625 skiplock
=> get_standard_option
('skiplock'),
1627 description
=> "The key (qemu monitor encoding).",
1632 returns
=> { type
=> 'null'},
1636 my $rpcenv = PVE
::RPCEnvironment
::get
();
1638 my $authuser = $rpcenv->get_user();
1640 my $node = extract_param
($param, 'node');
1642 my $vmid = extract_param
($param, 'vmid');
1644 my $skiplock = extract_param
($param, 'skiplock');
1645 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1646 if $skiplock && $authuser ne 'root@pam';
1648 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1653 __PACKAGE__-
>register_method({
1654 name
=> 'migrate_vm',
1655 path
=> '{vmid}/migrate',
1659 description
=> "Migrate virtual machine. Creates a new migration task.",
1661 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
1664 additionalProperties
=> 0,
1666 node
=> get_standard_option
('pve-node'),
1667 vmid
=> get_standard_option
('pve-vmid'),
1668 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
1671 description
=> "Use online/live migration.",
1676 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
1683 description
=> "the task ID.",
1688 my $rpcenv = PVE
::RPCEnvironment
::get
();
1690 my $authuser = $rpcenv->get_user();
1692 my $target = extract_param
($param, 'target');
1694 my $localnode = PVE
::INotify
::nodename
();
1695 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
1697 PVE
::Cluster
::check_cfs_quorum
();
1699 PVE
::Cluster
::check_node_exists
($target);
1701 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
1703 my $vmid = extract_param
($param, 'vmid');
1705 raise_param_exc
({ force
=> "Only root may use this option." })
1706 if $param->{force
} && $authuser ne 'root@pam';
1709 my $conf = PVE
::QemuServer
::load_config
($vmid);
1711 # try to detect errors early
1713 PVE
::QemuServer
::check_lock
($conf);
1715 if (PVE
::QemuServer
::check_running
($vmid)) {
1716 die "cant migrate running VM without --online\n"
1717 if !$param->{online
};
1720 my $storecfg = PVE
::Storage
::config
();
1721 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
1723 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1728 my $service = "pvevm:$vmid";
1730 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
1732 print "Executing HA migrate for VM $vmid to node $target\n";
1734 PVE
::Tools
::run_command
($cmd);
1739 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
1746 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
1749 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
1754 __PACKAGE__-
>register_method({
1756 path
=> '{vmid}/monitor',
1760 description
=> "Execute Qemu monitor commands.",
1762 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
1765 additionalProperties
=> 0,
1767 node
=> get_standard_option
('pve-node'),
1768 vmid
=> get_standard_option
('pve-vmid'),
1771 description
=> "The monitor command.",
1775 returns
=> { type
=> 'string'},
1779 my $vmid = $param->{vmid
};
1781 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
1785 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
1787 $res = "ERROR: $@" if $@;
1792 __PACKAGE__-
>register_method({
1793 name
=> 'resize_vm',
1794 path
=> '{vmid}/resize',
1798 description
=> "Extend volume size.",
1800 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
1803 additionalProperties
=> 0,
1805 node
=> get_standard_option
('pve-node'),
1806 vmid
=> get_standard_option
('pve-vmid'),
1807 skiplock
=> get_standard_option
('skiplock'),
1810 description
=> "The disk you want to resize.",
1811 enum
=> [PVE
::QemuServer
::disknames
()],
1815 pattern
=> '[+]?\d+(\.\d+)?[KMGT]?',
1816 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.",
1820 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1826 returns
=> { type
=> 'null'},
1830 my $rpcenv = PVE
::RPCEnvironment
::get
();
1832 my $authuser = $rpcenv->get_user();
1834 my $node = extract_param
($param, 'node');
1836 my $vmid = extract_param
($param, 'vmid');
1838 my $digest = extract_param
($param, 'digest');
1840 my $disk = extract_param
($param, 'disk');
1842 my $sizestr = extract_param
($param, 'size');
1844 my $skiplock = extract_param
($param, 'skiplock');
1845 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1846 if $skiplock && $authuser ne 'root@pam';
1849 my $storecfg = PVE
::Storage
::config
();
1851 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1853 my $updatefn = sub {
1855 my $conf = PVE
::QemuServer
::load_config
($vmid);
1857 die "checksum missmatch (file change by other user?)\n"
1858 if $digest && $digest ne $conf->{digest
};
1859 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
1861 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
1863 &$vmconfig_resize_disk($rpcenv, $authuser, $conf, $storecfg, $vmid, $disk, $sizestr);
1866 PVE
::QemuServer
::lock_config
($vmid, $updatefn);