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' },
459 { subdir
=> 'snapshot' },
460 { subdir
=> 'rollback' },
466 __PACKAGE__-
>register_method({
468 path
=> '{vmid}/rrd',
470 protected
=> 1, # fixme: can we avoid that?
472 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
474 description
=> "Read VM RRD statistics (returns PNG)",
476 additionalProperties
=> 0,
478 node
=> get_standard_option
('pve-node'),
479 vmid
=> get_standard_option
('pve-vmid'),
481 description
=> "Specify the time frame you are interested in.",
483 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
486 description
=> "The list of datasources you want to display.",
487 type
=> 'string', format
=> 'pve-configid-list',
490 description
=> "The RRD consolidation function",
492 enum
=> [ 'AVERAGE', 'MAX' ],
500 filename
=> { type
=> 'string' },
506 return PVE
::Cluster
::create_rrd_graph
(
507 "pve2-vm/$param->{vmid}", $param->{timeframe
},
508 $param->{ds
}, $param->{cf
});
512 __PACKAGE__-
>register_method({
514 path
=> '{vmid}/rrddata',
516 protected
=> 1, # fixme: can we avoid that?
518 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
520 description
=> "Read VM RRD statistics",
522 additionalProperties
=> 0,
524 node
=> get_standard_option
('pve-node'),
525 vmid
=> get_standard_option
('pve-vmid'),
527 description
=> "Specify the time frame you are interested in.",
529 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
532 description
=> "The RRD consolidation function",
534 enum
=> [ 'AVERAGE', 'MAX' ],
549 return PVE
::Cluster
::create_rrd_data
(
550 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
554 __PACKAGE__-
>register_method({
556 path
=> '{vmid}/config',
559 description
=> "Get virtual machine configuration.",
561 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
564 additionalProperties
=> 0,
566 node
=> get_standard_option
('pve-node'),
567 vmid
=> get_standard_option
('pve-vmid'),
575 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
582 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
584 delete $conf->{snapshots
};
589 my $vm_is_volid_owner = sub {
590 my ($storecfg, $vmid, $volid) =@_;
592 if ($volid !~ m
|^/|) {
594 eval { ($path, $owner) = PVE
::Storage
::path
($storecfg, $volid); };
595 if ($owner && ($owner == $vmid)) {
603 my $test_deallocate_drive = sub {
604 my ($storecfg, $vmid, $key, $drive, $force) = @_;
606 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
607 my $volid = $drive->{file
};
608 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
609 if ($force || $key =~ m/^unused/) {
610 my $sid = PVE
::Storage
::parse_volume_id
($volid);
619 my $delete_drive = sub {
620 my ($conf, $storecfg, $vmid, $key, $drive, $force) = @_;
622 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
623 my $volid = $drive->{file
};
624 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
625 if ($force || $key =~ m/^unused/) {
626 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
629 PVE
::QemuServer
::add_unused_volume
($conf, $volid, $vmid);
634 delete $conf->{$key};
637 my $vmconfig_delete_option = sub {
638 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force) = @_;
640 return if !defined($conf->{$opt});
642 my $isDisk = PVE
::QemuServer
::valid_drivename
($opt)|| ($opt =~ m/^unused/);
645 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
647 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
648 if (my $sid = &$test_deallocate_drive($storecfg, $vmid, $opt, $drive, $force)) {
649 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.Allocate']);
653 die "error hot-unplug $opt" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
656 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
657 &$delete_drive($conf, $storecfg, $vmid, $opt, $drive, $force);
659 delete $conf->{$opt};
662 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
665 my $safe_num_ne = sub {
668 return 0 if !defined($a) && !defined($b);
669 return 1 if !defined($a);
670 return 1 if !defined($b);
675 my $vmconfig_update_disk = sub {
676 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value, $force) = @_;
678 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
680 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { #cdrom
681 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
683 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
688 if (my $old_drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt})) {
690 my $media = $drive->{media
} || 'disk';
691 my $oldmedia = $old_drive->{media
} || 'disk';
692 die "unable to change media type\n" if $media ne $oldmedia;
694 if (!PVE
::QemuServer
::drive_is_cdrom
($old_drive) &&
695 ($drive->{file
} ne $old_drive->{file
})) { # delete old disks
697 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
698 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
701 if(&$safe_num_ne($drive->{mbps
}, $old_drive->{mbps
}) ||
702 &$safe_num_ne($drive->{mbps_rd
}, $old_drive->{mbps_rd
}) ||
703 &$safe_num_ne($drive->{mbps_wr
}, $old_drive->{mbps_wr
}) ||
704 &$safe_num_ne($drive->{iops
}, $old_drive->{iops
}) ||
705 &$safe_num_ne($drive->{iops_rd
}, $old_drive->{iops_rd
}) ||
706 &$safe_num_ne($drive->{iops_wr
}, $old_drive->{iops_wr
})) {
707 PVE
::QemuServer
::qemu_block_set_io_throttle
($vmid,"drive-$opt", $drive->{mbps
}*1024*1024,
708 $drive->{mbps_rd
}*1024*1024, $drive->{mbps_wr
}*1024*1024,
709 $drive->{iops
}, $drive->{iops_rd
}, $drive->{iops_wr
})
710 if !PVE
::QemuServer
::drive_is_cdrom
($drive);
715 &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, undef, {$opt => $value});
716 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
718 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
719 $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
721 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # cdrom
723 if (PVE
::QemuServer
::check_running
($vmid)) {
724 if ($drive->{file
} eq 'none') {
725 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt");
727 my $path = PVE
::QemuServer
::get_iso_path
($storecfg, $vmid, $drive->{file
});
728 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt"); #force eject if locked
729 PVE
::QemuServer
::vm_mon_cmd
($vmid, "change",device
=> "drive-$opt",target
=> "$path") if $path;
733 } else { # hotplug new disks
735 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $drive);
739 my $vmconfig_update_net = sub {
740 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value) = @_;
743 #if online update, then unplug first
744 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
747 $conf->{$opt} = $value;
748 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
749 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
751 my $net = PVE
::QemuServer
::parse_net
($conf->{$opt});
753 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $net);
756 my $vm_config_perm_list = [
766 __PACKAGE__-
>register_method({
768 path
=> '{vmid}/config',
772 description
=> "Set virtual machine options.",
774 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
777 additionalProperties
=> 0,
778 properties
=> PVE
::QemuServer
::json_config_properties
(
780 node
=> get_standard_option
('pve-node'),
781 vmid
=> get_standard_option
('pve-vmid'),
782 skiplock
=> get_standard_option
('skiplock'),
784 type
=> 'string', format
=> 'pve-configid-list',
785 description
=> "A list of settings you want to delete.",
790 description
=> $opt_force_description,
792 requires
=> 'delete',
796 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
802 returns
=> { type
=> 'null'},
806 my $rpcenv = PVE
::RPCEnvironment
::get
();
808 my $authuser = $rpcenv->get_user();
810 my $node = extract_param
($param, 'node');
812 my $vmid = extract_param
($param, 'vmid');
814 my $digest = extract_param
($param, 'digest');
816 my @paramarr = (); # used for log message
817 foreach my $key (keys %$param) {
818 push @paramarr, "-$key", $param->{$key};
821 my $skiplock = extract_param
($param, 'skiplock');
822 raise_param_exc
({ skiplock
=> "Only root may use this option." })
823 if $skiplock && $authuser ne 'root@pam';
825 my $delete_str = extract_param
($param, 'delete');
827 my $force = extract_param
($param, 'force');
829 die "no options specified\n" if !$delete_str && !scalar(keys %$param);
831 my $storecfg = PVE
::Storage
::config
();
833 &$resolve_cdrom_alias($param);
835 # now try to verify all parameters
838 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
839 $opt = 'ide2' if $opt eq 'cdrom';
840 raise_param_exc
({ delete => "you can't use '-$opt' and " .
841 "-delete $opt' at the same time" })
842 if defined($param->{$opt});
844 if (!PVE
::QemuServer
::option_exists
($opt)) {
845 raise_param_exc
({ delete => "unknown option '$opt'" });
851 foreach my $opt (keys %$param) {
852 if (PVE
::QemuServer
::valid_drivename
($opt)) {
854 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
855 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
856 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
857 } elsif ($opt =~ m/^net(\d+)$/) {
859 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
860 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
864 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
866 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
868 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
872 my $conf = PVE
::QemuServer
::load_config
($vmid);
874 die "checksum missmatch (file change by other user?)\n"
875 if $digest && $digest ne $conf->{digest
};
877 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
879 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
881 foreach my $opt (@delete) { # delete
882 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
883 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
886 foreach my $opt (keys %$param) { # add/change
888 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
890 next if $conf->{$opt} && ($param->{$opt} eq $conf->{$opt}); # skip if nothing changed
892 if (PVE
::QemuServer
::valid_drivename
($opt)) {
894 &$vmconfig_update_disk($rpcenv, $authuser, $conf, $storecfg, $vmid,
895 $opt, $param->{$opt}, $force);
897 } elsif ($opt =~ m/^net(\d+)$/) { #nics
899 &$vmconfig_update_net($rpcenv, $authuser, $conf, $storecfg, $vmid,
900 $opt, $param->{$opt});
904 $conf->{$opt} = $param->{$opt};
905 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
910 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
916 __PACKAGE__-
>register_method({
917 name
=> 'destroy_vm',
922 description
=> "Destroy the vm (also delete all used/owned volumes).",
924 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
927 additionalProperties
=> 0,
929 node
=> get_standard_option
('pve-node'),
930 vmid
=> get_standard_option
('pve-vmid'),
931 skiplock
=> get_standard_option
('skiplock'),
940 my $rpcenv = PVE
::RPCEnvironment
::get
();
942 my $authuser = $rpcenv->get_user();
944 my $vmid = $param->{vmid
};
946 my $skiplock = $param->{skiplock
};
947 raise_param_exc
({ skiplock
=> "Only root may use this option." })
948 if $skiplock && $authuser ne 'root@pam';
951 my $conf = PVE
::QemuServer
::load_config
($vmid);
953 my $storecfg = PVE
::Storage
::config
();
955 my $delVMfromPoolFn = sub {
956 my $usercfg = cfs_read_file
("user.cfg");
957 if (my $pool = $usercfg->{vms
}->{$vmid}) {
958 if (my $data = $usercfg->{pools
}->{$pool}) {
959 delete $data->{vms
}->{$vmid};
960 delete $usercfg->{vms
}->{$vmid};
961 cfs_write_file
("user.cfg", $usercfg);
969 syslog
('info', "destroy VM $vmid: $upid\n");
971 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
973 PVE
::AccessControl
::lock_user_config
($delVMfromPoolFn, "pool cleanup failed");
976 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
979 __PACKAGE__-
>register_method({
981 path
=> '{vmid}/unlink',
985 description
=> "Unlink/delete disk images.",
987 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
990 additionalProperties
=> 0,
992 node
=> get_standard_option
('pve-node'),
993 vmid
=> get_standard_option
('pve-vmid'),
995 type
=> 'string', format
=> 'pve-configid-list',
996 description
=> "A list of disk IDs you want to delete.",
1000 description
=> $opt_force_description,
1005 returns
=> { type
=> 'null'},
1009 $param->{delete} = extract_param
($param, 'idlist');
1011 __PACKAGE__-
>update_vm($param);
1018 __PACKAGE__-
>register_method({
1020 path
=> '{vmid}/vncproxy',
1024 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1026 description
=> "Creates a TCP VNC proxy connections.",
1028 additionalProperties
=> 0,
1030 node
=> get_standard_option
('pve-node'),
1031 vmid
=> get_standard_option
('pve-vmid'),
1035 additionalProperties
=> 0,
1037 user
=> { type
=> 'string' },
1038 ticket
=> { type
=> 'string' },
1039 cert
=> { type
=> 'string' },
1040 port
=> { type
=> 'integer' },
1041 upid
=> { type
=> 'string' },
1047 my $rpcenv = PVE
::RPCEnvironment
::get
();
1049 my $authuser = $rpcenv->get_user();
1051 my $vmid = $param->{vmid
};
1052 my $node = $param->{node
};
1054 my $authpath = "/vms/$vmid";
1056 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1058 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1061 my $port = PVE
::Tools
::next_vnc_port
();
1065 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1066 $remip = PVE
::Cluster
::remote_node_ip
($node);
1069 # NOTE: kvm VNC traffic is already TLS encrypted,
1070 # so we select the fastest chipher here (or 'none'?)
1071 my $remcmd = $remip ?
['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes',
1072 '-c', 'blowfish-cbc', $remip] : [];
1079 syslog
('info', "starting vnc proxy $upid\n");
1081 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1083 my $qmstr = join(' ', @$qmcmd);
1085 # also redirect stderr (else we get RFB protocol errors)
1086 my $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1088 PVE
::Tools
::run_command
($cmd);
1093 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1104 __PACKAGE__-
>register_method({
1106 path
=> '{vmid}/status',
1109 description
=> "Directory index",
1114 additionalProperties
=> 0,
1116 node
=> get_standard_option
('pve-node'),
1117 vmid
=> get_standard_option
('pve-vmid'),
1125 subdir
=> { type
=> 'string' },
1128 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1134 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1137 { subdir
=> 'current' },
1138 { subdir
=> 'start' },
1139 { subdir
=> 'stop' },
1145 my $vm_is_ha_managed = sub {
1148 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
1149 if (PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1)) {
1155 __PACKAGE__-
>register_method({
1156 name
=> 'vm_status',
1157 path
=> '{vmid}/status/current',
1160 protected
=> 1, # qemu pid files are only readable by root
1161 description
=> "Get virtual machine status.",
1163 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1166 additionalProperties
=> 0,
1168 node
=> get_standard_option
('pve-node'),
1169 vmid
=> get_standard_option
('pve-vmid'),
1172 returns
=> { type
=> 'object' },
1177 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1179 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1180 my $status = $vmstatus->{$param->{vmid
}};
1182 $status->{ha
} = &$vm_is_ha_managed($param->{vmid
});
1187 __PACKAGE__-
>register_method({
1189 path
=> '{vmid}/status/start',
1193 description
=> "Start virtual machine.",
1195 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1198 additionalProperties
=> 0,
1200 node
=> get_standard_option
('pve-node'),
1201 vmid
=> get_standard_option
('pve-vmid'),
1202 skiplock
=> get_standard_option
('skiplock'),
1203 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1204 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1214 my $rpcenv = PVE
::RPCEnvironment
::get
();
1216 my $authuser = $rpcenv->get_user();
1218 my $node = extract_param
($param, 'node');
1220 my $vmid = extract_param
($param, 'vmid');
1222 my $stateuri = extract_param
($param, 'stateuri');
1223 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1224 if $stateuri && $authuser ne 'root@pam';
1226 my $skiplock = extract_param
($param, 'skiplock');
1227 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1228 if $skiplock && $authuser ne 'root@pam';
1230 my $migratedfrom = extract_param
($param, 'migratedfrom');
1231 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1232 if $migratedfrom && $authuser ne 'root@pam';
1234 my $storecfg = PVE
::Storage
::config
();
1236 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1237 $rpcenv->{type
} ne 'ha') {
1242 my $service = "pvevm:$vmid";
1244 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1246 print "Executing HA start for VM $vmid\n";
1248 PVE
::Tools
::run_command
($cmd);
1253 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1260 syslog
('info', "start VM $vmid: $upid\n");
1262 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom);
1267 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1271 __PACKAGE__-
>register_method({
1273 path
=> '{vmid}/status/stop',
1277 description
=> "Stop virtual machine.",
1279 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1282 additionalProperties
=> 0,
1284 node
=> get_standard_option
('pve-node'),
1285 vmid
=> get_standard_option
('pve-vmid'),
1286 skiplock
=> get_standard_option
('skiplock'),
1287 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1289 description
=> "Wait maximal timeout seconds.",
1295 description
=> "Do not decativate storage volumes.",
1308 my $rpcenv = PVE
::RPCEnvironment
::get
();
1310 my $authuser = $rpcenv->get_user();
1312 my $node = extract_param
($param, 'node');
1314 my $vmid = extract_param
($param, 'vmid');
1316 my $skiplock = extract_param
($param, 'skiplock');
1317 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1318 if $skiplock && $authuser ne 'root@pam';
1320 my $keepActive = extract_param
($param, 'keepActive');
1321 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1322 if $keepActive && $authuser ne 'root@pam';
1324 my $migratedfrom = extract_param
($param, 'migratedfrom');
1325 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1326 if $migratedfrom && $authuser ne 'root@pam';
1329 my $storecfg = PVE
::Storage
::config
();
1331 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1336 my $service = "pvevm:$vmid";
1338 my $cmd = ['clusvcadm', '-d', $service];
1340 print "Executing HA stop for VM $vmid\n";
1342 PVE
::Tools
::run_command
($cmd);
1347 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1353 syslog
('info', "stop VM $vmid: $upid\n");
1355 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1356 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1361 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1365 __PACKAGE__-
>register_method({
1367 path
=> '{vmid}/status/reset',
1371 description
=> "Reset virtual machine.",
1373 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1376 additionalProperties
=> 0,
1378 node
=> get_standard_option
('pve-node'),
1379 vmid
=> get_standard_option
('pve-vmid'),
1380 skiplock
=> get_standard_option
('skiplock'),
1389 my $rpcenv = PVE
::RPCEnvironment
::get
();
1391 my $authuser = $rpcenv->get_user();
1393 my $node = extract_param
($param, 'node');
1395 my $vmid = extract_param
($param, 'vmid');
1397 my $skiplock = extract_param
($param, 'skiplock');
1398 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1399 if $skiplock && $authuser ne 'root@pam';
1401 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1406 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1411 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1414 __PACKAGE__-
>register_method({
1415 name
=> 'vm_shutdown',
1416 path
=> '{vmid}/status/shutdown',
1420 description
=> "Shutdown virtual machine.",
1422 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1425 additionalProperties
=> 0,
1427 node
=> get_standard_option
('pve-node'),
1428 vmid
=> get_standard_option
('pve-vmid'),
1429 skiplock
=> get_standard_option
('skiplock'),
1431 description
=> "Wait maximal timeout seconds.",
1437 description
=> "Make sure the VM stops.",
1443 description
=> "Do not decativate storage volumes.",
1456 my $rpcenv = PVE
::RPCEnvironment
::get
();
1458 my $authuser = $rpcenv->get_user();
1460 my $node = extract_param
($param, 'node');
1462 my $vmid = extract_param
($param, 'vmid');
1464 my $skiplock = extract_param
($param, 'skiplock');
1465 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1466 if $skiplock && $authuser ne 'root@pam';
1468 my $keepActive = extract_param
($param, 'keepActive');
1469 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1470 if $keepActive && $authuser ne 'root@pam';
1472 my $storecfg = PVE
::Storage
::config
();
1477 syslog
('info', "shutdown VM $vmid: $upid\n");
1479 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1480 1, $param->{forceStop
}, $keepActive);
1485 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1488 __PACKAGE__-
>register_method({
1489 name
=> 'vm_suspend',
1490 path
=> '{vmid}/status/suspend',
1494 description
=> "Suspend virtual machine.",
1496 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1499 additionalProperties
=> 0,
1501 node
=> get_standard_option
('pve-node'),
1502 vmid
=> get_standard_option
('pve-vmid'),
1503 skiplock
=> get_standard_option
('skiplock'),
1512 my $rpcenv = PVE
::RPCEnvironment
::get
();
1514 my $authuser = $rpcenv->get_user();
1516 my $node = extract_param
($param, 'node');
1518 my $vmid = extract_param
($param, 'vmid');
1520 my $skiplock = extract_param
($param, 'skiplock');
1521 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1522 if $skiplock && $authuser ne 'root@pam';
1524 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1529 syslog
('info', "suspend VM $vmid: $upid\n");
1531 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1536 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1539 __PACKAGE__-
>register_method({
1540 name
=> 'vm_resume',
1541 path
=> '{vmid}/status/resume',
1545 description
=> "Resume virtual machine.",
1547 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1550 additionalProperties
=> 0,
1552 node
=> get_standard_option
('pve-node'),
1553 vmid
=> get_standard_option
('pve-vmid'),
1554 skiplock
=> get_standard_option
('skiplock'),
1563 my $rpcenv = PVE
::RPCEnvironment
::get
();
1565 my $authuser = $rpcenv->get_user();
1567 my $node = extract_param
($param, 'node');
1569 my $vmid = extract_param
($param, 'vmid');
1571 my $skiplock = extract_param
($param, 'skiplock');
1572 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1573 if $skiplock && $authuser ne 'root@pam';
1575 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1580 syslog
('info', "resume VM $vmid: $upid\n");
1582 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1587 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1590 __PACKAGE__-
>register_method({
1591 name
=> 'vm_sendkey',
1592 path
=> '{vmid}/sendkey',
1596 description
=> "Send key event to virtual machine.",
1598 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1601 additionalProperties
=> 0,
1603 node
=> get_standard_option
('pve-node'),
1604 vmid
=> get_standard_option
('pve-vmid'),
1605 skiplock
=> get_standard_option
('skiplock'),
1607 description
=> "The key (qemu monitor encoding).",
1612 returns
=> { type
=> 'null'},
1616 my $rpcenv = PVE
::RPCEnvironment
::get
();
1618 my $authuser = $rpcenv->get_user();
1620 my $node = extract_param
($param, 'node');
1622 my $vmid = extract_param
($param, 'vmid');
1624 my $skiplock = extract_param
($param, 'skiplock');
1625 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1626 if $skiplock && $authuser ne 'root@pam';
1628 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1633 __PACKAGE__-
>register_method({
1634 name
=> 'migrate_vm',
1635 path
=> '{vmid}/migrate',
1639 description
=> "Migrate virtual machine. Creates a new migration task.",
1641 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
1644 additionalProperties
=> 0,
1646 node
=> get_standard_option
('pve-node'),
1647 vmid
=> get_standard_option
('pve-vmid'),
1648 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
1651 description
=> "Use online/live migration.",
1656 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
1663 description
=> "the task ID.",
1668 my $rpcenv = PVE
::RPCEnvironment
::get
();
1670 my $authuser = $rpcenv->get_user();
1672 my $target = extract_param
($param, 'target');
1674 my $localnode = PVE
::INotify
::nodename
();
1675 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
1677 PVE
::Cluster
::check_cfs_quorum
();
1679 PVE
::Cluster
::check_node_exists
($target);
1681 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
1683 my $vmid = extract_param
($param, 'vmid');
1685 raise_param_exc
({ force
=> "Only root may use this option." })
1686 if $param->{force
} && $authuser ne 'root@pam';
1689 my $conf = PVE
::QemuServer
::load_config
($vmid);
1691 # try to detect errors early
1693 PVE
::QemuServer
::check_lock
($conf);
1695 if (PVE
::QemuServer
::check_running
($vmid)) {
1696 die "cant migrate running VM without --online\n"
1697 if !$param->{online
};
1700 my $storecfg = PVE
::Storage
::config
();
1701 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
1703 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1708 my $service = "pvevm:$vmid";
1710 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
1712 print "Executing HA migrate for VM $vmid to node $target\n";
1714 PVE
::Tools
::run_command
($cmd);
1719 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
1726 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
1729 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
1734 __PACKAGE__-
>register_method({
1736 path
=> '{vmid}/monitor',
1740 description
=> "Execute Qemu monitor commands.",
1742 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
1745 additionalProperties
=> 0,
1747 node
=> get_standard_option
('pve-node'),
1748 vmid
=> get_standard_option
('pve-vmid'),
1751 description
=> "The monitor command.",
1755 returns
=> { type
=> 'string'},
1759 my $vmid = $param->{vmid
};
1761 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
1765 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
1767 $res = "ERROR: $@" if $@;
1772 __PACKAGE__-
>register_method({
1773 name
=> 'resize_vm',
1774 path
=> '{vmid}/resize',
1778 description
=> "Extend volume size.",
1780 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
1783 additionalProperties
=> 0,
1785 node
=> get_standard_option
('pve-node'),
1786 vmid
=> get_standard_option
('pve-vmid'),
1787 skiplock
=> get_standard_option
('skiplock'),
1790 description
=> "The disk you want to resize.",
1791 enum
=> [PVE
::QemuServer
::disknames
()],
1795 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
1796 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.",
1800 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1806 returns
=> { type
=> 'null'},
1810 my $rpcenv = PVE
::RPCEnvironment
::get
();
1812 my $authuser = $rpcenv->get_user();
1814 my $node = extract_param
($param, 'node');
1816 my $vmid = extract_param
($param, 'vmid');
1818 my $digest = extract_param
($param, 'digest');
1820 my $disk = extract_param
($param, 'disk');
1822 my $sizestr = extract_param
($param, 'size');
1824 my $skiplock = extract_param
($param, 'skiplock');
1825 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1826 if $skiplock && $authuser ne 'root@pam';
1828 my $storecfg = PVE
::Storage
::config
();
1830 my $updatefn = sub {
1832 my $conf = PVE
::QemuServer
::load_config
($vmid);
1834 die "checksum missmatch (file change by other user?)\n"
1835 if $digest && $digest ne $conf->{digest
};
1836 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
1838 die "disk '$disk' does not exist\n" if !$conf->{$disk};
1840 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
1842 my $volid = $drive->{file
};
1844 die "disk '$disk' has no associated volume\n" if !$volid;
1846 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
1848 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1850 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1852 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
1854 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
1855 my ($ext, $newsize, $unit) = ($1, $2, $4);
1858 $newsize = $newsize * 1024;
1859 } elsif ($unit eq 'M') {
1860 $newsize = $newsize * 1024 * 1024;
1861 } elsif ($unit eq 'G') {
1862 $newsize = $newsize * 1024 * 1024 * 1024;
1863 } elsif ($unit eq 'T') {
1864 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
1867 $newsize += $size if $ext;
1868 $newsize = int($newsize);
1870 die "unable to skrink disk size\n" if $newsize < $size;
1872 return if $size == $newsize;
1874 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
1876 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
1878 $drive->{size
} = $newsize;
1879 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
1881 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
1884 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
1888 __PACKAGE__-
>register_method({
1889 name
=> 'snapshot_list',
1890 path
=> '{vmid}/snapshot',
1892 description
=> "List all snapshots.",
1894 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1897 protected
=> 1, # qemu pid files are only readable by root
1899 additionalProperties
=> 0,
1901 vmid
=> get_standard_option
('pve-vmid'),
1902 node
=> get_standard_option
('pve-node'),
1911 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
1916 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1917 my $snaphash = $conf->{snapshots
} || {};
1921 foreach my $name (keys %$snaphash) {
1922 push @$res, { name
=> $name };
1928 __PACKAGE__-
>register_method({
1930 path
=> '{vmid}/snapshot',
1934 description
=> "Snapshot a VM.",
1936 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
1939 additionalProperties
=> 0,
1941 node
=> get_standard_option
('pve-node'),
1942 vmid
=> get_standard_option
('pve-vmid'),
1945 description
=> "The name of the snapshot",
1951 description
=> "Save the vmstate",
1956 description
=> "Freeze the filesystem",
1962 description
=> "the task ID.",
1967 my $rpcenv = PVE
::RPCEnvironment
::get
();
1969 my $authuser = $rpcenv->get_user();
1971 my $node = extract_param
($param, 'node');
1973 my $vmid = extract_param
($param, 'vmid');
1975 my $snapname = extract_param
($param, 'snapname');
1977 my $vmstate = extract_param
($param, 'vmstate');
1979 my $freezefs = extract_param
($param, 'freezefs');
1981 # fixme: access rights?
1982 # &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $conf);
1983 # fixme: need to implement a check to see if all storages support snapshots
1986 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
1987 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $vmstate, $freezefs);
1990 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
1993 __PACKAGE__-
>register_method({
1995 path
=> '{vmid}/rollback',
1999 description
=> "Rollback VM state to specified snapshot.",
2001 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2004 additionalProperties
=> 0,
2006 node
=> get_standard_option
('pve-node'),
2007 vmid
=> get_standard_option
('pve-vmid'),
2010 description
=> "The name of the snapshot",
2017 description
=> "the task ID.",
2022 my $rpcenv = PVE
::RPCEnvironment
::get
();
2024 my $authuser = $rpcenv->get_user();
2026 my $node = extract_param
($param, 'node');
2028 my $vmid = extract_param
($param, 'vmid');
2030 my $snapname = extract_param
($param, 'snapname');
2032 # fixme: access rights?
2035 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2036 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
2039 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2042 __PACKAGE__-
>register_method({
2043 name
=> 'delsnapshot',
2044 path
=> '{vmid}/snapshot/{snapname}',
2048 description
=> "Delete a VM snapshot.",
2050 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2053 additionalProperties
=> 0,
2055 node
=> get_standard_option
('pve-node'),
2056 vmid
=> get_standard_option
('pve-vmid'),
2059 description
=> "The name of the snapshot",
2066 description
=> "the task ID.",
2071 my $rpcenv = PVE
::RPCEnvironment
::get
();
2073 my $authuser = $rpcenv->get_user();
2075 my $node = extract_param
($param, 'node');
2077 my $vmid = extract_param
($param, 'vmid');
2079 my $snapname = extract_param
($param, 'snapname');
2081 # fixme: access rights?
2084 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
2085 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname);
2088 return $rpcenv->fork_worker('qmdelsnaphot', $vmid, $authuser, $realcmd);