1 package PVE
::API2
::Qemu
;
10 use Crypt
::OpenSSL
::Random
;
12 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
15 use PVE
::Tools
qw(extract_param);
16 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
18 use PVE
::JSONSchema
qw(get_standard_option);
20 use PVE
::ReplicationConfig
;
21 use PVE
::GuestHelpers
;
24 use PVE
::QemuServer
::Cloudinit
;
25 use PVE
::QemuServer
::CPUConfig
;
26 use PVE
::QemuServer
::Drive
;
27 use PVE
::QemuServer
::ImportDisk
;
28 use PVE
::QemuServer
::Monitor
qw(mon_cmd);
29 use PVE
::QemuServer
::Machine
;
31 use PVE
::RPCEnvironment
;
32 use PVE
::AccessControl
;
36 use PVE
::API2
::Firewall
::VM
;
37 use PVE
::API2
::Qemu
::Agent
;
38 use PVE
::VZDump
::Plugin
;
39 use PVE
::DataCenterConfig
;
44 if (!$ENV{PVE_GENERATING_DOCS
}) {
45 require PVE
::HA
::Env
::PVE2
;
46 import PVE
::HA
::Env
::PVE2
;
47 require PVE
::HA
::Config
;
48 import PVE
::HA
::Config
;
52 use Data
::Dumper
; # fixme: remove
54 use base
qw(PVE::RESTHandler);
56 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.";
58 my $resolve_cdrom_alias = sub {
61 if (my $value = $param->{cdrom
}) {
62 $value .= ",media=cdrom" if $value !~ m/media=/;
63 $param->{ide2
} = $value;
64 delete $param->{cdrom
};
68 # Used in import-enabled API endpoints. Parses drives using the extended '_with_alloc' schema.
69 my $foreach_volume_with_alloc = sub {
70 my ($param, $func) = @_;
72 for my $opt (sort keys $param->%*) {
73 next if !PVE
::QemuServer
::is_valid_drivename
($opt);
75 my $drive = PVE
::QemuServer
::Drive
::parse_drive
($opt, $param->{$opt}, 1);
78 $func->($opt, $drive);
82 my $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
84 my $check_drive_param = sub {
85 my ($param, $storecfg, $extra_checks) = @_;
87 for my $opt (sort keys $param->%*) {
88 next if !PVE
::QemuServer
::is_valid_drivename
($opt);
90 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt}, 1);
91 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
93 if ($drive->{'import-from'}) {
94 if ($drive->{file
} !~ $NEW_DISK_RE || $3 != 0) {
96 $opt => "'import-from' requires special syntax - ".
97 "use <storage ID>:0,import-from=<source>",
101 if ($opt eq 'efidisk0') {
102 for my $required (qw(efitype pre-enrolled-keys)) {
103 if (!defined($drive->{$required})) {
105 $opt => "need to specify '$required' when using 'import-from'",
109 } elsif ($opt eq 'tpmstate0') {
110 raise_param_exc
({ $opt => "need to specify 'version' when using 'import-from'" })
111 if !defined($drive->{version
});
115 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
117 $extra_checks->($drive) if $extra_checks;
119 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive, 1);
123 my $check_storage_access = sub {
124 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
126 $foreach_volume_with_alloc->($settings, sub {
127 my ($ds, $drive) = @_;
129 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
131 my $volid = $drive->{file
};
132 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
134 if (!$volid || ($volid eq 'none' || $volid eq 'cloudinit' || (defined($volname) && $volname eq 'cloudinit'))) {
136 } elsif ($isCDROM && ($volid eq 'cdrom')) {
137 $rpcenv->check($authuser, "/", ['Sys.Console']);
138 } elsif (!$isCDROM && ($volid =~ $NEW_DISK_RE)) {
139 my ($storeid, $size) = ($2 || $default_storage, $3);
140 die "no storage ID specified (and no default storage)\n" if !$storeid;
141 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
142 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
143 raise_param_exc
({ storage
=> "storage '$storeid' does not support vm images"})
144 if !$scfg->{content
}->{images
};
146 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
148 my ($vtype) = PVE
::Storage
::parse_volname
($storecfg, $volid);
149 raise_param_exc
({ $ds => "content type needs to be 'images' or 'iso'" })
150 if $vtype ne 'images' && $vtype ne 'iso';
154 if (my $src_image = $drive->{'import-from'}) {
156 if (PVE
::Storage
::parse_volume_id
($src_image, 1)) { # PVE-managed volume
157 (my $vtype, undef, $src_vmid) = PVE
::Storage
::parse_volname
($storecfg, $src_image);
158 raise_param_exc
({ $ds => "$src_image has wrong type '$vtype' - not an image" })
159 if $vtype ne 'images';
162 if ($src_vmid) { # might be actively used by VM and will be copied via clone_disk()
163 $rpcenv->check($authuser, "/vms/${src_vmid}", ['VM.Clone']);
165 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $src_image);
170 $rpcenv->check($authuser, "/storage/$settings->{vmstatestorage}", ['Datastore.AllocateSpace'])
171 if defined($settings->{vmstatestorage
});
174 my $check_storage_access_clone = sub {
175 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
179 PVE
::QemuConfig-
>foreach_volume($conf, sub {
180 my ($ds, $drive) = @_;
182 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
184 my $volid = $drive->{file
};
186 return if !$volid || $volid eq 'none';
189 if ($volid eq 'cdrom') {
190 $rpcenv->check($authuser, "/", ['Sys.Console']);
192 # we simply allow access
193 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
194 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
195 $sharedvm = 0 if !$scfg->{shared
};
199 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
200 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
201 $sharedvm = 0 if !$scfg->{shared
};
203 $sid = $storage if $storage;
204 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
208 $rpcenv->check($authuser, "/storage/$conf->{vmstatestorage}", ['Datastore.AllocateSpace'])
209 if defined($conf->{vmstatestorage
});
214 my $check_storage_access_migrate = sub {
215 my ($rpcenv, $authuser, $storecfg, $storage, $node) = @_;
217 PVE
::Storage
::storage_check_enabled
($storecfg, $storage, $node);
219 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace']);
221 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
222 die "storage '$storage' does not support vm images\n"
223 if !$scfg->{content
}->{images
};
226 my $import_from_volid = sub {
227 my ($storecfg, $src_volid, $dest_info, $vollist) = @_;
229 die "could not get size of $src_volid\n"
230 if !PVE
::Storage
::volume_size_info
($storecfg, $src_volid, 10);
232 die "cannot import from cloudinit disk\n"
233 if PVE
::QemuServer
::Drive
::drive_is_cloudinit
({ file
=> $src_volid });
235 my $src_vmid = (PVE
::Storage
::parse_volname
($storecfg, $src_volid))[2];
237 my $src_vm_state = sub {
238 my $exists = $src_vmid && PVE
::Cluster
::get_vmlist
()->{ids
}->{$src_vmid} ?
1 : 0;
242 eval { PVE
::QemuConfig
::assert_config_exists_on_node
($src_vmid); };
243 die "owner VM $src_vmid not on local node\n" if $@;
244 $runs = PVE
::QemuServer
::Helpers
::vm_running_locally
($src_vmid) || 0;
247 return ($exists, $runs);
250 my ($src_vm_exists, $running) = $src_vm_state->();
252 die "cannot import from '$src_volid' - full clone feature is not supported\n"
253 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $src_volid, undef, $running);
256 my ($src_vm_exists_now, $running_now) = $src_vm_state->();
258 die "owner VM $src_vmid changed state unexpectedly\n"
259 if $src_vm_exists_now != $src_vm_exists || $running_now != $running;
261 my $src_conf = $src_vm_exists_now ? PVE
::QemuConfig-
>load_config($src_vmid) : {};
263 my $src_drive = { file
=> $src_volid };
265 PVE
::QemuConfig-
>foreach_volume($src_conf, sub {
266 my ($ds, $drive) = @_;
268 return if $src_drivename;
270 if ($drive->{file
} eq $src_volid) {
272 $src_drivename = $ds;
278 running
=> $running_now,
279 drivename
=> $src_drivename,
284 my ($src_storeid) = PVE
::Storage
::parse_volume_id
($src_volid);
286 return PVE
::QemuServer
::clone_disk
(
295 PVE
::Storage
::get_bandwidth_limit
('clone', [$src_storeid, $dest_info->{storage
}]),
301 $cloned = PVE
::QemuConfig-
>lock_config_full($src_vmid, 30, $clonefn);
302 } elsif ($src_vmid) {
303 $cloned = PVE
::QemuConfig-
>lock_config_shared($src_vmid, 30, $clonefn);
305 $cloned = $clonefn->();
308 return $cloned->@{qw(file size)};
311 # Note: $pool is only needed when creating a VM, because pool permissions
312 # are automatically inherited if VM already exists inside a pool.
313 my $create_disks = sub {
314 my ($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
321 my ($ds, $disk) = @_;
323 my $volid = $disk->{file
};
324 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
326 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
327 delete $disk->{size
};
328 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
329 } elsif (defined($volname) && $volname eq 'cloudinit') {
330 $storeid = $storeid // $default_storage;
331 die "no storage ID specified (and no default storage)\n" if !$storeid;
334 my $ci_key = PVE
::QemuConfig-
>has_cloudinit($conf, $ds)
335 || PVE
::QemuConfig-
>has_cloudinit($conf->{pending
} || {}, $ds)
336 || PVE
::QemuConfig-
>has_cloudinit($res, $ds)
338 die "$ds - cloud-init drive is already attached at '$ci_key'\n";
341 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
342 my $name = "vm-$vmid-cloudinit";
346 $fmt = $disk->{format
} // "qcow2";
349 $fmt = $disk->{format
} // "raw";
352 # Initial disk created with 4 MB and aligned to 4MB on regeneration
353 my $ci_size = PVE
::QemuServer
::Cloudinit
::CLOUDINIT_DISK_SIZE
;
354 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, $name, $ci_size/1024);
355 $disk->{file
} = $volid;
356 $disk->{media
} = 'cdrom';
357 push @$vollist, $volid;
358 delete $disk->{format
}; # no longer needed
359 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
360 print "$ds: successfully created disk '$res->{$ds}'\n";
361 } elsif ($volid =~ $NEW_DISK_RE) {
362 my ($storeid, $size) = ($2 || $default_storage, $3);
363 die "no storage ID specified (and no default storage)\n" if !$storeid;
365 if (my $source = delete $disk->{'import-from'}) {
368 if (PVE
::Storage
::parse_volume_id
($source, 1)) { # PVE-managed volume
373 format
=> $disk->{format
},
376 $dest_info->{efisize
} = PVE
::QemuServer
::get_efivars_size
($conf, $disk)
377 if $ds eq 'efidisk0';
379 ($dst_volid, $size) = eval {
380 $import_from_volid->($storecfg, $source, $dest_info, $vollist);
382 die "cannot import from '$source' - $@" if $@;
384 $source = PVE
::Storage
::abs_filesystem_path
($storecfg, $source, 1);
385 $size = PVE
::Storage
::file_size_info
($source);
386 die "could not get file size of $source\n" if !$size;
388 (undef, $dst_volid) = PVE
::QemuServer
::ImportDisk
::do_import
(
394 format
=> $disk->{format
},
395 'skip-config-update' => 1,
398 push @$vollist, $dst_volid;
401 $disk->{file
} = $dst_volid;
402 $disk->{size
} = $size;
403 delete $disk->{format
}; # no longer needed
404 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
406 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
407 my $fmt = $disk->{format
} || $defformat;
409 $size = PVE
::Tools
::convert_size
($size, 'gb' => 'kb'); # vdisk_alloc uses kb
412 if ($ds eq 'efidisk0') {
413 my $smm = PVE
::QemuServer
::Machine
::machine_type_is_q35
($conf);
414 ($volid, $size) = PVE
::QemuServer
::create_efidisk
(
415 $storecfg, $storeid, $vmid, $fmt, $arch, $disk, $smm);
416 } elsif ($ds eq 'tpmstate0') {
417 # swtpm can only use raw volumes, and uses a fixed size
418 $size = PVE
::Tools
::convert_size
(PVE
::QemuServer
::Drive
::TPMSTATE_DISK_SIZE
, 'b' => 'kb');
419 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, "raw", undef, $size);
421 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, undef, $size);
423 push @$vollist, $volid;
424 $disk->{file
} = $volid;
425 $disk->{size
} = PVE
::Tools
::convert_size
($size, 'kb' => 'b');
426 delete $disk->{format
}; # no longer needed
427 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
430 print "$ds: successfully created disk '$res->{$ds}'\n";
432 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
434 my ($vtype) = PVE
::Storage
::parse_volname
($storecfg, $volid);
435 die "cannot use volume $volid - content type needs to be 'images' or 'iso'"
436 if $vtype ne 'images' && $vtype ne 'iso';
438 if (PVE
::QemuServer
::Drive
::drive_is_cloudinit
($disk)) {
440 my $ci_key = PVE
::QemuConfig-
>has_cloudinit($conf, $ds)
441 || PVE
::QemuConfig-
>has_cloudinit($conf->{pending
} || {}, $ds)
442 || PVE
::QemuConfig-
>has_cloudinit($res, $ds)
444 die "$ds - cloud-init drive is already attached at '$ci_key'\n";
449 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
451 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
452 die "volume $volid does not exist\n" if !$size;
453 $disk->{size
} = $size;
455 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
459 eval { $foreach_volume_with_alloc->($settings, $code); };
461 # free allocated images on error
463 syslog
('err', "VM $vmid creating disks failed");
464 foreach my $volid (@$vollist) {
465 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
471 return ($vollist, $res);
474 my $check_cpu_model_access = sub {
475 my ($rpcenv, $authuser, $new, $existing) = @_;
477 return if !defined($new->{cpu
});
479 my $cpu = PVE
::JSONSchema
::check_format
('pve-vm-cpu-conf', $new->{cpu
});
480 return if !$cpu || !$cpu->{cputype
}; # always allow default
481 my $cputype = $cpu->{cputype
};
483 if ($existing && $existing->{cpu
}) {
484 # changing only other settings doesn't require permissions for CPU model
485 my $existingCpu = PVE
::JSONSchema
::check_format
('pve-vm-cpu-conf', $existing->{cpu
});
486 return if $existingCpu->{cputype
} eq $cputype;
489 if (PVE
::QemuServer
::CPUConfig
::is_custom_model
($cputype)) {
490 $rpcenv->check($authuser, "/nodes", ['Sys.Audit']);
505 my $memoryoptions = {
511 my $hwtypeoptions = {
524 my $generaloptions = {
531 'migrate_downtime' => 1,
532 'migrate_speed' => 1,
545 my $vmpoweroptions = {
552 'vmstatestorage' => 1,
555 my $cloudinitoptions = {
565 my $check_vm_create_serial_perm = sub {
566 my ($rpcenv, $authuser, $vmid, $pool, $param) = @_;
568 return 1 if $authuser eq 'root@pam';
570 foreach my $opt (keys %{$param}) {
571 next if $opt !~ m/^serial\d+$/;
573 if ($param->{$opt} eq 'socket') {
574 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
576 die "only root can set '$opt' config for real devices\n";
583 my $check_vm_create_usb_perm = sub {
584 my ($rpcenv, $authuser, $vmid, $pool, $param) = @_;
586 return 1 if $authuser eq 'root@pam';
588 foreach my $opt (keys %{$param}) {
589 next if $opt !~ m/^usb\d+$/;
591 if ($param->{$opt} =~ m/spice/) {
592 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
594 die "only root can set '$opt' config for real devices\n";
601 my $check_vm_modify_config_perm = sub {
602 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
604 return 1 if $authuser eq 'root@pam';
606 foreach my $opt (@$key_list) {
607 # some checks (e.g., disk, serial port, usb) need to be done somewhere
608 # else, as there the permission can be value dependend
609 next if PVE
::QemuServer
::is_valid_drivename
($opt);
610 next if $opt eq 'cdrom';
611 next if $opt =~ m/^(?:unused|serial|usb)\d+$/;
614 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
615 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
616 } elsif ($memoryoptions->{$opt}) {
617 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
618 } elsif ($hwtypeoptions->{$opt}) {
619 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
620 } elsif ($generaloptions->{$opt}) {
621 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
622 # special case for startup since it changes host behaviour
623 if ($opt eq 'startup') {
624 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
626 } elsif ($vmpoweroptions->{$opt}) {
627 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
628 } elsif ($diskoptions->{$opt}) {
629 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
630 } elsif ($opt =~ m/^(?:net|ipconfig)\d+$/) {
631 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
632 } elsif ($cloudinitoptions->{$opt}) {
633 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Cloudinit', 'VM.Config.Network'], 1);
634 } elsif ($opt eq 'vmstate') {
635 # the user needs Disk and PowerMgmt privileges to change the vmstate
636 # also needs privileges on the storage, that will be checked later
637 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk', 'VM.PowerMgmt' ]);
639 # catches hostpci\d+, args, lock, etc.
640 # new options will be checked here
641 die "only root can set '$opt' config\n";
648 __PACKAGE__-
>register_method({
652 description
=> "Virtual machine index (per node).",
654 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
658 protected
=> 1, # qemu pid files are only readable by root
660 additionalProperties
=> 0,
662 node
=> get_standard_option
('pve-node'),
666 description
=> "Determine the full status of active VMs.",
674 properties
=> $PVE::QemuServer
::vmstatus_return_properties
,
676 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
681 my $rpcenv = PVE
::RPCEnvironment
::get
();
682 my $authuser = $rpcenv->get_user();
684 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
687 foreach my $vmid (keys %$vmstatus) {
688 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
690 my $data = $vmstatus->{$vmid};
697 my $parse_restore_archive = sub {
698 my ($storecfg, $archive) = @_;
700 my ($archive_storeid, $archive_volname) = PVE
::Storage
::parse_volume_id
($archive, 1);
702 if (defined($archive_storeid)) {
703 my $scfg = PVE
::Storage
::storage_config
($storecfg, $archive_storeid);
704 if ($scfg->{type
} eq 'pbs') {
711 my $path = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
719 __PACKAGE__-
>register_method({
723 description
=> "Create or restore a virtual machine.",
725 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
726 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
727 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
728 user
=> 'all', # check inside
733 additionalProperties
=> 0,
734 properties
=> PVE
::QemuServer
::json_config_properties
(
736 node
=> get_standard_option
('pve-node'),
737 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
739 description
=> "The backup archive. Either the file system path to a .tar or .vma file (use '-' to pipe data from stdin) or a proxmox storage backup volume identifier.",
743 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
745 storage
=> get_standard_option
('pve-storage-id', {
746 description
=> "Default storage.",
748 completion
=> \
&PVE
::QemuServer
::complete_storage
,
753 description
=> "Allow to overwrite existing VM.",
754 requires
=> 'archive',
759 description
=> "Assign a unique random ethernet address.",
760 requires
=> 'archive',
765 description
=> "Start the VM immediately from the backup and restore in background. PBS only.",
766 requires
=> 'archive',
770 type
=> 'string', format
=> 'pve-poolid',
771 description
=> "Add the VM to the specified pool.",
774 description
=> "Override I/O bandwidth limit (in KiB/s).",
778 default => 'restore limit from datacenter or storage config',
784 description
=> "Start VM after it was created successfully.",
796 my $rpcenv = PVE
::RPCEnvironment
::get
();
797 my $authuser = $rpcenv->get_user();
799 my $node = extract_param
($param, 'node');
800 my $vmid = extract_param
($param, 'vmid');
802 my $archive = extract_param
($param, 'archive');
803 my $is_restore = !!$archive;
805 my $bwlimit = extract_param
($param, 'bwlimit');
806 my $force = extract_param
($param, 'force');
807 my $pool = extract_param
($param, 'pool');
808 my $start_after_create = extract_param
($param, 'start');
809 my $storage = extract_param
($param, 'storage');
810 my $unique = extract_param
($param, 'unique');
811 my $live_restore = extract_param
($param, 'live-restore');
813 if (defined(my $ssh_keys = $param->{sshkeys
})) {
814 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
815 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
818 $param->{cpuunits
} = PVE
::GuestHelpers
::get_cpuunits
($param->{cpuunits
})
819 if defined($param->{cpuunits
}); # clamp value depending on cgroup version
821 PVE
::Cluster
::check_cfs_quorum
();
823 my $filename = PVE
::QemuConfig-
>config_file($vmid);
824 my $storecfg = PVE
::Storage
::config
();
826 if (defined($pool)) {
827 $rpcenv->check_pool_exist($pool);
830 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
831 if defined($storage);
833 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
835 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
837 } elsif ($archive && $force && (-f
$filename) &&
838 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
839 # OK: user has VM.Backup permissions, and want to restore an existing VM
845 for my $opt (sort keys $param->%*) {
846 if (PVE
::QemuServer
::Drive
::is_valid_drivename
($opt)) {
847 raise_param_exc
({ $opt => "option conflicts with option 'archive'" });
851 if ($archive eq '-') {
852 die "pipe requires cli environment\n" if $rpcenv->{type
} ne 'cli';
853 $archive = { type
=> 'pipe' };
855 PVE
::Storage
::check_volume_access
(
864 $archive = $parse_restore_archive->($storecfg, $archive);
868 if (scalar(keys $param->%*) > 0) {
869 &$resolve_cdrom_alias($param);
871 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
873 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
875 &$check_vm_create_serial_perm($rpcenv, $authuser, $vmid, $pool, $param);
876 &$check_vm_create_usb_perm($rpcenv, $authuser, $vmid, $pool, $param);
878 &$check_cpu_model_access($rpcenv, $authuser, $param);
880 $check_drive_param->($param, $storecfg);
882 PVE
::QemuServer
::add_random_macs
($param);
885 my $emsg = $is_restore ?
"unable to restore VM $vmid -" : "unable to create VM $vmid -";
887 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, $force) };
888 die "$emsg $@" if $@;
890 my $restored_data = 0;
891 my $restorefn = sub {
892 my $conf = PVE
::QemuConfig-
>load_config($vmid);
894 PVE
::QemuConfig-
>check_protection($conf, $emsg);
896 die "$emsg vm is running\n" if PVE
::QemuServer
::check_running
($vmid);
899 my $restore_options = {
904 live
=> $live_restore,
905 override_conf
=> $param,
907 if ($archive->{type
} eq 'file' || $archive->{type
} eq 'pipe') {
908 die "live-restore is only compatible with backup images from a Proxmox Backup Server\n"
910 PVE
::QemuServer
::restore_file_archive
($archive->{path
} // '-', $vmid, $authuser, $restore_options);
911 } elsif ($archive->{type
} eq 'pbs') {
912 PVE
::QemuServer
::restore_proxmox_backup_archive
($archive->{volid
}, $vmid, $authuser, $restore_options);
914 die "unknown backup archive type\n";
918 my $restored_conf = PVE
::QemuConfig-
>load_config($vmid);
919 # Convert restored VM to template if backup was VM template
920 if (PVE
::QemuConfig-
>is_template($restored_conf)) {
921 warn "Convert to template.\n";
922 eval { PVE
::QemuServer
::template_create
($vmid, $restored_conf) };
927 # ensure no old replication state are exists
928 PVE
::ReplicationState
::delete_guest_states
($vmid);
930 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
932 if ($start_after_create && !$live_restore) {
933 print "Execute autostart\n";
934 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
940 # ensure no old replication state are exists
941 PVE
::ReplicationState
::delete_guest_states
($vmid);
945 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
947 $conf->{meta
} = PVE
::QemuServer
::new_meta_info_string
();
951 ($vollist, my $created_opts) = $create_disks->(
962 $conf->{$_} = $created_opts->{$_} for keys $created_opts->%*;
964 if (!$conf->{boot
}) {
965 my $devs = PVE
::QemuServer
::get_default_bootdevices
($conf);
966 $conf->{boot
} = PVE
::QemuServer
::print_bootorder
($devs);
969 # auto generate uuid if user did not specify smbios1 option
970 if (!$conf->{smbios1
}) {
971 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
974 if ((!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') && $arch ne 'aarch64') {
975 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
978 my $machine = $conf->{machine
};
979 if (!$machine || $machine =~ m/^(?:pc|q35|virt)$/) {
980 # always pin Windows' machine version on create, they get to easily confused
981 if (PVE
::QemuServer
::windows_version
($conf->{ostype
})) {
982 $conf->{machine
} = PVE
::QemuServer
::windows_get_pinned_machine_version
($machine);
986 PVE
::QemuConfig-
>write_config($vmid, $conf);
992 foreach my $volid (@$vollist) {
993 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
999 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
1002 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
1004 if ($start_after_create) {
1005 print "Execute autostart\n";
1006 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
1011 my ($code, $worker_name);
1013 $worker_name = 'qmrestore';
1015 eval { $restorefn->() };
1017 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
1019 if ($restored_data) {
1020 warn "error after data was restored, VM disks should be OK but config may "
1021 ."require adaptions. VM $vmid state is NOT cleaned up.\n";
1023 warn "error before or during data restore, some or all disks were not "
1024 ."completely restored. VM $vmid state is NOT cleaned up.\n";
1030 $worker_name = 'qmcreate';
1032 eval { $createfn->() };
1035 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
1036 unlink($conffile) or die "failed to remove config file: $!\n";
1044 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
1047 __PACKAGE__-
>register_method({
1052 description
=> "Directory index",
1057 additionalProperties
=> 0,
1059 node
=> get_standard_option
('pve-node'),
1060 vmid
=> get_standard_option
('pve-vmid'),
1068 subdir
=> { type
=> 'string' },
1071 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1077 { subdir
=> 'config' },
1078 { subdir
=> 'pending' },
1079 { subdir
=> 'status' },
1080 { subdir
=> 'unlink' },
1081 { subdir
=> 'vncproxy' },
1082 { subdir
=> 'termproxy' },
1083 { subdir
=> 'migrate' },
1084 { subdir
=> 'resize' },
1085 { subdir
=> 'move' },
1086 { subdir
=> 'rrd' },
1087 { subdir
=> 'rrddata' },
1088 { subdir
=> 'monitor' },
1089 { subdir
=> 'agent' },
1090 { subdir
=> 'snapshot' },
1091 { subdir
=> 'spiceproxy' },
1092 { subdir
=> 'sendkey' },
1093 { subdir
=> 'firewall' },
1099 __PACKAGE__-
>register_method ({
1100 subclass
=> "PVE::API2::Firewall::VM",
1101 path
=> '{vmid}/firewall',
1104 __PACKAGE__-
>register_method ({
1105 subclass
=> "PVE::API2::Qemu::Agent",
1106 path
=> '{vmid}/agent',
1109 __PACKAGE__-
>register_method({
1111 path
=> '{vmid}/rrd',
1113 protected
=> 1, # fixme: can we avoid that?
1115 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1117 description
=> "Read VM RRD statistics (returns PNG)",
1119 additionalProperties
=> 0,
1121 node
=> get_standard_option
('pve-node'),
1122 vmid
=> get_standard_option
('pve-vmid'),
1124 description
=> "Specify the time frame you are interested in.",
1126 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
1129 description
=> "The list of datasources you want to display.",
1130 type
=> 'string', format
=> 'pve-configid-list',
1133 description
=> "The RRD consolidation function",
1135 enum
=> [ 'AVERAGE', 'MAX' ],
1143 filename
=> { type
=> 'string' },
1149 return PVE
::RRD
::create_rrd_graph
(
1150 "pve2-vm/$param->{vmid}", $param->{timeframe
},
1151 $param->{ds
}, $param->{cf
});
1155 __PACKAGE__-
>register_method({
1157 path
=> '{vmid}/rrddata',
1159 protected
=> 1, # fixme: can we avoid that?
1161 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1163 description
=> "Read VM RRD statistics",
1165 additionalProperties
=> 0,
1167 node
=> get_standard_option
('pve-node'),
1168 vmid
=> get_standard_option
('pve-vmid'),
1170 description
=> "Specify the time frame you are interested in.",
1172 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
1175 description
=> "The RRD consolidation function",
1177 enum
=> [ 'AVERAGE', 'MAX' ],
1192 return PVE
::RRD
::create_rrd_data
(
1193 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
1197 __PACKAGE__-
>register_method({
1198 name
=> 'vm_config',
1199 path
=> '{vmid}/config',
1202 description
=> "Get the virtual machine configuration with pending configuration " .
1203 "changes applied. Set the 'current' parameter to get the current configuration instead.",
1205 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1208 additionalProperties
=> 0,
1210 node
=> get_standard_option
('pve-node'),
1211 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1213 description
=> "Get current values (instead of pending values).",
1218 snapshot
=> get_standard_option
('pve-snapshot-name', {
1219 description
=> "Fetch config values from given snapshot.",
1222 my ($cmd, $pname, $cur, $args) = @_;
1223 PVE
::QemuConfig-
>snapshot_list($args->[0]);
1229 description
=> "The VM configuration.",
1231 properties
=> PVE
::QemuServer
::json_config_properties
({
1234 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
1241 raise_param_exc
({ snapshot
=> "cannot use 'snapshot' parameter with 'current'",
1242 current
=> "cannot use 'snapshot' parameter with 'current'"})
1243 if ($param->{snapshot
} && $param->{current
});
1246 if ($param->{snapshot
}) {
1247 $conf = PVE
::QemuConfig-
>load_snapshot_config($param->{vmid
}, $param->{snapshot
});
1249 $conf = PVE
::QemuConfig-
>load_current_config($param->{vmid
}, $param->{current
});
1251 $conf->{cipassword
} = '**********' if $conf->{cipassword
};
1256 __PACKAGE__-
>register_method({
1257 name
=> 'vm_pending',
1258 path
=> '{vmid}/pending',
1261 description
=> "Get the virtual machine configuration with both current and pending values.",
1263 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1266 additionalProperties
=> 0,
1268 node
=> get_standard_option
('pve-node'),
1269 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1278 description
=> "Configuration option name.",
1282 description
=> "Current value.",
1287 description
=> "Pending value.",
1292 description
=> "Indicates a pending delete request if present and not 0. " .
1293 "The value 2 indicates a force-delete request.",
1305 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1307 my $pending_delete_hash = PVE
::QemuConfig-
>parse_pending_delete($conf->{pending
}->{delete});
1309 $conf->{cipassword
} = '**********' if defined($conf->{cipassword
});
1310 $conf->{pending
}->{cipassword
} = '********** ' if defined($conf->{pending
}->{cipassword
});
1312 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);
1315 __PACKAGE__-
>register_method({
1316 name
=> 'cloudinit_pending',
1317 path
=> '{vmid}/cloudinit',
1320 description
=> "Get the cloudinit configuration with both current and pending values.",
1322 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1325 additionalProperties
=> 0,
1327 node
=> get_standard_option
('pve-node'),
1328 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1337 description
=> "Configuration option name.",
1341 description
=> "Current value.",
1346 description
=> "Pending value.",
1351 description
=> "Indicates a pending delete request if present and not 0. " .
1352 "The value 2 indicates a force-delete request.",
1364 my $vmid = $param->{vmid
};
1365 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1367 if (defined($conf->{cipassword
}) &&
1368 defined($conf->{cloudinit
}->{cipassword
}) &&
1369 $conf->{cipassword
} ne $conf->{cloudinit
}->{cipassword
}) {
1370 $conf->{cipassword
} = '********** ';
1371 } elsif (defined($conf->{cipassword
})) {
1372 $conf->{cipassword
} = '**********';
1375 $conf->{cloudinit
}->{cipassword
} = '**********' if defined($conf->{cloudinit
}->{cipassword
});
1377 my $res = PVE
::QemuServer
::Cloudinit
::get_pending_config
($conf, $vmid);
1382 # POST/PUT {vmid}/config implementation
1384 # The original API used PUT (idempotent) an we assumed that all operations
1385 # are fast. But it turned out that almost any configuration change can
1386 # involve hot-plug actions, or disk alloc/free. Such actions can take long
1387 # time to complete and have side effects (not idempotent).
1389 # The new implementation uses POST and forks a worker process. We added
1390 # a new option 'background_delay'. If specified we wait up to
1391 # 'background_delay' second for the worker task to complete. It returns null
1392 # if the task is finished within that time, else we return the UPID.
1394 my $update_vm_api = sub {
1395 my ($param, $sync) = @_;
1397 my $rpcenv = PVE
::RPCEnvironment
::get
();
1399 my $authuser = $rpcenv->get_user();
1401 my $node = extract_param
($param, 'node');
1403 my $vmid = extract_param
($param, 'vmid');
1405 my $digest = extract_param
($param, 'digest');
1407 my $background_delay = extract_param
($param, 'background_delay');
1409 if (defined(my $cipassword = $param->{cipassword
})) {
1410 # Same logic as in cloud-init (but with the regex fixed...)
1411 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
1412 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
1415 my @paramarr = (); # used for log message
1416 foreach my $key (sort keys %$param) {
1417 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
1418 push @paramarr, "-$key", $value;
1421 my $skiplock = extract_param
($param, 'skiplock');
1422 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1423 if $skiplock && $authuser ne 'root@pam';
1425 my $delete_str = extract_param
($param, 'delete');
1427 my $revert_str = extract_param
($param, 'revert');
1429 my $force = extract_param
($param, 'force');
1431 if (defined(my $ssh_keys = $param->{sshkeys
})) {
1432 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
1433 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
1436 $param->{cpuunits
} = PVE
::GuestHelpers
::get_cpuunits
($param->{cpuunits
})
1437 if defined($param->{cpuunits
}); # clamp value depending on cgroup version
1439 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
1441 my $storecfg = PVE
::Storage
::config
();
1443 my $defaults = PVE
::QemuServer
::load_defaults
();
1445 &$resolve_cdrom_alias($param);
1447 # now try to verify all parameters
1450 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1451 if (!PVE
::QemuServer
::option_exists
($opt)) {
1452 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1455 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1456 "-revert $opt' at the same time" })
1457 if defined($param->{$opt});
1459 $revert->{$opt} = 1;
1463 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1464 $opt = 'ide2' if $opt eq 'cdrom';
1466 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1467 "-delete $opt' at the same time" })
1468 if defined($param->{$opt});
1470 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1471 "-revert $opt' at the same time" })
1474 if (!PVE
::QemuServer
::option_exists
($opt)) {
1475 raise_param_exc
({ delete => "unknown option '$opt'" });
1481 my $repl_conf = PVE
::ReplicationConfig-
>new();
1482 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1483 my $check_replication = sub {
1485 return if !$is_replicated;
1486 my $volid = $drive->{file
};
1487 return if !$volid || !($drive->{replicate
}//1);
1488 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1490 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1491 die "cannot add non-managed/pass-through volume to a replicated VM\n"
1492 if !defined($storeid);
1494 return if defined($volname) && $volname eq 'cloudinit';
1497 if ($volid =~ $NEW_DISK_RE) {
1499 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1501 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1503 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1504 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1505 return if $scfg->{shared
};
1506 die "cannot add non-replicatable volume to a replicated VM\n";
1509 $check_drive_param->($param, $storecfg, $check_replication);
1511 foreach my $opt (keys %$param) {
1512 if ($opt =~ m/^net(\d+)$/) {
1514 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1515 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1516 } elsif ($opt eq 'vmgenid') {
1517 if ($param->{$opt} eq '1') {
1518 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1520 } elsif ($opt eq 'hookscript') {
1521 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1522 raise_param_exc
({ $opt => $@ }) if $@;
1526 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1528 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1530 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1532 my $updatefn = sub {
1534 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1536 die "checksum missmatch (file change by other user?)\n"
1537 if $digest && $digest ne $conf->{digest
};
1539 &$check_cpu_model_access($rpcenv, $authuser, $param, $conf);
1541 # FIXME: 'suspended' lock should probabyl be a state or "weak" lock?!
1542 if (scalar(@delete) && grep { $_ eq 'vmstate'} @delete) {
1543 if (defined($conf->{lock}) && $conf->{lock} eq 'suspended') {
1544 delete $conf->{lock}; # for check lock check, not written out
1545 push @delete, 'lock'; # this is the real deal to write it out
1547 push @delete, 'runningmachine' if $conf->{runningmachine
};
1548 push @delete, 'runningcpu' if $conf->{runningcpu
};
1551 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1553 foreach my $opt (keys %$revert) {
1554 if (defined($conf->{$opt})) {
1555 $param->{$opt} = $conf->{$opt};
1556 } elsif (defined($conf->{pending
}->{$opt})) {
1561 if ($param->{memory
} || defined($param->{balloon
})) {
1562 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1563 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1565 die "balloon value too large (must be smaller than assigned memory)\n"
1566 if $balloon && $balloon > $maxmem;
1569 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1573 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1575 # write updates to pending section
1577 my $modified = {}; # record what $option we modify
1580 if (my $boot = $conf->{boot
}) {
1581 my $bootcfg = PVE
::JSONSchema
::parse_property_string
('pve-qm-boot', $boot);
1582 @bootorder = PVE
::Tools
::split_list
($bootcfg->{order
}) if $bootcfg && $bootcfg->{order
};
1584 my $bootorder_deleted = grep {$_ eq 'bootorder'} @delete;
1586 my $check_drive_perms = sub {
1587 my ($opt, $val) = @_;
1588 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val, 1);
1589 # FIXME: cloudinit: CDROM or Disk?
1590 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1591 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1593 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1597 foreach my $opt (@delete) {
1598 $modified->{$opt} = 1;
1599 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1601 # value of what we want to delete, independent if pending or not
1602 my $val = $conf->{$opt} // $conf->{pending
}->{$opt};
1603 if (!defined($val)) {
1604 warn "cannot delete '$opt' - not set in current configuration!\n";
1605 $modified->{$opt} = 0;
1608 my $is_pending_val = defined($conf->{pending
}->{$opt});
1609 delete $conf->{pending
}->{$opt};
1611 # remove from bootorder if necessary
1612 if (!$bootorder_deleted && @bootorder && grep {$_ eq $opt} @bootorder) {
1613 @bootorder = grep {$_ ne $opt} @bootorder;
1614 $conf->{pending
}->{boot
} = PVE
::QemuServer
::print_bootorder
(\
@bootorder);
1615 $modified->{boot
} = 1;
1618 if ($opt =~ m/^unused/) {
1619 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1620 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1621 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1622 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1623 delete $conf->{$opt};
1624 PVE
::QemuConfig-
>write_config($vmid, $conf);
1626 } elsif ($opt eq 'vmstate') {
1627 PVE
::QemuConfig-
>check_protection($conf, "can't remove vmstate '$val'");
1628 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, { file
=> $val }, $rpcenv, $authuser, 1)) {
1629 delete $conf->{$opt};
1630 PVE
::QemuConfig-
>write_config($vmid, $conf);
1632 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1633 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1634 $check_drive_perms->($opt, $val);
1635 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $val))
1637 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1638 PVE
::QemuConfig-
>write_config($vmid, $conf);
1639 } elsif ($opt =~ m/^serial\d+$/) {
1640 if ($val eq 'socket') {
1641 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1642 } elsif ($authuser ne 'root@pam') {
1643 die "only root can delete '$opt' config for real devices\n";
1645 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1646 PVE
::QemuConfig-
>write_config($vmid, $conf);
1647 } elsif ($opt =~ m/^usb\d+$/) {
1648 if ($val =~ m/spice/) {
1649 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1650 } elsif ($authuser ne 'root@pam') {
1651 die "only root can delete '$opt' config for real devices\n";
1653 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1654 PVE
::QemuConfig-
>write_config($vmid, $conf);
1656 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1657 PVE
::QemuConfig-
>write_config($vmid, $conf);
1661 foreach my $opt (keys %$param) { # add/change
1662 $modified->{$opt} = 1;
1663 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1664 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1666 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
1668 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1670 if ($conf->{$opt}) {
1671 $check_drive_perms->($opt, $conf->{$opt});
1675 $check_drive_perms->($opt, $param->{$opt});
1676 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1677 if defined($conf->{pending
}->{$opt});
1679 my (undef, $created_opts) = $create_disks->(
1687 {$opt => $param->{$opt}},
1689 $conf->{pending
}->{$_} = $created_opts->{$_} for keys $created_opts->%*;
1691 # default legacy boot order implies all cdroms anyway
1693 # append new CD drives to bootorder to mark them bootable
1694 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt}, 1);
1695 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1) && !grep(/^$opt$/, @bootorder)) {
1696 push @bootorder, $opt;
1697 $conf->{pending
}->{boot
} = PVE
::QemuServer
::print_bootorder
(\
@bootorder);
1698 $modified->{boot
} = 1;
1701 } elsif ($opt =~ m/^serial\d+/) {
1702 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1703 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1704 } elsif ($authuser ne 'root@pam') {
1705 die "only root can modify '$opt' config for real devices\n";
1707 $conf->{pending
}->{$opt} = $param->{$opt};
1708 } elsif ($opt =~ m/^usb\d+/) {
1709 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1710 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1711 } elsif ($authuser ne 'root@pam') {
1712 die "only root can modify '$opt' config for real devices\n";
1714 $conf->{pending
}->{$opt} = $param->{$opt};
1716 $conf->{pending
}->{$opt} = $param->{$opt};
1718 if ($opt eq 'boot') {
1719 my $new_bootcfg = PVE
::JSONSchema
::parse_property_string
('pve-qm-boot', $param->{$opt});
1720 if ($new_bootcfg->{order
}) {
1721 my @devs = PVE
::Tools
::split_list
($new_bootcfg->{order
});
1722 for my $dev (@devs) {
1723 my $exists = $conf->{$dev} || $conf->{pending
}->{$dev} || $param->{$dev};
1724 my $deleted = grep {$_ eq $dev} @delete;
1725 die "invalid bootorder: device '$dev' does not exist'\n"
1726 if !$exists || $deleted;
1729 # remove legacy boot order settings if new one set
1730 $conf->{pending
}->{$opt} = PVE
::QemuServer
::print_bootorder
(\
@devs);
1731 PVE
::QemuConfig-
>add_to_pending_delete($conf, "bootdisk")
1732 if $conf->{bootdisk
};
1736 PVE
::QemuConfig-
>remove_from_pending_delete($conf, $opt);
1737 PVE
::QemuConfig-
>write_config($vmid, $conf);
1740 # remove pending changes when nothing changed
1741 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1742 my $changes = PVE
::QemuConfig-
>cleanup_pending($conf);
1743 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1745 return if !scalar(keys %{$conf->{pending
}});
1747 my $running = PVE
::QemuServer
::check_running
($vmid);
1749 # apply pending changes
1751 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1755 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1757 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $errors);
1759 raise_param_exc
($errors) if scalar(keys %$errors);
1768 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1770 if ($background_delay) {
1772 # Note: It would be better to do that in the Event based HTTPServer
1773 # to avoid blocking call to sleep.
1775 my $end_time = time() + $background_delay;
1777 my $task = PVE
::Tools
::upid_decode
($upid);
1780 while (time() < $end_time) {
1781 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1783 sleep(1); # this gets interrupted when child process ends
1787 my $status = PVE
::Tools
::upid_read_status
($upid);
1788 return if !PVE
::Tools
::upid_status_is_error
($status);
1789 die "failed to update VM $vmid: $status\n";
1797 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1800 my $vm_config_perm_list = [
1805 'VM.Config.Network',
1807 'VM.Config.Options',
1808 'VM.Config.Cloudinit',
1811 __PACKAGE__-
>register_method({
1812 name
=> 'update_vm_async',
1813 path
=> '{vmid}/config',
1817 description
=> "Set virtual machine options (asynchrounous API).",
1819 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1822 additionalProperties
=> 0,
1823 properties
=> PVE
::QemuServer
::json_config_properties
(
1825 node
=> get_standard_option
('pve-node'),
1826 vmid
=> get_standard_option
('pve-vmid'),
1827 skiplock
=> get_standard_option
('skiplock'),
1829 type
=> 'string', format
=> 'pve-configid-list',
1830 description
=> "A list of settings you want to delete.",
1834 type
=> 'string', format
=> 'pve-configid-list',
1835 description
=> "Revert a pending change.",
1840 description
=> $opt_force_description,
1842 requires
=> 'delete',
1846 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1850 background_delay
=> {
1852 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1858 1, # with_disk_alloc
1865 code
=> $update_vm_api,
1868 __PACKAGE__-
>register_method({
1869 name
=> 'update_vm',
1870 path
=> '{vmid}/config',
1874 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1876 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1879 additionalProperties
=> 0,
1880 properties
=> PVE
::QemuServer
::json_config_properties
(
1882 node
=> get_standard_option
('pve-node'),
1883 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1884 skiplock
=> get_standard_option
('skiplock'),
1886 type
=> 'string', format
=> 'pve-configid-list',
1887 description
=> "A list of settings you want to delete.",
1891 type
=> 'string', format
=> 'pve-configid-list',
1892 description
=> "Revert a pending change.",
1897 description
=> $opt_force_description,
1899 requires
=> 'delete',
1903 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1908 1, # with_disk_alloc
1911 returns
=> { type
=> 'null' },
1914 &$update_vm_api($param, 1);
1919 __PACKAGE__-
>register_method({
1920 name
=> 'destroy_vm',
1925 description
=> "Destroy the VM and all used/owned volumes. Removes any VM specific permissions"
1926 ." and firewall rules",
1928 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1931 additionalProperties
=> 0,
1933 node
=> get_standard_option
('pve-node'),
1934 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1935 skiplock
=> get_standard_option
('skiplock'),
1938 description
=> "Remove VMID from configurations, like backup & replication jobs and HA.",
1941 'destroy-unreferenced-disks' => {
1943 description
=> "If set, destroy additionally all disks not referenced in the config"
1944 ." but with a matching VMID from all enabled storages.",
1956 my $rpcenv = PVE
::RPCEnvironment
::get
();
1957 my $authuser = $rpcenv->get_user();
1958 my $vmid = $param->{vmid
};
1960 my $skiplock = $param->{skiplock
};
1961 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1962 if $skiplock && $authuser ne 'root@pam';
1964 my $early_checks = sub {
1966 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1967 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1969 my $ha_managed = PVE
::HA
::Config
::service_is_configured
("vm:$vmid");
1971 if (!$param->{purge
}) {
1972 die "unable to remove VM $vmid - used in HA resources and purge parameter not set.\n"
1974 # don't allow destroy if with replication jobs but no purge param
1975 my $repl_conf = PVE
::ReplicationConfig-
>new();
1976 $repl_conf->check_for_existing_jobs($vmid);
1979 die "VM $vmid is running - destroy failed\n"
1980 if PVE
::QemuServer
::check_running
($vmid);
1990 my $storecfg = PVE
::Storage
::config
();
1992 syslog
('info', "destroy VM $vmid: $upid\n");
1993 PVE
::QemuConfig-
>lock_config($vmid, sub {
1994 # repeat, config might have changed
1995 my $ha_managed = $early_checks->();
1997 my $purge_unreferenced = $param->{'destroy-unreferenced-disks'};
1999 PVE
::QemuServer
::destroy_vm
(
2002 $skiplock, { lock => 'destroyed' },
2003 $purge_unreferenced,
2006 PVE
::AccessControl
::remove_vm_access
($vmid);
2007 PVE
::Firewall
::remove_vmfw_conf
($vmid);
2008 if ($param->{purge
}) {
2009 print "purging VM $vmid from related configurations..\n";
2010 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
2011 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
2014 PVE
::HA
::Config
::delete_service_from_config
("vm:$vmid");
2015 print "NOTE: removed VM $vmid from HA resource configuration.\n";
2019 # only now remove the zombie config, else we can have reuse race
2020 PVE
::QemuConfig-
>destroy_config($vmid);
2024 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
2027 __PACKAGE__-
>register_method({
2029 path
=> '{vmid}/unlink',
2033 description
=> "Unlink/delete disk images.",
2035 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
2038 additionalProperties
=> 0,
2040 node
=> get_standard_option
('pve-node'),
2041 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2043 type
=> 'string', format
=> 'pve-configid-list',
2044 description
=> "A list of disk IDs you want to delete.",
2048 description
=> $opt_force_description,
2053 returns
=> { type
=> 'null'},
2057 $param->{delete} = extract_param
($param, 'idlist');
2059 __PACKAGE__-
>update_vm($param);
2064 # uses good entropy, each char is limited to 6 bit to get printable chars simply
2065 my $gen_rand_chars = sub {
2068 die "invalid length $length" if $length < 1;
2070 my $min = ord('!'); # first printable ascii
2072 my $rand_bytes = Crypt
::OpenSSL
::Random
::random_bytes
($length);
2073 die "failed to generate random bytes!\n"
2076 my $str = join('', map { chr((ord($_) & 0x3F) + $min) } split('', $rand_bytes));
2083 __PACKAGE__-
>register_method({
2085 path
=> '{vmid}/vncproxy',
2089 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2091 description
=> "Creates a TCP VNC proxy connections.",
2093 additionalProperties
=> 0,
2095 node
=> get_standard_option
('pve-node'),
2096 vmid
=> get_standard_option
('pve-vmid'),
2100 description
=> "starts websockify instead of vncproxy",
2102 'generate-password' => {
2106 description
=> "Generates a random password to be used as ticket instead of the API ticket.",
2111 additionalProperties
=> 0,
2113 user
=> { type
=> 'string' },
2114 ticket
=> { type
=> 'string' },
2117 description
=> "Returned if requested with 'generate-password' param."
2118 ." Consists of printable ASCII characters ('!' .. '~').",
2121 cert
=> { type
=> 'string' },
2122 port
=> { type
=> 'integer' },
2123 upid
=> { type
=> 'string' },
2129 my $rpcenv = PVE
::RPCEnvironment
::get
();
2131 my $authuser = $rpcenv->get_user();
2133 my $vmid = $param->{vmid
};
2134 my $node = $param->{node
};
2135 my $websocket = $param->{websocket
};
2137 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
2141 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
2142 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
2145 my $authpath = "/vms/$vmid";
2147 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
2148 my $password = $ticket;
2149 if ($param->{'generate-password'}) {
2150 $password = $gen_rand_chars->(8);
2153 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
2159 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
2160 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
2161 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
2162 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
2163 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, defined($serial) ?
'-t' : '-T');
2165 $family = PVE
::Tools
::get_host_address_family
($node);
2168 my $port = PVE
::Tools
::next_vnc_port
($family);
2175 syslog
('info', "starting vnc proxy $upid\n");
2179 if (defined($serial)) {
2181 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $serial, '-escape', '0' ];
2183 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
2184 '-timeout', $timeout, '-authpath', $authpath,
2185 '-perm', 'Sys.Console'];
2187 if ($param->{websocket
}) {
2188 $ENV{PVE_VNC_TICKET
} = $password; # pass ticket to vncterm
2189 push @$cmd, '-notls', '-listen', 'localhost';
2192 push @$cmd, '-c', @$remcmd, @$termcmd;
2194 PVE
::Tools
::run_command
($cmd);
2198 $ENV{LC_PVE_TICKET
} = $password if $websocket; # set ticket with "qm vncproxy"
2200 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
2202 my $sock = IO
::Socket
::IP-
>new(
2207 GetAddrInfoFlags
=> 0,
2208 ) or die "failed to create socket: $!\n";
2209 # Inside the worker we shouldn't have any previous alarms
2210 # running anyway...:
2212 local $SIG{ALRM
} = sub { die "connection timed out\n" };
2214 accept(my $cli, $sock) or die "connection failed: $!\n";
2217 if (PVE
::Tools
::run_command
($cmd,
2218 output
=> '>&'.fileno($cli),
2219 input
=> '<&'.fileno($cli),
2222 die "Failed to run vncproxy.\n";
2229 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
2231 PVE
::Tools
::wait_for_vnc_port
($port);
2240 $res->{password
} = $password if $param->{'generate-password'};
2245 __PACKAGE__-
>register_method({
2246 name
=> 'termproxy',
2247 path
=> '{vmid}/termproxy',
2251 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2253 description
=> "Creates a TCP proxy connections.",
2255 additionalProperties
=> 0,
2257 node
=> get_standard_option
('pve-node'),
2258 vmid
=> get_standard_option
('pve-vmid'),
2262 enum
=> [qw(serial0 serial1 serial2 serial3)],
2263 description
=> "opens a serial terminal (defaults to display)",
2268 additionalProperties
=> 0,
2270 user
=> { type
=> 'string' },
2271 ticket
=> { type
=> 'string' },
2272 port
=> { type
=> 'integer' },
2273 upid
=> { type
=> 'string' },
2279 my $rpcenv = PVE
::RPCEnvironment
::get
();
2281 my $authuser = $rpcenv->get_user();
2283 my $vmid = $param->{vmid
};
2284 my $node = $param->{node
};
2285 my $serial = $param->{serial
};
2287 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
2289 if (!defined($serial)) {
2291 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
2292 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
2296 my $authpath = "/vms/$vmid";
2298 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
2303 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
2304 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
2305 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
2306 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, '-t');
2307 push @$remcmd, '--';
2309 $family = PVE
::Tools
::get_host_address_family
($node);
2312 my $port = PVE
::Tools
::next_vnc_port
($family);
2314 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
2315 push @$termcmd, '-iface', $serial if $serial;
2320 syslog
('info', "starting qemu termproxy $upid\n");
2322 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
2323 '--perm', 'VM.Console', '--'];
2324 push @$cmd, @$remcmd, @$termcmd;
2326 PVE
::Tools
::run_command
($cmd);
2329 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
2331 PVE
::Tools
::wait_for_vnc_port
($port);
2341 __PACKAGE__-
>register_method({
2342 name
=> 'vncwebsocket',
2343 path
=> '{vmid}/vncwebsocket',
2346 description
=> "You also need to pass a valid ticket (vncticket).",
2347 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2349 description
=> "Opens a weksocket for VNC traffic.",
2351 additionalProperties
=> 0,
2353 node
=> get_standard_option
('pve-node'),
2354 vmid
=> get_standard_option
('pve-vmid'),
2356 description
=> "Ticket from previous call to vncproxy.",
2361 description
=> "Port number returned by previous vncproxy call.",
2371 port
=> { type
=> 'string' },
2377 my $rpcenv = PVE
::RPCEnvironment
::get
();
2379 my $authuser = $rpcenv->get_user();
2381 my $vmid = $param->{vmid
};
2382 my $node = $param->{node
};
2384 my $authpath = "/vms/$vmid";
2386 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
2388 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
2390 # Note: VNC ports are acessible from outside, so we do not gain any
2391 # security if we verify that $param->{port} belongs to VM $vmid. This
2392 # check is done by verifying the VNC ticket (inside VNC protocol).
2394 my $port = $param->{port
};
2396 return { port
=> $port };
2399 __PACKAGE__-
>register_method({
2400 name
=> 'spiceproxy',
2401 path
=> '{vmid}/spiceproxy',
2406 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2408 description
=> "Returns a SPICE configuration to connect to the VM.",
2410 additionalProperties
=> 0,
2412 node
=> get_standard_option
('pve-node'),
2413 vmid
=> get_standard_option
('pve-vmid'),
2414 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
2417 returns
=> get_standard_option
('remote-viewer-config'),
2421 my $rpcenv = PVE
::RPCEnvironment
::get
();
2423 my $authuser = $rpcenv->get_user();
2425 my $vmid = $param->{vmid
};
2426 my $node = $param->{node
};
2427 my $proxy = $param->{proxy
};
2429 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
2430 my $title = "VM $vmid";
2431 $title .= " - ". $conf->{name
} if $conf->{name
};
2433 my $port = PVE
::QemuServer
::spice_port
($vmid);
2435 my ($ticket, undef, $remote_viewer_config) =
2436 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
2438 mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
2439 mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
2441 return $remote_viewer_config;
2444 __PACKAGE__-
>register_method({
2446 path
=> '{vmid}/status',
2449 description
=> "Directory index",
2454 additionalProperties
=> 0,
2456 node
=> get_standard_option
('pve-node'),
2457 vmid
=> get_standard_option
('pve-vmid'),
2465 subdir
=> { type
=> 'string' },
2468 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
2474 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2477 { subdir
=> 'current' },
2478 { subdir
=> 'start' },
2479 { subdir
=> 'stop' },
2480 { subdir
=> 'reset' },
2481 { subdir
=> 'shutdown' },
2482 { subdir
=> 'suspend' },
2483 { subdir
=> 'reboot' },
2489 __PACKAGE__-
>register_method({
2490 name
=> 'vm_status',
2491 path
=> '{vmid}/status/current',
2494 protected
=> 1, # qemu pid files are only readable by root
2495 description
=> "Get virtual machine status.",
2497 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2500 additionalProperties
=> 0,
2502 node
=> get_standard_option
('pve-node'),
2503 vmid
=> get_standard_option
('pve-vmid'),
2509 %$PVE::QemuServer
::vmstatus_return_properties
,
2511 description
=> "HA manager service status.",
2515 description
=> "Qemu VGA configuration supports spice.",
2520 description
=> "Qemu GuestAgent enabled in config.",
2530 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2532 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
2533 my $status = $vmstatus->{$param->{vmid
}};
2535 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
2538 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
2539 my $spice = defined($vga->{type
}) && $vga->{type
} =~ /^virtio/;
2540 $spice ||= PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
2541 $status->{spice
} = 1 if $spice;
2543 $status->{agent
} = 1 if PVE
::QemuServer
::get_qga_key
($conf, 'enabled');
2548 __PACKAGE__-
>register_method({
2550 path
=> '{vmid}/status/start',
2554 description
=> "Start virtual machine.",
2556 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2559 additionalProperties
=> 0,
2561 node
=> get_standard_option
('pve-node'),
2562 vmid
=> get_standard_option
('pve-vmid',
2563 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
2564 skiplock
=> get_standard_option
('skiplock'),
2565 stateuri
=> get_standard_option
('pve-qm-stateuri'),
2566 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
2569 enum
=> ['secure', 'insecure'],
2570 description
=> "Migration traffic is encrypted using an SSH " .
2571 "tunnel by default. On secure, completely private networks " .
2572 "this can be disabled to increase performance.",
2575 migration_network
=> {
2576 type
=> 'string', format
=> 'CIDR',
2577 description
=> "CIDR of the (sub) network that is used for migration.",
2580 machine
=> get_standard_option
('pve-qemu-machine'),
2582 description
=> "Override QEMU's -cpu argument with the given string.",
2586 targetstorage
=> get_standard_option
('pve-targetstorage'),
2588 description
=> "Wait maximal timeout seconds.",
2591 default => 'max(30, vm memory in GiB)',
2602 my $rpcenv = PVE
::RPCEnvironment
::get
();
2603 my $authuser = $rpcenv->get_user();
2605 my $node = extract_param
($param, 'node');
2606 my $vmid = extract_param
($param, 'vmid');
2607 my $timeout = extract_param
($param, 'timeout');
2608 my $machine = extract_param
($param, 'machine');
2610 my $get_root_param = sub {
2611 my $value = extract_param
($param, $_[0]);
2612 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2613 if $value && $authuser ne 'root@pam';
2617 my $stateuri = $get_root_param->('stateuri');
2618 my $skiplock = $get_root_param->('skiplock');
2619 my $migratedfrom = $get_root_param->('migratedfrom');
2620 my $migration_type = $get_root_param->('migration_type');
2621 my $migration_network = $get_root_param->('migration_network');
2622 my $targetstorage = $get_root_param->('targetstorage');
2623 my $force_cpu = $get_root_param->('force-cpu');
2627 if ($targetstorage) {
2628 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2630 $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
2631 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
2635 # read spice ticket from STDIN
2637 my $nbd_protocol_version = 0;
2638 my $replicated_volumes = {};
2639 my $offline_volumes = {};
2640 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2641 while (defined(my $line = <STDIN
>)) {
2643 if ($line =~ m/^spice_ticket: (.+)$/) {
2645 } elsif ($line =~ m/^nbd_protocol_version: (\d+)$/) {
2646 $nbd_protocol_version = $1;
2647 } elsif ($line =~ m/^replicated_volume: (.*)$/) {
2648 $replicated_volumes->{$1} = 1;
2649 } elsif ($line =~ m/^tpmstate0: (.*)$/) { # Deprecated, use offline_volume instead
2650 $offline_volumes->{tpmstate0
} = $1;
2651 } elsif ($line =~ m/^offline_volume: ([^:]+): (.*)$/) {
2652 $offline_volumes->{$1} = $2;
2653 } elsif (!$spice_ticket) {
2654 # fallback for old source node
2655 $spice_ticket = $line;
2657 warn "unknown 'start' parameter on STDIN: '$line'\n";
2662 PVE
::Cluster
::check_cfs_quorum
();
2664 my $storecfg = PVE
::Storage
::config
();
2666 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2670 print "Requesting HA start for VM $vmid\n";
2672 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2673 PVE
::Tools
::run_command
($cmd);
2677 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2684 syslog
('info', "start VM $vmid: $upid\n");
2686 my $migrate_opts = {
2687 migratedfrom
=> $migratedfrom,
2688 spice_ticket
=> $spice_ticket,
2689 network
=> $migration_network,
2690 type
=> $migration_type,
2691 storagemap
=> $storagemap,
2692 nbd_proto_version
=> $nbd_protocol_version,
2693 replicated_volumes
=> $replicated_volumes,
2694 offline_volumes
=> $offline_volumes,
2698 statefile
=> $stateuri,
2699 skiplock
=> $skiplock,
2700 forcemachine
=> $machine,
2701 timeout
=> $timeout,
2702 forcecpu
=> $force_cpu,
2705 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $params, $migrate_opts);
2709 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2713 __PACKAGE__-
>register_method({
2715 path
=> '{vmid}/status/stop',
2719 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2720 "is akin to pulling the power plug of a running computer and may damage the VM data",
2722 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2725 additionalProperties
=> 0,
2727 node
=> get_standard_option
('pve-node'),
2728 vmid
=> get_standard_option
('pve-vmid',
2729 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2730 skiplock
=> get_standard_option
('skiplock'),
2731 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2733 description
=> "Wait maximal timeout seconds.",
2739 description
=> "Do not deactivate storage volumes.",
2752 my $rpcenv = PVE
::RPCEnvironment
::get
();
2753 my $authuser = $rpcenv->get_user();
2755 my $node = extract_param
($param, 'node');
2756 my $vmid = extract_param
($param, 'vmid');
2758 my $skiplock = extract_param
($param, 'skiplock');
2759 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2760 if $skiplock && $authuser ne 'root@pam';
2762 my $keepActive = extract_param
($param, 'keepActive');
2763 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2764 if $keepActive && $authuser ne 'root@pam';
2766 my $migratedfrom = extract_param
($param, 'migratedfrom');
2767 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2768 if $migratedfrom && $authuser ne 'root@pam';
2771 my $storecfg = PVE
::Storage
::config
();
2773 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2778 print "Requesting HA stop for VM $vmid\n";
2780 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
2781 PVE
::Tools
::run_command
($cmd);
2785 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2791 syslog
('info', "stop VM $vmid: $upid\n");
2793 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2794 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2798 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2802 __PACKAGE__-
>register_method({
2804 path
=> '{vmid}/status/reset',
2808 description
=> "Reset virtual machine.",
2810 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2813 additionalProperties
=> 0,
2815 node
=> get_standard_option
('pve-node'),
2816 vmid
=> get_standard_option
('pve-vmid',
2817 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2818 skiplock
=> get_standard_option
('skiplock'),
2827 my $rpcenv = PVE
::RPCEnvironment
::get
();
2829 my $authuser = $rpcenv->get_user();
2831 my $node = extract_param
($param, 'node');
2833 my $vmid = extract_param
($param, 'vmid');
2835 my $skiplock = extract_param
($param, 'skiplock');
2836 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2837 if $skiplock && $authuser ne 'root@pam';
2839 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2844 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2849 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2852 __PACKAGE__-
>register_method({
2853 name
=> 'vm_shutdown',
2854 path
=> '{vmid}/status/shutdown',
2858 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2859 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2861 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2864 additionalProperties
=> 0,
2866 node
=> get_standard_option
('pve-node'),
2867 vmid
=> get_standard_option
('pve-vmid',
2868 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2869 skiplock
=> get_standard_option
('skiplock'),
2871 description
=> "Wait maximal timeout seconds.",
2877 description
=> "Make sure the VM stops.",
2883 description
=> "Do not deactivate storage volumes.",
2896 my $rpcenv = PVE
::RPCEnvironment
::get
();
2897 my $authuser = $rpcenv->get_user();
2899 my $node = extract_param
($param, 'node');
2900 my $vmid = extract_param
($param, 'vmid');
2902 my $skiplock = extract_param
($param, 'skiplock');
2903 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2904 if $skiplock && $authuser ne 'root@pam';
2906 my $keepActive = extract_param
($param, 'keepActive');
2907 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2908 if $keepActive && $authuser ne 'root@pam';
2910 my $storecfg = PVE
::Storage
::config
();
2914 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2915 # otherwise, we will infer a shutdown command, but run into the timeout,
2916 # then when the vm is resumed, it will instantly shutdown
2918 # checking the qmp status here to get feedback to the gui/cli/api
2919 # and the status query should not take too long
2920 if (PVE
::QemuServer
::vm_is_paused
($vmid)) {
2921 if ($param->{forceStop
}) {
2922 warn "VM is paused - stop instead of shutdown\n";
2925 die "VM is paused - cannot shutdown\n";
2929 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2931 my $timeout = $param->{timeout
} // 60;
2935 print "Requesting HA stop for VM $vmid\n";
2937 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
2938 PVE
::Tools
::run_command
($cmd);
2942 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2949 syslog
('info', "shutdown VM $vmid: $upid\n");
2951 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2952 $shutdown, $param->{forceStop
}, $keepActive);
2956 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2960 __PACKAGE__-
>register_method({
2961 name
=> 'vm_reboot',
2962 path
=> '{vmid}/status/reboot',
2966 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2968 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2971 additionalProperties
=> 0,
2973 node
=> get_standard_option
('pve-node'),
2974 vmid
=> get_standard_option
('pve-vmid',
2975 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2977 description
=> "Wait maximal timeout seconds for the shutdown.",
2990 my $rpcenv = PVE
::RPCEnvironment
::get
();
2991 my $authuser = $rpcenv->get_user();
2993 my $node = extract_param
($param, 'node');
2994 my $vmid = extract_param
($param, 'vmid');
2996 die "VM is paused - cannot shutdown\n" if PVE
::QemuServer
::vm_is_paused
($vmid);
2998 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
3003 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
3004 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
3008 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
3011 __PACKAGE__-
>register_method({
3012 name
=> 'vm_suspend',
3013 path
=> '{vmid}/status/suspend',
3017 description
=> "Suspend virtual machine.",
3019 description
=> "You need 'VM.PowerMgmt' on /vms/{vmid}, and if you have set 'todisk',".
3020 " you need also 'VM.Config.Disk' on /vms/{vmid} and 'Datastore.AllocateSpace'".
3021 " on the storage for the vmstate.",
3022 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
3025 additionalProperties
=> 0,
3027 node
=> get_standard_option
('pve-node'),
3028 vmid
=> get_standard_option
('pve-vmid',
3029 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
3030 skiplock
=> get_standard_option
('skiplock'),
3035 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
3037 statestorage
=> get_standard_option
('pve-storage-id', {
3038 description
=> "The storage for the VM state",
3039 requires
=> 'todisk',
3041 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
3051 my $rpcenv = PVE
::RPCEnvironment
::get
();
3052 my $authuser = $rpcenv->get_user();
3054 my $node = extract_param
($param, 'node');
3055 my $vmid = extract_param
($param, 'vmid');
3057 my $todisk = extract_param
($param, 'todisk') // 0;
3059 my $statestorage = extract_param
($param, 'statestorage');
3061 my $skiplock = extract_param
($param, 'skiplock');
3062 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3063 if $skiplock && $authuser ne 'root@pam';
3065 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
3067 die "Cannot suspend HA managed VM to disk\n"
3068 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
3070 # early check for storage permission, for better user feedback
3072 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
3073 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3075 # cannot save the state of a non-virtualized PCIe device, so resume cannot really work
3076 for my $key (keys %$conf) {
3077 next if $key !~ /^hostpci\d+/;
3078 die "cannot suspend VM to disk due to passed-through PCI device(s), which lack the"
3079 ." possibility to save/restore their internal state\n";
3082 if (!$statestorage) {
3083 # get statestorage from config if none is given
3084 my $storecfg = PVE
::Storage
::config
();
3085 $statestorage = PVE
::QemuServer
::find_vmstate_storage
($conf, $storecfg);
3088 $rpcenv->check($authuser, "/storage/$statestorage", ['Datastore.AllocateSpace']);
3094 syslog
('info', "suspend VM $vmid: $upid\n");
3096 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
3101 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
3102 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
3105 __PACKAGE__-
>register_method({
3106 name
=> 'vm_resume',
3107 path
=> '{vmid}/status/resume',
3111 description
=> "Resume virtual machine.",
3113 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
3116 additionalProperties
=> 0,
3118 node
=> get_standard_option
('pve-node'),
3119 vmid
=> get_standard_option
('pve-vmid',
3120 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
3121 skiplock
=> get_standard_option
('skiplock'),
3122 nocheck
=> { type
=> 'boolean', optional
=> 1 },
3132 my $rpcenv = PVE
::RPCEnvironment
::get
();
3134 my $authuser = $rpcenv->get_user();
3136 my $node = extract_param
($param, 'node');
3138 my $vmid = extract_param
($param, 'vmid');
3140 my $skiplock = extract_param
($param, 'skiplock');
3141 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3142 if $skiplock && $authuser ne 'root@pam';
3144 my $nocheck = extract_param
($param, 'nocheck');
3145 raise_param_exc
({ nocheck
=> "Only root may use this option." })
3146 if $nocheck && $authuser ne 'root@pam';
3148 my $to_disk_suspended;
3150 PVE
::QemuConfig-
>lock_config($vmid, sub {
3151 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3152 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
3156 die "VM $vmid not running\n"
3157 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
3162 syslog
('info', "resume VM $vmid: $upid\n");
3164 if (!$to_disk_suspended) {
3165 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
3167 my $storecfg = PVE
::Storage
::config
();
3168 PVE
::QemuServer
::vm_start
($storecfg, $vmid, { skiplock
=> $skiplock });
3174 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
3177 __PACKAGE__-
>register_method({
3178 name
=> 'vm_sendkey',
3179 path
=> '{vmid}/sendkey',
3183 description
=> "Send key event to virtual machine.",
3185 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
3188 additionalProperties
=> 0,
3190 node
=> get_standard_option
('pve-node'),
3191 vmid
=> get_standard_option
('pve-vmid',
3192 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
3193 skiplock
=> get_standard_option
('skiplock'),
3195 description
=> "The key (qemu monitor encoding).",
3200 returns
=> { type
=> 'null'},
3204 my $rpcenv = PVE
::RPCEnvironment
::get
();
3206 my $authuser = $rpcenv->get_user();
3208 my $node = extract_param
($param, 'node');
3210 my $vmid = extract_param
($param, 'vmid');
3212 my $skiplock = extract_param
($param, 'skiplock');
3213 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3214 if $skiplock && $authuser ne 'root@pam';
3216 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
3221 __PACKAGE__-
>register_method({
3222 name
=> 'vm_feature',
3223 path
=> '{vmid}/feature',
3227 description
=> "Check if feature for virtual machine is available.",
3229 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3232 additionalProperties
=> 0,
3234 node
=> get_standard_option
('pve-node'),
3235 vmid
=> get_standard_option
('pve-vmid'),
3237 description
=> "Feature to check.",
3239 enum
=> [ 'snapshot', 'clone', 'copy' ],
3241 snapname
=> get_standard_option
('pve-snapshot-name', {
3249 hasFeature
=> { type
=> 'boolean' },
3252 items
=> { type
=> 'string' },
3259 my $node = extract_param
($param, 'node');
3261 my $vmid = extract_param
($param, 'vmid');
3263 my $snapname = extract_param
($param, 'snapname');
3265 my $feature = extract_param
($param, 'feature');
3267 my $running = PVE
::QemuServer
::check_running
($vmid);
3269 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3272 my $snap = $conf->{snapshots
}->{$snapname};
3273 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3276 my $storecfg = PVE
::Storage
::config
();
3278 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
3279 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
3282 hasFeature
=> $hasFeature,
3283 nodes
=> [ keys %$nodelist ],
3287 __PACKAGE__-
>register_method({
3289 path
=> '{vmid}/clone',
3293 description
=> "Create a copy of virtual machine/template.",
3295 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
3296 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
3297 "'Datastore.AllocateSpace' on any used storage.",
3300 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
3302 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
3303 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
3308 additionalProperties
=> 0,
3310 node
=> get_standard_option
('pve-node'),
3311 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3312 newid
=> get_standard_option
('pve-vmid', {
3313 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
3314 description
=> 'VMID for the clone.' }),
3317 type
=> 'string', format
=> 'dns-name',
3318 description
=> "Set a name for the new VM.",
3323 description
=> "Description for the new VM.",
3327 type
=> 'string', format
=> 'pve-poolid',
3328 description
=> "Add the new VM to the specified pool.",
3330 snapname
=> get_standard_option
('pve-snapshot-name', {
3333 storage
=> get_standard_option
('pve-storage-id', {
3334 description
=> "Target storage for full clone.",
3338 description
=> "Target format for file storage. Only valid for full clone.",
3341 enum
=> [ 'raw', 'qcow2', 'vmdk'],
3346 description
=> "Create a full copy of all disks. This is always done when " .
3347 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
3349 target
=> get_standard_option
('pve-node', {
3350 description
=> "Target node. Only allowed if the original VM is on shared storage.",
3354 description
=> "Override I/O bandwidth limit (in KiB/s).",
3358 default => 'clone limit from datacenter or storage config',
3368 my $rpcenv = PVE
::RPCEnvironment
::get
();
3369 my $authuser = $rpcenv->get_user();
3371 my $node = extract_param
($param, 'node');
3372 my $vmid = extract_param
($param, 'vmid');
3373 my $newid = extract_param
($param, 'newid');
3374 my $pool = extract_param
($param, 'pool');
3376 my $snapname = extract_param
($param, 'snapname');
3377 my $storage = extract_param
($param, 'storage');
3378 my $format = extract_param
($param, 'format');
3379 my $target = extract_param
($param, 'target');
3381 my $localnode = PVE
::INotify
::nodename
();
3383 if ($target && ($target eq $localnode || $target eq 'localhost')) {
3387 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
3389 my $load_and_check = sub {
3390 $rpcenv->check_pool_exist($pool) if defined($pool);
3391 PVE
::Cluster
::check_node_exists
($target) if $target;
3393 my $storecfg = PVE
::Storage
::config
();
3396 # check if storage is enabled on local node
3397 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
3399 # check if storage is available on target node
3400 PVE
::Storage
::storage_check_enabled
($storecfg, $storage, $target);
3401 # clone only works if target storage is shared
3402 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
3403 die "can't clone to non-shared storage '$storage'\n"
3404 if !$scfg->{shared
};
3408 PVE
::Cluster
::check_cfs_quorum
();
3410 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3411 PVE
::QemuConfig-
>check_lock($conf);
3413 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
3414 die "unexpected state change\n" if $verify_running != $running;
3416 die "snapshot '$snapname' does not exist\n"
3417 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
3419 my $full = $param->{full
} // !PVE
::QemuConfig-
>is_template($conf);
3421 die "parameter 'storage' not allowed for linked clones\n"
3422 if defined($storage) && !$full;
3424 die "parameter 'format' not allowed for linked clones\n"
3425 if defined($format) && !$full;
3427 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
3429 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
3431 die "can't clone VM to node '$target' (VM uses local storage)\n"
3432 if $target && !$sharedvm;
3434 my $conffile = PVE
::QemuConfig-
>config_file($newid);
3435 die "unable to create VM $newid: config file already exists\n"
3438 my $newconf = { lock => 'clone' };
3443 foreach my $opt (keys %$oldconf) {
3444 my $value = $oldconf->{$opt};
3446 # do not copy snapshot related info
3447 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
3448 $opt eq 'vmstate' || $opt eq 'snapstate';
3450 # no need to copy unused images, because VMID(owner) changes anyways
3451 next if $opt =~ m/^unused\d+$/;
3453 die "cannot clone TPM state while VM is running\n"
3454 if $full && $running && !$snapname && $opt eq 'tpmstate0';
3456 # always change MAC! address
3457 if ($opt =~ m/^net(\d+)$/) {
3458 my $net = PVE
::QemuServer
::parse_net
($value);
3459 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
3460 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
3461 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
3462 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
3463 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
3464 die "unable to parse drive options for '$opt'\n" if !$drive;
3465 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
3466 $newconf->{$opt} = $value; # simply copy configuration
3468 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
3469 die "Full clone feature is not supported for drive '$opt'\n"
3470 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
3471 $fullclone->{$opt} = 1;
3473 # not full means clone instead of copy
3474 die "Linked clone feature is not supported for drive '$opt'\n"
3475 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
3477 $drives->{$opt} = $drive;
3478 next if PVE
::QemuServer
::drive_is_cloudinit
($drive);
3479 push @$vollist, $drive->{file
};
3482 # copy everything else
3483 $newconf->{$opt} = $value;
3487 return ($conffile, $newconf, $oldconf, $vollist, $drives, $fullclone);
3491 my ($conffile, $newconf, $oldconf, $vollist, $drives, $fullclone) = $load_and_check->();
3492 my $storecfg = PVE
::Storage
::config
();
3494 # auto generate a new uuid
3495 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
3496 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
3497 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
3498 # auto generate a new vmgenid only if the option was set for template
3499 if ($newconf->{vmgenid
}) {
3500 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
3503 delete $newconf->{template
};
3505 if ($param->{name
}) {
3506 $newconf->{name
} = $param->{name
};
3508 $newconf->{name
} = "Copy-of-VM-" . ($oldconf->{name
} // $vmid);
3511 if ($param->{description
}) {
3512 $newconf->{description
} = $param->{description
};
3515 # create empty/temp config - this fails if VM already exists on other node
3516 # FIXME use PVE::QemuConfig->create_and_lock_config and adapt code
3517 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
3519 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
3521 my $newvollist = [];
3528 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3530 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
3532 my $bwlimit = extract_param
($param, 'bwlimit');
3534 my $total_jobs = scalar(keys %{$drives});
3537 foreach my $opt (sort keys %$drives) {
3538 my $drive = $drives->{$opt};
3539 my $skipcomplete = ($total_jobs != $i); # finish after last drive
3540 my $completion = $skipcomplete ?
'skip' : 'complete';
3542 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
3543 my $storage_list = [ $src_sid ];
3544 push @$storage_list, $storage if defined($storage);
3545 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
3549 running
=> $running,
3552 snapname
=> $snapname,
3558 storage
=> $storage,
3562 $dest_info->{efisize
} = PVE
::QemuServer
::get_efivars_size
($oldconf)
3563 if $opt eq 'efidisk0';
3565 my $newdrive = PVE
::QemuServer
::clone_disk
(
3577 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($newdrive);
3579 PVE
::QemuConfig-
>write_config($newid, $newconf);
3583 delete $newconf->{lock};
3585 # do not write pending changes
3586 if (my @changes = keys %{$newconf->{pending
}}) {
3587 my $pending = join(',', @changes);
3588 warn "found pending changes for '$pending', discarding for clone\n";
3589 delete $newconf->{pending
};
3592 PVE
::QemuConfig-
>write_config($newid, $newconf);
3595 # always deactivate volumes - avoid lvm LVs to be active on several nodes
3596 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
3597 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
3599 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
3600 die "Failed to move config to node '$target' - rename failed: $!\n"
3601 if !rename($conffile, $newconffile);
3604 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
3607 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
3608 sleep 1; # some storage like rbd need to wait before release volume - really?
3610 foreach my $volid (@$newvollist) {
3611 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3615 PVE
::Firewall
::remove_vmfw_conf
($newid);
3617 unlink $conffile; # avoid races -> last thing before die
3619 die "clone failed: $err";
3625 # Aquire exclusive lock lock for $newid
3626 my $lock_target_vm = sub {
3627 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
3630 my $lock_source_vm = sub {
3631 # exclusive lock if VM is running - else shared lock is enough;
3633 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $lock_target_vm);
3635 return PVE
::QemuConfig-
>lock_config_shared($vmid, 1, $lock_target_vm);
3639 $load_and_check->(); # early checks before forking/locking
3641 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $lock_source_vm);
3644 __PACKAGE__-
>register_method({
3645 name
=> 'move_vm_disk',
3646 path
=> '{vmid}/move_disk',
3650 description
=> "Move volume to different storage or to a different VM.",
3652 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
3653 "and 'Datastore.AllocateSpace' permissions on the storage. To move ".
3654 "a disk to another VM, you need the permissions on the target VM as well.",
3655 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3658 additionalProperties
=> 0,
3660 node
=> get_standard_option
('pve-node'),
3661 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3662 'target-vmid' => get_standard_option
('pve-vmid', {
3663 completion
=> \
&PVE
::QemuServer
::complete_vmid
,
3668 description
=> "The disk you want to move.",
3669 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names_with_unused
()],
3671 storage
=> get_standard_option
('pve-storage-id', {
3672 description
=> "Target storage.",
3673 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3678 description
=> "Target Format.",
3679 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
3684 description
=> "Delete the original disk after successful copy. By default the"
3685 ." original disk is kept as unused disk.",
3691 description
=> 'Prevent changes if current configuration file has different SHA1"
3692 ." digest. This can be used to prevent concurrent modifications.',
3697 description
=> "Override I/O bandwidth limit (in KiB/s).",
3701 default => 'move limit from datacenter or storage config',
3705 description
=> "The config key the disk will be moved to on the target VM"
3706 ." (for example, ide0 or scsi1). Default is the source disk key.",
3707 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names_with_unused
()],
3710 'target-digest' => {
3712 description
=> 'Prevent changes if the current config file of the target VM has a"
3713 ." different SHA1 digest. This can be used to detect concurrent modifications.',
3721 description
=> "the task ID.",
3726 my $rpcenv = PVE
::RPCEnvironment
::get
();
3727 my $authuser = $rpcenv->get_user();
3729 my $node = extract_param
($param, 'node');
3730 my $vmid = extract_param
($param, 'vmid');
3731 my $target_vmid = extract_param
($param, 'target-vmid');
3732 my $digest = extract_param
($param, 'digest');
3733 my $target_digest = extract_param
($param, 'target-digest');
3734 my $disk = extract_param
($param, 'disk');
3735 my $target_disk = extract_param
($param, 'target-disk') // $disk;
3736 my $storeid = extract_param
($param, 'storage');
3737 my $format = extract_param
($param, 'format');
3739 my $storecfg = PVE
::Storage
::config
();
3741 my $load_and_check_move = sub {
3742 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3743 PVE
::QemuConfig-
>check_lock($conf);
3745 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
3747 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3749 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3751 die "disk '$disk' has no associated volume\n" if !$drive->{file
};
3752 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3754 my $old_volid = $drive->{file
};
3756 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3757 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3761 die "you can't move to the same storage with same format\n"
3762 if $oldstoreid eq $storeid && (!$format || !$oldfmt || $oldfmt eq $format);
3764 # this only checks snapshots because $disk is passed!
3765 my $snapshotted = PVE
::QemuServer
::Drive
::is_volume_in_use
(
3771 die "you can't move a disk with snapshots and delete the source\n"
3772 if $snapshotted && $param->{delete};
3774 return ($conf, $drive, $oldstoreid, $snapshotted);
3777 my $move_updatefn = sub {
3778 my ($conf, $drive, $oldstoreid, $snapshotted) = $load_and_check_move->();
3779 my $old_volid = $drive->{file
};
3781 PVE
::Cluster
::log_msg
(
3784 "move disk VM $vmid: move --disk $disk --storage $storeid"
3787 my $running = PVE
::QemuServer
::check_running
($vmid);
3789 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3791 my $newvollist = [];
3797 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3799 warn "moving disk with snapshots, snapshots will not be moved!\n"
3802 my $bwlimit = extract_param
($param, 'bwlimit');
3803 my $movelimit = PVE
::Storage
::get_bandwidth_limit
(
3805 [$oldstoreid, $storeid],
3811 running
=> $running,
3820 storage
=> $storeid,
3824 $dest_info->{efisize
} = PVE
::QemuServer
::get_efivars_size
($conf)
3825 if $disk eq 'efidisk0';
3827 my $newdrive = PVE
::QemuServer
::clone_disk
(
3838 $conf->{$disk} = PVE
::QemuServer
::print_drive
($newdrive);
3840 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3842 # convert moved disk to base if part of template
3843 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3844 if PVE
::QemuConfig-
>is_template($conf);
3846 PVE
::QemuConfig-
>write_config($vmid, $conf);
3848 my $do_trim = PVE
::QemuServer
::get_qga_key
($conf, 'fstrim_cloned_disks');
3849 if ($running && $do_trim && PVE
::QemuServer
::qga_check_running
($vmid)) {
3850 eval { mon_cmd
($vmid, "guest-fstrim") };
3854 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3855 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3861 foreach my $volid (@$newvollist) {
3862 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid) };
3865 die "storage migration failed: $err";
3868 if ($param->{delete}) {
3870 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3871 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3877 my $load_and_check_reassign_configs = sub {
3878 my $vmlist = PVE
::Cluster
::get_vmlist
()->{ids
};
3880 die "could not find VM ${vmid}\n" if !exists($vmlist->{$vmid});
3881 die "could not find target VM ${target_vmid}\n" if !exists($vmlist->{$target_vmid});
3883 my $source_node = $vmlist->{$vmid}->{node
};
3884 my $target_node = $vmlist->{$target_vmid}->{node
};
3886 die "Both VMs need to be on the same node ($source_node != $target_node)\n"
3887 if $source_node ne $target_node;
3889 my $source_conf = PVE
::QemuConfig-
>load_config($vmid);
3890 PVE
::QemuConfig-
>check_lock($source_conf);
3891 my $target_conf = PVE
::QemuConfig-
>load_config($target_vmid);
3892 PVE
::QemuConfig-
>check_lock($target_conf);
3894 die "Can't move disks from or to template VMs\n"
3895 if ($source_conf->{template
} || $target_conf->{template
});
3898 eval { PVE
::Tools
::assert_if_modified
($digest, $source_conf->{digest
}) };
3899 die "VM ${vmid}: $@" if $@;
3902 if ($target_digest) {
3903 eval { PVE
::Tools
::assert_if_modified
($target_digest, $target_conf->{digest
}) };
3904 die "VM ${target_vmid}: $@" if $@;
3907 die "Disk '${disk}' for VM '$vmid' does not exist\n" if !defined($source_conf->{$disk});
3909 die "Target disk key '${target_disk}' is already in use for VM '$target_vmid'\n"
3910 if $target_conf->{$target_disk};
3912 my $drive = PVE
::QemuServer
::parse_drive
(
3914 $source_conf->{$disk},
3916 die "failed to parse source disk - $@\n" if !$drive;
3918 my $source_volid = $drive->{file
};
3920 die "disk '${disk}' has no associated volume\n" if !$source_volid;
3921 die "CD drive contents can't be moved to another VM\n"
3922 if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3924 my $storeid = PVE
::Storage
::parse_volume_id
($source_volid, 1);
3925 die "Volume '$source_volid' not managed by PVE\n" if !defined($storeid);
3927 die "Can't move disk used by a snapshot to another VM\n"
3928 if PVE
::QemuServer
::Drive
::is_volume_in_use
($storecfg, $source_conf, $disk, $source_volid);
3929 die "Storage does not support moving of this disk to another VM\n"
3930 if (!PVE
::Storage
::volume_has_feature
($storecfg, 'rename', $source_volid));
3931 die "Cannot move disk to another VM while the source VM is running - detach first\n"
3932 if PVE
::QemuServer
::check_running
($vmid) && $disk !~ m/^unused\d+$/;
3934 # now re-parse using target disk slot format
3935 if ($target_disk =~ /^unused\d+$/) {
3936 $drive = PVE
::QemuServer
::parse_drive
(
3941 $drive = PVE
::QemuServer
::parse_drive
(
3943 $source_conf->{$disk},
3946 die "failed to parse source disk for target disk format - $@\n" if !$drive;
3948 my $repl_conf = PVE
::ReplicationConfig-
>new();
3949 if ($repl_conf->check_for_existing_jobs($target_vmid, 1)) {
3950 my $format = (PVE
::Storage
::parse_volname
($storecfg, $source_volid))[6];
3951 die "Cannot move disk to a replicated VM. Storage does not support replication!\n"
3952 if !PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
3955 return ($source_conf, $target_conf, $drive);
3960 print STDERR
"$msg\n";
3963 my $disk_reassignfn = sub {
3964 return PVE
::QemuConfig-
>lock_config($vmid, sub {
3965 return PVE
::QemuConfig-
>lock_config($target_vmid, sub {
3966 my ($source_conf, $target_conf, $drive) = &$load_and_check_reassign_configs();
3968 my $source_volid = $drive->{file
};
3970 print "moving disk '$disk' from VM '$vmid' to '$target_vmid'\n";
3971 my ($storeid, $source_volname) = PVE
::Storage
::parse_volume_id
($source_volid);
3973 my $fmt = (PVE
::Storage
::parse_volname
($storecfg, $source_volid))[6];
3975 my $new_volid = PVE
::Storage
::rename_volume
(
3981 $drive->{file
} = $new_volid;
3983 my $boot_order = PVE
::QemuServer
::device_bootorder
($source_conf);
3984 if (defined(delete $boot_order->{$disk})) {
3985 print "removing disk '$disk' from boot order config\n";
3986 my $boot_devs = [ sort { $boot_order->{$a} <=> $boot_order->{$b} } keys %$boot_order ];
3987 $source_conf->{boot
} = PVE
::QemuServer
::print_bootorder
($boot_devs);
3990 delete $source_conf->{$disk};
3991 print "removing disk '${disk}' from VM '${vmid}' config\n";
3992 PVE
::QemuConfig-
>write_config($vmid, $source_conf);
3994 my $drive_string = PVE
::QemuServer
::print_drive
($drive);
3996 if ($target_disk =~ /^unused\d+$/) {
3997 $target_conf->{$target_disk} = $drive_string;
3998 PVE
::QemuConfig-
>write_config($target_vmid, $target_conf);
4003 vmid
=> $target_vmid,
4004 digest
=> $target_digest,
4005 $target_disk => $drive_string,
4011 # remove possible replication snapshots
4012 if (PVE
::Storage
::volume_has_feature
(
4018 PVE
::Replication
::prepare
(
4028 print "Failed to remove replication snapshots on moved disk " .
4029 "'$target_disk'. Manual cleanup could be necessary.\n";
4036 if ($target_vmid && $storeid) {
4037 my $msg = "either set 'storage' or 'target-vmid', but not both";
4038 raise_param_exc
({ 'target-vmid' => $msg, 'storage' => $msg });
4039 } elsif ($target_vmid) {
4040 $rpcenv->check_vm_perm($authuser, $target_vmid, undef, ['VM.Config.Disk'])
4041 if $authuser ne 'root@pam';
4043 raise_param_exc
({ 'target-vmid' => "must be different than source VMID to reassign disk" })
4044 if $vmid eq $target_vmid;
4046 my (undef, undef, $drive) = &$load_and_check_reassign_configs();
4047 my $storage = PVE
::Storage
::parse_volume_id
($drive->{file
});
4048 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace']);
4050 return $rpcenv->fork_worker(
4052 "${vmid}-${disk}>${target_vmid}-${target_disk}",
4056 } elsif ($storeid) {
4057 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
4059 die "cannot move disk '$disk', only configured disks can be moved to another storage\n"
4060 if $disk =~ m/^unused\d+$/;
4062 $load_and_check_move->(); # early checks before forking/locking
4065 PVE
::QemuConfig-
>lock_config($vmid, $move_updatefn);
4068 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
4070 my $msg = "both 'storage' and 'target-vmid' missing, either needs to be set";
4071 raise_param_exc
({ 'target-vmid' => $msg, 'storage' => $msg });
4075 my $check_vm_disks_local = sub {
4076 my ($storecfg, $vmconf, $vmid) = @_;
4078 my $local_disks = {};
4080 # add some more information to the disks e.g. cdrom
4081 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
4082 my ($volid, $attr) = @_;
4084 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
4086 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
4087 return if $scfg->{shared
};
4089 # The shared attr here is just a special case where the vdisk
4090 # is marked as shared manually
4091 return if $attr->{shared
};
4092 return if $attr->{cdrom
} and $volid eq "none";
4094 if (exists $local_disks->{$volid}) {
4095 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
4097 $local_disks->{$volid} = $attr;
4098 # ensure volid is present in case it's needed
4099 $local_disks->{$volid}->{volid
} = $volid;
4103 return $local_disks;
4106 __PACKAGE__-
>register_method({
4107 name
=> 'migrate_vm_precondition',
4108 path
=> '{vmid}/migrate',
4112 description
=> "Get preconditions for migration.",
4114 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
4117 additionalProperties
=> 0,
4119 node
=> get_standard_option
('pve-node'),
4120 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4121 target
=> get_standard_option
('pve-node', {
4122 description
=> "Target node.",
4123 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
4131 running
=> { type
=> 'boolean' },
4135 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
4137 not_allowed_nodes
=> {
4140 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
4144 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
4146 local_resources
=> {
4148 description
=> "List local resources e.g. pci, usb"
4155 my $rpcenv = PVE
::RPCEnvironment
::get
();
4157 my $authuser = $rpcenv->get_user();
4159 PVE
::Cluster
::check_cfs_quorum
();
4163 my $vmid = extract_param
($param, 'vmid');
4164 my $target = extract_param
($param, 'target');
4165 my $localnode = PVE
::INotify
::nodename
();
4169 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
4170 my $storecfg = PVE
::Storage
::config
();
4173 # try to detect errors early
4174 PVE
::QemuConfig-
>check_lock($vmconf);
4176 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
4178 # if vm is not running, return target nodes where local storage is available
4179 # for offline migration
4180 if (!$res->{running
}) {
4181 $res->{allowed_nodes
} = [];
4182 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
4183 delete $checked_nodes->{$localnode};
4185 foreach my $node (keys %$checked_nodes) {
4186 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
4187 push @{$res->{allowed_nodes
}}, $node;
4191 $res->{not_allowed_nodes
} = $checked_nodes;
4195 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
4196 $res->{local_disks
} = [ values %$local_disks ];;
4198 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
4200 $res->{local_resources
} = $local_resources;
4207 __PACKAGE__-
>register_method({
4208 name
=> 'migrate_vm',
4209 path
=> '{vmid}/migrate',
4213 description
=> "Migrate virtual machine. Creates a new migration task.",
4215 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
4218 additionalProperties
=> 0,
4220 node
=> get_standard_option
('pve-node'),
4221 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4222 target
=> get_standard_option
('pve-node', {
4223 description
=> "Target node.",
4224 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
4228 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
4233 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
4238 enum
=> ['secure', 'insecure'],
4239 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
4242 migration_network
=> {
4243 type
=> 'string', format
=> 'CIDR',
4244 description
=> "CIDR of the (sub) network that is used for migration.",
4247 "with-local-disks" => {
4249 description
=> "Enable live storage migration for local disk",
4252 targetstorage
=> get_standard_option
('pve-targetstorage', {
4253 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
4256 description
=> "Override I/O bandwidth limit (in KiB/s).",
4260 default => 'migrate limit from datacenter or storage config',
4266 description
=> "the task ID.",
4271 my $rpcenv = PVE
::RPCEnvironment
::get
();
4272 my $authuser = $rpcenv->get_user();
4274 my $target = extract_param
($param, 'target');
4276 my $localnode = PVE
::INotify
::nodename
();
4277 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
4279 PVE
::Cluster
::check_cfs_quorum
();
4281 PVE
::Cluster
::check_node_exists
($target);
4283 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
4285 my $vmid = extract_param
($param, 'vmid');
4287 raise_param_exc
({ force
=> "Only root may use this option." })
4288 if $param->{force
} && $authuser ne 'root@pam';
4290 raise_param_exc
({ migration_type
=> "Only root may use this option." })
4291 if $param->{migration_type
} && $authuser ne 'root@pam';
4293 # allow root only until better network permissions are available
4294 raise_param_exc
({ migration_network
=> "Only root may use this option." })
4295 if $param->{migration_network
} && $authuser ne 'root@pam';
4298 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4300 # try to detect errors early
4302 PVE
::QemuConfig-
>check_lock($conf);
4304 if (PVE
::QemuServer
::check_running
($vmid)) {
4305 die "can't migrate running VM without --online\n" if !$param->{online
};
4307 my $repl_conf = PVE
::ReplicationConfig-
>new();
4308 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
4309 my $is_replicated_to_target = defined($repl_conf->find_local_replication_job($vmid, $target));
4310 if (!$param->{force
} && $is_replicated && !$is_replicated_to_target) {
4311 die "Cannot live-migrate replicated VM to node '$target' - not a replication " .
4312 "target. Use 'force' to override.\n";
4315 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
4316 $param->{online
} = 0;
4319 my $storecfg = PVE
::Storage
::config
();
4320 if (my $targetstorage = $param->{targetstorage
}) {
4321 my $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
4322 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
4325 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk'])
4326 if !defined($storagemap->{identity
});
4328 foreach my $target_sid (values %{$storagemap->{entries
}}) {
4329 $check_storage_access_migrate->($rpcenv, $authuser, $storecfg, $target_sid, $target);
4332 $check_storage_access_migrate->($rpcenv, $authuser, $storecfg, $storagemap->{default}, $target)
4333 if $storagemap->{default};
4335 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target)
4336 if $storagemap->{identity
};
4338 $param->{storagemap
} = $storagemap;
4340 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
4343 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
4348 print "Requesting HA migration for VM $vmid to node $target\n";
4350 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
4351 PVE
::Tools
::run_command
($cmd);
4355 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
4360 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
4364 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
4367 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
4372 __PACKAGE__-
>register_method({
4374 path
=> '{vmid}/monitor',
4378 description
=> "Execute Qemu monitor commands.",
4380 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
4381 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
4384 additionalProperties
=> 0,
4386 node
=> get_standard_option
('pve-node'),
4387 vmid
=> get_standard_option
('pve-vmid'),
4390 description
=> "The monitor command.",
4394 returns
=> { type
=> 'string'},
4398 my $rpcenv = PVE
::RPCEnvironment
::get
();
4399 my $authuser = $rpcenv->get_user();
4402 my $command = shift;
4403 return $command =~ m/^\s*info(\s+|$)/
4404 || $command =~ m/^\s*help\s*$/;
4407 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
4408 if !&$is_ro($param->{command
});
4410 my $vmid = $param->{vmid
};
4412 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
4416 $res = PVE
::QemuServer
::Monitor
::hmp_cmd
($vmid, $param->{command
});
4418 $res = "ERROR: $@" if $@;
4423 __PACKAGE__-
>register_method({
4424 name
=> 'resize_vm',
4425 path
=> '{vmid}/resize',
4429 description
=> "Extend volume size.",
4431 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
4434 additionalProperties
=> 0,
4436 node
=> get_standard_option
('pve-node'),
4437 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4438 skiplock
=> get_standard_option
('skiplock'),
4441 description
=> "The disk you want to resize.",
4442 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
4446 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
4447 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.",
4451 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
4457 returns
=> { type
=> 'null'},
4461 my $rpcenv = PVE
::RPCEnvironment
::get
();
4463 my $authuser = $rpcenv->get_user();
4465 my $node = extract_param
($param, 'node');
4467 my $vmid = extract_param
($param, 'vmid');
4469 my $digest = extract_param
($param, 'digest');
4471 my $disk = extract_param
($param, 'disk');
4473 my $sizestr = extract_param
($param, 'size');
4475 my $skiplock = extract_param
($param, 'skiplock');
4476 raise_param_exc
({ skiplock
=> "Only root may use this option." })
4477 if $skiplock && $authuser ne 'root@pam';
4479 my $storecfg = PVE
::Storage
::config
();
4481 my $updatefn = sub {
4483 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4485 die "checksum missmatch (file change by other user?)\n"
4486 if $digest && $digest ne $conf->{digest
};
4487 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
4489 die "disk '$disk' does not exist\n" if !$conf->{$disk};
4491 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
4493 my (undef, undef, undef, undef, undef, undef, $format) =
4494 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
4496 die "can't resize volume: $disk if snapshot exists\n"
4497 if %{$conf->{snapshots
}} && $format eq 'qcow2';
4499 my $volid = $drive->{file
};
4501 die "disk '$disk' has no associated volume\n" if !$volid;
4503 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
4505 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
4507 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
4509 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
4510 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
4512 die "Could not determine current size of volume '$volid'\n" if !defined($size);
4514 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
4515 my ($ext, $newsize, $unit) = ($1, $2, $4);
4518 $newsize = $newsize * 1024;
4519 } elsif ($unit eq 'M') {
4520 $newsize = $newsize * 1024 * 1024;
4521 } elsif ($unit eq 'G') {
4522 $newsize = $newsize * 1024 * 1024 * 1024;
4523 } elsif ($unit eq 'T') {
4524 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
4527 $newsize += $size if $ext;
4528 $newsize = int($newsize);
4530 die "shrinking disks is not supported\n" if $newsize < $size;
4532 return if $size == $newsize;
4534 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
4536 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
4538 $drive->{size
} = $newsize;
4539 $conf->{$disk} = PVE
::QemuServer
::print_drive
($drive);
4541 PVE
::QemuConfig-
>write_config($vmid, $conf);
4544 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4548 __PACKAGE__-
>register_method({
4549 name
=> 'snapshot_list',
4550 path
=> '{vmid}/snapshot',
4552 description
=> "List all snapshots.",
4554 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4557 protected
=> 1, # qemu pid files are only readable by root
4559 additionalProperties
=> 0,
4561 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4562 node
=> get_standard_option
('pve-node'),
4571 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
4575 description
=> "Snapshot includes RAM.",
4580 description
=> "Snapshot description.",
4584 description
=> "Snapshot creation time",
4586 renderer
=> 'timestamp',
4590 description
=> "Parent snapshot identifier.",
4596 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
4601 my $vmid = $param->{vmid
};
4603 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4604 my $snaphash = $conf->{snapshots
} || {};
4608 foreach my $name (keys %$snaphash) {
4609 my $d = $snaphash->{$name};
4612 snaptime
=> $d->{snaptime
} || 0,
4613 vmstate
=> $d->{vmstate
} ?
1 : 0,
4614 description
=> $d->{description
} || '',
4616 $item->{parent
} = $d->{parent
} if $d->{parent
};
4617 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
4621 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
4624 digest
=> $conf->{digest
},
4625 running
=> $running,
4626 description
=> "You are here!",
4628 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
4630 push @$res, $current;
4635 __PACKAGE__-
>register_method({
4637 path
=> '{vmid}/snapshot',
4641 description
=> "Snapshot a VM.",
4643 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4646 additionalProperties
=> 0,
4648 node
=> get_standard_option
('pve-node'),
4649 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4650 snapname
=> get_standard_option
('pve-snapshot-name'),
4654 description
=> "Save the vmstate",
4659 description
=> "A textual description or comment.",
4665 description
=> "the task ID.",
4670 my $rpcenv = PVE
::RPCEnvironment
::get
();
4672 my $authuser = $rpcenv->get_user();
4674 my $node = extract_param
($param, 'node');
4676 my $vmid = extract_param
($param, 'vmid');
4678 my $snapname = extract_param
($param, 'snapname');
4680 die "unable to use snapshot name 'current' (reserved name)\n"
4681 if $snapname eq 'current';
4683 die "unable to use snapshot name 'pending' (reserved name)\n"
4684 if lc($snapname) eq 'pending';
4687 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
4688 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
4689 $param->{description
});
4692 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
4695 __PACKAGE__-
>register_method({
4696 name
=> 'snapshot_cmd_idx',
4697 path
=> '{vmid}/snapshot/{snapname}',
4704 additionalProperties
=> 0,
4706 vmid
=> get_standard_option
('pve-vmid'),
4707 node
=> get_standard_option
('pve-node'),
4708 snapname
=> get_standard_option
('pve-snapshot-name'),
4717 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
4724 push @$res, { cmd
=> 'rollback' };
4725 push @$res, { cmd
=> 'config' };
4730 __PACKAGE__-
>register_method({
4731 name
=> 'update_snapshot_config',
4732 path
=> '{vmid}/snapshot/{snapname}/config',
4736 description
=> "Update snapshot metadata.",
4738 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4741 additionalProperties
=> 0,
4743 node
=> get_standard_option
('pve-node'),
4744 vmid
=> get_standard_option
('pve-vmid'),
4745 snapname
=> get_standard_option
('pve-snapshot-name'),
4749 description
=> "A textual description or comment.",
4753 returns
=> { type
=> 'null' },
4757 my $rpcenv = PVE
::RPCEnvironment
::get
();
4759 my $authuser = $rpcenv->get_user();
4761 my $vmid = extract_param
($param, 'vmid');
4763 my $snapname = extract_param
($param, 'snapname');
4765 return if !defined($param->{description
});
4767 my $updatefn = sub {
4769 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4771 PVE
::QemuConfig-
>check_lock($conf);
4773 my $snap = $conf->{snapshots
}->{$snapname};
4775 die "snapshot '$snapname' does not exist\n" if !defined($snap);
4777 $snap->{description
} = $param->{description
} if defined($param->{description
});
4779 PVE
::QemuConfig-
>write_config($vmid, $conf);
4782 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4787 __PACKAGE__-
>register_method({
4788 name
=> 'get_snapshot_config',
4789 path
=> '{vmid}/snapshot/{snapname}/config',
4792 description
=> "Get snapshot configuration",
4794 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback', 'VM.Audit' ], any
=> 1],
4797 additionalProperties
=> 0,
4799 node
=> get_standard_option
('pve-node'),
4800 vmid
=> get_standard_option
('pve-vmid'),
4801 snapname
=> get_standard_option
('pve-snapshot-name'),
4804 returns
=> { type
=> "object" },
4808 my $rpcenv = PVE
::RPCEnvironment
::get
();
4810 my $authuser = $rpcenv->get_user();
4812 my $vmid = extract_param
($param, 'vmid');
4814 my $snapname = extract_param
($param, 'snapname');
4816 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4818 my $snap = $conf->{snapshots
}->{$snapname};
4820 die "snapshot '$snapname' does not exist\n" if !defined($snap);
4825 __PACKAGE__-
>register_method({
4827 path
=> '{vmid}/snapshot/{snapname}/rollback',
4831 description
=> "Rollback VM state to specified snapshot.",
4833 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
4836 additionalProperties
=> 0,
4838 node
=> get_standard_option
('pve-node'),
4839 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4840 snapname
=> get_standard_option
('pve-snapshot-name'),
4845 description
=> "the task ID.",
4850 my $rpcenv = PVE
::RPCEnvironment
::get
();
4852 my $authuser = $rpcenv->get_user();
4854 my $node = extract_param
($param, 'node');
4856 my $vmid = extract_param
($param, 'vmid');
4858 my $snapname = extract_param
($param, 'snapname');
4861 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
4862 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
4866 # hold migration lock, this makes sure that nobody create replication snapshots
4867 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
4870 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
4873 __PACKAGE__-
>register_method({
4874 name
=> 'delsnapshot',
4875 path
=> '{vmid}/snapshot/{snapname}',
4879 description
=> "Delete a VM snapshot.",
4881 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4884 additionalProperties
=> 0,
4886 node
=> get_standard_option
('pve-node'),
4887 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4888 snapname
=> get_standard_option
('pve-snapshot-name'),
4892 description
=> "For removal from config file, even if removing disk snapshots fails.",
4898 description
=> "the task ID.",
4903 my $rpcenv = PVE
::RPCEnvironment
::get
();
4905 my $authuser = $rpcenv->get_user();
4907 my $node = extract_param
($param, 'node');
4909 my $vmid = extract_param
($param, 'vmid');
4911 my $snapname = extract_param
($param, 'snapname');
4914 my $do_delete = sub {
4916 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
4917 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
4921 if ($param->{force
}) {
4924 eval { PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $do_delete); };
4926 die $err if $lock_obtained;
4927 die "Failed to obtain guest migration lock - replication running?\n";
4932 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
4935 __PACKAGE__-
>register_method({
4937 path
=> '{vmid}/template',
4941 description
=> "Create a Template.",
4943 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
4944 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
4947 additionalProperties
=> 0,
4949 node
=> get_standard_option
('pve-node'),
4950 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
4954 description
=> "If you want to convert only 1 disk to base image.",
4955 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
4962 description
=> "the task ID.",
4967 my $rpcenv = PVE
::RPCEnvironment
::get
();
4969 my $authuser = $rpcenv->get_user();
4971 my $node = extract_param
($param, 'node');
4973 my $vmid = extract_param
($param, 'vmid');
4975 my $disk = extract_param
($param, 'disk');
4977 my $load_and_check = sub {
4978 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4980 PVE
::QemuConfig-
>check_lock($conf);
4982 die "unable to create template, because VM contains snapshots\n"
4983 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
4985 die "you can't convert a template to a template\n"
4986 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
4988 die "you can't convert a VM to template if VM is running\n"
4989 if PVE
::QemuServer
::check_running
($vmid);
4994 $load_and_check->();
4997 PVE
::QemuConfig-
>lock_config($vmid, sub {
4998 my $conf = $load_and_check->();
5000 $conf->{template
} = 1;
5001 PVE
::QemuConfig-
>write_config($vmid, $conf);
5003 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
5007 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
5010 __PACKAGE__-
>register_method({
5011 name
=> 'cloudinit_generated_config_dump',
5012 path
=> '{vmid}/cloudinit/dump',
5015 description
=> "Get automatically generated cloudinit config.",
5017 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
5020 additionalProperties
=> 0,
5022 node
=> get_standard_option
('pve-node'),
5023 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
5025 description
=> 'Config type.',
5027 enum
=> ['user', 'network', 'meta'],
5037 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
5039 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});