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);
1094 PVE
::Tools
::wait_for_vnc_port
($port);
1105 __PACKAGE__-
>register_method({
1107 path
=> '{vmid}/status',
1110 description
=> "Directory index",
1115 additionalProperties
=> 0,
1117 node
=> get_standard_option
('pve-node'),
1118 vmid
=> get_standard_option
('pve-vmid'),
1126 subdir
=> { type
=> 'string' },
1129 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1135 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1138 { subdir
=> 'current' },
1139 { subdir
=> 'start' },
1140 { subdir
=> 'stop' },
1146 my $vm_is_ha_managed = sub {
1149 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
1150 if (PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1)) {
1156 __PACKAGE__-
>register_method({
1157 name
=> 'vm_status',
1158 path
=> '{vmid}/status/current',
1161 protected
=> 1, # qemu pid files are only readable by root
1162 description
=> "Get virtual machine status.",
1164 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1167 additionalProperties
=> 0,
1169 node
=> get_standard_option
('pve-node'),
1170 vmid
=> get_standard_option
('pve-vmid'),
1173 returns
=> { type
=> 'object' },
1178 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1180 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1181 my $status = $vmstatus->{$param->{vmid
}};
1183 $status->{ha
} = &$vm_is_ha_managed($param->{vmid
});
1188 __PACKAGE__-
>register_method({
1190 path
=> '{vmid}/status/start',
1194 description
=> "Start virtual machine.",
1196 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1199 additionalProperties
=> 0,
1201 node
=> get_standard_option
('pve-node'),
1202 vmid
=> get_standard_option
('pve-vmid'),
1203 skiplock
=> get_standard_option
('skiplock'),
1204 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1205 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1215 my $rpcenv = PVE
::RPCEnvironment
::get
();
1217 my $authuser = $rpcenv->get_user();
1219 my $node = extract_param
($param, 'node');
1221 my $vmid = extract_param
($param, 'vmid');
1223 my $stateuri = extract_param
($param, 'stateuri');
1224 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1225 if $stateuri && $authuser ne 'root@pam';
1227 my $skiplock = extract_param
($param, 'skiplock');
1228 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1229 if $skiplock && $authuser ne 'root@pam';
1231 my $migratedfrom = extract_param
($param, 'migratedfrom');
1232 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1233 if $migratedfrom && $authuser ne 'root@pam';
1235 my $storecfg = PVE
::Storage
::config
();
1237 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1238 $rpcenv->{type
} ne 'ha') {
1243 my $service = "pvevm:$vmid";
1245 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1247 print "Executing HA start for VM $vmid\n";
1249 PVE
::Tools
::run_command
($cmd);
1254 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1261 syslog
('info', "start VM $vmid: $upid\n");
1263 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom);
1268 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1272 __PACKAGE__-
>register_method({
1274 path
=> '{vmid}/status/stop',
1278 description
=> "Stop virtual machine.",
1280 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1283 additionalProperties
=> 0,
1285 node
=> get_standard_option
('pve-node'),
1286 vmid
=> get_standard_option
('pve-vmid'),
1287 skiplock
=> get_standard_option
('skiplock'),
1288 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1290 description
=> "Wait maximal timeout seconds.",
1296 description
=> "Do not decativate storage volumes.",
1309 my $rpcenv = PVE
::RPCEnvironment
::get
();
1311 my $authuser = $rpcenv->get_user();
1313 my $node = extract_param
($param, 'node');
1315 my $vmid = extract_param
($param, 'vmid');
1317 my $skiplock = extract_param
($param, 'skiplock');
1318 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1319 if $skiplock && $authuser ne 'root@pam';
1321 my $keepActive = extract_param
($param, 'keepActive');
1322 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1323 if $keepActive && $authuser ne 'root@pam';
1325 my $migratedfrom = extract_param
($param, 'migratedfrom');
1326 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1327 if $migratedfrom && $authuser ne 'root@pam';
1330 my $storecfg = PVE
::Storage
::config
();
1332 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1337 my $service = "pvevm:$vmid";
1339 my $cmd = ['clusvcadm', '-d', $service];
1341 print "Executing HA stop for VM $vmid\n";
1343 PVE
::Tools
::run_command
($cmd);
1348 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1354 syslog
('info', "stop VM $vmid: $upid\n");
1356 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1357 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1362 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1366 __PACKAGE__-
>register_method({
1368 path
=> '{vmid}/status/reset',
1372 description
=> "Reset virtual machine.",
1374 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1377 additionalProperties
=> 0,
1379 node
=> get_standard_option
('pve-node'),
1380 vmid
=> get_standard_option
('pve-vmid'),
1381 skiplock
=> get_standard_option
('skiplock'),
1390 my $rpcenv = PVE
::RPCEnvironment
::get
();
1392 my $authuser = $rpcenv->get_user();
1394 my $node = extract_param
($param, 'node');
1396 my $vmid = extract_param
($param, 'vmid');
1398 my $skiplock = extract_param
($param, 'skiplock');
1399 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1400 if $skiplock && $authuser ne 'root@pam';
1402 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1407 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1412 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1415 __PACKAGE__-
>register_method({
1416 name
=> 'vm_shutdown',
1417 path
=> '{vmid}/status/shutdown',
1421 description
=> "Shutdown virtual machine.",
1423 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1426 additionalProperties
=> 0,
1428 node
=> get_standard_option
('pve-node'),
1429 vmid
=> get_standard_option
('pve-vmid'),
1430 skiplock
=> get_standard_option
('skiplock'),
1432 description
=> "Wait maximal timeout seconds.",
1438 description
=> "Make sure the VM stops.",
1444 description
=> "Do not decativate storage volumes.",
1457 my $rpcenv = PVE
::RPCEnvironment
::get
();
1459 my $authuser = $rpcenv->get_user();
1461 my $node = extract_param
($param, 'node');
1463 my $vmid = extract_param
($param, 'vmid');
1465 my $skiplock = extract_param
($param, 'skiplock');
1466 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1467 if $skiplock && $authuser ne 'root@pam';
1469 my $keepActive = extract_param
($param, 'keepActive');
1470 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1471 if $keepActive && $authuser ne 'root@pam';
1473 my $storecfg = PVE
::Storage
::config
();
1478 syslog
('info', "shutdown VM $vmid: $upid\n");
1480 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1481 1, $param->{forceStop
}, $keepActive);
1486 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1489 __PACKAGE__-
>register_method({
1490 name
=> 'vm_suspend',
1491 path
=> '{vmid}/status/suspend',
1495 description
=> "Suspend virtual machine.",
1497 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1500 additionalProperties
=> 0,
1502 node
=> get_standard_option
('pve-node'),
1503 vmid
=> get_standard_option
('pve-vmid'),
1504 skiplock
=> get_standard_option
('skiplock'),
1513 my $rpcenv = PVE
::RPCEnvironment
::get
();
1515 my $authuser = $rpcenv->get_user();
1517 my $node = extract_param
($param, 'node');
1519 my $vmid = extract_param
($param, 'vmid');
1521 my $skiplock = extract_param
($param, 'skiplock');
1522 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1523 if $skiplock && $authuser ne 'root@pam';
1525 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1530 syslog
('info', "suspend VM $vmid: $upid\n");
1532 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1537 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1540 __PACKAGE__-
>register_method({
1541 name
=> 'vm_resume',
1542 path
=> '{vmid}/status/resume',
1546 description
=> "Resume virtual machine.",
1548 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1551 additionalProperties
=> 0,
1553 node
=> get_standard_option
('pve-node'),
1554 vmid
=> get_standard_option
('pve-vmid'),
1555 skiplock
=> get_standard_option
('skiplock'),
1564 my $rpcenv = PVE
::RPCEnvironment
::get
();
1566 my $authuser = $rpcenv->get_user();
1568 my $node = extract_param
($param, 'node');
1570 my $vmid = extract_param
($param, 'vmid');
1572 my $skiplock = extract_param
($param, 'skiplock');
1573 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1574 if $skiplock && $authuser ne 'root@pam';
1576 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1581 syslog
('info', "resume VM $vmid: $upid\n");
1583 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1588 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1591 __PACKAGE__-
>register_method({
1592 name
=> 'vm_sendkey',
1593 path
=> '{vmid}/sendkey',
1597 description
=> "Send key event to virtual machine.",
1599 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1602 additionalProperties
=> 0,
1604 node
=> get_standard_option
('pve-node'),
1605 vmid
=> get_standard_option
('pve-vmid'),
1606 skiplock
=> get_standard_option
('skiplock'),
1608 description
=> "The key (qemu monitor encoding).",
1613 returns
=> { type
=> 'null'},
1617 my $rpcenv = PVE
::RPCEnvironment
::get
();
1619 my $authuser = $rpcenv->get_user();
1621 my $node = extract_param
($param, 'node');
1623 my $vmid = extract_param
($param, 'vmid');
1625 my $skiplock = extract_param
($param, 'skiplock');
1626 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1627 if $skiplock && $authuser ne 'root@pam';
1629 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1634 __PACKAGE__-
>register_method({
1635 name
=> 'migrate_vm',
1636 path
=> '{vmid}/migrate',
1640 description
=> "Migrate virtual machine. Creates a new migration task.",
1642 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
1645 additionalProperties
=> 0,
1647 node
=> get_standard_option
('pve-node'),
1648 vmid
=> get_standard_option
('pve-vmid'),
1649 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
1652 description
=> "Use online/live migration.",
1657 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
1664 description
=> "the task ID.",
1669 my $rpcenv = PVE
::RPCEnvironment
::get
();
1671 my $authuser = $rpcenv->get_user();
1673 my $target = extract_param
($param, 'target');
1675 my $localnode = PVE
::INotify
::nodename
();
1676 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
1678 PVE
::Cluster
::check_cfs_quorum
();
1680 PVE
::Cluster
::check_node_exists
($target);
1682 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
1684 my $vmid = extract_param
($param, 'vmid');
1686 raise_param_exc
({ force
=> "Only root may use this option." })
1687 if $param->{force
} && $authuser ne 'root@pam';
1690 my $conf = PVE
::QemuServer
::load_config
($vmid);
1692 # try to detect errors early
1694 PVE
::QemuServer
::check_lock
($conf);
1696 if (PVE
::QemuServer
::check_running
($vmid)) {
1697 die "cant migrate running VM without --online\n"
1698 if !$param->{online
};
1701 my $storecfg = PVE
::Storage
::config
();
1702 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
1704 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1709 my $service = "pvevm:$vmid";
1711 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
1713 print "Executing HA migrate for VM $vmid to node $target\n";
1715 PVE
::Tools
::run_command
($cmd);
1720 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
1727 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
1730 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
1735 __PACKAGE__-
>register_method({
1737 path
=> '{vmid}/monitor',
1741 description
=> "Execute Qemu monitor commands.",
1743 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
1746 additionalProperties
=> 0,
1748 node
=> get_standard_option
('pve-node'),
1749 vmid
=> get_standard_option
('pve-vmid'),
1752 description
=> "The monitor command.",
1756 returns
=> { type
=> 'string'},
1760 my $vmid = $param->{vmid
};
1762 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
1766 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
1768 $res = "ERROR: $@" if $@;
1773 __PACKAGE__-
>register_method({
1774 name
=> 'resize_vm',
1775 path
=> '{vmid}/resize',
1779 description
=> "Extend volume size.",
1781 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
1784 additionalProperties
=> 0,
1786 node
=> get_standard_option
('pve-node'),
1787 vmid
=> get_standard_option
('pve-vmid'),
1788 skiplock
=> get_standard_option
('skiplock'),
1791 description
=> "The disk you want to resize.",
1792 enum
=> [PVE
::QemuServer
::disknames
()],
1796 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
1797 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.",
1801 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1807 returns
=> { type
=> 'null'},
1811 my $rpcenv = PVE
::RPCEnvironment
::get
();
1813 my $authuser = $rpcenv->get_user();
1815 my $node = extract_param
($param, 'node');
1817 my $vmid = extract_param
($param, 'vmid');
1819 my $digest = extract_param
($param, 'digest');
1821 my $disk = extract_param
($param, 'disk');
1823 my $sizestr = extract_param
($param, 'size');
1825 my $skiplock = extract_param
($param, 'skiplock');
1826 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1827 if $skiplock && $authuser ne 'root@pam';
1829 my $storecfg = PVE
::Storage
::config
();
1831 my $updatefn = sub {
1833 my $conf = PVE
::QemuServer
::load_config
($vmid);
1835 die "checksum missmatch (file change by other user?)\n"
1836 if $digest && $digest ne $conf->{digest
};
1837 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
1839 die "disk '$disk' does not exist\n" if !$conf->{$disk};
1841 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
1843 my $volid = $drive->{file
};
1845 die "disk '$disk' has no associated volume\n" if !$volid;
1847 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
1849 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1851 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1853 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
1855 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
1856 my ($ext, $newsize, $unit) = ($1, $2, $4);
1859 $newsize = $newsize * 1024;
1860 } elsif ($unit eq 'M') {
1861 $newsize = $newsize * 1024 * 1024;
1862 } elsif ($unit eq 'G') {
1863 $newsize = $newsize * 1024 * 1024 * 1024;
1864 } elsif ($unit eq 'T') {
1865 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
1868 $newsize += $size if $ext;
1869 $newsize = int($newsize);
1871 die "unable to skrink disk size\n" if $newsize < $size;
1873 return if $size == $newsize;
1875 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
1877 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
1879 $drive->{size
} = $newsize;
1880 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
1882 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
1885 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
1889 __PACKAGE__-
>register_method({
1890 name
=> 'snapshot_list',
1891 path
=> '{vmid}/snapshot',
1893 description
=> "List all snapshots.",
1895 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1898 protected
=> 1, # qemu pid files are only readable by root
1900 additionalProperties
=> 0,
1902 vmid
=> get_standard_option
('pve-vmid'),
1903 node
=> get_standard_option
('pve-node'),
1912 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
1917 my $vmid = $param->{vmid
};
1919 my $conf = PVE
::QemuServer
::load_config
($vmid);
1920 my $snaphash = $conf->{snapshots
} || {};
1924 foreach my $name (keys %$snaphash) {
1925 my $d = $snaphash->{$name};
1928 snaptime
=> $d->{snaptime
} || 0,
1929 vmstate
=> $d->{vmstate
} ?
1 : 0,
1930 description
=> $d->{description
} || '',
1932 $item->{parent
} = $d->{parent
} if $d->{parent
};
1933 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
1937 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
1938 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
1939 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
1941 push @$res, $current;
1946 __PACKAGE__-
>register_method({
1948 path
=> '{vmid}/snapshot',
1952 description
=> "Snapshot a VM.",
1954 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
1957 additionalProperties
=> 0,
1959 node
=> get_standard_option
('pve-node'),
1960 vmid
=> get_standard_option
('pve-vmid'),
1961 snapname
=> get_standard_option
('pve-snapshot-name'),
1965 description
=> "Save the vmstate",
1970 description
=> "Freeze the filesystem",
1975 description
=> "A textual description or comment.",
1981 description
=> "the task ID.",
1986 my $rpcenv = PVE
::RPCEnvironment
::get
();
1988 my $authuser = $rpcenv->get_user();
1990 my $node = extract_param
($param, 'node');
1992 my $vmid = extract_param
($param, 'vmid');
1994 my $snapname = extract_param
($param, 'snapname');
1996 die "unable to use snapshot name 'current' (reserved name)\n"
1997 if $snapname eq 'current';
2000 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2001 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2002 $param->{freezefs
}, $param->{description
});
2005 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2008 __PACKAGE__-
>register_method({
2009 name
=> 'snapshot_cmd_idx',
2010 path
=> '{vmid}/snapshot/{snapname}',
2017 additionalProperties
=> 0,
2019 vmid
=> get_standard_option
('pve-vmid'),
2020 node
=> get_standard_option
('pve-node'),
2021 snapname
=> get_standard_option
('pve-snapshot-name'),
2030 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2037 push @$res, { cmd
=> 'rollback' };
2038 push @$res, { cmd
=> 'config' };
2043 __PACKAGE__-
>register_method({
2044 name
=> 'update_snapshot_config',
2045 path
=> '{vmid}/snapshot/{snapname}/config',
2049 description
=> "Update snapshot metadata.",
2051 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2054 additionalProperties
=> 0,
2056 node
=> get_standard_option
('pve-node'),
2057 vmid
=> get_standard_option
('pve-vmid'),
2058 snapname
=> get_standard_option
('pve-snapshot-name'),
2062 description
=> "A textual description or comment.",
2066 returns
=> { type
=> 'null' },
2070 my $rpcenv = PVE
::RPCEnvironment
::get
();
2072 my $authuser = $rpcenv->get_user();
2074 my $vmid = extract_param
($param, 'vmid');
2076 my $snapname = extract_param
($param, 'snapname');
2078 return undef if !defined($param->{description
});
2080 my $updatefn = sub {
2082 my $conf = PVE
::QemuServer
::load_config
($vmid);
2084 PVE
::QemuServer
::check_lock
($conf);
2086 my $snap = $conf->{snapshots
}->{$snapname};
2088 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2090 $snap->{description
} = $param->{description
} if defined($param->{description
});
2092 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2095 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2100 __PACKAGE__-
>register_method({
2101 name
=> 'get_snapshot_config',
2102 path
=> '{vmid}/snapshot/{snapname}/config',
2105 description
=> "Get snapshot configuration",
2107 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2110 additionalProperties
=> 0,
2112 node
=> get_standard_option
('pve-node'),
2113 vmid
=> get_standard_option
('pve-vmid'),
2114 snapname
=> get_standard_option
('pve-snapshot-name'),
2117 returns
=> { type
=> "object" },
2121 my $rpcenv = PVE
::RPCEnvironment
::get
();
2123 my $authuser = $rpcenv->get_user();
2125 my $vmid = extract_param
($param, 'vmid');
2127 my $snapname = extract_param
($param, 'snapname');
2129 my $conf = PVE
::QemuServer
::load_config
($vmid);
2131 my $snap = $conf->{snapshots
}->{$snapname};
2133 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2138 __PACKAGE__-
>register_method({
2140 path
=> '{vmid}/snapshot/{snapname}/rollback',
2144 description
=> "Rollback VM state to specified snapshot.",
2146 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2149 additionalProperties
=> 0,
2151 node
=> get_standard_option
('pve-node'),
2152 vmid
=> get_standard_option
('pve-vmid'),
2153 snapname
=> get_standard_option
('pve-snapshot-name'),
2158 description
=> "the task ID.",
2163 my $rpcenv = PVE
::RPCEnvironment
::get
();
2165 my $authuser = $rpcenv->get_user();
2167 my $node = extract_param
($param, 'node');
2169 my $vmid = extract_param
($param, 'vmid');
2171 my $snapname = extract_param
($param, 'snapname');
2174 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2175 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
2178 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2181 __PACKAGE__-
>register_method({
2182 name
=> 'delsnapshot',
2183 path
=> '{vmid}/snapshot/{snapname}',
2187 description
=> "Delete a VM snapshot.",
2189 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2192 additionalProperties
=> 0,
2194 node
=> get_standard_option
('pve-node'),
2195 vmid
=> get_standard_option
('pve-vmid'),
2196 snapname
=> get_standard_option
('pve-snapshot-name'),
2200 description
=> "For removal from config file, even if removing disk snapshots fails.",
2206 description
=> "the task ID.",
2211 my $rpcenv = PVE
::RPCEnvironment
::get
();
2213 my $authuser = $rpcenv->get_user();
2215 my $node = extract_param
($param, 'node');
2217 my $vmid = extract_param
($param, 'vmid');
2219 my $snapname = extract_param
($param, 'snapname');
2222 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
2223 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
2226 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);