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' },
465 __PACKAGE__-
>register_method({
467 path
=> '{vmid}/rrd',
469 protected
=> 1, # fixme: can we avoid that?
471 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
473 description
=> "Read VM RRD statistics (returns PNG)",
475 additionalProperties
=> 0,
477 node
=> get_standard_option
('pve-node'),
478 vmid
=> get_standard_option
('pve-vmid'),
480 description
=> "Specify the time frame you are interested in.",
482 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
485 description
=> "The list of datasources you want to display.",
486 type
=> 'string', format
=> 'pve-configid-list',
489 description
=> "The RRD consolidation function",
491 enum
=> [ 'AVERAGE', 'MAX' ],
499 filename
=> { type
=> 'string' },
505 return PVE
::Cluster
::create_rrd_graph
(
506 "pve2-vm/$param->{vmid}", $param->{timeframe
},
507 $param->{ds
}, $param->{cf
});
511 __PACKAGE__-
>register_method({
513 path
=> '{vmid}/rrddata',
515 protected
=> 1, # fixme: can we avoid that?
517 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
519 description
=> "Read VM RRD statistics",
521 additionalProperties
=> 0,
523 node
=> get_standard_option
('pve-node'),
524 vmid
=> get_standard_option
('pve-vmid'),
526 description
=> "Specify the time frame you are interested in.",
528 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
531 description
=> "The RRD consolidation function",
533 enum
=> [ 'AVERAGE', 'MAX' ],
548 return PVE
::Cluster
::create_rrd_data
(
549 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
553 __PACKAGE__-
>register_method({
555 path
=> '{vmid}/config',
558 description
=> "Get virtual machine configuration.",
560 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
563 additionalProperties
=> 0,
565 node
=> get_standard_option
('pve-node'),
566 vmid
=> get_standard_option
('pve-vmid'),
574 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
581 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
583 delete $conf->{snapshots
};
588 my $vm_is_volid_owner = sub {
589 my ($storecfg, $vmid, $volid) =@_;
591 if ($volid !~ m
|^/|) {
593 eval { ($path, $owner) = PVE
::Storage
::path
($storecfg, $volid); };
594 if ($owner && ($owner == $vmid)) {
602 my $test_deallocate_drive = sub {
603 my ($storecfg, $vmid, $key, $drive, $force) = @_;
605 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
606 my $volid = $drive->{file
};
607 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
608 if ($force || $key =~ m/^unused/) {
609 my $sid = PVE
::Storage
::parse_volume_id
($volid);
618 my $delete_drive = sub {
619 my ($conf, $storecfg, $vmid, $key, $drive, $force) = @_;
621 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
622 my $volid = $drive->{file
};
623 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
624 if ($force || $key =~ m/^unused/) {
625 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
628 PVE
::QemuServer
::add_unused_volume
($conf, $volid, $vmid);
633 delete $conf->{$key};
636 my $vmconfig_delete_option = sub {
637 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force) = @_;
639 return if !defined($conf->{$opt});
641 my $isDisk = PVE
::QemuServer
::valid_drivename
($opt)|| ($opt =~ m/^unused/);
644 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
646 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
647 if (my $sid = &$test_deallocate_drive($storecfg, $vmid, $opt, $drive, $force)) {
648 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.Allocate']);
652 die "error hot-unplug $opt" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
655 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
656 &$delete_drive($conf, $storecfg, $vmid, $opt, $drive, $force);
658 delete $conf->{$opt};
661 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
664 my $safe_num_ne = sub {
667 return 0 if !defined($a) && !defined($b);
668 return 1 if !defined($a);
669 return 1 if !defined($b);
674 my $vmconfig_update_disk = sub {
675 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value, $force) = @_;
677 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
679 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { #cdrom
680 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
682 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
687 if (my $old_drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt})) {
689 my $media = $drive->{media
} || 'disk';
690 my $oldmedia = $old_drive->{media
} || 'disk';
691 die "unable to change media type\n" if $media ne $oldmedia;
693 if (!PVE
::QemuServer
::drive_is_cdrom
($old_drive) &&
694 ($drive->{file
} ne $old_drive->{file
})) { # delete old disks
696 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
697 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
700 if(&$safe_num_ne($drive->{mbps
}, $old_drive->{mbps
}) ||
701 &$safe_num_ne($drive->{mbps_rd
}, $old_drive->{mbps_rd
}) ||
702 &$safe_num_ne($drive->{mbps_wr
}, $old_drive->{mbps_wr
}) ||
703 &$safe_num_ne($drive->{iops
}, $old_drive->{iops
}) ||
704 &$safe_num_ne($drive->{iops_rd
}, $old_drive->{iops_rd
}) ||
705 &$safe_num_ne($drive->{iops_wr
}, $old_drive->{iops_wr
})) {
706 PVE
::QemuServer
::qemu_block_set_io_throttle
($vmid,"drive-$opt", $drive->{mbps
}*1024*1024,
707 $drive->{mbps_rd
}*1024*1024, $drive->{mbps_wr
}*1024*1024,
708 $drive->{iops
}, $drive->{iops_rd
}, $drive->{iops_wr
})
709 if !PVE
::QemuServer
::drive_is_cdrom
($drive);
714 &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, undef, {$opt => $value});
715 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
717 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
718 $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
720 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # cdrom
722 if (PVE
::QemuServer
::check_running
($vmid)) {
723 if ($drive->{file
} eq 'none') {
724 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt");
726 my $path = PVE
::QemuServer
::get_iso_path
($storecfg, $vmid, $drive->{file
});
727 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt"); #force eject if locked
728 PVE
::QemuServer
::vm_mon_cmd
($vmid, "change",device
=> "drive-$opt",target
=> "$path") if $path;
732 } else { # hotplug new disks
734 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $drive);
738 my $vmconfig_update_net = sub {
739 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value) = @_;
742 #if online update, then unplug first
743 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
746 $conf->{$opt} = $value;
747 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
748 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
750 my $net = PVE
::QemuServer
::parse_net
($conf->{$opt});
752 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $net);
755 my $vm_config_perm_list = [
765 __PACKAGE__-
>register_method({
767 path
=> '{vmid}/config',
771 description
=> "Set virtual machine options.",
773 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
776 additionalProperties
=> 0,
777 properties
=> PVE
::QemuServer
::json_config_properties
(
779 node
=> get_standard_option
('pve-node'),
780 vmid
=> get_standard_option
('pve-vmid'),
781 skiplock
=> get_standard_option
('skiplock'),
783 type
=> 'string', format
=> 'pve-configid-list',
784 description
=> "A list of settings you want to delete.",
789 description
=> $opt_force_description,
791 requires
=> 'delete',
795 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
801 returns
=> { type
=> 'null'},
805 my $rpcenv = PVE
::RPCEnvironment
::get
();
807 my $authuser = $rpcenv->get_user();
809 my $node = extract_param
($param, 'node');
811 my $vmid = extract_param
($param, 'vmid');
813 my $digest = extract_param
($param, 'digest');
815 my @paramarr = (); # used for log message
816 foreach my $key (keys %$param) {
817 push @paramarr, "-$key", $param->{$key};
820 my $skiplock = extract_param
($param, 'skiplock');
821 raise_param_exc
({ skiplock
=> "Only root may use this option." })
822 if $skiplock && $authuser ne 'root@pam';
824 my $delete_str = extract_param
($param, 'delete');
826 my $force = extract_param
($param, 'force');
828 die "no options specified\n" if !$delete_str && !scalar(keys %$param);
830 my $storecfg = PVE
::Storage
::config
();
832 &$resolve_cdrom_alias($param);
834 # now try to verify all parameters
837 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
838 $opt = 'ide2' if $opt eq 'cdrom';
839 raise_param_exc
({ delete => "you can't use '-$opt' and " .
840 "-delete $opt' at the same time" })
841 if defined($param->{$opt});
843 if (!PVE
::QemuServer
::option_exists
($opt)) {
844 raise_param_exc
({ delete => "unknown option '$opt'" });
850 foreach my $opt (keys %$param) {
851 if (PVE
::QemuServer
::valid_drivename
($opt)) {
853 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
854 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
855 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
856 } elsif ($opt =~ m/^net(\d+)$/) {
858 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
859 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
863 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
865 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
867 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
871 my $conf = PVE
::QemuServer
::load_config
($vmid);
873 die "checksum missmatch (file change by other user?)\n"
874 if $digest && $digest ne $conf->{digest
};
876 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
878 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
880 foreach my $opt (@delete) { # delete
881 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
882 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
885 foreach my $opt (keys %$param) { # add/change
887 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
889 next if $conf->{$opt} && ($param->{$opt} eq $conf->{$opt}); # skip if nothing changed
891 if (PVE
::QemuServer
::valid_drivename
($opt)) {
893 &$vmconfig_update_disk($rpcenv, $authuser, $conf, $storecfg, $vmid,
894 $opt, $param->{$opt}, $force);
896 } elsif ($opt =~ m/^net(\d+)$/) { #nics
898 &$vmconfig_update_net($rpcenv, $authuser, $conf, $storecfg, $vmid,
899 $opt, $param->{$opt});
903 $conf->{$opt} = $param->{$opt};
904 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
909 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
915 __PACKAGE__-
>register_method({
916 name
=> 'destroy_vm',
921 description
=> "Destroy the vm (also delete all used/owned volumes).",
923 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
926 additionalProperties
=> 0,
928 node
=> get_standard_option
('pve-node'),
929 vmid
=> get_standard_option
('pve-vmid'),
930 skiplock
=> get_standard_option
('skiplock'),
939 my $rpcenv = PVE
::RPCEnvironment
::get
();
941 my $authuser = $rpcenv->get_user();
943 my $vmid = $param->{vmid
};
945 my $skiplock = $param->{skiplock
};
946 raise_param_exc
({ skiplock
=> "Only root may use this option." })
947 if $skiplock && $authuser ne 'root@pam';
950 my $conf = PVE
::QemuServer
::load_config
($vmid);
952 my $storecfg = PVE
::Storage
::config
();
954 my $delVMfromPoolFn = sub {
955 my $usercfg = cfs_read_file
("user.cfg");
956 if (my $pool = $usercfg->{vms
}->{$vmid}) {
957 if (my $data = $usercfg->{pools
}->{$pool}) {
958 delete $data->{vms
}->{$vmid};
959 delete $usercfg->{vms
}->{$vmid};
960 cfs_write_file
("user.cfg", $usercfg);
968 syslog
('info', "destroy VM $vmid: $upid\n");
970 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
972 PVE
::AccessControl
::lock_user_config
($delVMfromPoolFn, "pool cleanup failed");
975 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
978 __PACKAGE__-
>register_method({
980 path
=> '{vmid}/unlink',
984 description
=> "Unlink/delete disk images.",
986 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
989 additionalProperties
=> 0,
991 node
=> get_standard_option
('pve-node'),
992 vmid
=> get_standard_option
('pve-vmid'),
994 type
=> 'string', format
=> 'pve-configid-list',
995 description
=> "A list of disk IDs you want to delete.",
999 description
=> $opt_force_description,
1004 returns
=> { type
=> 'null'},
1008 $param->{delete} = extract_param
($param, 'idlist');
1010 __PACKAGE__-
>update_vm($param);
1017 __PACKAGE__-
>register_method({
1019 path
=> '{vmid}/vncproxy',
1023 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1025 description
=> "Creates a TCP VNC proxy connections.",
1027 additionalProperties
=> 0,
1029 node
=> get_standard_option
('pve-node'),
1030 vmid
=> get_standard_option
('pve-vmid'),
1034 additionalProperties
=> 0,
1036 user
=> { type
=> 'string' },
1037 ticket
=> { type
=> 'string' },
1038 cert
=> { type
=> 'string' },
1039 port
=> { type
=> 'integer' },
1040 upid
=> { type
=> 'string' },
1046 my $rpcenv = PVE
::RPCEnvironment
::get
();
1048 my $authuser = $rpcenv->get_user();
1050 my $vmid = $param->{vmid
};
1051 my $node = $param->{node
};
1053 my $authpath = "/vms/$vmid";
1055 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1057 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1060 my $port = PVE
::Tools
::next_vnc_port
();
1064 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1065 $remip = PVE
::Cluster
::remote_node_ip
($node);
1068 # NOTE: kvm VNC traffic is already TLS encrypted,
1069 # so we select the fastest chipher here (or 'none'?)
1070 my $remcmd = $remip ?
['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes',
1071 '-c', 'blowfish-cbc', $remip] : [];
1078 syslog
('info', "starting vnc proxy $upid\n");
1080 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1082 my $qmstr = join(' ', @$qmcmd);
1084 # also redirect stderr (else we get RFB protocol errors)
1085 my $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1087 PVE
::Tools
::run_command
($cmd);
1092 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1103 __PACKAGE__-
>register_method({
1105 path
=> '{vmid}/status',
1108 description
=> "Directory index",
1113 additionalProperties
=> 0,
1115 node
=> get_standard_option
('pve-node'),
1116 vmid
=> get_standard_option
('pve-vmid'),
1124 subdir
=> { type
=> 'string' },
1127 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1133 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1136 { subdir
=> 'current' },
1137 { subdir
=> 'start' },
1138 { subdir
=> 'stop' },
1144 my $vm_is_ha_managed = sub {
1147 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
1148 if (PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1)) {
1154 __PACKAGE__-
>register_method({
1155 name
=> 'vm_status',
1156 path
=> '{vmid}/status/current',
1159 protected
=> 1, # qemu pid files are only readable by root
1160 description
=> "Get virtual machine status.",
1162 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1165 additionalProperties
=> 0,
1167 node
=> get_standard_option
('pve-node'),
1168 vmid
=> get_standard_option
('pve-vmid'),
1171 returns
=> { type
=> 'object' },
1176 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1178 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1179 my $status = $vmstatus->{$param->{vmid
}};
1181 $status->{ha
} = &$vm_is_ha_managed($param->{vmid
});
1186 __PACKAGE__-
>register_method({
1188 path
=> '{vmid}/status/start',
1192 description
=> "Start virtual machine.",
1194 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1197 additionalProperties
=> 0,
1199 node
=> get_standard_option
('pve-node'),
1200 vmid
=> get_standard_option
('pve-vmid'),
1201 skiplock
=> get_standard_option
('skiplock'),
1202 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1203 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1213 my $rpcenv = PVE
::RPCEnvironment
::get
();
1215 my $authuser = $rpcenv->get_user();
1217 my $node = extract_param
($param, 'node');
1219 my $vmid = extract_param
($param, 'vmid');
1221 my $stateuri = extract_param
($param, 'stateuri');
1222 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1223 if $stateuri && $authuser ne 'root@pam';
1225 my $skiplock = extract_param
($param, 'skiplock');
1226 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1227 if $skiplock && $authuser ne 'root@pam';
1229 my $migratedfrom = extract_param
($param, 'migratedfrom');
1230 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1231 if $migratedfrom && $authuser ne 'root@pam';
1233 my $storecfg = PVE
::Storage
::config
();
1235 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1236 $rpcenv->{type
} ne 'ha') {
1241 my $service = "pvevm:$vmid";
1243 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1245 print "Executing HA start for VM $vmid\n";
1247 PVE
::Tools
::run_command
($cmd);
1252 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1259 syslog
('info', "start VM $vmid: $upid\n");
1261 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom);
1266 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1270 __PACKAGE__-
>register_method({
1272 path
=> '{vmid}/status/stop',
1276 description
=> "Stop virtual machine.",
1278 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1281 additionalProperties
=> 0,
1283 node
=> get_standard_option
('pve-node'),
1284 vmid
=> get_standard_option
('pve-vmid'),
1285 skiplock
=> get_standard_option
('skiplock'),
1286 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1288 description
=> "Wait maximal timeout seconds.",
1294 description
=> "Do not decativate storage volumes.",
1307 my $rpcenv = PVE
::RPCEnvironment
::get
();
1309 my $authuser = $rpcenv->get_user();
1311 my $node = extract_param
($param, 'node');
1313 my $vmid = extract_param
($param, 'vmid');
1315 my $skiplock = extract_param
($param, 'skiplock');
1316 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1317 if $skiplock && $authuser ne 'root@pam';
1319 my $keepActive = extract_param
($param, 'keepActive');
1320 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1321 if $keepActive && $authuser ne 'root@pam';
1323 my $migratedfrom = extract_param
($param, 'migratedfrom');
1324 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1325 if $migratedfrom && $authuser ne 'root@pam';
1328 my $storecfg = PVE
::Storage
::config
();
1330 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1335 my $service = "pvevm:$vmid";
1337 my $cmd = ['clusvcadm', '-d', $service];
1339 print "Executing HA stop for VM $vmid\n";
1341 PVE
::Tools
::run_command
($cmd);
1346 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1352 syslog
('info', "stop VM $vmid: $upid\n");
1354 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1355 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1360 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1364 __PACKAGE__-
>register_method({
1366 path
=> '{vmid}/status/reset',
1370 description
=> "Reset virtual machine.",
1372 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1375 additionalProperties
=> 0,
1377 node
=> get_standard_option
('pve-node'),
1378 vmid
=> get_standard_option
('pve-vmid'),
1379 skiplock
=> get_standard_option
('skiplock'),
1388 my $rpcenv = PVE
::RPCEnvironment
::get
();
1390 my $authuser = $rpcenv->get_user();
1392 my $node = extract_param
($param, 'node');
1394 my $vmid = extract_param
($param, 'vmid');
1396 my $skiplock = extract_param
($param, 'skiplock');
1397 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1398 if $skiplock && $authuser ne 'root@pam';
1400 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1405 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1410 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1413 __PACKAGE__-
>register_method({
1414 name
=> 'vm_shutdown',
1415 path
=> '{vmid}/status/shutdown',
1419 description
=> "Shutdown virtual machine.",
1421 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1424 additionalProperties
=> 0,
1426 node
=> get_standard_option
('pve-node'),
1427 vmid
=> get_standard_option
('pve-vmid'),
1428 skiplock
=> get_standard_option
('skiplock'),
1430 description
=> "Wait maximal timeout seconds.",
1436 description
=> "Make sure the VM stops.",
1442 description
=> "Do not decativate storage volumes.",
1455 my $rpcenv = PVE
::RPCEnvironment
::get
();
1457 my $authuser = $rpcenv->get_user();
1459 my $node = extract_param
($param, 'node');
1461 my $vmid = extract_param
($param, 'vmid');
1463 my $skiplock = extract_param
($param, 'skiplock');
1464 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1465 if $skiplock && $authuser ne 'root@pam';
1467 my $keepActive = extract_param
($param, 'keepActive');
1468 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1469 if $keepActive && $authuser ne 'root@pam';
1471 my $storecfg = PVE
::Storage
::config
();
1476 syslog
('info', "shutdown VM $vmid: $upid\n");
1478 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1479 1, $param->{forceStop
}, $keepActive);
1484 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1487 __PACKAGE__-
>register_method({
1488 name
=> 'vm_suspend',
1489 path
=> '{vmid}/status/suspend',
1493 description
=> "Suspend virtual machine.",
1495 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1498 additionalProperties
=> 0,
1500 node
=> get_standard_option
('pve-node'),
1501 vmid
=> get_standard_option
('pve-vmid'),
1502 skiplock
=> get_standard_option
('skiplock'),
1511 my $rpcenv = PVE
::RPCEnvironment
::get
();
1513 my $authuser = $rpcenv->get_user();
1515 my $node = extract_param
($param, 'node');
1517 my $vmid = extract_param
($param, 'vmid');
1519 my $skiplock = extract_param
($param, 'skiplock');
1520 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1521 if $skiplock && $authuser ne 'root@pam';
1523 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1528 syslog
('info', "suspend VM $vmid: $upid\n");
1530 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1535 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1538 __PACKAGE__-
>register_method({
1539 name
=> 'vm_resume',
1540 path
=> '{vmid}/status/resume',
1544 description
=> "Resume virtual machine.",
1546 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1549 additionalProperties
=> 0,
1551 node
=> get_standard_option
('pve-node'),
1552 vmid
=> get_standard_option
('pve-vmid'),
1553 skiplock
=> get_standard_option
('skiplock'),
1562 my $rpcenv = PVE
::RPCEnvironment
::get
();
1564 my $authuser = $rpcenv->get_user();
1566 my $node = extract_param
($param, 'node');
1568 my $vmid = extract_param
($param, 'vmid');
1570 my $skiplock = extract_param
($param, 'skiplock');
1571 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1572 if $skiplock && $authuser ne 'root@pam';
1574 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1579 syslog
('info', "resume VM $vmid: $upid\n");
1581 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1586 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1589 __PACKAGE__-
>register_method({
1590 name
=> 'vm_sendkey',
1591 path
=> '{vmid}/sendkey',
1595 description
=> "Send key event to virtual machine.",
1597 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1600 additionalProperties
=> 0,
1602 node
=> get_standard_option
('pve-node'),
1603 vmid
=> get_standard_option
('pve-vmid'),
1604 skiplock
=> get_standard_option
('skiplock'),
1606 description
=> "The key (qemu monitor encoding).",
1611 returns
=> { type
=> 'null'},
1615 my $rpcenv = PVE
::RPCEnvironment
::get
();
1617 my $authuser = $rpcenv->get_user();
1619 my $node = extract_param
($param, 'node');
1621 my $vmid = extract_param
($param, 'vmid');
1623 my $skiplock = extract_param
($param, 'skiplock');
1624 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1625 if $skiplock && $authuser ne 'root@pam';
1627 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1632 __PACKAGE__-
>register_method({
1633 name
=> 'migrate_vm',
1634 path
=> '{vmid}/migrate',
1638 description
=> "Migrate virtual machine. Creates a new migration task.",
1640 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
1643 additionalProperties
=> 0,
1645 node
=> get_standard_option
('pve-node'),
1646 vmid
=> get_standard_option
('pve-vmid'),
1647 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
1650 description
=> "Use online/live migration.",
1655 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
1662 description
=> "the task ID.",
1667 my $rpcenv = PVE
::RPCEnvironment
::get
();
1669 my $authuser = $rpcenv->get_user();
1671 my $target = extract_param
($param, 'target');
1673 my $localnode = PVE
::INotify
::nodename
();
1674 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
1676 PVE
::Cluster
::check_cfs_quorum
();
1678 PVE
::Cluster
::check_node_exists
($target);
1680 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
1682 my $vmid = extract_param
($param, 'vmid');
1684 raise_param_exc
({ force
=> "Only root may use this option." })
1685 if $param->{force
} && $authuser ne 'root@pam';
1688 my $conf = PVE
::QemuServer
::load_config
($vmid);
1690 # try to detect errors early
1692 PVE
::QemuServer
::check_lock
($conf);
1694 if (PVE
::QemuServer
::check_running
($vmid)) {
1695 die "cant migrate running VM without --online\n"
1696 if !$param->{online
};
1699 my $storecfg = PVE
::Storage
::config
();
1700 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
1702 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1707 my $service = "pvevm:$vmid";
1709 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
1711 print "Executing HA migrate for VM $vmid to node $target\n";
1713 PVE
::Tools
::run_command
($cmd);
1718 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
1725 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
1728 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
1733 __PACKAGE__-
>register_method({
1735 path
=> '{vmid}/monitor',
1739 description
=> "Execute Qemu monitor commands.",
1741 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
1744 additionalProperties
=> 0,
1746 node
=> get_standard_option
('pve-node'),
1747 vmid
=> get_standard_option
('pve-vmid'),
1750 description
=> "The monitor command.",
1754 returns
=> { type
=> 'string'},
1758 my $vmid = $param->{vmid
};
1760 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
1764 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
1766 $res = "ERROR: $@" if $@;
1771 __PACKAGE__-
>register_method({
1772 name
=> 'resize_vm',
1773 path
=> '{vmid}/resize',
1777 description
=> "Extend volume size.",
1779 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
1782 additionalProperties
=> 0,
1784 node
=> get_standard_option
('pve-node'),
1785 vmid
=> get_standard_option
('pve-vmid'),
1786 skiplock
=> get_standard_option
('skiplock'),
1789 description
=> "The disk you want to resize.",
1790 enum
=> [PVE
::QemuServer
::disknames
()],
1794 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
1795 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.",
1799 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1805 returns
=> { type
=> 'null'},
1809 my $rpcenv = PVE
::RPCEnvironment
::get
();
1811 my $authuser = $rpcenv->get_user();
1813 my $node = extract_param
($param, 'node');
1815 my $vmid = extract_param
($param, 'vmid');
1817 my $digest = extract_param
($param, 'digest');
1819 my $disk = extract_param
($param, 'disk');
1821 my $sizestr = extract_param
($param, 'size');
1823 my $skiplock = extract_param
($param, 'skiplock');
1824 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1825 if $skiplock && $authuser ne 'root@pam';
1827 my $storecfg = PVE
::Storage
::config
();
1829 my $updatefn = sub {
1831 my $conf = PVE
::QemuServer
::load_config
($vmid);
1833 die "checksum missmatch (file change by other user?)\n"
1834 if $digest && $digest ne $conf->{digest
};
1835 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
1837 die "disk '$disk' does not exist\n" if !$conf->{$disk};
1839 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
1841 my $volid = $drive->{file
};
1843 die "disk '$disk' has no associated volume\n" if !$volid;
1845 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
1847 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1849 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1851 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
1853 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
1854 my ($ext, $newsize, $unit) = ($1, $2, $4);
1857 $newsize = $newsize * 1024;
1858 } elsif ($unit eq 'M') {
1859 $newsize = $newsize * 1024 * 1024;
1860 } elsif ($unit eq 'G') {
1861 $newsize = $newsize * 1024 * 1024 * 1024;
1862 } elsif ($unit eq 'T') {
1863 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
1866 $newsize += $size if $ext;
1867 $newsize = int($newsize);
1869 die "unable to skrink disk size\n" if $newsize < $size;
1871 return if $size == $newsize;
1873 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
1875 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
1877 $drive->{size
} = $newsize;
1878 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
1880 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
1883 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
1887 __PACKAGE__-
>register_method({
1888 name
=> 'snapshot_list',
1889 path
=> '{vmid}/snapshot',
1891 description
=> "List all snapshots.",
1893 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1896 protected
=> 1, # qemu pid files are only readable by root
1898 additionalProperties
=> 0,
1900 vmid
=> get_standard_option
('pve-vmid'),
1901 node
=> get_standard_option
('pve-node'),
1910 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
1915 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1916 my $snaphash = $conf->{snapshots
} || {};
1920 foreach my $name (keys %$snaphash) {
1921 push @$res, { name
=> $name };
1927 __PACKAGE__-
>register_method({
1929 path
=> '{vmid}/snapshot',
1933 description
=> "Snapshot a VM.",
1935 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
1938 additionalProperties
=> 0,
1940 node
=> get_standard_option
('pve-node'),
1941 vmid
=> get_standard_option
('pve-vmid'),
1944 description
=> "The name of the snapshot",
1950 description
=> "Save the vmstate",
1955 description
=> "Freeze the filesystem",
1961 description
=> "the task ID.",
1966 my $rpcenv = PVE
::RPCEnvironment
::get
();
1968 my $authuser = $rpcenv->get_user();
1970 my $node = extract_param
($param, 'node');
1972 my $vmid = extract_param
($param, 'vmid');
1974 my $snapname = extract_param
($param, 'snapname');
1976 my $vmstate = extract_param
($param, 'vmstate');
1978 my $freezefs = extract_param
($param, 'freezefs');
1980 # fixme: access rights?
1981 # &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $conf);
1982 # fixme: need to implement a check to see if all storages support snapshots
1985 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
1986 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $vmstate, $freezefs);
1989 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
1992 __PACKAGE__-
>register_method({
1993 name
=> 'snapshot_cmd_idx',
1994 path
=> '{vmid}/snapshot/{snapname}',
2001 additionalProperties
=> 0,
2003 vmid
=> get_standard_option
('pve-vmid'),
2004 node
=> get_standard_option
('pve-node'),
2007 description
=> "The name of the snapshot",
2018 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2025 push @$res, { cmd
=> 'rollback' };
2030 __PACKAGE__-
>register_method({
2032 path
=> '{vmid}/snapshot/{snapname}/rollback',
2036 description
=> "Rollback VM state to specified snapshot.",
2038 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2041 additionalProperties
=> 0,
2043 node
=> get_standard_option
('pve-node'),
2044 vmid
=> get_standard_option
('pve-vmid'),
2047 description
=> "The name of the snapshot",
2054 description
=> "the task ID.",
2059 my $rpcenv = PVE
::RPCEnvironment
::get
();
2061 my $authuser = $rpcenv->get_user();
2063 my $node = extract_param
($param, 'node');
2065 my $vmid = extract_param
($param, 'vmid');
2067 my $snapname = extract_param
($param, 'snapname');
2069 # fixme: access rights?
2072 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2073 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
2076 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2079 __PACKAGE__-
>register_method({
2080 name
=> 'delsnapshot',
2081 path
=> '{vmid}/snapshot/{snapname}',
2085 description
=> "Delete a VM snapshot.",
2087 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2090 additionalProperties
=> 0,
2092 node
=> get_standard_option
('pve-node'),
2093 vmid
=> get_standard_option
('pve-vmid'),
2096 description
=> "The name of the snapshot",
2103 description
=> "the task ID.",
2108 my $rpcenv = PVE
::RPCEnvironment
::get
();
2110 my $authuser = $rpcenv->get_user();
2112 my $node = extract_param
($param, 'node');
2114 my $vmid = extract_param
($param, 'vmid');
2116 my $snapname = extract_param
($param, 'snapname');
2118 # fixme: access rights?
2121 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
2122 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname);
2125 return $rpcenv->fork_worker('qmdelsnaphot', $vmid, $authuser, $realcmd);