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
});
582 delete $conf->{snapshots
};
587 my $vm_is_volid_owner = sub {
588 my ($storecfg, $vmid, $volid) =@_;
590 if ($volid !~ m
|^/|) {
592 eval { ($path, $owner) = PVE
::Storage
::path
($storecfg, $volid); };
593 if ($owner && ($owner == $vmid)) {
601 my $test_deallocate_drive = sub {
602 my ($storecfg, $vmid, $key, $drive, $force) = @_;
604 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
605 my $volid = $drive->{file
};
606 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
607 if ($force || $key =~ m/^unused/) {
608 my $sid = PVE
::Storage
::parse_volume_id
($volid);
617 my $delete_drive = sub {
618 my ($conf, $storecfg, $vmid, $key, $drive, $force) = @_;
620 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
621 my $volid = $drive->{file
};
622 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
623 if ($force || $key =~ m/^unused/) {
624 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
627 PVE
::QemuServer
::add_unused_volume
($conf, $volid, $vmid);
632 delete $conf->{$key};
635 my $vmconfig_delete_option = sub {
636 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force) = @_;
638 return if !defined($conf->{$opt});
640 my $isDisk = PVE
::QemuServer
::valid_drivename
($opt)|| ($opt =~ m/^unused/);
643 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
645 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
646 if (my $sid = &$test_deallocate_drive($storecfg, $vmid, $opt, $drive, $force)) {
647 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.Allocate']);
651 die "error hot-unplug $opt" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
654 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
655 &$delete_drive($conf, $storecfg, $vmid, $opt, $drive, $force);
657 delete $conf->{$opt};
660 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
663 my $safe_num_ne = sub {
666 return 0 if !defined($a) && !defined($b);
667 return 1 if !defined($a);
668 return 1 if !defined($b);
673 my $vmconfig_update_disk = sub {
674 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value, $force) = @_;
676 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
678 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { #cdrom
679 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
681 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
686 if (my $old_drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt})) {
688 my $media = $drive->{media
} || 'disk';
689 my $oldmedia = $old_drive->{media
} || 'disk';
690 die "unable to change media type\n" if $media ne $oldmedia;
692 if (!PVE
::QemuServer
::drive_is_cdrom
($old_drive) &&
693 ($drive->{file
} ne $old_drive->{file
})) { # delete old disks
695 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
696 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
699 if(&$safe_num_ne($drive->{mbps
}, $old_drive->{mbps
}) ||
700 &$safe_num_ne($drive->{mbps_rd
}, $old_drive->{mbps_rd
}) ||
701 &$safe_num_ne($drive->{mbps_wr
}, $old_drive->{mbps_wr
}) ||
702 &$safe_num_ne($drive->{iops
}, $old_drive->{iops
}) ||
703 &$safe_num_ne($drive->{iops_rd
}, $old_drive->{iops_rd
}) ||
704 &$safe_num_ne($drive->{iops_wr
}, $old_drive->{iops_wr
})) {
705 PVE
::QemuServer
::qemu_block_set_io_throttle
($vmid,"drive-$opt", $drive->{mbps
}*1024*1024,
706 $drive->{mbps_rd
}*1024*1024, $drive->{mbps_wr
}*1024*1024,
707 $drive->{iops
}, $drive->{iops_rd
}, $drive->{iops_wr
})
708 if !PVE
::QemuServer
::drive_is_cdrom
($drive);
713 &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, undef, {$opt => $value});
714 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
716 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
717 $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
719 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # cdrom
721 if (PVE
::QemuServer
::check_running
($vmid)) {
722 if ($drive->{file
} eq 'none') {
723 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt");
725 my $path = PVE
::QemuServer
::get_iso_path
($storecfg, $vmid, $drive->{file
});
726 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt"); #force eject if locked
727 PVE
::QemuServer
::vm_mon_cmd
($vmid, "change",device
=> "drive-$opt",target
=> "$path") if $path;
731 } else { # hotplug new disks
733 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $drive);
737 my $vmconfig_update_net = sub {
738 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value) = @_;
741 #if online update, then unplug first
742 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
745 $conf->{$opt} = $value;
746 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
747 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
749 my $net = PVE
::QemuServer
::parse_net
($conf->{$opt});
751 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $net);
754 my $vm_config_perm_list = [
764 __PACKAGE__-
>register_method({
766 path
=> '{vmid}/config',
770 description
=> "Set virtual machine options.",
772 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
775 additionalProperties
=> 0,
776 properties
=> PVE
::QemuServer
::json_config_properties
(
778 node
=> get_standard_option
('pve-node'),
779 vmid
=> get_standard_option
('pve-vmid'),
780 skiplock
=> get_standard_option
('skiplock'),
782 type
=> 'string', format
=> 'pve-configid-list',
783 description
=> "A list of settings you want to delete.",
788 description
=> $opt_force_description,
790 requires
=> 'delete',
794 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
800 returns
=> { type
=> 'null'},
804 my $rpcenv = PVE
::RPCEnvironment
::get
();
806 my $authuser = $rpcenv->get_user();
808 my $node = extract_param
($param, 'node');
810 my $vmid = extract_param
($param, 'vmid');
812 my $digest = extract_param
($param, 'digest');
814 my @paramarr = (); # used for log message
815 foreach my $key (keys %$param) {
816 push @paramarr, "-$key", $param->{$key};
819 my $skiplock = extract_param
($param, 'skiplock');
820 raise_param_exc
({ skiplock
=> "Only root may use this option." })
821 if $skiplock && $authuser ne 'root@pam';
823 my $delete_str = extract_param
($param, 'delete');
825 my $force = extract_param
($param, 'force');
827 die "no options specified\n" if !$delete_str && !scalar(keys %$param);
829 my $storecfg = PVE
::Storage
::config
();
831 &$resolve_cdrom_alias($param);
833 # now try to verify all parameters
836 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
837 $opt = 'ide2' if $opt eq 'cdrom';
838 raise_param_exc
({ delete => "you can't use '-$opt' and " .
839 "-delete $opt' at the same time" })
840 if defined($param->{$opt});
842 if (!PVE
::QemuServer
::option_exists
($opt)) {
843 raise_param_exc
({ delete => "unknown option '$opt'" });
849 foreach my $opt (keys %$param) {
850 if (PVE
::QemuServer
::valid_drivename
($opt)) {
852 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
853 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
854 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
855 } elsif ($opt =~ m/^net(\d+)$/) {
857 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
858 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
862 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
864 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
866 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
870 my $conf = PVE
::QemuServer
::load_config
($vmid);
872 die "checksum missmatch (file change by other user?)\n"
873 if $digest && $digest ne $conf->{digest
};
875 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
877 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
879 foreach my $opt (@delete) { # delete
880 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
881 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
884 foreach my $opt (keys %$param) { # add/change
886 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
888 next if $conf->{$opt} && ($param->{$opt} eq $conf->{$opt}); # skip if nothing changed
890 if (PVE
::QemuServer
::valid_drivename
($opt)) {
892 &$vmconfig_update_disk($rpcenv, $authuser, $conf, $storecfg, $vmid,
893 $opt, $param->{$opt}, $force);
895 } elsif ($opt =~ m/^net(\d+)$/) { #nics
897 &$vmconfig_update_net($rpcenv, $authuser, $conf, $storecfg, $vmid,
898 $opt, $param->{$opt});
902 $conf->{$opt} = $param->{$opt};
903 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
908 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
914 __PACKAGE__-
>register_method({
915 name
=> 'destroy_vm',
920 description
=> "Destroy the vm (also delete all used/owned volumes).",
922 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
925 additionalProperties
=> 0,
927 node
=> get_standard_option
('pve-node'),
928 vmid
=> get_standard_option
('pve-vmid'),
929 skiplock
=> get_standard_option
('skiplock'),
938 my $rpcenv = PVE
::RPCEnvironment
::get
();
940 my $authuser = $rpcenv->get_user();
942 my $vmid = $param->{vmid
};
944 my $skiplock = $param->{skiplock
};
945 raise_param_exc
({ skiplock
=> "Only root may use this option." })
946 if $skiplock && $authuser ne 'root@pam';
949 my $conf = PVE
::QemuServer
::load_config
($vmid);
951 my $storecfg = PVE
::Storage
::config
();
953 my $delVMfromPoolFn = sub {
954 my $usercfg = cfs_read_file
("user.cfg");
955 if (my $pool = $usercfg->{vms
}->{$vmid}) {
956 if (my $data = $usercfg->{pools
}->{$pool}) {
957 delete $data->{vms
}->{$vmid};
958 delete $usercfg->{vms
}->{$vmid};
959 cfs_write_file
("user.cfg", $usercfg);
967 syslog
('info', "destroy VM $vmid: $upid\n");
969 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
971 PVE
::AccessControl
::lock_user_config
($delVMfromPoolFn, "pool cleanup failed");
974 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
977 __PACKAGE__-
>register_method({
979 path
=> '{vmid}/unlink',
983 description
=> "Unlink/delete disk images.",
985 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
988 additionalProperties
=> 0,
990 node
=> get_standard_option
('pve-node'),
991 vmid
=> get_standard_option
('pve-vmid'),
993 type
=> 'string', format
=> 'pve-configid-list',
994 description
=> "A list of disk IDs you want to delete.",
998 description
=> $opt_force_description,
1003 returns
=> { type
=> 'null'},
1007 $param->{delete} = extract_param
($param, 'idlist');
1009 __PACKAGE__-
>update_vm($param);
1016 __PACKAGE__-
>register_method({
1018 path
=> '{vmid}/vncproxy',
1022 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1024 description
=> "Creates a TCP VNC proxy connections.",
1026 additionalProperties
=> 0,
1028 node
=> get_standard_option
('pve-node'),
1029 vmid
=> get_standard_option
('pve-vmid'),
1033 additionalProperties
=> 0,
1035 user
=> { type
=> 'string' },
1036 ticket
=> { type
=> 'string' },
1037 cert
=> { type
=> 'string' },
1038 port
=> { type
=> 'integer' },
1039 upid
=> { type
=> 'string' },
1045 my $rpcenv = PVE
::RPCEnvironment
::get
();
1047 my $authuser = $rpcenv->get_user();
1049 my $vmid = $param->{vmid
};
1050 my $node = $param->{node
};
1052 my $authpath = "/vms/$vmid";
1054 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1056 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1059 my $port = PVE
::Tools
::next_vnc_port
();
1063 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1064 $remip = PVE
::Cluster
::remote_node_ip
($node);
1067 # NOTE: kvm VNC traffic is already TLS encrypted,
1068 # so we select the fastest chipher here (or 'none'?)
1069 my $remcmd = $remip ?
['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes',
1070 '-c', 'blowfish-cbc', $remip] : [];
1077 syslog
('info', "starting vnc proxy $upid\n");
1079 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1081 my $qmstr = join(' ', @$qmcmd);
1083 # also redirect stderr (else we get RFB protocol errors)
1084 my $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1086 PVE
::Tools
::run_command
($cmd);
1091 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1102 __PACKAGE__-
>register_method({
1104 path
=> '{vmid}/status',
1107 description
=> "Directory index",
1112 additionalProperties
=> 0,
1114 node
=> get_standard_option
('pve-node'),
1115 vmid
=> get_standard_option
('pve-vmid'),
1123 subdir
=> { type
=> 'string' },
1126 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1132 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1135 { subdir
=> 'current' },
1136 { subdir
=> 'start' },
1137 { subdir
=> 'stop' },
1143 my $vm_is_ha_managed = sub {
1146 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
1147 if (PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1)) {
1153 __PACKAGE__-
>register_method({
1154 name
=> 'vm_status',
1155 path
=> '{vmid}/status/current',
1158 protected
=> 1, # qemu pid files are only readable by root
1159 description
=> "Get virtual machine status.",
1161 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1164 additionalProperties
=> 0,
1166 node
=> get_standard_option
('pve-node'),
1167 vmid
=> get_standard_option
('pve-vmid'),
1170 returns
=> { type
=> 'object' },
1175 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1177 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1178 my $status = $vmstatus->{$param->{vmid
}};
1180 $status->{ha
} = &$vm_is_ha_managed($param->{vmid
});
1185 __PACKAGE__-
>register_method({
1187 path
=> '{vmid}/status/start',
1191 description
=> "Start virtual machine.",
1193 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1196 additionalProperties
=> 0,
1198 node
=> get_standard_option
('pve-node'),
1199 vmid
=> get_standard_option
('pve-vmid'),
1200 skiplock
=> get_standard_option
('skiplock'),
1201 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1202 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1212 my $rpcenv = PVE
::RPCEnvironment
::get
();
1214 my $authuser = $rpcenv->get_user();
1216 my $node = extract_param
($param, 'node');
1218 my $vmid = extract_param
($param, 'vmid');
1220 my $stateuri = extract_param
($param, 'stateuri');
1221 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1222 if $stateuri && $authuser ne 'root@pam';
1224 my $skiplock = extract_param
($param, 'skiplock');
1225 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1226 if $skiplock && $authuser ne 'root@pam';
1228 my $migratedfrom = extract_param
($param, 'migratedfrom');
1229 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1230 if $migratedfrom && $authuser ne 'root@pam';
1232 my $storecfg = PVE
::Storage
::config
();
1234 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1235 $rpcenv->{type
} ne 'ha') {
1240 my $service = "pvevm:$vmid";
1242 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1244 print "Executing HA start for VM $vmid\n";
1246 PVE
::Tools
::run_command
($cmd);
1251 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1258 syslog
('info', "start VM $vmid: $upid\n");
1260 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom);
1265 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1269 __PACKAGE__-
>register_method({
1271 path
=> '{vmid}/status/stop',
1275 description
=> "Stop virtual machine.",
1277 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1280 additionalProperties
=> 0,
1282 node
=> get_standard_option
('pve-node'),
1283 vmid
=> get_standard_option
('pve-vmid'),
1284 skiplock
=> get_standard_option
('skiplock'),
1285 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1287 description
=> "Wait maximal timeout seconds.",
1293 description
=> "Do not decativate storage volumes.",
1306 my $rpcenv = PVE
::RPCEnvironment
::get
();
1308 my $authuser = $rpcenv->get_user();
1310 my $node = extract_param
($param, 'node');
1312 my $vmid = extract_param
($param, 'vmid');
1314 my $skiplock = extract_param
($param, 'skiplock');
1315 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1316 if $skiplock && $authuser ne 'root@pam';
1318 my $keepActive = extract_param
($param, 'keepActive');
1319 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1320 if $keepActive && $authuser ne 'root@pam';
1322 my $migratedfrom = extract_param
($param, 'migratedfrom');
1323 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1324 if $migratedfrom && $authuser ne 'root@pam';
1327 my $storecfg = PVE
::Storage
::config
();
1329 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1334 my $service = "pvevm:$vmid";
1336 my $cmd = ['clusvcadm', '-d', $service];
1338 print "Executing HA stop for VM $vmid\n";
1340 PVE
::Tools
::run_command
($cmd);
1345 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1351 syslog
('info', "stop VM $vmid: $upid\n");
1353 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1354 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1359 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1363 __PACKAGE__-
>register_method({
1365 path
=> '{vmid}/status/reset',
1369 description
=> "Reset virtual machine.",
1371 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1374 additionalProperties
=> 0,
1376 node
=> get_standard_option
('pve-node'),
1377 vmid
=> get_standard_option
('pve-vmid'),
1378 skiplock
=> get_standard_option
('skiplock'),
1387 my $rpcenv = PVE
::RPCEnvironment
::get
();
1389 my $authuser = $rpcenv->get_user();
1391 my $node = extract_param
($param, 'node');
1393 my $vmid = extract_param
($param, 'vmid');
1395 my $skiplock = extract_param
($param, 'skiplock');
1396 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1397 if $skiplock && $authuser ne 'root@pam';
1399 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1404 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1409 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1412 __PACKAGE__-
>register_method({
1413 name
=> 'vm_shutdown',
1414 path
=> '{vmid}/status/shutdown',
1418 description
=> "Shutdown virtual machine.",
1420 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1423 additionalProperties
=> 0,
1425 node
=> get_standard_option
('pve-node'),
1426 vmid
=> get_standard_option
('pve-vmid'),
1427 skiplock
=> get_standard_option
('skiplock'),
1429 description
=> "Wait maximal timeout seconds.",
1435 description
=> "Make sure the VM stops.",
1441 description
=> "Do not decativate storage volumes.",
1454 my $rpcenv = PVE
::RPCEnvironment
::get
();
1456 my $authuser = $rpcenv->get_user();
1458 my $node = extract_param
($param, 'node');
1460 my $vmid = extract_param
($param, 'vmid');
1462 my $skiplock = extract_param
($param, 'skiplock');
1463 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1464 if $skiplock && $authuser ne 'root@pam';
1466 my $keepActive = extract_param
($param, 'keepActive');
1467 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1468 if $keepActive && $authuser ne 'root@pam';
1470 my $storecfg = PVE
::Storage
::config
();
1475 syslog
('info', "shutdown VM $vmid: $upid\n");
1477 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1478 1, $param->{forceStop
}, $keepActive);
1483 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1486 __PACKAGE__-
>register_method({
1487 name
=> 'vm_suspend',
1488 path
=> '{vmid}/status/suspend',
1492 description
=> "Suspend virtual machine.",
1494 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1497 additionalProperties
=> 0,
1499 node
=> get_standard_option
('pve-node'),
1500 vmid
=> get_standard_option
('pve-vmid'),
1501 skiplock
=> get_standard_option
('skiplock'),
1510 my $rpcenv = PVE
::RPCEnvironment
::get
();
1512 my $authuser = $rpcenv->get_user();
1514 my $node = extract_param
($param, 'node');
1516 my $vmid = extract_param
($param, 'vmid');
1518 my $skiplock = extract_param
($param, 'skiplock');
1519 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1520 if $skiplock && $authuser ne 'root@pam';
1522 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1527 syslog
('info', "suspend VM $vmid: $upid\n");
1529 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1534 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1537 __PACKAGE__-
>register_method({
1538 name
=> 'vm_resume',
1539 path
=> '{vmid}/status/resume',
1543 description
=> "Resume virtual machine.",
1545 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1548 additionalProperties
=> 0,
1550 node
=> get_standard_option
('pve-node'),
1551 vmid
=> get_standard_option
('pve-vmid'),
1552 skiplock
=> get_standard_option
('skiplock'),
1561 my $rpcenv = PVE
::RPCEnvironment
::get
();
1563 my $authuser = $rpcenv->get_user();
1565 my $node = extract_param
($param, 'node');
1567 my $vmid = extract_param
($param, 'vmid');
1569 my $skiplock = extract_param
($param, 'skiplock');
1570 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1571 if $skiplock && $authuser ne 'root@pam';
1573 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1578 syslog
('info', "resume VM $vmid: $upid\n");
1580 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1585 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1588 __PACKAGE__-
>register_method({
1589 name
=> 'vm_sendkey',
1590 path
=> '{vmid}/sendkey',
1594 description
=> "Send key event to virtual machine.",
1596 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1599 additionalProperties
=> 0,
1601 node
=> get_standard_option
('pve-node'),
1602 vmid
=> get_standard_option
('pve-vmid'),
1603 skiplock
=> get_standard_option
('skiplock'),
1605 description
=> "The key (qemu monitor encoding).",
1610 returns
=> { type
=> 'null'},
1614 my $rpcenv = PVE
::RPCEnvironment
::get
();
1616 my $authuser = $rpcenv->get_user();
1618 my $node = extract_param
($param, 'node');
1620 my $vmid = extract_param
($param, 'vmid');
1622 my $skiplock = extract_param
($param, 'skiplock');
1623 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1624 if $skiplock && $authuser ne 'root@pam';
1626 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1631 __PACKAGE__-
>register_method({
1632 name
=> 'migrate_vm',
1633 path
=> '{vmid}/migrate',
1637 description
=> "Migrate virtual machine. Creates a new migration task.",
1639 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
1642 additionalProperties
=> 0,
1644 node
=> get_standard_option
('pve-node'),
1645 vmid
=> get_standard_option
('pve-vmid'),
1646 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
1649 description
=> "Use online/live migration.",
1654 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
1661 description
=> "the task ID.",
1666 my $rpcenv = PVE
::RPCEnvironment
::get
();
1668 my $authuser = $rpcenv->get_user();
1670 my $target = extract_param
($param, 'target');
1672 my $localnode = PVE
::INotify
::nodename
();
1673 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
1675 PVE
::Cluster
::check_cfs_quorum
();
1677 PVE
::Cluster
::check_node_exists
($target);
1679 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
1681 my $vmid = extract_param
($param, 'vmid');
1683 raise_param_exc
({ force
=> "Only root may use this option." })
1684 if $param->{force
} && $authuser ne 'root@pam';
1687 my $conf = PVE
::QemuServer
::load_config
($vmid);
1689 # try to detect errors early
1691 PVE
::QemuServer
::check_lock
($conf);
1693 if (PVE
::QemuServer
::check_running
($vmid)) {
1694 die "cant migrate running VM without --online\n"
1695 if !$param->{online
};
1698 my $storecfg = PVE
::Storage
::config
();
1699 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
1701 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1706 my $service = "pvevm:$vmid";
1708 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
1710 print "Executing HA migrate for VM $vmid to node $target\n";
1712 PVE
::Tools
::run_command
($cmd);
1717 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
1724 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
1727 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
1732 __PACKAGE__-
>register_method({
1734 path
=> '{vmid}/monitor',
1738 description
=> "Execute Qemu monitor commands.",
1740 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
1743 additionalProperties
=> 0,
1745 node
=> get_standard_option
('pve-node'),
1746 vmid
=> get_standard_option
('pve-vmid'),
1749 description
=> "The monitor command.",
1753 returns
=> { type
=> 'string'},
1757 my $vmid = $param->{vmid
};
1759 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
1763 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
1765 $res = "ERROR: $@" if $@;
1770 __PACKAGE__-
>register_method({
1771 name
=> 'resize_vm',
1772 path
=> '{vmid}/resize',
1776 description
=> "Extend volume size.",
1778 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
1781 additionalProperties
=> 0,
1783 node
=> get_standard_option
('pve-node'),
1784 vmid
=> get_standard_option
('pve-vmid'),
1785 skiplock
=> get_standard_option
('skiplock'),
1788 description
=> "The disk you want to resize.",
1789 enum
=> [PVE
::QemuServer
::disknames
()],
1793 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
1794 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.",
1798 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1804 returns
=> { type
=> 'null'},
1808 my $rpcenv = PVE
::RPCEnvironment
::get
();
1810 my $authuser = $rpcenv->get_user();
1812 my $node = extract_param
($param, 'node');
1814 my $vmid = extract_param
($param, 'vmid');
1816 my $digest = extract_param
($param, 'digest');
1818 my $disk = extract_param
($param, 'disk');
1820 my $sizestr = extract_param
($param, 'size');
1822 my $skiplock = extract_param
($param, 'skiplock');
1823 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1824 if $skiplock && $authuser ne 'root@pam';
1826 my $storecfg = PVE
::Storage
::config
();
1828 my $updatefn = sub {
1830 my $conf = PVE
::QemuServer
::load_config
($vmid);
1832 die "checksum missmatch (file change by other user?)\n"
1833 if $digest && $digest ne $conf->{digest
};
1834 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
1836 die "disk '$disk' does not exist\n" if !$conf->{$disk};
1838 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
1840 my $volid = $drive->{file
};
1842 die "disk '$disk' has no associated volume\n" if !$volid;
1844 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
1846 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1848 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1850 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
1852 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
1853 my ($ext, $newsize, $unit) = ($1, $2, $4);
1856 $newsize = $newsize * 1024;
1857 } elsif ($unit eq 'M') {
1858 $newsize = $newsize * 1024 * 1024;
1859 } elsif ($unit eq 'G') {
1860 $newsize = $newsize * 1024 * 1024 * 1024;
1861 } elsif ($unit eq 'T') {
1862 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
1865 $newsize += $size if $ext;
1866 $newsize = int($newsize);
1868 die "unable to skrink disk size\n" if $newsize < $size;
1870 return if $size == $newsize;
1872 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
1874 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
1876 $drive->{size
} = $newsize;
1877 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
1879 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
1882 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
1886 __PACKAGE__-
>register_method({
1887 name
=> 'snapshot_vm',
1888 path
=> '{vmid}/snapshot',
1892 description
=> "Snapshot a VM.",
1894 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
1897 additionalProperties
=> 0,
1899 node
=> get_standard_option
('pve-node'),
1900 vmid
=> get_standard_option
('pve-vmid'),
1901 skiplock
=> get_standard_option
('skiplock'),
1904 description
=> "Action",
1905 enum
=> [ 'create', 'delete', 'rollback' ],
1909 description
=> "The name of the snapshot",
1915 description
=> "Save the vmstate",
1920 description
=> "Freeze the filesystem",
1924 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1930 returns
=> { type
=> 'null'},
1934 my $rpcenv = PVE
::RPCEnvironment
::get
();
1936 my $authuser = $rpcenv->get_user();
1938 my $node = extract_param
($param, 'node');
1940 my $vmid = extract_param
($param, 'vmid');
1942 my $digest = extract_param
($param, 'digest');
1944 my $action = extract_param
($param, 'action');
1946 my $snapname = extract_param
($param, 'snapname');
1948 my $vmstate = extract_param
($param, 'vmstate');
1950 my $freezefs = extract_param
($param, 'freezefs');
1952 my $skiplock = extract_param
($param, 'skiplock');
1953 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1954 if $skiplock && $authuser ne 'root@pam';
1958 # fixme: access rights?
1959 # &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $conf);
1960 # fixme: need to implement a check to see if all storages support snapshots
1962 if($action eq 'create') {
1963 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
1964 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $vmstate, $freezefs);
1965 } elsif($action eq 'rollback'){
1966 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
1967 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
1968 } elsif($action eq 'delete') {
1969 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
1970 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $vmstate);