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
=> 'cloudinit' },
1079 { subdir
=> 'pending' },
1080 { subdir
=> 'status' },
1081 { subdir
=> 'unlink' },
1082 { subdir
=> 'vncproxy' },
1083 { subdir
=> 'termproxy' },
1084 { subdir
=> 'migrate' },
1085 { subdir
=> 'resize' },
1086 { subdir
=> 'move' },
1087 { subdir
=> 'rrd' },
1088 { subdir
=> 'rrddata' },
1089 { subdir
=> 'monitor' },
1090 { subdir
=> 'agent' },
1091 { subdir
=> 'snapshot' },
1092 { subdir
=> 'spiceproxy' },
1093 { subdir
=> 'sendkey' },
1094 { subdir
=> 'firewall' },
1100 __PACKAGE__-
>register_method ({
1101 subclass
=> "PVE::API2::Firewall::VM",
1102 path
=> '{vmid}/firewall',
1105 __PACKAGE__-
>register_method ({
1106 subclass
=> "PVE::API2::Qemu::Agent",
1107 path
=> '{vmid}/agent',
1110 __PACKAGE__-
>register_method({
1112 path
=> '{vmid}/rrd',
1114 protected
=> 1, # fixme: can we avoid that?
1116 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1118 description
=> "Read VM RRD statistics (returns PNG)",
1120 additionalProperties
=> 0,
1122 node
=> get_standard_option
('pve-node'),
1123 vmid
=> get_standard_option
('pve-vmid'),
1125 description
=> "Specify the time frame you are interested in.",
1127 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
1130 description
=> "The list of datasources you want to display.",
1131 type
=> 'string', format
=> 'pve-configid-list',
1134 description
=> "The RRD consolidation function",
1136 enum
=> [ 'AVERAGE', 'MAX' ],
1144 filename
=> { type
=> 'string' },
1150 return PVE
::RRD
::create_rrd_graph
(
1151 "pve2-vm/$param->{vmid}", $param->{timeframe
},
1152 $param->{ds
}, $param->{cf
});
1156 __PACKAGE__-
>register_method({
1158 path
=> '{vmid}/rrddata',
1160 protected
=> 1, # fixme: can we avoid that?
1162 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1164 description
=> "Read VM RRD statistics",
1166 additionalProperties
=> 0,
1168 node
=> get_standard_option
('pve-node'),
1169 vmid
=> get_standard_option
('pve-vmid'),
1171 description
=> "Specify the time frame you are interested in.",
1173 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
1176 description
=> "The RRD consolidation function",
1178 enum
=> [ 'AVERAGE', 'MAX' ],
1193 return PVE
::RRD
::create_rrd_data
(
1194 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
1198 __PACKAGE__-
>register_method({
1199 name
=> 'vm_config',
1200 path
=> '{vmid}/config',
1203 description
=> "Get the virtual machine configuration with pending configuration " .
1204 "changes applied. Set the 'current' parameter to get the current configuration instead.",
1206 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1209 additionalProperties
=> 0,
1211 node
=> get_standard_option
('pve-node'),
1212 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1214 description
=> "Get current values (instead of pending values).",
1219 snapshot
=> get_standard_option
('pve-snapshot-name', {
1220 description
=> "Fetch config values from given snapshot.",
1223 my ($cmd, $pname, $cur, $args) = @_;
1224 PVE
::QemuConfig-
>snapshot_list($args->[0]);
1230 description
=> "The VM configuration.",
1232 properties
=> PVE
::QemuServer
::json_config_properties
({
1235 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
1242 raise_param_exc
({ snapshot
=> "cannot use 'snapshot' parameter with 'current'",
1243 current
=> "cannot use 'snapshot' parameter with 'current'"})
1244 if ($param->{snapshot
} && $param->{current
});
1247 if ($param->{snapshot
}) {
1248 $conf = PVE
::QemuConfig-
>load_snapshot_config($param->{vmid
}, $param->{snapshot
});
1250 $conf = PVE
::QemuConfig-
>load_current_config($param->{vmid
}, $param->{current
});
1252 $conf->{cipassword
} = '**********' if $conf->{cipassword
};
1257 __PACKAGE__-
>register_method({
1258 name
=> 'vm_pending',
1259 path
=> '{vmid}/pending',
1262 description
=> "Get the virtual machine configuration with both current and pending values.",
1264 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1267 additionalProperties
=> 0,
1269 node
=> get_standard_option
('pve-node'),
1270 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1279 description
=> "Configuration option name.",
1283 description
=> "Current value.",
1288 description
=> "Pending value.",
1293 description
=> "Indicates a pending delete request if present and not 0. " .
1294 "The value 2 indicates a force-delete request.",
1306 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1308 my $pending_delete_hash = PVE
::QemuConfig-
>parse_pending_delete($conf->{pending
}->{delete});
1310 $conf->{cipassword
} = '**********' if defined($conf->{cipassword
});
1311 $conf->{pending
}->{cipassword
} = '********** ' if defined($conf->{pending
}->{cipassword
});
1313 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);
1316 __PACKAGE__-
>register_method({
1317 name
=> 'cloudinit_pending',
1318 path
=> '{vmid}/cloudinit',
1321 description
=> "Get the cloudinit configuration with both current and pending values.",
1323 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1326 additionalProperties
=> 0,
1328 node
=> get_standard_option
('pve-node'),
1329 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1338 description
=> "Configuration option name.",
1342 description
=> "Current value.",
1347 description
=> "Pending value.",
1352 description
=> "Indicates a pending delete request if present and not 0. " .
1353 "The value 2 indicates a force-delete request.",
1365 my $vmid = $param->{vmid
};
1366 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1368 if (defined($conf->{cipassword
}) &&
1369 defined($conf->{cloudinit
}->{cipassword
}) &&
1370 $conf->{cipassword
} ne $conf->{cloudinit
}->{cipassword
}) {
1371 $conf->{cipassword
} = '********** ';
1372 } elsif (defined($conf->{cipassword
})) {
1373 $conf->{cipassword
} = '**********';
1376 $conf->{cloudinit
}->{cipassword
} = '**********' if defined($conf->{cloudinit
}->{cipassword
});
1378 my $res = PVE
::QemuServer
::Cloudinit
::get_pending_config
($conf, $vmid);
1383 __PACKAGE__-
>register_method({
1384 name
=> 'cloudinit_update',
1385 path
=> '{vmid}/cloudinit',
1389 description
=> "Regenerate and change cloudinit config drive.",
1391 check
=> ['perm', '/vms/{vmid}', 'VM.Config.Cloudinit'],
1394 additionalProperties
=> 0,
1396 node
=> get_standard_option
('pve-node'),
1397 vmid
=> get_standard_option
('pve-vmid'),
1400 returns
=> { type
=> 'null' },
1404 my $rpcenv = PVE
::RPCEnvironment
::get
();
1405 my $authuser = $rpcenv->get_user();
1407 my $vmid = extract_param
($param, 'vmid');
1409 PVE
::QemuConfig-
>lock_config($vmid, sub {
1410 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1411 PVE
::QemuConfig-
>check_lock($conf);
1413 my $storecfg = PVE
::Storage
::config
();
1414 PVE
::QemuServer
::vmconfig_update_cloudinit_drive
($storecfg, $conf, $vmid);
1419 # POST/PUT {vmid}/config implementation
1421 # The original API used PUT (idempotent) an we assumed that all operations
1422 # are fast. But it turned out that almost any configuration change can
1423 # involve hot-plug actions, or disk alloc/free. Such actions can take long
1424 # time to complete and have side effects (not idempotent).
1426 # The new implementation uses POST and forks a worker process. We added
1427 # a new option 'background_delay'. If specified we wait up to
1428 # 'background_delay' second for the worker task to complete. It returns null
1429 # if the task is finished within that time, else we return the UPID.
1431 my $update_vm_api = sub {
1432 my ($param, $sync) = @_;
1434 my $rpcenv = PVE
::RPCEnvironment
::get
();
1436 my $authuser = $rpcenv->get_user();
1438 my $node = extract_param
($param, 'node');
1440 my $vmid = extract_param
($param, 'vmid');
1442 my $digest = extract_param
($param, 'digest');
1444 my $background_delay = extract_param
($param, 'background_delay');
1446 if (defined(my $cipassword = $param->{cipassword
})) {
1447 # Same logic as in cloud-init (but with the regex fixed...)
1448 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
1449 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
1452 my @paramarr = (); # used for log message
1453 foreach my $key (sort keys %$param) {
1454 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
1455 push @paramarr, "-$key", $value;
1458 my $skiplock = extract_param
($param, 'skiplock');
1459 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1460 if $skiplock && $authuser ne 'root@pam';
1462 my $delete_str = extract_param
($param, 'delete');
1464 my $revert_str = extract_param
($param, 'revert');
1466 my $force = extract_param
($param, 'force');
1468 if (defined(my $ssh_keys = $param->{sshkeys
})) {
1469 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
1470 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
1473 $param->{cpuunits
} = PVE
::GuestHelpers
::get_cpuunits
($param->{cpuunits
})
1474 if defined($param->{cpuunits
}); # clamp value depending on cgroup version
1476 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
1478 my $storecfg = PVE
::Storage
::config
();
1480 my $defaults = PVE
::QemuServer
::load_defaults
();
1482 &$resolve_cdrom_alias($param);
1484 # now try to verify all parameters
1487 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1488 if (!PVE
::QemuServer
::option_exists
($opt)) {
1489 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1492 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1493 "-revert $opt' at the same time" })
1494 if defined($param->{$opt});
1496 $revert->{$opt} = 1;
1500 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1501 $opt = 'ide2' if $opt eq 'cdrom';
1503 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1504 "-delete $opt' at the same time" })
1505 if defined($param->{$opt});
1507 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1508 "-revert $opt' at the same time" })
1511 if (!PVE
::QemuServer
::option_exists
($opt)) {
1512 raise_param_exc
({ delete => "unknown option '$opt'" });
1518 my $repl_conf = PVE
::ReplicationConfig-
>new();
1519 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1520 my $check_replication = sub {
1522 return if !$is_replicated;
1523 my $volid = $drive->{file
};
1524 return if !$volid || !($drive->{replicate
}//1);
1525 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1527 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1528 die "cannot add non-managed/pass-through volume to a replicated VM\n"
1529 if !defined($storeid);
1531 return if defined($volname) && $volname eq 'cloudinit';
1534 if ($volid =~ $NEW_DISK_RE) {
1536 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1538 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1540 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1541 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1542 return if $scfg->{shared
};
1543 die "cannot add non-replicatable volume to a replicated VM\n";
1546 $check_drive_param->($param, $storecfg, $check_replication);
1548 foreach my $opt (keys %$param) {
1549 if ($opt =~ m/^net(\d+)$/) {
1551 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1552 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1553 } elsif ($opt eq 'vmgenid') {
1554 if ($param->{$opt} eq '1') {
1555 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1557 } elsif ($opt eq 'hookscript') {
1558 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1559 raise_param_exc
({ $opt => $@ }) if $@;
1563 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1565 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1567 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1569 my $updatefn = sub {
1571 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1573 die "checksum missmatch (file change by other user?)\n"
1574 if $digest && $digest ne $conf->{digest
};
1576 &$check_cpu_model_access($rpcenv, $authuser, $param, $conf);
1578 # FIXME: 'suspended' lock should probabyl be a state or "weak" lock?!
1579 if (scalar(@delete) && grep { $_ eq 'vmstate'} @delete) {
1580 if (defined($conf->{lock}) && $conf->{lock} eq 'suspended') {
1581 delete $conf->{lock}; # for check lock check, not written out
1582 push @delete, 'lock'; # this is the real deal to write it out
1584 push @delete, 'runningmachine' if $conf->{runningmachine
};
1585 push @delete, 'runningcpu' if $conf->{runningcpu
};
1588 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1590 foreach my $opt (keys %$revert) {
1591 if (defined($conf->{$opt})) {
1592 $param->{$opt} = $conf->{$opt};
1593 } elsif (defined($conf->{pending
}->{$opt})) {
1598 if ($param->{memory
} || defined($param->{balloon
})) {
1599 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1600 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1602 die "balloon value too large (must be smaller than assigned memory)\n"
1603 if $balloon && $balloon > $maxmem;
1606 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1610 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1612 # write updates to pending section
1614 my $modified = {}; # record what $option we modify
1617 if (my $boot = $conf->{boot
}) {
1618 my $bootcfg = PVE
::JSONSchema
::parse_property_string
('pve-qm-boot', $boot);
1619 @bootorder = PVE
::Tools
::split_list
($bootcfg->{order
}) if $bootcfg && $bootcfg->{order
};
1621 my $bootorder_deleted = grep {$_ eq 'bootorder'} @delete;
1623 my $check_drive_perms = sub {
1624 my ($opt, $val) = @_;
1625 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val, 1);
1626 # FIXME: cloudinit: CDROM or Disk?
1627 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1628 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1630 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1634 foreach my $opt (@delete) {
1635 $modified->{$opt} = 1;
1636 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1638 # value of what we want to delete, independent if pending or not
1639 my $val = $conf->{$opt} // $conf->{pending
}->{$opt};
1640 if (!defined($val)) {
1641 warn "cannot delete '$opt' - not set in current configuration!\n";
1642 $modified->{$opt} = 0;
1645 my $is_pending_val = defined($conf->{pending
}->{$opt});
1646 delete $conf->{pending
}->{$opt};
1648 # remove from bootorder if necessary
1649 if (!$bootorder_deleted && @bootorder && grep {$_ eq $opt} @bootorder) {
1650 @bootorder = grep {$_ ne $opt} @bootorder;
1651 $conf->{pending
}->{boot
} = PVE
::QemuServer
::print_bootorder
(\
@bootorder);
1652 $modified->{boot
} = 1;
1655 if ($opt =~ m/^unused/) {
1656 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1657 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1658 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1659 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1660 delete $conf->{$opt};
1661 PVE
::QemuConfig-
>write_config($vmid, $conf);
1663 } elsif ($opt eq 'vmstate') {
1664 PVE
::QemuConfig-
>check_protection($conf, "can't remove vmstate '$val'");
1665 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, { file
=> $val }, $rpcenv, $authuser, 1)) {
1666 delete $conf->{$opt};
1667 PVE
::QemuConfig-
>write_config($vmid, $conf);
1669 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1670 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1671 $check_drive_perms->($opt, $val);
1672 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $val))
1674 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1675 PVE
::QemuConfig-
>write_config($vmid, $conf);
1676 } elsif ($opt =~ m/^serial\d+$/) {
1677 if ($val eq 'socket') {
1678 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1679 } elsif ($authuser ne 'root@pam') {
1680 die "only root can delete '$opt' config for real devices\n";
1682 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1683 PVE
::QemuConfig-
>write_config($vmid, $conf);
1684 } elsif ($opt =~ m/^usb\d+$/) {
1685 if ($val =~ m/spice/) {
1686 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1687 } elsif ($authuser ne 'root@pam') {
1688 die "only root can delete '$opt' config for real devices\n";
1690 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1691 PVE
::QemuConfig-
>write_config($vmid, $conf);
1693 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1694 PVE
::QemuConfig-
>write_config($vmid, $conf);
1698 foreach my $opt (keys %$param) { # add/change
1699 $modified->{$opt} = 1;
1700 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1701 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1703 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
1705 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1707 if ($conf->{$opt}) {
1708 $check_drive_perms->($opt, $conf->{$opt});
1712 $check_drive_perms->($opt, $param->{$opt});
1713 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1714 if defined($conf->{pending
}->{$opt});
1716 my (undef, $created_opts) = $create_disks->(
1724 {$opt => $param->{$opt}},
1726 $conf->{pending
}->{$_} = $created_opts->{$_} for keys $created_opts->%*;
1728 # default legacy boot order implies all cdroms anyway
1730 # append new CD drives to bootorder to mark them bootable
1731 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt}, 1);
1732 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1) && !grep(/^$opt$/, @bootorder)) {
1733 push @bootorder, $opt;
1734 $conf->{pending
}->{boot
} = PVE
::QemuServer
::print_bootorder
(\
@bootorder);
1735 $modified->{boot
} = 1;
1738 } elsif ($opt =~ m/^serial\d+/) {
1739 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1740 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1741 } elsif ($authuser ne 'root@pam') {
1742 die "only root can modify '$opt' config for real devices\n";
1744 $conf->{pending
}->{$opt} = $param->{$opt};
1745 } elsif ($opt =~ m/^usb\d+/) {
1746 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1747 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1748 } elsif ($authuser ne 'root@pam') {
1749 die "only root can modify '$opt' config for real devices\n";
1751 $conf->{pending
}->{$opt} = $param->{$opt};
1753 $conf->{pending
}->{$opt} = $param->{$opt};
1755 if ($opt eq 'boot') {
1756 my $new_bootcfg = PVE
::JSONSchema
::parse_property_string
('pve-qm-boot', $param->{$opt});
1757 if ($new_bootcfg->{order
}) {
1758 my @devs = PVE
::Tools
::split_list
($new_bootcfg->{order
});
1759 for my $dev (@devs) {
1760 my $exists = $conf->{$dev} || $conf->{pending
}->{$dev} || $param->{$dev};
1761 my $deleted = grep {$_ eq $dev} @delete;
1762 die "invalid bootorder: device '$dev' does not exist'\n"
1763 if !$exists || $deleted;
1766 # remove legacy boot order settings if new one set
1767 $conf->{pending
}->{$opt} = PVE
::QemuServer
::print_bootorder
(\
@devs);
1768 PVE
::QemuConfig-
>add_to_pending_delete($conf, "bootdisk")
1769 if $conf->{bootdisk
};
1773 PVE
::QemuConfig-
>remove_from_pending_delete($conf, $opt);
1774 PVE
::QemuConfig-
>write_config($vmid, $conf);
1777 # remove pending changes when nothing changed
1778 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1779 my $changes = PVE
::QemuConfig-
>cleanup_pending($conf);
1780 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1782 return if !scalar(keys %{$conf->{pending
}});
1784 my $running = PVE
::QemuServer
::check_running
($vmid);
1786 # apply pending changes
1788 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1792 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1794 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $errors);
1796 raise_param_exc
($errors) if scalar(keys %$errors);
1805 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1807 if ($background_delay) {
1809 # Note: It would be better to do that in the Event based HTTPServer
1810 # to avoid blocking call to sleep.
1812 my $end_time = time() + $background_delay;
1814 my $task = PVE
::Tools
::upid_decode
($upid);
1817 while (time() < $end_time) {
1818 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1820 sleep(1); # this gets interrupted when child process ends
1824 my $status = PVE
::Tools
::upid_read_status
($upid);
1825 return if !PVE
::Tools
::upid_status_is_error
($status);
1826 die "failed to update VM $vmid: $status\n";
1834 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1837 my $vm_config_perm_list = [
1842 'VM.Config.Network',
1844 'VM.Config.Options',
1845 'VM.Config.Cloudinit',
1848 __PACKAGE__-
>register_method({
1849 name
=> 'update_vm_async',
1850 path
=> '{vmid}/config',
1854 description
=> "Set virtual machine options (asynchrounous API).",
1856 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1859 additionalProperties
=> 0,
1860 properties
=> PVE
::QemuServer
::json_config_properties
(
1862 node
=> get_standard_option
('pve-node'),
1863 vmid
=> get_standard_option
('pve-vmid'),
1864 skiplock
=> get_standard_option
('skiplock'),
1866 type
=> 'string', format
=> 'pve-configid-list',
1867 description
=> "A list of settings you want to delete.",
1871 type
=> 'string', format
=> 'pve-configid-list',
1872 description
=> "Revert a pending change.",
1877 description
=> $opt_force_description,
1879 requires
=> 'delete',
1883 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1887 background_delay
=> {
1889 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1895 1, # with_disk_alloc
1902 code
=> $update_vm_api,
1905 __PACKAGE__-
>register_method({
1906 name
=> 'update_vm',
1907 path
=> '{vmid}/config',
1911 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1913 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1916 additionalProperties
=> 0,
1917 properties
=> PVE
::QemuServer
::json_config_properties
(
1919 node
=> get_standard_option
('pve-node'),
1920 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1921 skiplock
=> get_standard_option
('skiplock'),
1923 type
=> 'string', format
=> 'pve-configid-list',
1924 description
=> "A list of settings you want to delete.",
1928 type
=> 'string', format
=> 'pve-configid-list',
1929 description
=> "Revert a pending change.",
1934 description
=> $opt_force_description,
1936 requires
=> 'delete',
1940 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1945 1, # with_disk_alloc
1948 returns
=> { type
=> 'null' },
1951 &$update_vm_api($param, 1);
1956 __PACKAGE__-
>register_method({
1957 name
=> 'destroy_vm',
1962 description
=> "Destroy the VM and all used/owned volumes. Removes any VM specific permissions"
1963 ." and firewall rules",
1965 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1968 additionalProperties
=> 0,
1970 node
=> get_standard_option
('pve-node'),
1971 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1972 skiplock
=> get_standard_option
('skiplock'),
1975 description
=> "Remove VMID from configurations, like backup & replication jobs and HA.",
1978 'destroy-unreferenced-disks' => {
1980 description
=> "If set, destroy additionally all disks not referenced in the config"
1981 ." but with a matching VMID from all enabled storages.",
1993 my $rpcenv = PVE
::RPCEnvironment
::get
();
1994 my $authuser = $rpcenv->get_user();
1995 my $vmid = $param->{vmid
};
1997 my $skiplock = $param->{skiplock
};
1998 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1999 if $skiplock && $authuser ne 'root@pam';
2001 my $early_checks = sub {
2003 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2004 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
2006 my $ha_managed = PVE
::HA
::Config
::service_is_configured
("vm:$vmid");
2008 if (!$param->{purge
}) {
2009 die "unable to remove VM $vmid - used in HA resources and purge parameter not set.\n"
2011 # don't allow destroy if with replication jobs but no purge param
2012 my $repl_conf = PVE
::ReplicationConfig-
>new();
2013 $repl_conf->check_for_existing_jobs($vmid);
2016 die "VM $vmid is running - destroy failed\n"
2017 if PVE
::QemuServer
::check_running
($vmid);
2027 my $storecfg = PVE
::Storage
::config
();
2029 syslog
('info', "destroy VM $vmid: $upid\n");
2030 PVE
::QemuConfig-
>lock_config($vmid, sub {
2031 # repeat, config might have changed
2032 my $ha_managed = $early_checks->();
2034 my $purge_unreferenced = $param->{'destroy-unreferenced-disks'};
2036 PVE
::QemuServer
::destroy_vm
(
2039 $skiplock, { lock => 'destroyed' },
2040 $purge_unreferenced,
2043 PVE
::AccessControl
::remove_vm_access
($vmid);
2044 PVE
::Firewall
::remove_vmfw_conf
($vmid);
2045 if ($param->{purge
}) {
2046 print "purging VM $vmid from related configurations..\n";
2047 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
2048 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
2051 PVE
::HA
::Config
::delete_service_from_config
("vm:$vmid");
2052 print "NOTE: removed VM $vmid from HA resource configuration.\n";
2056 # only now remove the zombie config, else we can have reuse race
2057 PVE
::QemuConfig-
>destroy_config($vmid);
2061 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
2064 __PACKAGE__-
>register_method({
2066 path
=> '{vmid}/unlink',
2070 description
=> "Unlink/delete disk images.",
2072 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
2075 additionalProperties
=> 0,
2077 node
=> get_standard_option
('pve-node'),
2078 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2080 type
=> 'string', format
=> 'pve-configid-list',
2081 description
=> "A list of disk IDs you want to delete.",
2085 description
=> $opt_force_description,
2090 returns
=> { type
=> 'null'},
2094 $param->{delete} = extract_param
($param, 'idlist');
2096 __PACKAGE__-
>update_vm($param);
2101 # uses good entropy, each char is limited to 6 bit to get printable chars simply
2102 my $gen_rand_chars = sub {
2105 die "invalid length $length" if $length < 1;
2107 my $min = ord('!'); # first printable ascii
2109 my $rand_bytes = Crypt
::OpenSSL
::Random
::random_bytes
($length);
2110 die "failed to generate random bytes!\n"
2113 my $str = join('', map { chr((ord($_) & 0x3F) + $min) } split('', $rand_bytes));
2120 __PACKAGE__-
>register_method({
2122 path
=> '{vmid}/vncproxy',
2126 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2128 description
=> "Creates a TCP VNC proxy connections.",
2130 additionalProperties
=> 0,
2132 node
=> get_standard_option
('pve-node'),
2133 vmid
=> get_standard_option
('pve-vmid'),
2137 description
=> "starts websockify instead of vncproxy",
2139 'generate-password' => {
2143 description
=> "Generates a random password to be used as ticket instead of the API ticket.",
2148 additionalProperties
=> 0,
2150 user
=> { type
=> 'string' },
2151 ticket
=> { type
=> 'string' },
2154 description
=> "Returned if requested with 'generate-password' param."
2155 ." Consists of printable ASCII characters ('!' .. '~').",
2158 cert
=> { type
=> 'string' },
2159 port
=> { type
=> 'integer' },
2160 upid
=> { type
=> 'string' },
2166 my $rpcenv = PVE
::RPCEnvironment
::get
();
2168 my $authuser = $rpcenv->get_user();
2170 my $vmid = $param->{vmid
};
2171 my $node = $param->{node
};
2172 my $websocket = $param->{websocket
};
2174 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
2178 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
2179 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
2182 my $authpath = "/vms/$vmid";
2184 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
2185 my $password = $ticket;
2186 if ($param->{'generate-password'}) {
2187 $password = $gen_rand_chars->(8);
2190 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
2196 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
2197 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
2198 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
2199 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
2200 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, defined($serial) ?
'-t' : '-T');
2202 $family = PVE
::Tools
::get_host_address_family
($node);
2205 my $port = PVE
::Tools
::next_vnc_port
($family);
2212 syslog
('info', "starting vnc proxy $upid\n");
2216 if (defined($serial)) {
2218 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $serial, '-escape', '0' ];
2220 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
2221 '-timeout', $timeout, '-authpath', $authpath,
2222 '-perm', 'Sys.Console'];
2224 if ($param->{websocket
}) {
2225 $ENV{PVE_VNC_TICKET
} = $password; # pass ticket to vncterm
2226 push @$cmd, '-notls', '-listen', 'localhost';
2229 push @$cmd, '-c', @$remcmd, @$termcmd;
2231 PVE
::Tools
::run_command
($cmd);
2235 $ENV{LC_PVE_TICKET
} = $password if $websocket; # set ticket with "qm vncproxy"
2237 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
2239 my $sock = IO
::Socket
::IP-
>new(
2244 GetAddrInfoFlags
=> 0,
2245 ) or die "failed to create socket: $!\n";
2246 # Inside the worker we shouldn't have any previous alarms
2247 # running anyway...:
2249 local $SIG{ALRM
} = sub { die "connection timed out\n" };
2251 accept(my $cli, $sock) or die "connection failed: $!\n";
2254 if (PVE
::Tools
::run_command
($cmd,
2255 output
=> '>&'.fileno($cli),
2256 input
=> '<&'.fileno($cli),
2259 die "Failed to run vncproxy.\n";
2266 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
2268 PVE
::Tools
::wait_for_vnc_port
($port);
2277 $res->{password
} = $password if $param->{'generate-password'};
2282 __PACKAGE__-
>register_method({
2283 name
=> 'termproxy',
2284 path
=> '{vmid}/termproxy',
2288 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2290 description
=> "Creates a TCP proxy connections.",
2292 additionalProperties
=> 0,
2294 node
=> get_standard_option
('pve-node'),
2295 vmid
=> get_standard_option
('pve-vmid'),
2299 enum
=> [qw(serial0 serial1 serial2 serial3)],
2300 description
=> "opens a serial terminal (defaults to display)",
2305 additionalProperties
=> 0,
2307 user
=> { type
=> 'string' },
2308 ticket
=> { type
=> 'string' },
2309 port
=> { type
=> 'integer' },
2310 upid
=> { type
=> 'string' },
2316 my $rpcenv = PVE
::RPCEnvironment
::get
();
2318 my $authuser = $rpcenv->get_user();
2320 my $vmid = $param->{vmid
};
2321 my $node = $param->{node
};
2322 my $serial = $param->{serial
};
2324 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
2326 if (!defined($serial)) {
2328 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
2329 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
2333 my $authpath = "/vms/$vmid";
2335 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
2340 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
2341 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
2342 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
2343 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, '-t');
2344 push @$remcmd, '--';
2346 $family = PVE
::Tools
::get_host_address_family
($node);
2349 my $port = PVE
::Tools
::next_vnc_port
($family);
2351 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
2352 push @$termcmd, '-iface', $serial if $serial;
2357 syslog
('info', "starting qemu termproxy $upid\n");
2359 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
2360 '--perm', 'VM.Console', '--'];
2361 push @$cmd, @$remcmd, @$termcmd;
2363 PVE
::Tools
::run_command
($cmd);
2366 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
2368 PVE
::Tools
::wait_for_vnc_port
($port);
2378 __PACKAGE__-
>register_method({
2379 name
=> 'vncwebsocket',
2380 path
=> '{vmid}/vncwebsocket',
2383 description
=> "You also need to pass a valid ticket (vncticket).",
2384 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2386 description
=> "Opens a weksocket for VNC traffic.",
2388 additionalProperties
=> 0,
2390 node
=> get_standard_option
('pve-node'),
2391 vmid
=> get_standard_option
('pve-vmid'),
2393 description
=> "Ticket from previous call to vncproxy.",
2398 description
=> "Port number returned by previous vncproxy call.",
2408 port
=> { type
=> 'string' },
2414 my $rpcenv = PVE
::RPCEnvironment
::get
();
2416 my $authuser = $rpcenv->get_user();
2418 my $vmid = $param->{vmid
};
2419 my $node = $param->{node
};
2421 my $authpath = "/vms/$vmid";
2423 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
2425 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
2427 # Note: VNC ports are acessible from outside, so we do not gain any
2428 # security if we verify that $param->{port} belongs to VM $vmid. This
2429 # check is done by verifying the VNC ticket (inside VNC protocol).
2431 my $port = $param->{port
};
2433 return { port
=> $port };
2436 __PACKAGE__-
>register_method({
2437 name
=> 'spiceproxy',
2438 path
=> '{vmid}/spiceproxy',
2443 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2445 description
=> "Returns a SPICE configuration to connect to the VM.",
2447 additionalProperties
=> 0,
2449 node
=> get_standard_option
('pve-node'),
2450 vmid
=> get_standard_option
('pve-vmid'),
2451 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
2454 returns
=> get_standard_option
('remote-viewer-config'),
2458 my $rpcenv = PVE
::RPCEnvironment
::get
();
2460 my $authuser = $rpcenv->get_user();
2462 my $vmid = $param->{vmid
};
2463 my $node = $param->{node
};
2464 my $proxy = $param->{proxy
};
2466 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
2467 my $title = "VM $vmid";
2468 $title .= " - ". $conf->{name
} if $conf->{name
};
2470 my $port = PVE
::QemuServer
::spice_port
($vmid);
2472 my ($ticket, undef, $remote_viewer_config) =
2473 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
2475 mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
2476 mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
2478 return $remote_viewer_config;
2481 __PACKAGE__-
>register_method({
2483 path
=> '{vmid}/status',
2486 description
=> "Directory index",
2491 additionalProperties
=> 0,
2493 node
=> get_standard_option
('pve-node'),
2494 vmid
=> get_standard_option
('pve-vmid'),
2502 subdir
=> { type
=> 'string' },
2505 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
2511 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2514 { subdir
=> 'current' },
2515 { subdir
=> 'start' },
2516 { subdir
=> 'stop' },
2517 { subdir
=> 'reset' },
2518 { subdir
=> 'shutdown' },
2519 { subdir
=> 'suspend' },
2520 { subdir
=> 'reboot' },
2526 __PACKAGE__-
>register_method({
2527 name
=> 'vm_status',
2528 path
=> '{vmid}/status/current',
2531 protected
=> 1, # qemu pid files are only readable by root
2532 description
=> "Get virtual machine status.",
2534 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2537 additionalProperties
=> 0,
2539 node
=> get_standard_option
('pve-node'),
2540 vmid
=> get_standard_option
('pve-vmid'),
2546 %$PVE::QemuServer
::vmstatus_return_properties
,
2548 description
=> "HA manager service status.",
2552 description
=> "Qemu VGA configuration supports spice.",
2557 description
=> "Qemu GuestAgent enabled in config.",
2567 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2569 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
2570 my $status = $vmstatus->{$param->{vmid
}};
2572 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
2575 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
2576 my $spice = defined($vga->{type
}) && $vga->{type
} =~ /^virtio/;
2577 $spice ||= PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
2578 $status->{spice
} = 1 if $spice;
2580 $status->{agent
} = 1 if PVE
::QemuServer
::get_qga_key
($conf, 'enabled');
2585 __PACKAGE__-
>register_method({
2587 path
=> '{vmid}/status/start',
2591 description
=> "Start virtual machine.",
2593 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2596 additionalProperties
=> 0,
2598 node
=> get_standard_option
('pve-node'),
2599 vmid
=> get_standard_option
('pve-vmid',
2600 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
2601 skiplock
=> get_standard_option
('skiplock'),
2602 stateuri
=> get_standard_option
('pve-qm-stateuri'),
2603 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
2606 enum
=> ['secure', 'insecure'],
2607 description
=> "Migration traffic is encrypted using an SSH " .
2608 "tunnel by default. On secure, completely private networks " .
2609 "this can be disabled to increase performance.",
2612 migration_network
=> {
2613 type
=> 'string', format
=> 'CIDR',
2614 description
=> "CIDR of the (sub) network that is used for migration.",
2617 machine
=> get_standard_option
('pve-qemu-machine'),
2619 description
=> "Override QEMU's -cpu argument with the given string.",
2623 targetstorage
=> get_standard_option
('pve-targetstorage'),
2625 description
=> "Wait maximal timeout seconds.",
2628 default => 'max(30, vm memory in GiB)',
2639 my $rpcenv = PVE
::RPCEnvironment
::get
();
2640 my $authuser = $rpcenv->get_user();
2642 my $node = extract_param
($param, 'node');
2643 my $vmid = extract_param
($param, 'vmid');
2644 my $timeout = extract_param
($param, 'timeout');
2645 my $machine = extract_param
($param, 'machine');
2647 my $get_root_param = sub {
2648 my $value = extract_param
($param, $_[0]);
2649 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2650 if $value && $authuser ne 'root@pam';
2654 my $stateuri = $get_root_param->('stateuri');
2655 my $skiplock = $get_root_param->('skiplock');
2656 my $migratedfrom = $get_root_param->('migratedfrom');
2657 my $migration_type = $get_root_param->('migration_type');
2658 my $migration_network = $get_root_param->('migration_network');
2659 my $targetstorage = $get_root_param->('targetstorage');
2660 my $force_cpu = $get_root_param->('force-cpu');
2664 if ($targetstorage) {
2665 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2667 $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
2668 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
2672 # read spice ticket from STDIN
2674 my $nbd_protocol_version = 0;
2675 my $replicated_volumes = {};
2676 my $offline_volumes = {};
2677 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2678 while (defined(my $line = <STDIN
>)) {
2680 if ($line =~ m/^spice_ticket: (.+)$/) {
2682 } elsif ($line =~ m/^nbd_protocol_version: (\d+)$/) {
2683 $nbd_protocol_version = $1;
2684 } elsif ($line =~ m/^replicated_volume: (.*)$/) {
2685 $replicated_volumes->{$1} = 1;
2686 } elsif ($line =~ m/^tpmstate0: (.*)$/) { # Deprecated, use offline_volume instead
2687 $offline_volumes->{tpmstate0
} = $1;
2688 } elsif ($line =~ m/^offline_volume: ([^:]+): (.*)$/) {
2689 $offline_volumes->{$1} = $2;
2690 } elsif (!$spice_ticket) {
2691 # fallback for old source node
2692 $spice_ticket = $line;
2694 warn "unknown 'start' parameter on STDIN: '$line'\n";
2699 PVE
::Cluster
::check_cfs_quorum
();
2701 my $storecfg = PVE
::Storage
::config
();
2703 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2707 print "Requesting HA start for VM $vmid\n";
2709 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2710 PVE
::Tools
::run_command
($cmd);
2714 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2721 syslog
('info', "start VM $vmid: $upid\n");
2723 my $migrate_opts = {
2724 migratedfrom
=> $migratedfrom,
2725 spice_ticket
=> $spice_ticket,
2726 network
=> $migration_network,
2727 type
=> $migration_type,
2728 storagemap
=> $storagemap,
2729 nbd_proto_version
=> $nbd_protocol_version,
2730 replicated_volumes
=> $replicated_volumes,
2731 offline_volumes
=> $offline_volumes,
2735 statefile
=> $stateuri,
2736 skiplock
=> $skiplock,
2737 forcemachine
=> $machine,
2738 timeout
=> $timeout,
2739 forcecpu
=> $force_cpu,
2742 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $params, $migrate_opts);
2746 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2750 __PACKAGE__-
>register_method({
2752 path
=> '{vmid}/status/stop',
2756 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2757 "is akin to pulling the power plug of a running computer and may damage the VM data",
2759 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2762 additionalProperties
=> 0,
2764 node
=> get_standard_option
('pve-node'),
2765 vmid
=> get_standard_option
('pve-vmid',
2766 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2767 skiplock
=> get_standard_option
('skiplock'),
2768 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2770 description
=> "Wait maximal timeout seconds.",
2776 description
=> "Do not deactivate storage volumes.",
2789 my $rpcenv = PVE
::RPCEnvironment
::get
();
2790 my $authuser = $rpcenv->get_user();
2792 my $node = extract_param
($param, 'node');
2793 my $vmid = extract_param
($param, 'vmid');
2795 my $skiplock = extract_param
($param, 'skiplock');
2796 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2797 if $skiplock && $authuser ne 'root@pam';
2799 my $keepActive = extract_param
($param, 'keepActive');
2800 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2801 if $keepActive && $authuser ne 'root@pam';
2803 my $migratedfrom = extract_param
($param, 'migratedfrom');
2804 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2805 if $migratedfrom && $authuser ne 'root@pam';
2808 my $storecfg = PVE
::Storage
::config
();
2810 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2815 print "Requesting HA stop for VM $vmid\n";
2817 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
2818 PVE
::Tools
::run_command
($cmd);
2822 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2828 syslog
('info', "stop VM $vmid: $upid\n");
2830 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2831 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2835 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2839 __PACKAGE__-
>register_method({
2841 path
=> '{vmid}/status/reset',
2845 description
=> "Reset virtual machine.",
2847 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2850 additionalProperties
=> 0,
2852 node
=> get_standard_option
('pve-node'),
2853 vmid
=> get_standard_option
('pve-vmid',
2854 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2855 skiplock
=> get_standard_option
('skiplock'),
2864 my $rpcenv = PVE
::RPCEnvironment
::get
();
2866 my $authuser = $rpcenv->get_user();
2868 my $node = extract_param
($param, 'node');
2870 my $vmid = extract_param
($param, 'vmid');
2872 my $skiplock = extract_param
($param, 'skiplock');
2873 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2874 if $skiplock && $authuser ne 'root@pam';
2876 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2881 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2886 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2889 __PACKAGE__-
>register_method({
2890 name
=> 'vm_shutdown',
2891 path
=> '{vmid}/status/shutdown',
2895 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2896 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2898 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2901 additionalProperties
=> 0,
2903 node
=> get_standard_option
('pve-node'),
2904 vmid
=> get_standard_option
('pve-vmid',
2905 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2906 skiplock
=> get_standard_option
('skiplock'),
2908 description
=> "Wait maximal timeout seconds.",
2914 description
=> "Make sure the VM stops.",
2920 description
=> "Do not deactivate storage volumes.",
2933 my $rpcenv = PVE
::RPCEnvironment
::get
();
2934 my $authuser = $rpcenv->get_user();
2936 my $node = extract_param
($param, 'node');
2937 my $vmid = extract_param
($param, 'vmid');
2939 my $skiplock = extract_param
($param, 'skiplock');
2940 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2941 if $skiplock && $authuser ne 'root@pam';
2943 my $keepActive = extract_param
($param, 'keepActive');
2944 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2945 if $keepActive && $authuser ne 'root@pam';
2947 my $storecfg = PVE
::Storage
::config
();
2951 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2952 # otherwise, we will infer a shutdown command, but run into the timeout,
2953 # then when the vm is resumed, it will instantly shutdown
2955 # checking the qmp status here to get feedback to the gui/cli/api
2956 # and the status query should not take too long
2957 if (PVE
::QemuServer
::vm_is_paused
($vmid)) {
2958 if ($param->{forceStop
}) {
2959 warn "VM is paused - stop instead of shutdown\n";
2962 die "VM is paused - cannot shutdown\n";
2966 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2968 my $timeout = $param->{timeout
} // 60;
2972 print "Requesting HA stop for VM $vmid\n";
2974 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
2975 PVE
::Tools
::run_command
($cmd);
2979 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2986 syslog
('info', "shutdown VM $vmid: $upid\n");
2988 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2989 $shutdown, $param->{forceStop
}, $keepActive);
2993 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2997 __PACKAGE__-
>register_method({
2998 name
=> 'vm_reboot',
2999 path
=> '{vmid}/status/reboot',
3003 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
3005 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
3008 additionalProperties
=> 0,
3010 node
=> get_standard_option
('pve-node'),
3011 vmid
=> get_standard_option
('pve-vmid',
3012 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
3014 description
=> "Wait maximal timeout seconds for the shutdown.",
3027 my $rpcenv = PVE
::RPCEnvironment
::get
();
3028 my $authuser = $rpcenv->get_user();
3030 my $node = extract_param
($param, 'node');
3031 my $vmid = extract_param
($param, 'vmid');
3033 die "VM is paused - cannot shutdown\n" if PVE
::QemuServer
::vm_is_paused
($vmid);
3035 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
3040 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
3041 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
3045 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
3048 __PACKAGE__-
>register_method({
3049 name
=> 'vm_suspend',
3050 path
=> '{vmid}/status/suspend',
3054 description
=> "Suspend virtual machine.",
3056 description
=> "You need 'VM.PowerMgmt' on /vms/{vmid}, and if you have set 'todisk',".
3057 " you need also 'VM.Config.Disk' on /vms/{vmid} and 'Datastore.AllocateSpace'".
3058 " on the storage for the vmstate.",
3059 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
3062 additionalProperties
=> 0,
3064 node
=> get_standard_option
('pve-node'),
3065 vmid
=> get_standard_option
('pve-vmid',
3066 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
3067 skiplock
=> get_standard_option
('skiplock'),
3072 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
3074 statestorage
=> get_standard_option
('pve-storage-id', {
3075 description
=> "The storage for the VM state",
3076 requires
=> 'todisk',
3078 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
3088 my $rpcenv = PVE
::RPCEnvironment
::get
();
3089 my $authuser = $rpcenv->get_user();
3091 my $node = extract_param
($param, 'node');
3092 my $vmid = extract_param
($param, 'vmid');
3094 my $todisk = extract_param
($param, 'todisk') // 0;
3096 my $statestorage = extract_param
($param, 'statestorage');
3098 my $skiplock = extract_param
($param, 'skiplock');
3099 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3100 if $skiplock && $authuser ne 'root@pam';
3102 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
3104 die "Cannot suspend HA managed VM to disk\n"
3105 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
3107 # early check for storage permission, for better user feedback
3109 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
3110 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3112 # cannot save the state of a non-virtualized PCIe device, so resume cannot really work
3113 for my $key (keys %$conf) {
3114 next if $key !~ /^hostpci\d+/;
3115 die "cannot suspend VM to disk due to passed-through PCI device(s), which lack the"
3116 ." possibility to save/restore their internal state\n";
3119 if (!$statestorage) {
3120 # get statestorage from config if none is given
3121 my $storecfg = PVE
::Storage
::config
();
3122 $statestorage = PVE
::QemuServer
::find_vmstate_storage
($conf, $storecfg);
3125 $rpcenv->check($authuser, "/storage/$statestorage", ['Datastore.AllocateSpace']);
3131 syslog
('info', "suspend VM $vmid: $upid\n");
3133 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
3138 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
3139 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
3142 __PACKAGE__-
>register_method({
3143 name
=> 'vm_resume',
3144 path
=> '{vmid}/status/resume',
3148 description
=> "Resume virtual machine.",
3150 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
3153 additionalProperties
=> 0,
3155 node
=> get_standard_option
('pve-node'),
3156 vmid
=> get_standard_option
('pve-vmid',
3157 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
3158 skiplock
=> get_standard_option
('skiplock'),
3159 nocheck
=> { type
=> 'boolean', optional
=> 1 },
3169 my $rpcenv = PVE
::RPCEnvironment
::get
();
3171 my $authuser = $rpcenv->get_user();
3173 my $node = extract_param
($param, 'node');
3175 my $vmid = extract_param
($param, 'vmid');
3177 my $skiplock = extract_param
($param, 'skiplock');
3178 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3179 if $skiplock && $authuser ne 'root@pam';
3181 my $nocheck = extract_param
($param, 'nocheck');
3182 raise_param_exc
({ nocheck
=> "Only root may use this option." })
3183 if $nocheck && $authuser ne 'root@pam';
3185 my $to_disk_suspended;
3187 PVE
::QemuConfig-
>lock_config($vmid, sub {
3188 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3189 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
3193 die "VM $vmid not running\n"
3194 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
3199 syslog
('info', "resume VM $vmid: $upid\n");
3201 if (!$to_disk_suspended) {
3202 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
3204 my $storecfg = PVE
::Storage
::config
();
3205 PVE
::QemuServer
::vm_start
($storecfg, $vmid, { skiplock
=> $skiplock });
3211 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
3214 __PACKAGE__-
>register_method({
3215 name
=> 'vm_sendkey',
3216 path
=> '{vmid}/sendkey',
3220 description
=> "Send key event to virtual machine.",
3222 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
3225 additionalProperties
=> 0,
3227 node
=> get_standard_option
('pve-node'),
3228 vmid
=> get_standard_option
('pve-vmid',
3229 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
3230 skiplock
=> get_standard_option
('skiplock'),
3232 description
=> "The key (qemu monitor encoding).",
3237 returns
=> { type
=> 'null'},
3241 my $rpcenv = PVE
::RPCEnvironment
::get
();
3243 my $authuser = $rpcenv->get_user();
3245 my $node = extract_param
($param, 'node');
3247 my $vmid = extract_param
($param, 'vmid');
3249 my $skiplock = extract_param
($param, 'skiplock');
3250 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3251 if $skiplock && $authuser ne 'root@pam';
3253 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
3258 __PACKAGE__-
>register_method({
3259 name
=> 'vm_feature',
3260 path
=> '{vmid}/feature',
3264 description
=> "Check if feature for virtual machine is available.",
3266 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3269 additionalProperties
=> 0,
3271 node
=> get_standard_option
('pve-node'),
3272 vmid
=> get_standard_option
('pve-vmid'),
3274 description
=> "Feature to check.",
3276 enum
=> [ 'snapshot', 'clone', 'copy' ],
3278 snapname
=> get_standard_option
('pve-snapshot-name', {
3286 hasFeature
=> { type
=> 'boolean' },
3289 items
=> { type
=> 'string' },
3296 my $node = extract_param
($param, 'node');
3298 my $vmid = extract_param
($param, 'vmid');
3300 my $snapname = extract_param
($param, 'snapname');
3302 my $feature = extract_param
($param, 'feature');
3304 my $running = PVE
::QemuServer
::check_running
($vmid);
3306 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3309 my $snap = $conf->{snapshots
}->{$snapname};
3310 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3313 my $storecfg = PVE
::Storage
::config
();
3315 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
3316 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
3319 hasFeature
=> $hasFeature,
3320 nodes
=> [ keys %$nodelist ],
3324 __PACKAGE__-
>register_method({
3326 path
=> '{vmid}/clone',
3330 description
=> "Create a copy of virtual machine/template.",
3332 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
3333 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
3334 "'Datastore.AllocateSpace' on any used storage.",
3337 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
3339 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
3340 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
3345 additionalProperties
=> 0,
3347 node
=> get_standard_option
('pve-node'),
3348 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3349 newid
=> get_standard_option
('pve-vmid', {
3350 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
3351 description
=> 'VMID for the clone.' }),
3354 type
=> 'string', format
=> 'dns-name',
3355 description
=> "Set a name for the new VM.",
3360 description
=> "Description for the new VM.",
3364 type
=> 'string', format
=> 'pve-poolid',
3365 description
=> "Add the new VM to the specified pool.",
3367 snapname
=> get_standard_option
('pve-snapshot-name', {
3370 storage
=> get_standard_option
('pve-storage-id', {
3371 description
=> "Target storage for full clone.",
3375 description
=> "Target format for file storage. Only valid for full clone.",
3378 enum
=> [ 'raw', 'qcow2', 'vmdk'],
3383 description
=> "Create a full copy of all disks. This is always done when " .
3384 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
3386 target
=> get_standard_option
('pve-node', {
3387 description
=> "Target node. Only allowed if the original VM is on shared storage.",
3391 description
=> "Override I/O bandwidth limit (in KiB/s).",
3395 default => 'clone limit from datacenter or storage config',
3405 my $rpcenv = PVE
::RPCEnvironment
::get
();
3406 my $authuser = $rpcenv->get_user();
3408 my $node = extract_param
($param, 'node');
3409 my $vmid = extract_param
($param, 'vmid');
3410 my $newid = extract_param
($param, 'newid');
3411 my $pool = extract_param
($param, 'pool');
3413 my $snapname = extract_param
($param, 'snapname');
3414 my $storage = extract_param
($param, 'storage');
3415 my $format = extract_param
($param, 'format');
3416 my $target = extract_param
($param, 'target');
3418 my $localnode = PVE
::INotify
::nodename
();
3420 if ($target && ($target eq $localnode || $target eq 'localhost')) {
3424 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
3426 my $load_and_check = sub {
3427 $rpcenv->check_pool_exist($pool) if defined($pool);
3428 PVE
::Cluster
::check_node_exists
($target) if $target;
3430 my $storecfg = PVE
::Storage
::config
();
3433 # check if storage is enabled on local node
3434 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
3436 # check if storage is available on target node
3437 PVE
::Storage
::storage_check_enabled
($storecfg, $storage, $target);
3438 # clone only works if target storage is shared
3439 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
3440 die "can't clone to non-shared storage '$storage'\n"
3441 if !$scfg->{shared
};
3445 PVE
::Cluster
::check_cfs_quorum
();
3447 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3448 PVE
::QemuConfig-
>check_lock($conf);
3450 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
3451 die "unexpected state change\n" if $verify_running != $running;
3453 die "snapshot '$snapname' does not exist\n"
3454 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
3456 my $full = $param->{full
} // !PVE
::QemuConfig-
>is_template($conf);
3458 die "parameter 'storage' not allowed for linked clones\n"
3459 if defined($storage) && !$full;
3461 die "parameter 'format' not allowed for linked clones\n"
3462 if defined($format) && !$full;
3464 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
3466 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
3468 die "can't clone VM to node '$target' (VM uses local storage)\n"
3469 if $target && !$sharedvm;
3471 my $conffile = PVE
::QemuConfig-
>config_file($newid);
3472 die "unable to create VM $newid: config file already exists\n"
3475 my $newconf = { lock => 'clone' };
3480 foreach my $opt (keys %$oldconf) {
3481 my $value = $oldconf->{$opt};
3483 # do not copy snapshot related info
3484 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
3485 $opt eq 'vmstate' || $opt eq 'snapstate';
3487 # no need to copy unused images, because VMID(owner) changes anyways
3488 next if $opt =~ m/^unused\d+$/;
3490 die "cannot clone TPM state while VM is running\n"
3491 if $full && $running && !$snapname && $opt eq 'tpmstate0';
3493 # always change MAC! address
3494 if ($opt =~ m/^net(\d+)$/) {
3495 my $net = PVE
::QemuServer
::parse_net
($value);
3496 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
3497 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
3498 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
3499 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
3500 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
3501 die "unable to parse drive options for '$opt'\n" if !$drive;
3502 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
3503 $newconf->{$opt} = $value; # simply copy configuration
3505 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
3506 die "Full clone feature is not supported for drive '$opt'\n"
3507 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
3508 $fullclone->{$opt} = 1;
3510 # not full means clone instead of copy
3511 die "Linked clone feature is not supported for drive '$opt'\n"
3512 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
3514 $drives->{$opt} = $drive;
3515 next if PVE
::QemuServer
::drive_is_cloudinit
($drive);
3516 push @$vollist, $drive->{file
};
3519 # copy everything else
3520 $newconf->{$opt} = $value;
3524 return ($conffile, $newconf, $oldconf, $vollist, $drives, $fullclone);
3528 my ($conffile, $newconf, $oldconf, $vollist, $drives, $fullclone) = $load_and_check->();
3529 my $storecfg = PVE
::Storage
::config
();
3531 # auto generate a new uuid
3532 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
3533 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
3534 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
3535 # auto generate a new vmgenid only if the option was set for template
3536 if ($newconf->{vmgenid
}) {
3537 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
3540 delete $newconf->{template
};
3542 if ($param->{name
}) {
3543 $newconf->{name
} = $param->{name
};
3545 $newconf->{name
} = "Copy-of-VM-" . ($oldconf->{name
} // $vmid);
3548 if ($param->{description
}) {
3549 $newconf->{description
} = $param->{description
};
3552 # create empty/temp config - this fails if VM already exists on other node
3553 # FIXME use PVE::QemuConfig->create_and_lock_config and adapt code
3554 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
3556 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
3558 my $newvollist = [];
3565 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3567 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
3569 my $bwlimit = extract_param
($param, 'bwlimit');
3571 my $total_jobs = scalar(keys %{$drives});
3574 foreach my $opt (sort keys %$drives) {
3575 my $drive = $drives->{$opt};
3576 my $skipcomplete = ($total_jobs != $i); # finish after last drive
3577 my $completion = $skipcomplete ?
'skip' : 'complete';
3579 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
3580 my $storage_list = [ $src_sid ];
3581 push @$storage_list, $storage if defined($storage);
3582 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
3586 running
=> $running,
3589 snapname
=> $snapname,
3595 storage
=> $storage,
3599 $dest_info->{efisize
} = PVE
::QemuServer
::get_efivars_size
($oldconf)
3600 if $opt eq 'efidisk0';
3602 my $newdrive = PVE
::QemuServer
::clone_disk
(
3614 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($newdrive);
3616 PVE
::QemuConfig-
>write_config($newid, $newconf);
3620 delete $newconf->{lock};
3622 # do not write pending changes
3623 if (my @changes = keys %{$newconf->{pending
}}) {
3624 my $pending = join(',', @changes);
3625 warn "found pending changes for '$pending', discarding for clone\n";
3626 delete $newconf->{pending
};
3629 PVE
::QemuConfig-
>write_config($newid, $newconf);
3632 # always deactivate volumes - avoid lvm LVs to be active on several nodes
3633 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
3634 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
3636 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
3637 die "Failed to move config to node '$target' - rename failed: $!\n"
3638 if !rename($conffile, $newconffile);
3641 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
3644 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
3645 sleep 1; # some storage like rbd need to wait before release volume - really?
3647 foreach my $volid (@$newvollist) {
3648 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3652 PVE
::Firewall
::remove_vmfw_conf
($newid);
3654 unlink $conffile; # avoid races -> last thing before die
3656 die "clone failed: $err";
3662 # Aquire exclusive lock lock for $newid
3663 my $lock_target_vm = sub {
3664 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
3667 my $lock_source_vm = sub {
3668 # exclusive lock if VM is running - else shared lock is enough;
3670 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $lock_target_vm);
3672 return PVE
::QemuConfig-
>lock_config_shared($vmid, 1, $lock_target_vm);
3676 $load_and_check->(); # early checks before forking/locking
3678 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $lock_source_vm);
3681 __PACKAGE__-
>register_method({
3682 name
=> 'move_vm_disk',
3683 path
=> '{vmid}/move_disk',
3687 description
=> "Move volume to different storage or to a different VM.",
3689 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
3690 "and 'Datastore.AllocateSpace' permissions on the storage. To move ".
3691 "a disk to another VM, you need the permissions on the target VM as well.",
3692 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3695 additionalProperties
=> 0,
3697 node
=> get_standard_option
('pve-node'),
3698 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3699 'target-vmid' => get_standard_option
('pve-vmid', {
3700 completion
=> \
&PVE
::QemuServer
::complete_vmid
,
3705 description
=> "The disk you want to move.",
3706 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names_with_unused
()],
3708 storage
=> get_standard_option
('pve-storage-id', {
3709 description
=> "Target storage.",
3710 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3715 description
=> "Target Format.",
3716 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
3721 description
=> "Delete the original disk after successful copy. By default the"
3722 ." original disk is kept as unused disk.",
3728 description
=> 'Prevent changes if current configuration file has different SHA1"
3729 ." digest. This can be used to prevent concurrent modifications.',
3734 description
=> "Override I/O bandwidth limit (in KiB/s).",
3738 default => 'move limit from datacenter or storage config',
3742 description
=> "The config key the disk will be moved to on the target VM"
3743 ." (for example, ide0 or scsi1). Default is the source disk key.",
3744 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names_with_unused
()],
3747 'target-digest' => {
3749 description
=> 'Prevent changes if the current config file of the target VM has a"
3750 ." different SHA1 digest. This can be used to detect concurrent modifications.',
3758 description
=> "the task ID.",
3763 my $rpcenv = PVE
::RPCEnvironment
::get
();
3764 my $authuser = $rpcenv->get_user();
3766 my $node = extract_param
($param, 'node');
3767 my $vmid = extract_param
($param, 'vmid');
3768 my $target_vmid = extract_param
($param, 'target-vmid');
3769 my $digest = extract_param
($param, 'digest');
3770 my $target_digest = extract_param
($param, 'target-digest');
3771 my $disk = extract_param
($param, 'disk');
3772 my $target_disk = extract_param
($param, 'target-disk') // $disk;
3773 my $storeid = extract_param
($param, 'storage');
3774 my $format = extract_param
($param, 'format');
3776 my $storecfg = PVE
::Storage
::config
();
3778 my $load_and_check_move = sub {
3779 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3780 PVE
::QemuConfig-
>check_lock($conf);
3782 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
3784 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3786 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3788 die "disk '$disk' has no associated volume\n" if !$drive->{file
};
3789 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3791 my $old_volid = $drive->{file
};
3793 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3794 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3798 die "you can't move to the same storage with same format\n"
3799 if $oldstoreid eq $storeid && (!$format || !$oldfmt || $oldfmt eq $format);
3801 # this only checks snapshots because $disk is passed!
3802 my $snapshotted = PVE
::QemuServer
::Drive
::is_volume_in_use
(
3808 die "you can't move a disk with snapshots and delete the source\n"
3809 if $snapshotted && $param->{delete};
3811 return ($conf, $drive, $oldstoreid, $snapshotted);
3814 my $move_updatefn = sub {
3815 my ($conf, $drive, $oldstoreid, $snapshotted) = $load_and_check_move->();
3816 my $old_volid = $drive->{file
};
3818 PVE
::Cluster
::log_msg
(
3821 "move disk VM $vmid: move --disk $disk --storage $storeid"
3824 my $running = PVE
::QemuServer
::check_running
($vmid);
3826 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3828 my $newvollist = [];
3834 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3836 warn "moving disk with snapshots, snapshots will not be moved!\n"
3839 my $bwlimit = extract_param
($param, 'bwlimit');
3840 my $movelimit = PVE
::Storage
::get_bandwidth_limit
(
3842 [$oldstoreid, $storeid],
3848 running
=> $running,
3857 storage
=> $storeid,
3861 $dest_info->{efisize
} = PVE
::QemuServer
::get_efivars_size
($conf)
3862 if $disk eq 'efidisk0';
3864 my $newdrive = PVE
::QemuServer
::clone_disk
(
3875 $conf->{$disk} = PVE
::QemuServer
::print_drive
($newdrive);
3877 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3879 # convert moved disk to base if part of template
3880 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3881 if PVE
::QemuConfig-
>is_template($conf);
3883 PVE
::QemuConfig-
>write_config($vmid, $conf);
3885 my $do_trim = PVE
::QemuServer
::get_qga_key
($conf, 'fstrim_cloned_disks');
3886 if ($running && $do_trim && PVE
::QemuServer
::qga_check_running
($vmid)) {
3887 eval { mon_cmd
($vmid, "guest-fstrim") };
3891 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3892 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3898 foreach my $volid (@$newvollist) {
3899 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid) };
3902 die "storage migration failed: $err";
3905 if ($param->{delete}) {
3907 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3908 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3914 my $load_and_check_reassign_configs = sub {
3915 my $vmlist = PVE
::Cluster
::get_vmlist
()->{ids
};
3917 die "could not find VM ${vmid}\n" if !exists($vmlist->{$vmid});
3918 die "could not find target VM ${target_vmid}\n" if !exists($vmlist->{$target_vmid});
3920 my $source_node = $vmlist->{$vmid}->{node
};
3921 my $target_node = $vmlist->{$target_vmid}->{node
};
3923 die "Both VMs need to be on the same node ($source_node != $target_node)\n"
3924 if $source_node ne $target_node;
3926 my $source_conf = PVE
::QemuConfig-
>load_config($vmid);
3927 PVE
::QemuConfig-
>check_lock($source_conf);
3928 my $target_conf = PVE
::QemuConfig-
>load_config($target_vmid);
3929 PVE
::QemuConfig-
>check_lock($target_conf);
3931 die "Can't move disks from or to template VMs\n"
3932 if ($source_conf->{template
} || $target_conf->{template
});
3935 eval { PVE
::Tools
::assert_if_modified
($digest, $source_conf->{digest
}) };
3936 die "VM ${vmid}: $@" if $@;
3939 if ($target_digest) {
3940 eval { PVE
::Tools
::assert_if_modified
($target_digest, $target_conf->{digest
}) };
3941 die "VM ${target_vmid}: $@" if $@;
3944 die "Disk '${disk}' for VM '$vmid' does not exist\n" if !defined($source_conf->{$disk});
3946 die "Target disk key '${target_disk}' is already in use for VM '$target_vmid'\n"
3947 if $target_conf->{$target_disk};
3949 my $drive = PVE
::QemuServer
::parse_drive
(
3951 $source_conf->{$disk},
3953 die "failed to parse source disk - $@\n" if !$drive;
3955 my $source_volid = $drive->{file
};
3957 die "disk '${disk}' has no associated volume\n" if !$source_volid;
3958 die "CD drive contents can't be moved to another VM\n"
3959 if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3961 my $storeid = PVE
::Storage
::parse_volume_id
($source_volid, 1);
3962 die "Volume '$source_volid' not managed by PVE\n" if !defined($storeid);
3964 die "Can't move disk used by a snapshot to another VM\n"
3965 if PVE
::QemuServer
::Drive
::is_volume_in_use
($storecfg, $source_conf, $disk, $source_volid);
3966 die "Storage does not support moving of this disk to another VM\n"
3967 if (!PVE
::Storage
::volume_has_feature
($storecfg, 'rename', $source_volid));
3968 die "Cannot move disk to another VM while the source VM is running - detach first\n"
3969 if PVE
::QemuServer
::check_running
($vmid) && $disk !~ m/^unused\d+$/;
3971 # now re-parse using target disk slot format
3972 if ($target_disk =~ /^unused\d+$/) {
3973 $drive = PVE
::QemuServer
::parse_drive
(
3978 $drive = PVE
::QemuServer
::parse_drive
(
3980 $source_conf->{$disk},
3983 die "failed to parse source disk for target disk format - $@\n" if !$drive;
3985 my $repl_conf = PVE
::ReplicationConfig-
>new();
3986 if ($repl_conf->check_for_existing_jobs($target_vmid, 1)) {
3987 my $format = (PVE
::Storage
::parse_volname
($storecfg, $source_volid))[6];
3988 die "Cannot move disk to a replicated VM. Storage does not support replication!\n"
3989 if !PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
3992 return ($source_conf, $target_conf, $drive);
3997 print STDERR
"$msg\n";
4000 my $disk_reassignfn = sub {
4001 return PVE
::QemuConfig-
>lock_config($vmid, sub {
4002 return PVE
::QemuConfig-
>lock_config($target_vmid, sub {
4003 my ($source_conf, $target_conf, $drive) = &$load_and_check_reassign_configs();
4005 my $source_volid = $drive->{file
};
4007 print "moving disk '$disk' from VM '$vmid' to '$target_vmid'\n";
4008 my ($storeid, $source_volname) = PVE
::Storage
::parse_volume_id
($source_volid);
4010 my $fmt = (PVE
::Storage
::parse_volname
($storecfg, $source_volid))[6];
4012 my $new_volid = PVE
::Storage
::rename_volume
(
4018 $drive->{file
} = $new_volid;
4020 my $boot_order = PVE
::QemuServer
::device_bootorder
($source_conf);
4021 if (defined(delete $boot_order->{$disk})) {
4022 print "removing disk '$disk' from boot order config\n";
4023 my $boot_devs = [ sort { $boot_order->{$a} <=> $boot_order->{$b} } keys %$boot_order ];
4024 $source_conf->{boot
} = PVE
::QemuServer
::print_bootorder
($boot_devs);
4027 delete $source_conf->{$disk};
4028 print "removing disk '${disk}' from VM '${vmid}' config\n";
4029 PVE
::QemuConfig-
>write_config($vmid, $source_conf);
4031 my $drive_string = PVE
::QemuServer
::print_drive
($drive);
4033 if ($target_disk =~ /^unused\d+$/) {
4034 $target_conf->{$target_disk} = $drive_string;
4035 PVE
::QemuConfig-
>write_config($target_vmid, $target_conf);
4040 vmid
=> $target_vmid,
4041 digest
=> $target_digest,
4042 $target_disk => $drive_string,
4048 # remove possible replication snapshots
4049 if (PVE
::Storage
::volume_has_feature
(
4055 PVE
::Replication
::prepare
(
4065 print "Failed to remove replication snapshots on moved disk " .
4066 "'$target_disk'. Manual cleanup could be necessary.\n";
4073 if ($target_vmid && $storeid) {
4074 my $msg = "either set 'storage' or 'target-vmid', but not both";
4075 raise_param_exc
({ 'target-vmid' => $msg, 'storage' => $msg });
4076 } elsif ($target_vmid) {
4077 $rpcenv->check_vm_perm($authuser, $target_vmid, undef, ['VM.Config.Disk'])
4078 if $authuser ne 'root@pam';
4080 raise_param_exc
({ 'target-vmid' => "must be different than source VMID to reassign disk" })
4081 if $vmid eq $target_vmid;
4083 my (undef, undef, $drive) = &$load_and_check_reassign_configs();
4084 my $storage = PVE
::Storage
::parse_volume_id
($drive->{file
});
4085 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace']);
4087 return $rpcenv->fork_worker(
4089 "${vmid}-${disk}>${target_vmid}-${target_disk}",
4093 } elsif ($storeid) {
4094 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
4096 die "cannot move disk '$disk', only configured disks can be moved to another storage\n"
4097 if $disk =~ m/^unused\d+$/;
4099 $load_and_check_move->(); # early checks before forking/locking
4102 PVE
::QemuConfig-
>lock_config($vmid, $move_updatefn);
4105 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
4107 my $msg = "both 'storage' and 'target-vmid' missing, either needs to be set";
4108 raise_param_exc
({ 'target-vmid' => $msg, 'storage' => $msg });
4112 my $check_vm_disks_local = sub {
4113 my ($storecfg, $vmconf, $vmid) = @_;
4115 my $local_disks = {};
4117 # add some more information to the disks e.g. cdrom
4118 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
4119 my ($volid, $attr) = @_;
4121 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
4123 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
4124 return if $scfg->{shared
};
4126 # The shared attr here is just a special case where the vdisk
4127 # is marked as shared manually
4128 return if $attr->{shared
};
4129 return if $attr->{cdrom
} and $volid eq "none";
4131 if (exists $local_disks->{$volid}) {
4132 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
4134 $local_disks->{$volid} = $attr;
4135 # ensure volid is present in case it's needed
4136 $local_disks->{$volid}->{volid
} = $volid;
4140 return $local_disks;
4143 __PACKAGE__-
>register_method({
4144 name
=> 'migrate_vm_precondition',
4145 path
=> '{vmid}/migrate',
4149 description
=> "Get preconditions for migration.",
4151 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
4154 additionalProperties
=> 0,
4156 node
=> get_standard_option
('pve-node'),
4157 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4158 target
=> get_standard_option
('pve-node', {
4159 description
=> "Target node.",
4160 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
4168 running
=> { type
=> 'boolean' },
4172 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
4174 not_allowed_nodes
=> {
4177 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
4181 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
4183 local_resources
=> {
4185 description
=> "List local resources e.g. pci, usb"
4192 my $rpcenv = PVE
::RPCEnvironment
::get
();
4194 my $authuser = $rpcenv->get_user();
4196 PVE
::Cluster
::check_cfs_quorum
();
4200 my $vmid = extract_param
($param, 'vmid');
4201 my $target = extract_param
($param, 'target');
4202 my $localnode = PVE
::INotify
::nodename
();
4206 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
4207 my $storecfg = PVE
::Storage
::config
();
4210 # try to detect errors early
4211 PVE
::QemuConfig-
>check_lock($vmconf);
4213 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
4215 # if vm is not running, return target nodes where local storage is available
4216 # for offline migration
4217 if (!$res->{running
}) {
4218 $res->{allowed_nodes
} = [];
4219 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
4220 delete $checked_nodes->{$localnode};
4222 foreach my $node (keys %$checked_nodes) {
4223 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
4224 push @{$res->{allowed_nodes
}}, $node;
4228 $res->{not_allowed_nodes
} = $checked_nodes;
4232 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
4233 $res->{local_disks
} = [ values %$local_disks ];;
4235 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
4237 $res->{local_resources
} = $local_resources;
4244 __PACKAGE__-
>register_method({
4245 name
=> 'migrate_vm',
4246 path
=> '{vmid}/migrate',
4250 description
=> "Migrate virtual machine. Creates a new migration task.",
4252 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
4255 additionalProperties
=> 0,
4257 node
=> get_standard_option
('pve-node'),
4258 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4259 target
=> get_standard_option
('pve-node', {
4260 description
=> "Target node.",
4261 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
4265 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
4270 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
4275 enum
=> ['secure', 'insecure'],
4276 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
4279 migration_network
=> {
4280 type
=> 'string', format
=> 'CIDR',
4281 description
=> "CIDR of the (sub) network that is used for migration.",
4284 "with-local-disks" => {
4286 description
=> "Enable live storage migration for local disk",
4289 targetstorage
=> get_standard_option
('pve-targetstorage', {
4290 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
4293 description
=> "Override I/O bandwidth limit (in KiB/s).",
4297 default => 'migrate limit from datacenter or storage config',
4303 description
=> "the task ID.",
4308 my $rpcenv = PVE
::RPCEnvironment
::get
();
4309 my $authuser = $rpcenv->get_user();
4311 my $target = extract_param
($param, 'target');
4313 my $localnode = PVE
::INotify
::nodename
();
4314 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
4316 PVE
::Cluster
::check_cfs_quorum
();
4318 PVE
::Cluster
::check_node_exists
($target);
4320 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
4322 my $vmid = extract_param
($param, 'vmid');
4324 raise_param_exc
({ force
=> "Only root may use this option." })
4325 if $param->{force
} && $authuser ne 'root@pam';
4327 raise_param_exc
({ migration_type
=> "Only root may use this option." })
4328 if $param->{migration_type
} && $authuser ne 'root@pam';
4330 # allow root only until better network permissions are available
4331 raise_param_exc
({ migration_network
=> "Only root may use this option." })
4332 if $param->{migration_network
} && $authuser ne 'root@pam';
4335 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4337 # try to detect errors early
4339 PVE
::QemuConfig-
>check_lock($conf);
4341 if (PVE
::QemuServer
::check_running
($vmid)) {
4342 die "can't migrate running VM without --online\n" if !$param->{online
};
4344 my $repl_conf = PVE
::ReplicationConfig-
>new();
4345 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
4346 my $is_replicated_to_target = defined($repl_conf->find_local_replication_job($vmid, $target));
4347 if (!$param->{force
} && $is_replicated && !$is_replicated_to_target) {
4348 die "Cannot live-migrate replicated VM to node '$target' - not a replication " .
4349 "target. Use 'force' to override.\n";
4352 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
4353 $param->{online
} = 0;
4356 my $storecfg = PVE
::Storage
::config
();
4357 if (my $targetstorage = $param->{targetstorage
}) {
4358 my $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
4359 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
4362 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk'])
4363 if !defined($storagemap->{identity
});
4365 foreach my $target_sid (values %{$storagemap->{entries
}}) {
4366 $check_storage_access_migrate->($rpcenv, $authuser, $storecfg, $target_sid, $target);
4369 $check_storage_access_migrate->($rpcenv, $authuser, $storecfg, $storagemap->{default}, $target)
4370 if $storagemap->{default};
4372 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target)
4373 if $storagemap->{identity
};
4375 $param->{storagemap
} = $storagemap;
4377 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
4380 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
4385 print "Requesting HA migration for VM $vmid to node $target\n";
4387 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
4388 PVE
::Tools
::run_command
($cmd);
4392 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
4397 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
4401 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
4404 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
4409 __PACKAGE__-
>register_method({
4411 path
=> '{vmid}/monitor',
4415 description
=> "Execute Qemu monitor commands.",
4417 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
4418 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
4421 additionalProperties
=> 0,
4423 node
=> get_standard_option
('pve-node'),
4424 vmid
=> get_standard_option
('pve-vmid'),
4427 description
=> "The monitor command.",
4431 returns
=> { type
=> 'string'},
4435 my $rpcenv = PVE
::RPCEnvironment
::get
();
4436 my $authuser = $rpcenv->get_user();
4439 my $command = shift;
4440 return $command =~ m/^\s*info(\s+|$)/
4441 || $command =~ m/^\s*help\s*$/;
4444 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
4445 if !&$is_ro($param->{command
});
4447 my $vmid = $param->{vmid
};
4449 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
4453 $res = PVE
::QemuServer
::Monitor
::hmp_cmd
($vmid, $param->{command
});
4455 $res = "ERROR: $@" if $@;
4460 __PACKAGE__-
>register_method({
4461 name
=> 'resize_vm',
4462 path
=> '{vmid}/resize',
4466 description
=> "Extend volume size.",
4468 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
4471 additionalProperties
=> 0,
4473 node
=> get_standard_option
('pve-node'),
4474 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4475 skiplock
=> get_standard_option
('skiplock'),
4478 description
=> "The disk you want to resize.",
4479 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
4483 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
4484 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.",
4488 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
4494 returns
=> { type
=> 'null'},
4498 my $rpcenv = PVE
::RPCEnvironment
::get
();
4500 my $authuser = $rpcenv->get_user();
4502 my $node = extract_param
($param, 'node');
4504 my $vmid = extract_param
($param, 'vmid');
4506 my $digest = extract_param
($param, 'digest');
4508 my $disk = extract_param
($param, 'disk');
4510 my $sizestr = extract_param
($param, 'size');
4512 my $skiplock = extract_param
($param, 'skiplock');
4513 raise_param_exc
({ skiplock
=> "Only root may use this option." })
4514 if $skiplock && $authuser ne 'root@pam';
4516 my $storecfg = PVE
::Storage
::config
();
4518 my $updatefn = sub {
4520 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4522 die "checksum missmatch (file change by other user?)\n"
4523 if $digest && $digest ne $conf->{digest
};
4524 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
4526 die "disk '$disk' does not exist\n" if !$conf->{$disk};
4528 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
4530 my (undef, undef, undef, undef, undef, undef, $format) =
4531 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
4533 die "can't resize volume: $disk if snapshot exists\n"
4534 if %{$conf->{snapshots
}} && $format eq 'qcow2';
4536 my $volid = $drive->{file
};
4538 die "disk '$disk' has no associated volume\n" if !$volid;
4540 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
4542 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
4544 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
4546 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
4547 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
4549 die "Could not determine current size of volume '$volid'\n" if !defined($size);
4551 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
4552 my ($ext, $newsize, $unit) = ($1, $2, $4);
4555 $newsize = $newsize * 1024;
4556 } elsif ($unit eq 'M') {
4557 $newsize = $newsize * 1024 * 1024;
4558 } elsif ($unit eq 'G') {
4559 $newsize = $newsize * 1024 * 1024 * 1024;
4560 } elsif ($unit eq 'T') {
4561 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
4564 $newsize += $size if $ext;
4565 $newsize = int($newsize);
4567 die "shrinking disks is not supported\n" if $newsize < $size;
4569 return if $size == $newsize;
4571 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
4573 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
4575 $drive->{size
} = $newsize;
4576 $conf->{$disk} = PVE
::QemuServer
::print_drive
($drive);
4578 PVE
::QemuConfig-
>write_config($vmid, $conf);
4581 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4585 __PACKAGE__-
>register_method({
4586 name
=> 'snapshot_list',
4587 path
=> '{vmid}/snapshot',
4589 description
=> "List all snapshots.",
4591 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4594 protected
=> 1, # qemu pid files are only readable by root
4596 additionalProperties
=> 0,
4598 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4599 node
=> get_standard_option
('pve-node'),
4608 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
4612 description
=> "Snapshot includes RAM.",
4617 description
=> "Snapshot description.",
4621 description
=> "Snapshot creation time",
4623 renderer
=> 'timestamp',
4627 description
=> "Parent snapshot identifier.",
4633 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
4638 my $vmid = $param->{vmid
};
4640 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4641 my $snaphash = $conf->{snapshots
} || {};
4645 foreach my $name (keys %$snaphash) {
4646 my $d = $snaphash->{$name};
4649 snaptime
=> $d->{snaptime
} || 0,
4650 vmstate
=> $d->{vmstate
} ?
1 : 0,
4651 description
=> $d->{description
} || '',
4653 $item->{parent
} = $d->{parent
} if $d->{parent
};
4654 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
4658 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
4661 digest
=> $conf->{digest
},
4662 running
=> $running,
4663 description
=> "You are here!",
4665 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
4667 push @$res, $current;
4672 __PACKAGE__-
>register_method({
4674 path
=> '{vmid}/snapshot',
4678 description
=> "Snapshot a VM.",
4680 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4683 additionalProperties
=> 0,
4685 node
=> get_standard_option
('pve-node'),
4686 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4687 snapname
=> get_standard_option
('pve-snapshot-name'),
4691 description
=> "Save the vmstate",
4696 description
=> "A textual description or comment.",
4702 description
=> "the task ID.",
4707 my $rpcenv = PVE
::RPCEnvironment
::get
();
4709 my $authuser = $rpcenv->get_user();
4711 my $node = extract_param
($param, 'node');
4713 my $vmid = extract_param
($param, 'vmid');
4715 my $snapname = extract_param
($param, 'snapname');
4717 die "unable to use snapshot name 'current' (reserved name)\n"
4718 if $snapname eq 'current';
4720 die "unable to use snapshot name 'pending' (reserved name)\n"
4721 if lc($snapname) eq 'pending';
4724 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
4725 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
4726 $param->{description
});
4729 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
4732 __PACKAGE__-
>register_method({
4733 name
=> 'snapshot_cmd_idx',
4734 path
=> '{vmid}/snapshot/{snapname}',
4741 additionalProperties
=> 0,
4743 vmid
=> get_standard_option
('pve-vmid'),
4744 node
=> get_standard_option
('pve-node'),
4745 snapname
=> get_standard_option
('pve-snapshot-name'),
4754 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
4761 push @$res, { cmd
=> 'rollback' };
4762 push @$res, { cmd
=> 'config' };
4767 __PACKAGE__-
>register_method({
4768 name
=> 'update_snapshot_config',
4769 path
=> '{vmid}/snapshot/{snapname}/config',
4773 description
=> "Update snapshot metadata.",
4775 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4778 additionalProperties
=> 0,
4780 node
=> get_standard_option
('pve-node'),
4781 vmid
=> get_standard_option
('pve-vmid'),
4782 snapname
=> get_standard_option
('pve-snapshot-name'),
4786 description
=> "A textual description or comment.",
4790 returns
=> { type
=> 'null' },
4794 my $rpcenv = PVE
::RPCEnvironment
::get
();
4796 my $authuser = $rpcenv->get_user();
4798 my $vmid = extract_param
($param, 'vmid');
4800 my $snapname = extract_param
($param, 'snapname');
4802 return if !defined($param->{description
});
4804 my $updatefn = sub {
4806 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4808 PVE
::QemuConfig-
>check_lock($conf);
4810 my $snap = $conf->{snapshots
}->{$snapname};
4812 die "snapshot '$snapname' does not exist\n" if !defined($snap);
4814 $snap->{description
} = $param->{description
} if defined($param->{description
});
4816 PVE
::QemuConfig-
>write_config($vmid, $conf);
4819 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4824 __PACKAGE__-
>register_method({
4825 name
=> 'get_snapshot_config',
4826 path
=> '{vmid}/snapshot/{snapname}/config',
4829 description
=> "Get snapshot configuration",
4831 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback', 'VM.Audit' ], any
=> 1],
4834 additionalProperties
=> 0,
4836 node
=> get_standard_option
('pve-node'),
4837 vmid
=> get_standard_option
('pve-vmid'),
4838 snapname
=> get_standard_option
('pve-snapshot-name'),
4841 returns
=> { type
=> "object" },
4845 my $rpcenv = PVE
::RPCEnvironment
::get
();
4847 my $authuser = $rpcenv->get_user();
4849 my $vmid = extract_param
($param, 'vmid');
4851 my $snapname = extract_param
($param, 'snapname');
4853 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4855 my $snap = $conf->{snapshots
}->{$snapname};
4857 die "snapshot '$snapname' does not exist\n" if !defined($snap);
4862 __PACKAGE__-
>register_method({
4864 path
=> '{vmid}/snapshot/{snapname}/rollback',
4868 description
=> "Rollback VM state to specified snapshot.",
4870 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
4873 additionalProperties
=> 0,
4875 node
=> get_standard_option
('pve-node'),
4876 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4877 snapname
=> get_standard_option
('pve-snapshot-name'),
4882 description
=> "the task ID.",
4887 my $rpcenv = PVE
::RPCEnvironment
::get
();
4889 my $authuser = $rpcenv->get_user();
4891 my $node = extract_param
($param, 'node');
4893 my $vmid = extract_param
($param, 'vmid');
4895 my $snapname = extract_param
($param, 'snapname');
4898 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
4899 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
4903 # hold migration lock, this makes sure that nobody create replication snapshots
4904 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
4907 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
4910 __PACKAGE__-
>register_method({
4911 name
=> 'delsnapshot',
4912 path
=> '{vmid}/snapshot/{snapname}',
4916 description
=> "Delete a VM snapshot.",
4918 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4921 additionalProperties
=> 0,
4923 node
=> get_standard_option
('pve-node'),
4924 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4925 snapname
=> get_standard_option
('pve-snapshot-name'),
4929 description
=> "For removal from config file, even if removing disk snapshots fails.",
4935 description
=> "the task ID.",
4940 my $rpcenv = PVE
::RPCEnvironment
::get
();
4942 my $authuser = $rpcenv->get_user();
4944 my $node = extract_param
($param, 'node');
4946 my $vmid = extract_param
($param, 'vmid');
4948 my $snapname = extract_param
($param, 'snapname');
4951 my $do_delete = sub {
4953 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
4954 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
4958 if ($param->{force
}) {
4961 eval { PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $do_delete); };
4963 die $err if $lock_obtained;
4964 die "Failed to obtain guest migration lock - replication running?\n";
4969 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
4972 __PACKAGE__-
>register_method({
4974 path
=> '{vmid}/template',
4978 description
=> "Create a Template.",
4980 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
4981 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
4984 additionalProperties
=> 0,
4986 node
=> get_standard_option
('pve-node'),
4987 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
4991 description
=> "If you want to convert only 1 disk to base image.",
4992 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
4999 description
=> "the task ID.",
5004 my $rpcenv = PVE
::RPCEnvironment
::get
();
5006 my $authuser = $rpcenv->get_user();
5008 my $node = extract_param
($param, 'node');
5010 my $vmid = extract_param
($param, 'vmid');
5012 my $disk = extract_param
($param, 'disk');
5014 my $load_and_check = sub {
5015 my $conf = PVE
::QemuConfig-
>load_config($vmid);
5017 PVE
::QemuConfig-
>check_lock($conf);
5019 die "unable to create template, because VM contains snapshots\n"
5020 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
5022 die "you can't convert a template to a template\n"
5023 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
5025 die "you can't convert a VM to template if VM is running\n"
5026 if PVE
::QemuServer
::check_running
($vmid);
5031 $load_and_check->();
5034 PVE
::QemuConfig-
>lock_config($vmid, sub {
5035 my $conf = $load_and_check->();
5037 $conf->{template
} = 1;
5038 PVE
::QemuConfig-
>write_config($vmid, $conf);
5040 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
5044 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
5047 __PACKAGE__-
>register_method({
5048 name
=> 'cloudinit_generated_config_dump',
5049 path
=> '{vmid}/cloudinit/dump',
5052 description
=> "Get automatically generated cloudinit config.",
5054 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
5057 additionalProperties
=> 0,
5059 node
=> get_standard_option
('pve-node'),
5060 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
5062 description
=> 'Config type.',
5064 enum
=> ['user', 'network', 'meta'],
5074 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
5076 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});