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 raise_perm_exc);
12 use PVE
::JSONSchema
qw(get_standard_option);
16 use PVE
::RPCEnvironment
;
17 use PVE
::AccessControl
;
21 use Data
::Dumper
; # fixme: remove
23 use base
qw(PVE::RESTHandler);
25 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.";
27 my $resolve_cdrom_alias = sub {
30 if (my $value = $param->{cdrom
}) {
31 $value .= ",media=cdrom" if $value !~ m/media=/;
32 $param->{ide2
} = $value;
33 delete $param->{cdrom
};
38 my $check_storage_access = sub {
39 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
41 PVE
::QemuServer
::foreach_drive
($settings, sub {
42 my ($ds, $drive) = @_;
44 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
46 my $volid = $drive->{file
};
48 if (!$volid || $volid eq 'none') {
50 } elsif ($isCDROM && ($volid eq 'cdrom')) {
51 $rpcenv->check($authuser, "/", ['Sys.Console']);
52 } elsif (!$isCDROM && ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/)) {
53 my ($storeid, $size) = ($2 || $default_storage, $3);
54 die "no storage ID specified (and no default storage)\n" if !$storeid;
55 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
57 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
62 my $check_storage_access_clone = sub {
63 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
67 PVE
::QemuServer
::foreach_drive
($conf, sub {
68 my ($ds, $drive) = @_;
70 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
72 my $volid = $drive->{file
};
74 return if !$volid || $volid eq 'none';
77 if ($volid eq 'cdrom') {
78 $rpcenv->check($authuser, "/", ['Sys.Console']);
80 # we simply allow access
81 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
82 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
83 $sharedvm = 0 if !$scfg->{shared
};
87 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
88 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
89 $sharedvm = 0 if !$scfg->{shared
};
91 $sid = $storage if $storage;
92 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
99 # Note: $pool is only needed when creating a VM, because pool permissions
100 # are automatically inherited if VM already exists inside a pool.
101 my $create_disks = sub {
102 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
107 PVE
::QemuServer
::foreach_drive
($settings, sub {
108 my ($ds, $disk) = @_;
110 my $volid = $disk->{file
};
112 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
113 delete $disk->{size
};
114 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
115 } elsif ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/) {
116 my ($storeid, $size) = ($2 || $default_storage, $3);
117 die "no storage ID specified (and no default storage)\n" if !$storeid;
118 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
119 my $fmt = $disk->{format
} || $defformat;
120 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
121 $fmt, undef, $size*1024*1024);
122 $disk->{file
} = $volid;
123 $disk->{size
} = $size*1024*1024*1024;
124 push @$vollist, $volid;
125 delete $disk->{format
}; # no longer needed
126 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
129 my $path = $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
131 my $volid_is_new = 1;
134 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
135 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
140 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
142 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
144 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
146 die "volume $volid does not exists\n" if !$size;
148 $disk->{size
} = $size;
151 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
155 # free allocated images on error
157 syslog
('err', "VM $vmid creating disks failed");
158 foreach my $volid (@$vollist) {
159 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
165 # modify vm config if everything went well
166 foreach my $ds (keys %$res) {
167 $conf->{$ds} = $res->{$ds};
173 my $check_vm_modify_config_perm = sub {
174 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
176 return 1 if $authuser eq 'root@pam';
178 foreach my $opt (@$key_list) {
179 # disk checks need to be done somewhere else
180 next if PVE
::QemuServer
::valid_drivename
($opt);
182 if ($opt eq 'sockets' || $opt eq 'cores' ||
183 $opt eq 'cpu' || $opt eq 'smp' ||
184 $opt eq 'cpulimit' || $opt eq 'cpuunits') {
185 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
186 } elsif ($opt eq 'boot' || $opt eq 'bootdisk') {
187 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
188 } elsif ($opt eq 'memory' || $opt eq 'balloon' || $opt eq 'shares') {
189 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
190 } elsif ($opt eq 'args' || $opt eq 'lock') {
191 die "only root can set '$opt' config\n";
192 } elsif ($opt eq 'cpu' || $opt eq 'kvm' || $opt eq 'acpi' || $opt eq 'machine' ||
193 $opt eq 'vga' || $opt eq 'watchdog' || $opt eq 'tablet') {
194 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
195 } elsif ($opt =~ m/^net\d+$/) {
196 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
198 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
205 __PACKAGE__-
>register_method({
209 description
=> "Virtual machine index (per node).",
211 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
215 protected
=> 1, # qemu pid files are only readable by root
217 additionalProperties
=> 0,
219 node
=> get_standard_option
('pve-node'),
228 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
233 my $rpcenv = PVE
::RPCEnvironment
::get
();
234 my $authuser = $rpcenv->get_user();
236 my $vmstatus = PVE
::QemuServer
::vmstatus
();
239 foreach my $vmid (keys %$vmstatus) {
240 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
242 my $data = $vmstatus->{$vmid};
243 $data->{vmid
} = $vmid;
252 __PACKAGE__-
>register_method({
256 description
=> "Create or restore a virtual machine.",
258 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
259 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
260 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
261 user
=> 'all', # check inside
266 additionalProperties
=> 0,
267 properties
=> PVE
::QemuServer
::json_config_properties
(
269 node
=> get_standard_option
('pve-node'),
270 vmid
=> get_standard_option
('pve-vmid'),
272 description
=> "The backup file.",
277 storage
=> get_standard_option
('pve-storage-id', {
278 description
=> "Default storage.",
284 description
=> "Allow to overwrite existing VM.",
285 requires
=> 'archive',
290 description
=> "Assign a unique random ethernet address.",
291 requires
=> 'archive',
295 type
=> 'string', format
=> 'pve-poolid',
296 description
=> "Add the VM to the specified pool.",
306 my $rpcenv = PVE
::RPCEnvironment
::get
();
308 my $authuser = $rpcenv->get_user();
310 my $node = extract_param
($param, 'node');
312 my $vmid = extract_param
($param, 'vmid');
314 my $archive = extract_param
($param, 'archive');
316 my $storage = extract_param
($param, 'storage');
318 my $force = extract_param
($param, 'force');
320 my $unique = extract_param
($param, 'unique');
322 my $pool = extract_param
($param, 'pool');
324 my $filename = PVE
::QemuServer
::config_file
($vmid);
326 my $storecfg = PVE
::Storage
::config
();
328 PVE
::Cluster
::check_cfs_quorum
();
330 if (defined($pool)) {
331 $rpcenv->check_pool_exist($pool);
334 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
335 if defined($storage);
337 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
339 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
341 } elsif ($archive && $force && (-f
$filename) &&
342 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
343 # OK: user has VM.Backup permissions, and want to restore an existing VM
349 &$resolve_cdrom_alias($param);
351 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
353 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
355 foreach my $opt (keys %$param) {
356 if (PVE
::QemuServer
::valid_drivename
($opt)) {
357 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
358 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
360 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
361 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
365 PVE
::QemuServer
::add_random_macs
($param);
367 my $keystr = join(' ', keys %$param);
368 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
370 if ($archive eq '-') {
371 die "pipe requires cli environment\n"
372 if $rpcenv->{type
} ne 'cli';
374 my $path = $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $archive);
376 PVE
::Storage
::activate_volumes
($storecfg, [ $archive ])
377 if PVE
::Storage
::parse_volume_id
($archive, 1);
379 die "can't find archive file '$archive'\n" if !($path && -f
$path);
384 my $restorefn = sub {
386 # fixme: this test does not work if VM exists on other node!
388 die "unable to restore vm $vmid: config file already exists\n"
391 die "unable to restore vm $vmid: vm is running\n"
392 if PVE
::QemuServer
::check_running
($vmid);
396 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
399 unique
=> $unique });
401 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
404 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
410 die "unable to create vm $vmid: config file already exists\n"
421 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
423 # try to be smart about bootdisk
424 my @disks = PVE
::QemuServer
::disknames
();
426 foreach my $ds (reverse @disks) {
427 next if !$conf->{$ds};
428 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
429 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
433 if (!$conf->{bootdisk
} && $firstdisk) {
434 $conf->{bootdisk
} = $firstdisk;
437 PVE
::QemuServer
::update_config_nolock
($vmid, $conf);
443 foreach my $volid (@$vollist) {
444 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
447 die "create failed - $err";
450 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
453 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
456 return PVE
::QemuServer
::lock_config_full
($vmid, 1, $archive ?
$restorefn : $createfn);
459 __PACKAGE__-
>register_method({
464 description
=> "Directory index",
469 additionalProperties
=> 0,
471 node
=> get_standard_option
('pve-node'),
472 vmid
=> get_standard_option
('pve-vmid'),
480 subdir
=> { type
=> 'string' },
483 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
489 { subdir
=> 'config' },
490 { subdir
=> 'status' },
491 { subdir
=> 'unlink' },
492 { subdir
=> 'vncproxy' },
493 { subdir
=> 'migrate' },
494 { subdir
=> 'resize' },
495 { subdir
=> 'move' },
497 { subdir
=> 'rrddata' },
498 { subdir
=> 'monitor' },
499 { subdir
=> 'snapshot' },
500 { subdir
=> 'spiceproxy' },
506 __PACKAGE__-
>register_method({
508 path
=> '{vmid}/rrd',
510 protected
=> 1, # fixme: can we avoid that?
512 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
514 description
=> "Read VM RRD statistics (returns PNG)",
516 additionalProperties
=> 0,
518 node
=> get_standard_option
('pve-node'),
519 vmid
=> get_standard_option
('pve-vmid'),
521 description
=> "Specify the time frame you are interested in.",
523 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
526 description
=> "The list of datasources you want to display.",
527 type
=> 'string', format
=> 'pve-configid-list',
530 description
=> "The RRD consolidation function",
532 enum
=> [ 'AVERAGE', 'MAX' ],
540 filename
=> { type
=> 'string' },
546 return PVE
::Cluster
::create_rrd_graph
(
547 "pve2-vm/$param->{vmid}", $param->{timeframe
},
548 $param->{ds
}, $param->{cf
});
552 __PACKAGE__-
>register_method({
554 path
=> '{vmid}/rrddata',
556 protected
=> 1, # fixme: can we avoid that?
558 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
560 description
=> "Read VM RRD statistics",
562 additionalProperties
=> 0,
564 node
=> get_standard_option
('pve-node'),
565 vmid
=> get_standard_option
('pve-vmid'),
567 description
=> "Specify the time frame you are interested in.",
569 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
572 description
=> "The RRD consolidation function",
574 enum
=> [ 'AVERAGE', 'MAX' ],
589 return PVE
::Cluster
::create_rrd_data
(
590 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
594 __PACKAGE__-
>register_method({
596 path
=> '{vmid}/config',
599 description
=> "Get virtual machine configuration.",
601 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
604 additionalProperties
=> 0,
606 node
=> get_standard_option
('pve-node'),
607 vmid
=> get_standard_option
('pve-vmid'),
615 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
622 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
624 delete $conf->{snapshots
};
629 my $vm_is_volid_owner = sub {
630 my ($storecfg, $vmid, $volid) =@_;
632 if ($volid !~ m
|^/|) {
634 eval { ($path, $owner) = PVE
::Storage
::path
($storecfg, $volid); };
635 if ($owner && ($owner == $vmid)) {
643 my $test_deallocate_drive = sub {
644 my ($storecfg, $vmid, $key, $drive, $force) = @_;
646 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
647 my $volid = $drive->{file
};
648 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
649 if ($force || $key =~ m/^unused/) {
650 my $sid = PVE
::Storage
::parse_volume_id
($volid);
659 my $delete_drive = sub {
660 my ($conf, $storecfg, $vmid, $key, $drive, $force) = @_;
662 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
663 my $volid = $drive->{file
};
665 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
666 if ($force || $key =~ m/^unused/) {
668 # check if the disk is really unused
669 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, $key);
670 my $path = PVE
::Storage
::path
($storecfg, $volid);
672 die "unable to delete '$volid' - volume is still in use (snapshot?)\n"
673 if $used_paths->{$path};
675 PVE
::Storage
::vdisk_free
($storecfg, $volid);
679 PVE
::QemuServer
::add_unused_volume
($conf, $volid, $vmid);
684 delete $conf->{$key};
687 my $vmconfig_delete_option = sub {
688 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force) = @_;
690 return if !defined($conf->{$opt});
692 my $isDisk = PVE
::QemuServer
::valid_drivename
($opt)|| ($opt =~ m/^unused/);
695 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
697 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
698 if (my $sid = &$test_deallocate_drive($storecfg, $vmid, $opt, $drive, $force)) {
699 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.Allocate']);
703 my $unplugwarning = "";
704 if($conf->{ostype
} && $conf->{ostype
} eq 'l26'){
705 $unplugwarning = "<br>verify that you have acpiphp && pci_hotplug modules loaded in your guest VM";
706 }elsif($conf->{ostype
} && $conf->{ostype
} eq 'l24'){
707 $unplugwarning = "<br>kernel 2.4 don't support hotplug, please disable hotplug in options";
708 }elsif(!$conf->{ostype
} || ($conf->{ostype
} && $conf->{ostype
} eq 'other')){
709 $unplugwarning = "<br>verify that your guest support acpi hotplug";
712 if($opt eq 'tablet'){
713 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
715 die "error hot-unplug $opt $unplugwarning" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
719 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
720 &$delete_drive($conf, $storecfg, $vmid, $opt, $drive, $force);
722 delete $conf->{$opt};
725 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
728 my $safe_num_ne = sub {
731 return 0 if !defined($a) && !defined($b);
732 return 1 if !defined($a);
733 return 1 if !defined($b);
738 my $vmconfig_update_disk = sub {
739 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value, $force) = @_;
741 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
743 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { #cdrom
744 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
746 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
751 if (my $old_drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt})) {
753 my $media = $drive->{media
} || 'disk';
754 my $oldmedia = $old_drive->{media
} || 'disk';
755 die "unable to change media type\n" if $media ne $oldmedia;
757 if (!PVE
::QemuServer
::drive_is_cdrom
($old_drive) &&
758 ($drive->{file
} ne $old_drive->{file
})) { # delete old disks
760 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
761 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
764 if(&$safe_num_ne($drive->{mbps
}, $old_drive->{mbps
}) ||
765 &$safe_num_ne($drive->{mbps_rd
}, $old_drive->{mbps_rd
}) ||
766 &$safe_num_ne($drive->{mbps_wr
}, $old_drive->{mbps_wr
}) ||
767 &$safe_num_ne($drive->{iops
}, $old_drive->{iops
}) ||
768 &$safe_num_ne($drive->{iops_rd
}, $old_drive->{iops_rd
}) ||
769 &$safe_num_ne($drive->{iops_wr
}, $old_drive->{iops_wr
})) {
770 PVE
::QemuServer
::qemu_block_set_io_throttle
($vmid,"drive-$opt",
771 ($drive->{mbps
} || 0)*1024*1024,
772 ($drive->{mbps_rd
} || 0)*1024*1024,
773 ($drive->{mbps_wr
} || 0)*1024*1024,
775 $drive->{iops_rd
} || 0,
776 $drive->{iops_wr
} || 0)
777 if !PVE
::QemuServer
::drive_is_cdrom
($drive);
782 &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, undef, {$opt => $value});
783 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
785 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
786 $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
788 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # cdrom
790 if (PVE
::QemuServer
::check_running
($vmid)) {
791 if ($drive->{file
} eq 'none') {
792 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt");
794 my $path = PVE
::QemuServer
::get_iso_path
($storecfg, $vmid, $drive->{file
});
795 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt"); #force eject if locked
796 PVE
::QemuServer
::vm_mon_cmd
($vmid, "change",device
=> "drive-$opt",target
=> "$path") if $path;
800 } else { # hotplug new disks
802 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $drive);
806 my $vmconfig_update_net = sub {
807 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value) = @_;
809 if ($conf->{$opt} && PVE
::QemuServer
::check_running
($vmid)) {
810 my $oldnet = PVE
::QemuServer
::parse_net
($conf->{$opt});
811 my $newnet = PVE
::QemuServer
::parse_net
($value);
813 if($oldnet->{model
} ne $newnet->{model
}){
814 #if model change, we try to hot-unplug
815 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
818 if($newnet->{bridge
} && $oldnet->{bridge
}){
819 my $iface = "tap".$vmid."i".$1 if $opt =~ m/net(\d+)/;
821 if($newnet->{rate
} ne $oldnet->{rate
}){
822 PVE
::Network
::tap_rate_limit
($iface, $newnet->{rate
});
825 if(($newnet->{bridge
} ne $oldnet->{bridge
}) || ($newnet->{tag
} ne $oldnet->{tag
})){
826 eval{PVE
::Network
::tap_unplug
($iface, $oldnet->{bridge
}, $oldnet->{tag
});};
827 PVE
::Network
::tap_plug
($iface, $newnet->{bridge
}, $newnet->{tag
});
831 #if bridge/nat mode change, we try to hot-unplug
832 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
837 $conf->{$opt} = $value;
838 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
839 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
841 my $net = PVE
::QemuServer
::parse_net
($conf->{$opt});
843 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $net);
846 # POST/PUT {vmid}/config implementation
848 # The original API used PUT (idempotent) an we assumed that all operations
849 # are fast. But it turned out that almost any configuration change can
850 # involve hot-plug actions, or disk alloc/free. Such actions can take long
851 # time to complete and have side effects (not idempotent).
853 # The new implementation uses POST and forks a worker process. We added
854 # a new option 'background_delay'. If specified we wait up to
855 # 'background_delay' second for the worker task to complete. It returns null
856 # if the task is finished within that time, else we return the UPID.
858 my $update_vm_api = sub {
859 my ($param, $sync) = @_;
861 my $rpcenv = PVE
::RPCEnvironment
::get
();
863 my $authuser = $rpcenv->get_user();
865 my $node = extract_param
($param, 'node');
867 my $vmid = extract_param
($param, 'vmid');
869 my $digest = extract_param
($param, 'digest');
871 my $background_delay = extract_param
($param, 'background_delay');
873 my @paramarr = (); # used for log message
874 foreach my $key (keys %$param) {
875 push @paramarr, "-$key", $param->{$key};
878 my $skiplock = extract_param
($param, 'skiplock');
879 raise_param_exc
({ skiplock
=> "Only root may use this option." })
880 if $skiplock && $authuser ne 'root@pam';
882 my $delete_str = extract_param
($param, 'delete');
884 my $force = extract_param
($param, 'force');
886 die "no options specified\n" if !$delete_str && !scalar(keys %$param);
888 my $storecfg = PVE
::Storage
::config
();
890 my $defaults = PVE
::QemuServer
::load_defaults
();
892 &$resolve_cdrom_alias($param);
894 # now try to verify all parameters
897 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
898 $opt = 'ide2' if $opt eq 'cdrom';
899 raise_param_exc
({ delete => "you can't use '-$opt' and " .
900 "-delete $opt' at the same time" })
901 if defined($param->{$opt});
903 if (!PVE
::QemuServer
::option_exists
($opt)) {
904 raise_param_exc
({ delete => "unknown option '$opt'" });
910 foreach my $opt (keys %$param) {
911 if (PVE
::QemuServer
::valid_drivename
($opt)) {
913 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
914 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
915 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
916 } elsif ($opt =~ m/^net(\d+)$/) {
918 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
919 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
923 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
925 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
927 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
931 my $conf = PVE
::QemuServer
::load_config
($vmid);
933 die "checksum missmatch (file change by other user?)\n"
934 if $digest && $digest ne $conf->{digest
};
936 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
938 if ($param->{memory
} || defined($param->{balloon
})) {
939 my $maxmem = $param->{memory
} || $conf->{memory
} || $defaults->{memory
};
940 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{balloon
};
942 die "balloon value too large (must be smaller than assigned memory)\n"
943 if $balloon && $balloon > $maxmem;
946 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
950 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
952 foreach my $opt (@delete) { # delete
953 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
954 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
957 my $running = PVE
::QemuServer
::check_running
($vmid);
959 foreach my $opt (keys %$param) { # add/change
961 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
963 next if $conf->{$opt} && ($param->{$opt} eq $conf->{$opt}); # skip if nothing changed
965 if (PVE
::QemuServer
::valid_drivename
($opt)) {
967 &$vmconfig_update_disk($rpcenv, $authuser, $conf, $storecfg, $vmid,
968 $opt, $param->{$opt}, $force);
970 } elsif ($opt =~ m/^net(\d+)$/) { #nics
972 &$vmconfig_update_net($rpcenv, $authuser, $conf, $storecfg, $vmid,
973 $opt, $param->{$opt});
977 if($opt eq 'tablet' && $param->{$opt} == 1){
978 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
979 } elsif($opt eq 'tablet' && $param->{$opt} == 0){
980 PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
983 $conf->{$opt} = $param->{$opt};
984 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
988 # allow manual ballooning if shares is set to zero
989 if ($running && defined($param->{balloon
}) &&
990 defined($conf->{shares
}) && ($conf->{shares
} == 0)) {
991 my $balloon = $param->{'balloon'} || $conf->{memory
} || $defaults->{memory
};
992 PVE
::QemuServer
::vm_mon_cmd
($vmid, "balloon", value
=> $balloon*1024*1024);
1000 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1002 if ($background_delay) {
1004 # Note: It would be better to do that in the Event based HTTPServer
1005 # to avoid blocking call to sleep.
1007 my $end_time = time() + $background_delay;
1009 my $task = PVE
::Tools
::upid_decode
($upid);
1012 while (time() < $end_time) {
1013 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1015 sleep(1); # this gets interrupted when child process ends
1019 my $status = PVE
::Tools
::upid_read_status
($upid);
1020 return undef if $status eq 'OK';
1029 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
1032 my $vm_config_perm_list = [
1037 'VM.Config.Network',
1039 'VM.Config.Options',
1042 __PACKAGE__-
>register_method({
1043 name
=> 'update_vm_async',
1044 path
=> '{vmid}/config',
1048 description
=> "Set virtual machine options (asynchrounous API).",
1050 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1053 additionalProperties
=> 0,
1054 properties
=> PVE
::QemuServer
::json_config_properties
(
1056 node
=> get_standard_option
('pve-node'),
1057 vmid
=> get_standard_option
('pve-vmid'),
1058 skiplock
=> get_standard_option
('skiplock'),
1060 type
=> 'string', format
=> 'pve-configid-list',
1061 description
=> "A list of settings you want to delete.",
1066 description
=> $opt_force_description,
1068 requires
=> 'delete',
1072 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1076 background_delay
=> {
1078 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1089 code
=> $update_vm_api,
1092 __PACKAGE__-
>register_method({
1093 name
=> 'update_vm',
1094 path
=> '{vmid}/config',
1098 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1100 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1103 additionalProperties
=> 0,
1104 properties
=> PVE
::QemuServer
::json_config_properties
(
1106 node
=> get_standard_option
('pve-node'),
1107 vmid
=> get_standard_option
('pve-vmid'),
1108 skiplock
=> get_standard_option
('skiplock'),
1110 type
=> 'string', format
=> 'pve-configid-list',
1111 description
=> "A list of settings you want to delete.",
1116 description
=> $opt_force_description,
1118 requires
=> 'delete',
1122 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1128 returns
=> { type
=> 'null' },
1131 &$update_vm_api($param, 1);
1137 __PACKAGE__-
>register_method({
1138 name
=> 'destroy_vm',
1143 description
=> "Destroy the vm (also delete all used/owned volumes).",
1145 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1148 additionalProperties
=> 0,
1150 node
=> get_standard_option
('pve-node'),
1151 vmid
=> get_standard_option
('pve-vmid'),
1152 skiplock
=> get_standard_option
('skiplock'),
1161 my $rpcenv = PVE
::RPCEnvironment
::get
();
1163 my $authuser = $rpcenv->get_user();
1165 my $vmid = $param->{vmid
};
1167 my $skiplock = $param->{skiplock
};
1168 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1169 if $skiplock && $authuser ne 'root@pam';
1172 my $conf = PVE
::QemuServer
::load_config
($vmid);
1174 my $storecfg = PVE
::Storage
::config
();
1176 my $delVMfromPoolFn = sub {
1177 my $usercfg = cfs_read_file
("user.cfg");
1178 if (my $pool = $usercfg->{vms
}->{$vmid}) {
1179 if (my $data = $usercfg->{pools
}->{$pool}) {
1180 delete $data->{vms
}->{$vmid};
1181 delete $usercfg->{vms
}->{$vmid};
1182 cfs_write_file
("user.cfg", $usercfg);
1190 syslog
('info', "destroy VM $vmid: $upid\n");
1192 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1194 PVE
::AccessControl
::remove_vm_from_pool
($vmid);
1197 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1200 __PACKAGE__-
>register_method({
1202 path
=> '{vmid}/unlink',
1206 description
=> "Unlink/delete disk images.",
1208 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1211 additionalProperties
=> 0,
1213 node
=> get_standard_option
('pve-node'),
1214 vmid
=> get_standard_option
('pve-vmid'),
1216 type
=> 'string', format
=> 'pve-configid-list',
1217 description
=> "A list of disk IDs you want to delete.",
1221 description
=> $opt_force_description,
1226 returns
=> { type
=> 'null'},
1230 $param->{delete} = extract_param
($param, 'idlist');
1232 __PACKAGE__-
>update_vm($param);
1239 __PACKAGE__-
>register_method({
1241 path
=> '{vmid}/vncproxy',
1245 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1247 description
=> "Creates a TCP VNC proxy connections.",
1249 additionalProperties
=> 0,
1251 node
=> get_standard_option
('pve-node'),
1252 vmid
=> get_standard_option
('pve-vmid'),
1256 additionalProperties
=> 0,
1258 user
=> { type
=> 'string' },
1259 ticket
=> { type
=> 'string' },
1260 cert
=> { type
=> 'string' },
1261 port
=> { type
=> 'integer' },
1262 upid
=> { type
=> 'string' },
1268 my $rpcenv = PVE
::RPCEnvironment
::get
();
1270 my $authuser = $rpcenv->get_user();
1272 my $vmid = $param->{vmid
};
1273 my $node = $param->{node
};
1275 my $authpath = "/vms/$vmid";
1277 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1279 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1282 my $port = PVE
::Tools
::next_vnc_port
();
1286 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1287 $remip = PVE
::Cluster
::remote_node_ip
($node);
1290 # NOTE: kvm VNC traffic is already TLS encrypted
1291 my $remcmd = $remip ?
['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip] : [];
1298 syslog
('info', "starting vnc proxy $upid\n");
1300 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1302 my $qmstr = join(' ', @$qmcmd);
1304 # also redirect stderr (else we get RFB protocol errors)
1305 my $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1307 PVE
::Tools
::run_command
($cmd);
1312 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1314 PVE
::Tools
::wait_for_vnc_port
($port);
1325 __PACKAGE__-
>register_method({
1326 name
=> 'spiceproxy',
1327 path
=> '{vmid}/spiceproxy',
1330 proxyto
=> 'node', # fixme: use direct connections or ssh tunnel?
1332 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1334 description
=> "Returns a SPICE configuration to connect to the VM.",
1336 additionalProperties
=> 0,
1338 node
=> get_standard_option
('pve-node'),
1339 vmid
=> get_standard_option
('pve-vmid'),
1343 additionalProperties
=> 1,
1345 type
=> { type
=> 'string' },
1346 password
=> { type
=> 'string' },
1347 proxy
=> { type
=> 'string' },
1348 host
=> { type
=> 'string' },
1349 port
=> { type
=> 'integer' },
1355 my $rpcenv = PVE
::RPCEnvironment
::get
();
1357 my $authuser = $rpcenv->get_user();
1359 my $vmid = $param->{vmid
};
1360 my $node = $param->{node
};
1364 # Note: we currectly use "proxyto => 'node'", so this code will never trigger
1365 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1366 $remip = PVE
::Cluster
::remote_node_ip
($node);
1369 my ($ticket, $proxyticket) = PVE
::AccessControl
::assemble_spice_ticket
($authuser, $vmid, $node);
1373 # Note: this only works if VM is on local node
1374 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1375 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1377 # allow access for group www-data to the spice socket,
1378 # so that spiceproxy can access it
1379 my $socket = PVE
::QemuServer
::spice_socket
($vmid);
1380 my $gid = getgrnam('www-data') || die "getgrnam failed - $!\n";
1381 chown 0, $gid, $socket;
1382 chmod 0770, $socket;
1385 my $host = `hostname -f` || PVE
::INotify
::nodename
();
1390 host
=> $proxyticket,
1392 port
=> 0, # not used for now
1397 __PACKAGE__-
>register_method({
1399 path
=> '{vmid}/status',
1402 description
=> "Directory index",
1407 additionalProperties
=> 0,
1409 node
=> get_standard_option
('pve-node'),
1410 vmid
=> get_standard_option
('pve-vmid'),
1418 subdir
=> { type
=> 'string' },
1421 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1427 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1430 { subdir
=> 'current' },
1431 { subdir
=> 'start' },
1432 { subdir
=> 'stop' },
1438 my $vm_is_ha_managed = sub {
1441 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
1442 if (PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1)) {
1448 __PACKAGE__-
>register_method({
1449 name
=> 'vm_status',
1450 path
=> '{vmid}/status/current',
1453 protected
=> 1, # qemu pid files are only readable by root
1454 description
=> "Get virtual machine status.",
1456 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1459 additionalProperties
=> 0,
1461 node
=> get_standard_option
('pve-node'),
1462 vmid
=> get_standard_option
('pve-vmid'),
1465 returns
=> { type
=> 'object' },
1470 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1472 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1473 my $status = $vmstatus->{$param->{vmid
}};
1475 $status->{ha
} = &$vm_is_ha_managed($param->{vmid
});
1480 __PACKAGE__-
>register_method({
1482 path
=> '{vmid}/status/start',
1486 description
=> "Start virtual machine.",
1488 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1491 additionalProperties
=> 0,
1493 node
=> get_standard_option
('pve-node'),
1494 vmid
=> get_standard_option
('pve-vmid'),
1495 skiplock
=> get_standard_option
('skiplock'),
1496 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1497 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1498 machine
=> get_standard_option
('pve-qm-machine'),
1507 my $rpcenv = PVE
::RPCEnvironment
::get
();
1509 my $authuser = $rpcenv->get_user();
1511 my $node = extract_param
($param, 'node');
1513 my $vmid = extract_param
($param, 'vmid');
1515 my $machine = extract_param
($param, 'machine');
1517 my $stateuri = extract_param
($param, 'stateuri');
1518 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1519 if $stateuri && $authuser ne 'root@pam';
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 my $migratedfrom = extract_param
($param, 'migratedfrom');
1526 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1527 if $migratedfrom && $authuser ne 'root@pam';
1529 my $storecfg = PVE
::Storage
::config
();
1531 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1532 $rpcenv->{type
} ne 'ha') {
1537 my $service = "pvevm:$vmid";
1539 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1541 print "Executing HA start for VM $vmid\n";
1543 PVE
::Tools
::run_command
($cmd);
1548 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1555 syslog
('info', "start VM $vmid: $upid\n");
1557 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef, $machine);
1562 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1566 __PACKAGE__-
>register_method({
1568 path
=> '{vmid}/status/stop',
1572 description
=> "Stop virtual machine.",
1574 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1577 additionalProperties
=> 0,
1579 node
=> get_standard_option
('pve-node'),
1580 vmid
=> get_standard_option
('pve-vmid'),
1581 skiplock
=> get_standard_option
('skiplock'),
1582 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1584 description
=> "Wait maximal timeout seconds.",
1590 description
=> "Do not decativate storage volumes.",
1603 my $rpcenv = PVE
::RPCEnvironment
::get
();
1605 my $authuser = $rpcenv->get_user();
1607 my $node = extract_param
($param, 'node');
1609 my $vmid = extract_param
($param, 'vmid');
1611 my $skiplock = extract_param
($param, 'skiplock');
1612 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1613 if $skiplock && $authuser ne 'root@pam';
1615 my $keepActive = extract_param
($param, 'keepActive');
1616 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1617 if $keepActive && $authuser ne 'root@pam';
1619 my $migratedfrom = extract_param
($param, 'migratedfrom');
1620 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1621 if $migratedfrom && $authuser ne 'root@pam';
1624 my $storecfg = PVE
::Storage
::config
();
1626 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1631 my $service = "pvevm:$vmid";
1633 my $cmd = ['clusvcadm', '-d', $service];
1635 print "Executing HA stop for VM $vmid\n";
1637 PVE
::Tools
::run_command
($cmd);
1642 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1648 syslog
('info', "stop VM $vmid: $upid\n");
1650 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1651 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1656 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1660 __PACKAGE__-
>register_method({
1662 path
=> '{vmid}/status/reset',
1666 description
=> "Reset virtual machine.",
1668 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1671 additionalProperties
=> 0,
1673 node
=> get_standard_option
('pve-node'),
1674 vmid
=> get_standard_option
('pve-vmid'),
1675 skiplock
=> get_standard_option
('skiplock'),
1684 my $rpcenv = PVE
::RPCEnvironment
::get
();
1686 my $authuser = $rpcenv->get_user();
1688 my $node = extract_param
($param, 'node');
1690 my $vmid = extract_param
($param, 'vmid');
1692 my $skiplock = extract_param
($param, 'skiplock');
1693 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1694 if $skiplock && $authuser ne 'root@pam';
1696 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1701 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1706 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1709 __PACKAGE__-
>register_method({
1710 name
=> 'vm_shutdown',
1711 path
=> '{vmid}/status/shutdown',
1715 description
=> "Shutdown virtual machine.",
1717 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1720 additionalProperties
=> 0,
1722 node
=> get_standard_option
('pve-node'),
1723 vmid
=> get_standard_option
('pve-vmid'),
1724 skiplock
=> get_standard_option
('skiplock'),
1726 description
=> "Wait maximal timeout seconds.",
1732 description
=> "Make sure the VM stops.",
1738 description
=> "Do not decativate storage volumes.",
1751 my $rpcenv = PVE
::RPCEnvironment
::get
();
1753 my $authuser = $rpcenv->get_user();
1755 my $node = extract_param
($param, 'node');
1757 my $vmid = extract_param
($param, 'vmid');
1759 my $skiplock = extract_param
($param, 'skiplock');
1760 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1761 if $skiplock && $authuser ne 'root@pam';
1763 my $keepActive = extract_param
($param, 'keepActive');
1764 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1765 if $keepActive && $authuser ne 'root@pam';
1767 my $storecfg = PVE
::Storage
::config
();
1772 syslog
('info', "shutdown VM $vmid: $upid\n");
1774 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1775 1, $param->{forceStop
}, $keepActive);
1780 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1783 __PACKAGE__-
>register_method({
1784 name
=> 'vm_suspend',
1785 path
=> '{vmid}/status/suspend',
1789 description
=> "Suspend virtual machine.",
1791 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1794 additionalProperties
=> 0,
1796 node
=> get_standard_option
('pve-node'),
1797 vmid
=> get_standard_option
('pve-vmid'),
1798 skiplock
=> get_standard_option
('skiplock'),
1807 my $rpcenv = PVE
::RPCEnvironment
::get
();
1809 my $authuser = $rpcenv->get_user();
1811 my $node = extract_param
($param, 'node');
1813 my $vmid = extract_param
($param, 'vmid');
1815 my $skiplock = extract_param
($param, 'skiplock');
1816 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1817 if $skiplock && $authuser ne 'root@pam';
1819 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1824 syslog
('info', "suspend VM $vmid: $upid\n");
1826 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1831 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1834 __PACKAGE__-
>register_method({
1835 name
=> 'vm_resume',
1836 path
=> '{vmid}/status/resume',
1840 description
=> "Resume virtual machine.",
1842 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1845 additionalProperties
=> 0,
1847 node
=> get_standard_option
('pve-node'),
1848 vmid
=> get_standard_option
('pve-vmid'),
1849 skiplock
=> get_standard_option
('skiplock'),
1858 my $rpcenv = PVE
::RPCEnvironment
::get
();
1860 my $authuser = $rpcenv->get_user();
1862 my $node = extract_param
($param, 'node');
1864 my $vmid = extract_param
($param, 'vmid');
1866 my $skiplock = extract_param
($param, 'skiplock');
1867 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1868 if $skiplock && $authuser ne 'root@pam';
1870 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1875 syslog
('info', "resume VM $vmid: $upid\n");
1877 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1882 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1885 __PACKAGE__-
>register_method({
1886 name
=> 'vm_sendkey',
1887 path
=> '{vmid}/sendkey',
1891 description
=> "Send key event to virtual machine.",
1893 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1896 additionalProperties
=> 0,
1898 node
=> get_standard_option
('pve-node'),
1899 vmid
=> get_standard_option
('pve-vmid'),
1900 skiplock
=> get_standard_option
('skiplock'),
1902 description
=> "The key (qemu monitor encoding).",
1907 returns
=> { type
=> 'null'},
1911 my $rpcenv = PVE
::RPCEnvironment
::get
();
1913 my $authuser = $rpcenv->get_user();
1915 my $node = extract_param
($param, 'node');
1917 my $vmid = extract_param
($param, 'vmid');
1919 my $skiplock = extract_param
($param, 'skiplock');
1920 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1921 if $skiplock && $authuser ne 'root@pam';
1923 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1928 __PACKAGE__-
>register_method({
1929 name
=> 'vm_feature',
1930 path
=> '{vmid}/feature',
1934 description
=> "Check if feature for virtual machine is available.",
1936 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1939 additionalProperties
=> 0,
1941 node
=> get_standard_option
('pve-node'),
1942 vmid
=> get_standard_option
('pve-vmid'),
1944 description
=> "Feature to check.",
1946 enum
=> [ 'snapshot', 'clone', 'copy' ],
1948 snapname
=> get_standard_option
('pve-snapshot-name', {
1956 hasFeature
=> { type
=> 'boolean' },
1959 items
=> { type
=> 'string' },
1966 my $node = extract_param
($param, 'node');
1968 my $vmid = extract_param
($param, 'vmid');
1970 my $snapname = extract_param
($param, 'snapname');
1972 my $feature = extract_param
($param, 'feature');
1974 my $running = PVE
::QemuServer
::check_running
($vmid);
1976 my $conf = PVE
::QemuServer
::load_config
($vmid);
1979 my $snap = $conf->{snapshots
}->{$snapname};
1980 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1983 my $storecfg = PVE
::Storage
::config
();
1985 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
1986 my $hasFeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
1989 hasFeature
=> $hasFeature,
1990 nodes
=> [ keys %$nodelist ],
1994 __PACKAGE__-
>register_method({
1996 path
=> '{vmid}/clone',
2000 description
=> "Create a copy of virtual machine/template.",
2002 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2003 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2004 "'Datastore.AllocateSpace' on any used storage.",
2007 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2009 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2010 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2015 additionalProperties
=> 0,
2017 node
=> get_standard_option
('pve-node'),
2018 vmid
=> get_standard_option
('pve-vmid'),
2019 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2022 type
=> 'string', format
=> 'dns-name',
2023 description
=> "Set a name for the new VM.",
2028 description
=> "Description for the new VM.",
2032 type
=> 'string', format
=> 'pve-poolid',
2033 description
=> "Add the new VM to the specified pool.",
2035 snapname
=> get_standard_option
('pve-snapshot-name', {
2039 storage
=> get_standard_option
('pve-storage-id', {
2040 description
=> "Target storage for full clone.",
2045 description
=> "Target format for file storage.",
2049 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2054 description
=> "Create a full copy of all disk. This is always done when " .
2055 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2058 target
=> get_standard_option
('pve-node', {
2059 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2070 my $rpcenv = PVE
::RPCEnvironment
::get
();
2072 my $authuser = $rpcenv->get_user();
2074 my $node = extract_param
($param, 'node');
2076 my $vmid = extract_param
($param, 'vmid');
2078 my $newid = extract_param
($param, 'newid');
2080 my $pool = extract_param
($param, 'pool');
2082 if (defined($pool)) {
2083 $rpcenv->check_pool_exist($pool);
2086 my $snapname = extract_param
($param, 'snapname');
2088 my $storage = extract_param
($param, 'storage');
2090 my $format = extract_param
($param, 'format');
2092 my $target = extract_param
($param, 'target');
2094 my $localnode = PVE
::INotify
::nodename
();
2096 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2098 PVE
::Cluster
::check_node_exists
($target) if $target;
2100 my $storecfg = PVE
::Storage
::config
();
2103 # check if storage is enabled on local node
2104 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2106 # check if storage is available on target node
2107 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2108 # clone only works if target storage is shared
2109 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2110 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2114 PVE
::Cluster
::check_cfs_quorum
();
2116 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2118 # exclusive lock if VM is running - else shared lock is enough;
2119 my $shared_lock = $running ?
0 : 1;
2123 # do all tests after lock
2124 # we also try to do all tests before we fork the worker
2126 my $conf = PVE
::QemuServer
::load_config
($vmid);
2128 PVE
::QemuServer
::check_lock
($conf);
2130 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2132 die "unexpected state change\n" if $verify_running != $running;
2134 die "snapshot '$snapname' does not exist\n"
2135 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2137 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2139 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2141 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2143 my $conffile = PVE
::QemuServer
::config_file
($newid);
2145 die "unable to create VM $newid: config file already exists\n"
2148 my $newconf = { lock => 'clone' };
2152 foreach my $opt (keys %$oldconf) {
2153 my $value = $oldconf->{$opt};
2155 # do not copy snapshot related info
2156 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2157 $opt eq 'vmstate' || $opt eq 'snapstate';
2159 # always change MAC! address
2160 if ($opt =~ m/^net(\d+)$/) {
2161 my $net = PVE
::QemuServer
::parse_net
($value);
2162 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
2163 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2164 } elsif (my $drive = PVE
::QemuServer
::parse_drive
($opt, $value)) {
2165 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2166 $newconf->{$opt} = $value; # simply copy configuration
2168 if ($param->{full
} || !PVE
::Storage
::volume_is_base
($storecfg, $drive->{file
})) {
2169 die "Full clone feature is not available"
2170 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2173 $drives->{$opt} = $drive;
2174 push @$vollist, $drive->{file
};
2177 # copy everything else
2178 $newconf->{$opt} = $value;
2182 delete $newconf->{template
};
2184 if ($param->{name
}) {
2185 $newconf->{name
} = $param->{name
};
2187 if ($oldconf->{name
}) {
2188 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2190 $newconf->{name
} = "Copy-of-VM-$vmid";
2194 if ($param->{description
}) {
2195 $newconf->{description
} = $param->{description
};
2198 # create empty/temp config - this fails if VM already exists on other node
2199 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2204 my $newvollist = [];
2207 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2209 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2211 foreach my $opt (keys %$drives) {
2212 my $drive = $drives->{$opt};
2214 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2215 $newid, $storage, $format, $drive->{full
}, $newvollist);
2217 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2219 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2222 delete $newconf->{lock};
2223 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2226 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2227 die "Failed to move config to node '$target' - rename failed: $!\n"
2228 if !rename($conffile, $newconffile);
2231 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2236 sleep 1; # some storage like rbd need to wait before release volume - really?
2238 foreach my $volid (@$newvollist) {
2239 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2242 die "clone failed: $err";
2248 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2251 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2252 # Aquire exclusive lock lock for $newid
2253 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2258 __PACKAGE__-
>register_method({
2259 name
=> 'move_vm_disk',
2260 path
=> '{vmid}/move_disk',
2264 description
=> "Move volume to different storage.",
2266 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2267 "and 'Datastore.AllocateSpace' permissions on the storage.",
2270 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2271 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2275 additionalProperties
=> 0,
2277 node
=> get_standard_option
('pve-node'),
2278 vmid
=> get_standard_option
('pve-vmid'),
2281 description
=> "The disk you want to move.",
2282 enum
=> [ PVE
::QemuServer
::disknames
() ],
2284 storage
=> get_standard_option
('pve-storage-id', { description
=> "Target Storage." }),
2287 description
=> "Target Format.",
2288 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2293 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2299 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2307 description
=> "the task ID.",
2312 my $rpcenv = PVE
::RPCEnvironment
::get
();
2314 my $authuser = $rpcenv->get_user();
2316 my $node = extract_param
($param, 'node');
2318 my $vmid = extract_param
($param, 'vmid');
2320 my $digest = extract_param
($param, 'digest');
2322 my $disk = extract_param
($param, 'disk');
2324 my $storeid = extract_param
($param, 'storage');
2326 my $format = extract_param
($param, 'format');
2328 my $storecfg = PVE
::Storage
::config
();
2330 my $updatefn = sub {
2332 my $conf = PVE
::QemuServer
::load_config
($vmid);
2334 die "checksum missmatch (file change by other user?)\n"
2335 if $digest && $digest ne $conf->{digest
};
2337 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2339 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2341 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2343 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2346 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2347 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2351 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2352 (!$format || !$oldfmt || $oldfmt eq $format);
2354 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2356 my $running = PVE
::QemuServer
::check_running
($vmid);
2358 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2362 my $newvollist = [];
2365 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2367 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2368 $vmid, $storeid, $format, 1, $newvollist);
2370 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2372 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2374 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2378 foreach my $volid (@$newvollist) {
2379 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2382 die "storage migration failed: $err";
2385 if ($param->{delete}) {
2386 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2391 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2394 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2397 __PACKAGE__-
>register_method({
2398 name
=> 'migrate_vm',
2399 path
=> '{vmid}/migrate',
2403 description
=> "Migrate virtual machine. Creates a new migration task.",
2405 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2408 additionalProperties
=> 0,
2410 node
=> get_standard_option
('pve-node'),
2411 vmid
=> get_standard_option
('pve-vmid'),
2412 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2415 description
=> "Use online/live migration.",
2420 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2427 description
=> "the task ID.",
2432 my $rpcenv = PVE
::RPCEnvironment
::get
();
2434 my $authuser = $rpcenv->get_user();
2436 my $target = extract_param
($param, 'target');
2438 my $localnode = PVE
::INotify
::nodename
();
2439 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2441 PVE
::Cluster
::check_cfs_quorum
();
2443 PVE
::Cluster
::check_node_exists
($target);
2445 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2447 my $vmid = extract_param
($param, 'vmid');
2449 raise_param_exc
({ force
=> "Only root may use this option." })
2450 if $param->{force
} && $authuser ne 'root@pam';
2453 my $conf = PVE
::QemuServer
::load_config
($vmid);
2455 # try to detect errors early
2457 PVE
::QemuServer
::check_lock
($conf);
2459 if (PVE
::QemuServer
::check_running
($vmid)) {
2460 die "cant migrate running VM without --online\n"
2461 if !$param->{online
};
2464 my $storecfg = PVE
::Storage
::config
();
2465 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2467 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2472 my $service = "pvevm:$vmid";
2474 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2476 print "Executing HA migrate for VM $vmid to node $target\n";
2478 PVE
::Tools
::run_command
($cmd);
2483 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2490 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2493 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2498 __PACKAGE__-
>register_method({
2500 path
=> '{vmid}/monitor',
2504 description
=> "Execute Qemu monitor commands.",
2506 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2509 additionalProperties
=> 0,
2511 node
=> get_standard_option
('pve-node'),
2512 vmid
=> get_standard_option
('pve-vmid'),
2515 description
=> "The monitor command.",
2519 returns
=> { type
=> 'string'},
2523 my $vmid = $param->{vmid
};
2525 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2529 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2531 $res = "ERROR: $@" if $@;
2536 __PACKAGE__-
>register_method({
2537 name
=> 'resize_vm',
2538 path
=> '{vmid}/resize',
2542 description
=> "Extend volume size.",
2544 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2547 additionalProperties
=> 0,
2549 node
=> get_standard_option
('pve-node'),
2550 vmid
=> get_standard_option
('pve-vmid'),
2551 skiplock
=> get_standard_option
('skiplock'),
2554 description
=> "The disk you want to resize.",
2555 enum
=> [PVE
::QemuServer
::disknames
()],
2559 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2560 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.",
2564 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2570 returns
=> { type
=> 'null'},
2574 my $rpcenv = PVE
::RPCEnvironment
::get
();
2576 my $authuser = $rpcenv->get_user();
2578 my $node = extract_param
($param, 'node');
2580 my $vmid = extract_param
($param, 'vmid');
2582 my $digest = extract_param
($param, 'digest');
2584 my $disk = extract_param
($param, 'disk');
2586 my $sizestr = extract_param
($param, 'size');
2588 my $skiplock = extract_param
($param, 'skiplock');
2589 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2590 if $skiplock && $authuser ne 'root@pam';
2592 my $storecfg = PVE
::Storage
::config
();
2594 my $updatefn = sub {
2596 my $conf = PVE
::QemuServer
::load_config
($vmid);
2598 die "checksum missmatch (file change by other user?)\n"
2599 if $digest && $digest ne $conf->{digest
};
2600 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2602 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2604 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2606 my $volid = $drive->{file
};
2608 die "disk '$disk' has no associated volume\n" if !$volid;
2610 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2612 die "you can't online resize a virtio windows bootdisk\n"
2613 if PVE
::QemuServer
::check_running
($vmid) && $conf->{bootdisk
} eq $disk && $conf->{ostype
} =~ m/^w/ && $disk =~ m/^virtio/;
2615 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2617 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2619 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2621 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2622 my ($ext, $newsize, $unit) = ($1, $2, $4);
2625 $newsize = $newsize * 1024;
2626 } elsif ($unit eq 'M') {
2627 $newsize = $newsize * 1024 * 1024;
2628 } elsif ($unit eq 'G') {
2629 $newsize = $newsize * 1024 * 1024 * 1024;
2630 } elsif ($unit eq 'T') {
2631 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2634 $newsize += $size if $ext;
2635 $newsize = int($newsize);
2637 die "unable to skrink disk size\n" if $newsize < $size;
2639 return if $size == $newsize;
2641 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2643 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2645 $drive->{size
} = $newsize;
2646 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2648 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2651 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2655 __PACKAGE__-
>register_method({
2656 name
=> 'snapshot_list',
2657 path
=> '{vmid}/snapshot',
2659 description
=> "List all snapshots.",
2661 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2664 protected
=> 1, # qemu pid files are only readable by root
2666 additionalProperties
=> 0,
2668 vmid
=> get_standard_option
('pve-vmid'),
2669 node
=> get_standard_option
('pve-node'),
2678 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2683 my $vmid = $param->{vmid
};
2685 my $conf = PVE
::QemuServer
::load_config
($vmid);
2686 my $snaphash = $conf->{snapshots
} || {};
2690 foreach my $name (keys %$snaphash) {
2691 my $d = $snaphash->{$name};
2694 snaptime
=> $d->{snaptime
} || 0,
2695 vmstate
=> $d->{vmstate
} ?
1 : 0,
2696 description
=> $d->{description
} || '',
2698 $item->{parent
} = $d->{parent
} if $d->{parent
};
2699 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2703 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2704 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2705 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2707 push @$res, $current;
2712 __PACKAGE__-
>register_method({
2714 path
=> '{vmid}/snapshot',
2718 description
=> "Snapshot a VM.",
2720 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2723 additionalProperties
=> 0,
2725 node
=> get_standard_option
('pve-node'),
2726 vmid
=> get_standard_option
('pve-vmid'),
2727 snapname
=> get_standard_option
('pve-snapshot-name'),
2731 description
=> "Save the vmstate",
2736 description
=> "Freeze the filesystem",
2741 description
=> "A textual description or comment.",
2747 description
=> "the task ID.",
2752 my $rpcenv = PVE
::RPCEnvironment
::get
();
2754 my $authuser = $rpcenv->get_user();
2756 my $node = extract_param
($param, 'node');
2758 my $vmid = extract_param
($param, 'vmid');
2760 my $snapname = extract_param
($param, 'snapname');
2762 die "unable to use snapshot name 'current' (reserved name)\n"
2763 if $snapname eq 'current';
2766 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2767 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2768 $param->{freezefs
}, $param->{description
});
2771 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2774 __PACKAGE__-
>register_method({
2775 name
=> 'snapshot_cmd_idx',
2776 path
=> '{vmid}/snapshot/{snapname}',
2783 additionalProperties
=> 0,
2785 vmid
=> get_standard_option
('pve-vmid'),
2786 node
=> get_standard_option
('pve-node'),
2787 snapname
=> get_standard_option
('pve-snapshot-name'),
2796 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2803 push @$res, { cmd
=> 'rollback' };
2804 push @$res, { cmd
=> 'config' };
2809 __PACKAGE__-
>register_method({
2810 name
=> 'update_snapshot_config',
2811 path
=> '{vmid}/snapshot/{snapname}/config',
2815 description
=> "Update snapshot metadata.",
2817 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2820 additionalProperties
=> 0,
2822 node
=> get_standard_option
('pve-node'),
2823 vmid
=> get_standard_option
('pve-vmid'),
2824 snapname
=> get_standard_option
('pve-snapshot-name'),
2828 description
=> "A textual description or comment.",
2832 returns
=> { type
=> 'null' },
2836 my $rpcenv = PVE
::RPCEnvironment
::get
();
2838 my $authuser = $rpcenv->get_user();
2840 my $vmid = extract_param
($param, 'vmid');
2842 my $snapname = extract_param
($param, 'snapname');
2844 return undef if !defined($param->{description
});
2846 my $updatefn = sub {
2848 my $conf = PVE
::QemuServer
::load_config
($vmid);
2850 PVE
::QemuServer
::check_lock
($conf);
2852 my $snap = $conf->{snapshots
}->{$snapname};
2854 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2856 $snap->{description
} = $param->{description
} if defined($param->{description
});
2858 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2861 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2866 __PACKAGE__-
>register_method({
2867 name
=> 'get_snapshot_config',
2868 path
=> '{vmid}/snapshot/{snapname}/config',
2871 description
=> "Get snapshot configuration",
2873 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2876 additionalProperties
=> 0,
2878 node
=> get_standard_option
('pve-node'),
2879 vmid
=> get_standard_option
('pve-vmid'),
2880 snapname
=> get_standard_option
('pve-snapshot-name'),
2883 returns
=> { type
=> "object" },
2887 my $rpcenv = PVE
::RPCEnvironment
::get
();
2889 my $authuser = $rpcenv->get_user();
2891 my $vmid = extract_param
($param, 'vmid');
2893 my $snapname = extract_param
($param, 'snapname');
2895 my $conf = PVE
::QemuServer
::load_config
($vmid);
2897 my $snap = $conf->{snapshots
}->{$snapname};
2899 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2904 __PACKAGE__-
>register_method({
2906 path
=> '{vmid}/snapshot/{snapname}/rollback',
2910 description
=> "Rollback VM state to specified snapshot.",
2912 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2915 additionalProperties
=> 0,
2917 node
=> get_standard_option
('pve-node'),
2918 vmid
=> get_standard_option
('pve-vmid'),
2919 snapname
=> get_standard_option
('pve-snapshot-name'),
2924 description
=> "the task ID.",
2929 my $rpcenv = PVE
::RPCEnvironment
::get
();
2931 my $authuser = $rpcenv->get_user();
2933 my $node = extract_param
($param, 'node');
2935 my $vmid = extract_param
($param, 'vmid');
2937 my $snapname = extract_param
($param, 'snapname');
2940 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2941 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
2944 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2947 __PACKAGE__-
>register_method({
2948 name
=> 'delsnapshot',
2949 path
=> '{vmid}/snapshot/{snapname}',
2953 description
=> "Delete a VM snapshot.",
2955 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2958 additionalProperties
=> 0,
2960 node
=> get_standard_option
('pve-node'),
2961 vmid
=> get_standard_option
('pve-vmid'),
2962 snapname
=> get_standard_option
('pve-snapshot-name'),
2966 description
=> "For removal from config file, even if removing disk snapshots fails.",
2972 description
=> "the task ID.",
2977 my $rpcenv = PVE
::RPCEnvironment
::get
();
2979 my $authuser = $rpcenv->get_user();
2981 my $node = extract_param
($param, 'node');
2983 my $vmid = extract_param
($param, 'vmid');
2985 my $snapname = extract_param
($param, 'snapname');
2988 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
2989 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
2992 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
2995 __PACKAGE__-
>register_method({
2997 path
=> '{vmid}/template',
3001 description
=> "Create a Template.",
3003 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3004 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3007 additionalProperties
=> 0,
3009 node
=> get_standard_option
('pve-node'),
3010 vmid
=> get_standard_option
('pve-vmid'),
3014 description
=> "If you want to convert only 1 disk to base image.",
3015 enum
=> [PVE
::QemuServer
::disknames
()],
3020 returns
=> { type
=> 'null'},
3024 my $rpcenv = PVE
::RPCEnvironment
::get
();
3026 my $authuser = $rpcenv->get_user();
3028 my $node = extract_param
($param, 'node');
3030 my $vmid = extract_param
($param, 'vmid');
3032 my $disk = extract_param
($param, 'disk');
3034 my $updatefn = sub {
3036 my $conf = PVE
::QemuServer
::load_config
($vmid);
3038 PVE
::QemuServer
::check_lock
($conf);
3040 die "unable to create template, because VM contains snapshots\n"
3041 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3043 die "you can't convert a template to a template\n"
3044 if PVE
::QemuServer
::is_template
($conf) && !$disk;
3046 die "you can't convert a VM to template if VM is running\n"
3047 if PVE
::QemuServer
::check_running
($vmid);
3050 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3053 $conf->{template
} = 1;
3054 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3056 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3059 PVE
::QemuServer
::lock_config
($vmid, $updatefn);