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 my $remcmd = $remip ?
['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip] : [];
1076 syslog
('info', "starting vnc proxy $upid\n");
1078 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1080 my $qmstr = join(' ', @$qmcmd);
1082 # also redirect stderr (else we get RFB protocol errors)
1083 my $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1085 PVE
::Tools
::run_command
($cmd);
1090 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1092 PVE
::Tools
::wait_for_vnc_port
($port);
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
=> 'vm_feature',
1634 path
=> '{vmid}/feature',
1638 description
=> "Check if feature for virtual machine is available.",
1640 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1643 additionalProperties
=> 0,
1645 node
=> get_standard_option
('pve-node'),
1646 vmid
=> get_standard_option
('pve-vmid'),
1648 description
=> "Feature to check.",
1650 enum
=> [ 'snapshot', 'clone' ],
1652 snapname
=> get_standard_option
('pve-snapshot-name', {
1664 my $node = extract_param
($param, 'node');
1666 my $vmid = extract_param
($param, 'vmid');
1668 my $snapname = extract_param
($param, 'snapname');
1670 my $feature = extract_param
($param, 'feature');
1672 my $running = PVE
::QemuServer
::check_running
($vmid);
1674 my $conf = PVE
::QemuServer
::load_config
($vmid);
1677 my $snap = $conf->{snapshots
}->{$snapname};
1678 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1681 my $storecfg = PVE
::Storage
::config
();
1683 my $hasfeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
1684 my $res = $hasfeature ?
1 : 0 ;
1688 __PACKAGE__-
>register_method({
1689 name
=> 'migrate_vm',
1690 path
=> '{vmid}/migrate',
1694 description
=> "Migrate virtual machine. Creates a new migration task.",
1696 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
1699 additionalProperties
=> 0,
1701 node
=> get_standard_option
('pve-node'),
1702 vmid
=> get_standard_option
('pve-vmid'),
1703 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
1706 description
=> "Use online/live migration.",
1711 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
1718 description
=> "the task ID.",
1723 my $rpcenv = PVE
::RPCEnvironment
::get
();
1725 my $authuser = $rpcenv->get_user();
1727 my $target = extract_param
($param, 'target');
1729 my $localnode = PVE
::INotify
::nodename
();
1730 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
1732 PVE
::Cluster
::check_cfs_quorum
();
1734 PVE
::Cluster
::check_node_exists
($target);
1736 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
1738 my $vmid = extract_param
($param, 'vmid');
1740 raise_param_exc
({ force
=> "Only root may use this option." })
1741 if $param->{force
} && $authuser ne 'root@pam';
1744 my $conf = PVE
::QemuServer
::load_config
($vmid);
1746 # try to detect errors early
1748 PVE
::QemuServer
::check_lock
($conf);
1750 if (PVE
::QemuServer
::check_running
($vmid)) {
1751 die "cant migrate running VM without --online\n"
1752 if !$param->{online
};
1755 my $storecfg = PVE
::Storage
::config
();
1756 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
1758 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1763 my $service = "pvevm:$vmid";
1765 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
1767 print "Executing HA migrate for VM $vmid to node $target\n";
1769 PVE
::Tools
::run_command
($cmd);
1774 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
1781 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
1784 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
1789 __PACKAGE__-
>register_method({
1791 path
=> '{vmid}/monitor',
1795 description
=> "Execute Qemu monitor commands.",
1797 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
1800 additionalProperties
=> 0,
1802 node
=> get_standard_option
('pve-node'),
1803 vmid
=> get_standard_option
('pve-vmid'),
1806 description
=> "The monitor command.",
1810 returns
=> { type
=> 'string'},
1814 my $vmid = $param->{vmid
};
1816 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
1820 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
1822 $res = "ERROR: $@" if $@;
1827 __PACKAGE__-
>register_method({
1828 name
=> 'resize_vm',
1829 path
=> '{vmid}/resize',
1833 description
=> "Extend volume size.",
1835 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
1838 additionalProperties
=> 0,
1840 node
=> get_standard_option
('pve-node'),
1841 vmid
=> get_standard_option
('pve-vmid'),
1842 skiplock
=> get_standard_option
('skiplock'),
1845 description
=> "The disk you want to resize.",
1846 enum
=> [PVE
::QemuServer
::disknames
()],
1850 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
1851 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.",
1855 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1861 returns
=> { type
=> 'null'},
1865 my $rpcenv = PVE
::RPCEnvironment
::get
();
1867 my $authuser = $rpcenv->get_user();
1869 my $node = extract_param
($param, 'node');
1871 my $vmid = extract_param
($param, 'vmid');
1873 my $digest = extract_param
($param, 'digest');
1875 my $disk = extract_param
($param, 'disk');
1877 my $sizestr = extract_param
($param, 'size');
1879 my $skiplock = extract_param
($param, 'skiplock');
1880 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1881 if $skiplock && $authuser ne 'root@pam';
1883 my $storecfg = PVE
::Storage
::config
();
1885 my $updatefn = sub {
1887 my $conf = PVE
::QemuServer
::load_config
($vmid);
1889 die "checksum missmatch (file change by other user?)\n"
1890 if $digest && $digest ne $conf->{digest
};
1891 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
1893 die "disk '$disk' does not exist\n" if !$conf->{$disk};
1895 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
1897 my $volid = $drive->{file
};
1899 die "disk '$disk' has no associated volume\n" if !$volid;
1901 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
1903 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1905 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1907 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
1909 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
1910 my ($ext, $newsize, $unit) = ($1, $2, $4);
1913 $newsize = $newsize * 1024;
1914 } elsif ($unit eq 'M') {
1915 $newsize = $newsize * 1024 * 1024;
1916 } elsif ($unit eq 'G') {
1917 $newsize = $newsize * 1024 * 1024 * 1024;
1918 } elsif ($unit eq 'T') {
1919 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
1922 $newsize += $size if $ext;
1923 $newsize = int($newsize);
1925 die "unable to skrink disk size\n" if $newsize < $size;
1927 return if $size == $newsize;
1929 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
1931 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
1933 $drive->{size
} = $newsize;
1934 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
1936 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
1939 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
1943 __PACKAGE__-
>register_method({
1944 name
=> 'snapshot_list',
1945 path
=> '{vmid}/snapshot',
1947 description
=> "List all snapshots.",
1949 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1952 protected
=> 1, # qemu pid files are only readable by root
1954 additionalProperties
=> 0,
1956 vmid
=> get_standard_option
('pve-vmid'),
1957 node
=> get_standard_option
('pve-node'),
1966 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
1971 my $vmid = $param->{vmid
};
1973 my $conf = PVE
::QemuServer
::load_config
($vmid);
1974 my $snaphash = $conf->{snapshots
} || {};
1978 foreach my $name (keys %$snaphash) {
1979 my $d = $snaphash->{$name};
1982 snaptime
=> $d->{snaptime
} || 0,
1983 vmstate
=> $d->{vmstate
} ?
1 : 0,
1984 description
=> $d->{description
} || '',
1986 $item->{parent
} = $d->{parent
} if $d->{parent
};
1987 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
1991 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
1992 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
1993 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
1995 push @$res, $current;
2000 __PACKAGE__-
>register_method({
2002 path
=> '{vmid}/snapshot',
2006 description
=> "Snapshot a VM.",
2008 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2011 additionalProperties
=> 0,
2013 node
=> get_standard_option
('pve-node'),
2014 vmid
=> get_standard_option
('pve-vmid'),
2015 snapname
=> get_standard_option
('pve-snapshot-name'),
2019 description
=> "Save the vmstate",
2024 description
=> "Freeze the filesystem",
2029 description
=> "A textual description or comment.",
2035 description
=> "the task ID.",
2040 my $rpcenv = PVE
::RPCEnvironment
::get
();
2042 my $authuser = $rpcenv->get_user();
2044 my $node = extract_param
($param, 'node');
2046 my $vmid = extract_param
($param, 'vmid');
2048 my $snapname = extract_param
($param, 'snapname');
2050 die "unable to use snapshot name 'current' (reserved name)\n"
2051 if $snapname eq 'current';
2054 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2055 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2056 $param->{freezefs
}, $param->{description
});
2059 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2062 __PACKAGE__-
>register_method({
2063 name
=> 'snapshot_cmd_idx',
2064 path
=> '{vmid}/snapshot/{snapname}',
2071 additionalProperties
=> 0,
2073 vmid
=> get_standard_option
('pve-vmid'),
2074 node
=> get_standard_option
('pve-node'),
2075 snapname
=> get_standard_option
('pve-snapshot-name'),
2084 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2091 push @$res, { cmd
=> 'rollback' };
2092 push @$res, { cmd
=> 'config' };
2097 __PACKAGE__-
>register_method({
2098 name
=> 'update_snapshot_config',
2099 path
=> '{vmid}/snapshot/{snapname}/config',
2103 description
=> "Update snapshot metadata.",
2105 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2108 additionalProperties
=> 0,
2110 node
=> get_standard_option
('pve-node'),
2111 vmid
=> get_standard_option
('pve-vmid'),
2112 snapname
=> get_standard_option
('pve-snapshot-name'),
2116 description
=> "A textual description or comment.",
2120 returns
=> { type
=> 'null' },
2124 my $rpcenv = PVE
::RPCEnvironment
::get
();
2126 my $authuser = $rpcenv->get_user();
2128 my $vmid = extract_param
($param, 'vmid');
2130 my $snapname = extract_param
($param, 'snapname');
2132 return undef if !defined($param->{description
});
2134 my $updatefn = sub {
2136 my $conf = PVE
::QemuServer
::load_config
($vmid);
2138 PVE
::QemuServer
::check_lock
($conf);
2140 my $snap = $conf->{snapshots
}->{$snapname};
2142 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2144 $snap->{description
} = $param->{description
} if defined($param->{description
});
2146 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2149 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2154 __PACKAGE__-
>register_method({
2155 name
=> 'get_snapshot_config',
2156 path
=> '{vmid}/snapshot/{snapname}/config',
2159 description
=> "Get snapshot configuration",
2161 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2164 additionalProperties
=> 0,
2166 node
=> get_standard_option
('pve-node'),
2167 vmid
=> get_standard_option
('pve-vmid'),
2168 snapname
=> get_standard_option
('pve-snapshot-name'),
2171 returns
=> { type
=> "object" },
2175 my $rpcenv = PVE
::RPCEnvironment
::get
();
2177 my $authuser = $rpcenv->get_user();
2179 my $vmid = extract_param
($param, 'vmid');
2181 my $snapname = extract_param
($param, 'snapname');
2183 my $conf = PVE
::QemuServer
::load_config
($vmid);
2185 my $snap = $conf->{snapshots
}->{$snapname};
2187 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2192 __PACKAGE__-
>register_method({
2194 path
=> '{vmid}/snapshot/{snapname}/rollback',
2198 description
=> "Rollback VM state to specified snapshot.",
2200 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2203 additionalProperties
=> 0,
2205 node
=> get_standard_option
('pve-node'),
2206 vmid
=> get_standard_option
('pve-vmid'),
2207 snapname
=> get_standard_option
('pve-snapshot-name'),
2212 description
=> "the task ID.",
2217 my $rpcenv = PVE
::RPCEnvironment
::get
();
2219 my $authuser = $rpcenv->get_user();
2221 my $node = extract_param
($param, 'node');
2223 my $vmid = extract_param
($param, 'vmid');
2225 my $snapname = extract_param
($param, 'snapname');
2228 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2229 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
2232 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2235 __PACKAGE__-
>register_method({
2236 name
=> 'delsnapshot',
2237 path
=> '{vmid}/snapshot/{snapname}',
2241 description
=> "Delete a VM snapshot.",
2243 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2246 additionalProperties
=> 0,
2248 node
=> get_standard_option
('pve-node'),
2249 vmid
=> get_standard_option
('pve-vmid'),
2250 snapname
=> get_standard_option
('pve-snapshot-name'),
2254 description
=> "For removal from config file, even if removing disk snapshots fails.",
2260 description
=> "the task ID.",
2265 my $rpcenv = PVE
::RPCEnvironment
::get
();
2267 my $authuser = $rpcenv->get_user();
2269 my $node = extract_param
($param, 'node');
2271 my $vmid = extract_param
($param, 'vmid');
2273 my $snapname = extract_param
($param, 'snapname');
2276 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
2277 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
2280 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);