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
::CPUConfig
;
25 use PVE
::QemuServer
::Drive
;
26 use PVE
::QemuServer
::ImportDisk
;
27 use PVE
::QemuServer
::Monitor
qw(mon_cmd);
28 use PVE
::QemuServer
::Machine
;
30 use PVE
::RPCEnvironment
;
31 use PVE
::AccessControl
;
35 use PVE
::API2
::Firewall
::VM
;
36 use PVE
::API2
::Qemu
::Agent
;
37 use PVE
::VZDump
::Plugin
;
38 use PVE
::DataCenterConfig
;
43 if (!$ENV{PVE_GENERATING_DOCS
}) {
44 require PVE
::HA
::Env
::PVE2
;
45 import PVE
::HA
::Env
::PVE2
;
46 require PVE
::HA
::Config
;
47 import PVE
::HA
::Config
;
51 use Data
::Dumper
; # fixme: remove
53 use base
qw(PVE::RESTHandler);
55 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.";
57 my $resolve_cdrom_alias = sub {
60 if (my $value = $param->{cdrom
}) {
61 $value .= ",media=cdrom" if $value !~ m/media=/;
62 $param->{ide2
} = $value;
63 delete $param->{cdrom
};
67 # Used in import-enabled API endpoints. Parses drives using the extended '_with_alloc' schema.
68 my $foreach_volume_with_alloc = sub {
69 my ($param, $func) = @_;
71 for my $opt (sort keys $param->%*) {
72 next if !PVE
::QemuServer
::is_valid_drivename
($opt);
74 my $drive = PVE
::QemuServer
::Drive
::parse_drive
($opt, $param->{$opt}, 1);
77 $func->($opt, $drive);
81 my $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
83 my $check_drive_param = sub {
84 my ($param, $storecfg, $extra_checks) = @_;
86 for my $opt (sort keys $param->%*) {
87 next if !PVE
::QemuServer
::is_valid_drivename
($opt);
89 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt}, 1);
90 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
92 if ($drive->{'import-from'}) {
93 if ($drive->{file
} !~ $NEW_DISK_RE || $3 != 0) {
95 $opt => "'import-from' requires special syntax - ".
96 "use <storage ID>:0,import-from=<source>",
100 if ($opt eq 'efidisk0') {
101 for my $required (qw(efitype pre-enrolled-keys)) {
102 if (!defined($drive->{$required})) {
104 $opt => "need to specify '$required' when using 'import-from'",
108 } elsif ($opt eq 'tpmstate0') {
109 raise_param_exc
({ $opt => "need to specify 'version' when using 'import-from'" })
110 if !defined($drive->{version
});
114 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
116 $extra_checks->($drive) if $extra_checks;
118 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive, 1);
122 my $check_storage_access = sub {
123 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
125 $foreach_volume_with_alloc->($settings, sub {
126 my ($ds, $drive) = @_;
128 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
130 my $volid = $drive->{file
};
131 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
133 if (!$volid || ($volid eq 'none' || $volid eq 'cloudinit' || (defined($volname) && $volname eq 'cloudinit'))) {
135 } elsif ($isCDROM && ($volid eq 'cdrom')) {
136 $rpcenv->check($authuser, "/", ['Sys.Console']);
137 } elsif (!$isCDROM && ($volid =~ $NEW_DISK_RE)) {
138 my ($storeid, $size) = ($2 || $default_storage, $3);
139 die "no storage ID specified (and no default storage)\n" if !$storeid;
140 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
141 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
142 raise_param_exc
({ storage
=> "storage '$storeid' does not support vm images"})
143 if !$scfg->{content
}->{images
};
145 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
147 my ($vtype) = PVE
::Storage
::parse_volname
($storecfg, $volid);
148 raise_param_exc
({ $ds => "content type needs to be 'images' or 'iso'" })
149 if $vtype ne 'images' && $vtype ne 'iso';
153 if (my $src_image = $drive->{'import-from'}) {
155 if (PVE
::Storage
::parse_volume_id
($src_image, 1)) { # PVE-managed volume
156 (my $vtype, undef, $src_vmid) = PVE
::Storage
::parse_volname
($storecfg, $src_image);
157 raise_param_exc
({ $ds => "$src_image has wrong type '$vtype' - not an image" })
158 if $vtype ne 'images';
161 if ($src_vmid) { # might be actively used by VM and will be copied via clone_disk()
162 $rpcenv->check($authuser, "/vms/${src_vmid}", ['VM.Clone']);
164 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $src_image);
169 $rpcenv->check($authuser, "/storage/$settings->{vmstatestorage}", ['Datastore.AllocateSpace'])
170 if defined($settings->{vmstatestorage
});
173 my $check_storage_access_clone = sub {
174 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
178 PVE
::QemuConfig-
>foreach_volume($conf, sub {
179 my ($ds, $drive) = @_;
181 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
183 my $volid = $drive->{file
};
185 return if !$volid || $volid eq 'none';
188 if ($volid eq 'cdrom') {
189 $rpcenv->check($authuser, "/", ['Sys.Console']);
191 # we simply allow access
192 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
193 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
194 $sharedvm = 0 if !$scfg->{shared
};
198 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
199 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
200 $sharedvm = 0 if !$scfg->{shared
};
202 $sid = $storage if $storage;
203 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
207 $rpcenv->check($authuser, "/storage/$conf->{vmstatestorage}", ['Datastore.AllocateSpace'])
208 if defined($conf->{vmstatestorage
});
213 my $check_storage_access_migrate = sub {
214 my ($rpcenv, $authuser, $storecfg, $storage, $node) = @_;
216 PVE
::Storage
::storage_check_enabled
($storecfg, $storage, $node);
218 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace']);
220 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
221 die "storage '$storage' does not support vm images\n"
222 if !$scfg->{content
}->{images
};
225 my $import_from_volid = sub {
226 my ($storecfg, $src_volid, $dest_info, $vollist) = @_;
228 die "could not get size of $src_volid\n"
229 if !PVE
::Storage
::volume_size_info
($storecfg, $src_volid, 10);
231 die "cannot import from cloudinit disk\n"
232 if PVE
::QemuServer
::Drive
::drive_is_cloudinit
({ file
=> $src_volid });
234 my $src_vmid = (PVE
::Storage
::parse_volname
($storecfg, $src_volid))[2];
236 my $src_vm_state = sub {
237 my $exists = $src_vmid && PVE
::Cluster
::get_vmlist
()->{ids
}->{$src_vmid} ?
1 : 0;
241 eval { PVE
::QemuConfig
::assert_config_exists_on_node
($src_vmid); };
242 die "owner VM $src_vmid not on local node\n" if $@;
243 $runs = PVE
::QemuServer
::Helpers
::vm_running_locally
($src_vmid) || 0;
246 return ($exists, $runs);
249 my ($src_vm_exists, $running) = $src_vm_state->();
251 die "cannot import from '$src_volid' - full clone feature is not supported\n"
252 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $src_volid, undef, $running);
255 my ($src_vm_exists_now, $running_now) = $src_vm_state->();
257 die "owner VM $src_vmid changed state unexpectedly\n"
258 if $src_vm_exists_now != $src_vm_exists || $running_now != $running;
260 my $src_conf = $src_vm_exists_now ? PVE
::QemuConfig-
>load_config($src_vmid) : {};
262 my $src_drive = { file
=> $src_volid };
264 PVE
::QemuConfig-
>foreach_volume($src_conf, sub {
265 my ($ds, $drive) = @_;
267 return if $src_drivename;
269 if ($drive->{file
} eq $src_volid) {
271 $src_drivename = $ds;
277 running
=> $running_now,
278 drivename
=> $src_drivename,
283 my ($src_storeid) = PVE
::Storage
::parse_volume_id
($src_volid);
285 return PVE
::QemuServer
::clone_disk
(
294 PVE
::Storage
::get_bandwidth_limit
('clone', [$src_storeid, $dest_info->{storage
}]),
300 $cloned = PVE
::QemuConfig-
>lock_config_full($src_vmid, 30, $clonefn);
301 } elsif ($src_vmid) {
302 $cloned = PVE
::QemuConfig-
>lock_config_shared($src_vmid, 30, $clonefn);
304 $cloned = $clonefn->();
307 return $cloned->@{qw(file size)};
310 # Note: $pool is only needed when creating a VM, because pool permissions
311 # are automatically inherited if VM already exists inside a pool.
312 my $create_disks = sub {
313 my ($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
320 my ($ds, $disk) = @_;
322 my $volid = $disk->{file
};
323 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
325 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
326 delete $disk->{size
};
327 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
328 } elsif (defined($volname) && $volname eq 'cloudinit') {
329 $storeid = $storeid // $default_storage;
330 die "no storage ID specified (and no default storage)\n" if !$storeid;
331 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
332 my $name = "vm-$vmid-cloudinit";
336 $fmt = $disk->{format
} // "qcow2";
339 $fmt = $disk->{format
} // "raw";
342 # Initial disk created with 4 MB and aligned to 4MB on regeneration
343 my $ci_size = PVE
::QemuServer
::Cloudinit
::CLOUDINIT_DISK_SIZE
;
344 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, $name, $ci_size/1024);
345 $disk->{file
} = $volid;
346 $disk->{media
} = 'cdrom';
347 push @$vollist, $volid;
348 delete $disk->{format
}; # no longer needed
349 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
350 print "$ds: successfully created disk '$res->{$ds}'\n";
351 } elsif ($volid =~ $NEW_DISK_RE) {
352 my ($storeid, $size) = ($2 || $default_storage, $3);
353 die "no storage ID specified (and no default storage)\n" if !$storeid;
355 if (my $source = delete $disk->{'import-from'}) {
358 if (PVE
::Storage
::parse_volume_id
($source, 1)) { # PVE-managed volume
363 format
=> $disk->{format
},
366 $dest_info->{efisize
} = PVE
::QemuServer
::get_efivars_size
($conf, $disk)
367 if $ds eq 'efidisk0';
369 ($dst_volid, $size) = eval {
370 $import_from_volid->($storecfg, $source, $dest_info, $vollist);
372 die "cannot import from '$source' - $@" if $@;
374 $source = PVE
::Storage
::abs_filesystem_path
($storecfg, $source, 1);
375 $size = PVE
::Storage
::file_size_info
($source);
376 die "could not get file size of $source\n" if !$size;
378 (undef, $dst_volid) = PVE
::QemuServer
::ImportDisk
::do_import
(
384 format
=> $disk->{format
},
385 'skip-config-update' => 1,
388 push @$vollist, $dst_volid;
391 $disk->{file
} = $dst_volid;
392 $disk->{size
} = $size;
393 delete $disk->{format
}; # no longer needed
394 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
396 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
397 my $fmt = $disk->{format
} || $defformat;
399 $size = PVE
::Tools
::convert_size
($size, 'gb' => 'kb'); # vdisk_alloc uses kb
402 if ($ds eq 'efidisk0') {
403 my $smm = PVE
::QemuServer
::Machine
::machine_type_is_q35
($conf);
404 ($volid, $size) = PVE
::QemuServer
::create_efidisk
(
405 $storecfg, $storeid, $vmid, $fmt, $arch, $disk, $smm);
406 } elsif ($ds eq 'tpmstate0') {
407 # swtpm can only use raw volumes, and uses a fixed size
408 $size = PVE
::Tools
::convert_size
(PVE
::QemuServer
::Drive
::TPMSTATE_DISK_SIZE
, 'b' => 'kb');
409 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, "raw", undef, $size);
411 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, undef, $size);
413 push @$vollist, $volid;
414 $disk->{file
} = $volid;
415 $disk->{size
} = PVE
::Tools
::convert_size
($size, 'kb' => 'b');
416 delete $disk->{format
}; # no longer needed
417 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
420 print "$ds: successfully created disk '$res->{$ds}'\n";
422 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
424 my ($vtype) = PVE
::Storage
::parse_volname
($storecfg, $volid);
425 die "cannot use volume $volid - content type needs to be 'images' or 'iso'"
426 if $vtype ne 'images' && $vtype ne 'iso';
429 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
431 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
432 die "volume $volid does not exist\n" if !$size;
433 $disk->{size
} = $size;
435 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
439 eval { $foreach_volume_with_alloc->($settings, $code); };
441 # free allocated images on error
443 syslog
('err', "VM $vmid creating disks failed");
444 foreach my $volid (@$vollist) {
445 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
451 return ($vollist, $res);
454 my $check_cpu_model_access = sub {
455 my ($rpcenv, $authuser, $new, $existing) = @_;
457 return if !defined($new->{cpu
});
459 my $cpu = PVE
::JSONSchema
::check_format
('pve-vm-cpu-conf', $new->{cpu
});
460 return if !$cpu || !$cpu->{cputype
}; # always allow default
461 my $cputype = $cpu->{cputype
};
463 if ($existing && $existing->{cpu
}) {
464 # changing only other settings doesn't require permissions for CPU model
465 my $existingCpu = PVE
::JSONSchema
::check_format
('pve-vm-cpu-conf', $existing->{cpu
});
466 return if $existingCpu->{cputype
} eq $cputype;
469 if (PVE
::QemuServer
::CPUConfig
::is_custom_model
($cputype)) {
470 $rpcenv->check($authuser, "/nodes", ['Sys.Audit']);
485 my $memoryoptions = {
491 my $hwtypeoptions = {
504 my $generaloptions = {
511 'migrate_downtime' => 1,
512 'migrate_speed' => 1,
525 my $vmpoweroptions = {
532 'vmstatestorage' => 1,
535 my $cloudinitoptions = {
545 my $check_vm_create_serial_perm = sub {
546 my ($rpcenv, $authuser, $vmid, $pool, $param) = @_;
548 return 1 if $authuser eq 'root@pam';
550 foreach my $opt (keys %{$param}) {
551 next if $opt !~ m/^serial\d+$/;
553 if ($param->{$opt} eq 'socket') {
554 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
556 die "only root can set '$opt' config for real devices\n";
563 my $check_vm_create_usb_perm = sub {
564 my ($rpcenv, $authuser, $vmid, $pool, $param) = @_;
566 return 1 if $authuser eq 'root@pam';
568 foreach my $opt (keys %{$param}) {
569 next if $opt !~ m/^usb\d+$/;
571 if ($param->{$opt} =~ m/spice/) {
572 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
574 die "only root can set '$opt' config for real devices\n";
581 my $check_vm_modify_config_perm = sub {
582 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
584 return 1 if $authuser eq 'root@pam';
586 foreach my $opt (@$key_list) {
587 # some checks (e.g., disk, serial port, usb) need to be done somewhere
588 # else, as there the permission can be value dependend
589 next if PVE
::QemuServer
::is_valid_drivename
($opt);
590 next if $opt eq 'cdrom';
591 next if $opt =~ m/^(?:unused|serial|usb)\d+$/;
594 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
595 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
596 } elsif ($memoryoptions->{$opt}) {
597 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
598 } elsif ($hwtypeoptions->{$opt}) {
599 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
600 } elsif ($generaloptions->{$opt}) {
601 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
602 # special case for startup since it changes host behaviour
603 if ($opt eq 'startup') {
604 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
606 } elsif ($vmpoweroptions->{$opt}) {
607 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
608 } elsif ($diskoptions->{$opt}) {
609 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
610 } elsif ($opt =~ m/^(?:net|ipconfig)\d+$/) {
611 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
612 } elsif ($cloudinitoptions->{$opt}) {
613 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Cloudinit', 'VM.Config.Network'], 1);
614 } elsif ($opt eq 'vmstate') {
615 # the user needs Disk and PowerMgmt privileges to change the vmstate
616 # also needs privileges on the storage, that will be checked later
617 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk', 'VM.PowerMgmt' ]);
619 # catches hostpci\d+, args, lock, etc.
620 # new options will be checked here
621 die "only root can set '$opt' config\n";
628 __PACKAGE__-
>register_method({
632 description
=> "Virtual machine index (per node).",
634 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
638 protected
=> 1, # qemu pid files are only readable by root
640 additionalProperties
=> 0,
642 node
=> get_standard_option
('pve-node'),
646 description
=> "Determine the full status of active VMs.",
654 properties
=> $PVE::QemuServer
::vmstatus_return_properties
,
656 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
661 my $rpcenv = PVE
::RPCEnvironment
::get
();
662 my $authuser = $rpcenv->get_user();
664 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
667 foreach my $vmid (keys %$vmstatus) {
668 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
670 my $data = $vmstatus->{$vmid};
677 my $parse_restore_archive = sub {
678 my ($storecfg, $archive) = @_;
680 my ($archive_storeid, $archive_volname) = PVE
::Storage
::parse_volume_id
($archive, 1);
682 if (defined($archive_storeid)) {
683 my $scfg = PVE
::Storage
::storage_config
($storecfg, $archive_storeid);
684 if ($scfg->{type
} eq 'pbs') {
691 my $path = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
699 __PACKAGE__-
>register_method({
703 description
=> "Create or restore a virtual machine.",
705 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
706 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
707 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
708 user
=> 'all', # check inside
713 additionalProperties
=> 0,
714 properties
=> PVE
::QemuServer
::json_config_properties
(
716 node
=> get_standard_option
('pve-node'),
717 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
719 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.",
723 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
725 storage
=> get_standard_option
('pve-storage-id', {
726 description
=> "Default storage.",
728 completion
=> \
&PVE
::QemuServer
::complete_storage
,
733 description
=> "Allow to overwrite existing VM.",
734 requires
=> 'archive',
739 description
=> "Assign a unique random ethernet address.",
740 requires
=> 'archive',
745 description
=> "Start the VM immediately from the backup and restore in background. PBS only.",
746 requires
=> 'archive',
750 type
=> 'string', format
=> 'pve-poolid',
751 description
=> "Add the VM to the specified pool.",
754 description
=> "Override I/O bandwidth limit (in KiB/s).",
758 default => 'restore limit from datacenter or storage config',
764 description
=> "Start VM after it was created successfully.",
776 my $rpcenv = PVE
::RPCEnvironment
::get
();
777 my $authuser = $rpcenv->get_user();
779 my $node = extract_param
($param, 'node');
780 my $vmid = extract_param
($param, 'vmid');
782 my $archive = extract_param
($param, 'archive');
783 my $is_restore = !!$archive;
785 my $bwlimit = extract_param
($param, 'bwlimit');
786 my $force = extract_param
($param, 'force');
787 my $pool = extract_param
($param, 'pool');
788 my $start_after_create = extract_param
($param, 'start');
789 my $storage = extract_param
($param, 'storage');
790 my $unique = extract_param
($param, 'unique');
791 my $live_restore = extract_param
($param, 'live-restore');
793 if (defined(my $ssh_keys = $param->{sshkeys
})) {
794 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
795 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
798 PVE
::Cluster
::check_cfs_quorum
();
800 my $filename = PVE
::QemuConfig-
>config_file($vmid);
801 my $storecfg = PVE
::Storage
::config
();
803 if (defined($pool)) {
804 $rpcenv->check_pool_exist($pool);
807 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
808 if defined($storage);
810 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
812 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
814 } elsif ($archive && $force && (-f
$filename) &&
815 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
816 # OK: user has VM.Backup permissions, and want to restore an existing VM
822 for my $opt (sort keys $param->%*) {
823 if (PVE
::QemuServer
::Drive
::is_valid_drivename
($opt)) {
824 raise_param_exc
({ $opt => "option conflicts with option 'archive'" });
828 if ($archive eq '-') {
829 die "pipe requires cli environment\n" if $rpcenv->{type
} ne 'cli';
830 $archive = { type
=> 'pipe' };
832 PVE
::Storage
::check_volume_access
(
841 $archive = $parse_restore_archive->($storecfg, $archive);
845 if (scalar(keys $param->%*) > 0) {
846 &$resolve_cdrom_alias($param);
848 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
850 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
852 &$check_vm_create_serial_perm($rpcenv, $authuser, $vmid, $pool, $param);
853 &$check_vm_create_usb_perm($rpcenv, $authuser, $vmid, $pool, $param);
855 &$check_cpu_model_access($rpcenv, $authuser, $param);
857 $check_drive_param->($param, $storecfg);
859 PVE
::QemuServer
::add_random_macs
($param);
862 my $emsg = $is_restore ?
"unable to restore VM $vmid -" : "unable to create VM $vmid -";
864 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, $force) };
865 die "$emsg $@" if $@;
867 my $restored_data = 0;
868 my $restorefn = sub {
869 my $conf = PVE
::QemuConfig-
>load_config($vmid);
871 PVE
::QemuConfig-
>check_protection($conf, $emsg);
873 die "$emsg vm is running\n" if PVE
::QemuServer
::check_running
($vmid);
876 my $restore_options = {
881 live
=> $live_restore,
882 override_conf
=> $param,
884 if ($archive->{type
} eq 'file' || $archive->{type
} eq 'pipe') {
885 die "live-restore is only compatible with backup images from a Proxmox Backup Server\n"
887 PVE
::QemuServer
::restore_file_archive
($archive->{path
} // '-', $vmid, $authuser, $restore_options);
888 } elsif ($archive->{type
} eq 'pbs') {
889 PVE
::QemuServer
::restore_proxmox_backup_archive
($archive->{volid
}, $vmid, $authuser, $restore_options);
891 die "unknown backup archive type\n";
895 my $restored_conf = PVE
::QemuConfig-
>load_config($vmid);
896 # Convert restored VM to template if backup was VM template
897 if (PVE
::QemuConfig-
>is_template($restored_conf)) {
898 warn "Convert to template.\n";
899 eval { PVE
::QemuServer
::template_create
($vmid, $restored_conf) };
904 # ensure no old replication state are exists
905 PVE
::ReplicationState
::delete_guest_states
($vmid);
907 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
909 if ($start_after_create && !$live_restore) {
910 print "Execute autostart\n";
911 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
917 # ensure no old replication state are exists
918 PVE
::ReplicationState
::delete_guest_states
($vmid);
922 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
924 $conf->{meta
} = PVE
::QemuServer
::new_meta_info_string
();
928 ($vollist, my $created_opts) = $create_disks->(
939 $conf->{$_} = $created_opts->{$_} for keys $created_opts->%*;
941 if (!$conf->{boot
}) {
942 my $devs = PVE
::QemuServer
::get_default_bootdevices
($conf);
943 $conf->{boot
} = PVE
::QemuServer
::print_bootorder
($devs);
946 # auto generate uuid if user did not specify smbios1 option
947 if (!$conf->{smbios1
}) {
948 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
951 if ((!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') && $arch ne 'aarch64') {
952 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
955 my $machine = $conf->{machine
};
956 if (!$machine || $machine =~ m/^(?:pc|q35|virt)$/) {
957 # always pin Windows' machine version on create, they get to easily confused
958 if (PVE
::QemuServer
::windows_version
($conf->{ostype
})) {
959 $conf->{machine
} = PVE
::QemuServer
::windows_get_pinned_machine_version
($machine);
963 PVE
::QemuConfig-
>write_config($vmid, $conf);
969 foreach my $volid (@$vollist) {
970 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
976 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
979 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
981 if ($start_after_create) {
982 print "Execute autostart\n";
983 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
988 my ($code, $worker_name);
990 $worker_name = 'qmrestore';
992 eval { $restorefn->() };
994 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
996 if ($restored_data) {
997 warn "error after data was restored, VM disks should be OK but config may "
998 ."require adaptions. VM $vmid state is NOT cleaned up.\n";
1000 warn "error before or during data restore, some or all disks were not "
1001 ."completely restored. VM $vmid state is NOT cleaned up.\n";
1007 $worker_name = 'qmcreate';
1009 eval { $createfn->() };
1012 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
1013 unlink($conffile) or die "failed to remove config file: $!\n";
1021 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
1024 __PACKAGE__-
>register_method({
1029 description
=> "Directory index",
1034 additionalProperties
=> 0,
1036 node
=> get_standard_option
('pve-node'),
1037 vmid
=> get_standard_option
('pve-vmid'),
1045 subdir
=> { type
=> 'string' },
1048 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1054 { subdir
=> 'config' },
1055 { subdir
=> 'pending' },
1056 { subdir
=> 'status' },
1057 { subdir
=> 'unlink' },
1058 { subdir
=> 'vncproxy' },
1059 { subdir
=> 'termproxy' },
1060 { subdir
=> 'migrate' },
1061 { subdir
=> 'resize' },
1062 { subdir
=> 'move' },
1063 { subdir
=> 'rrd' },
1064 { subdir
=> 'rrddata' },
1065 { subdir
=> 'monitor' },
1066 { subdir
=> 'agent' },
1067 { subdir
=> 'snapshot' },
1068 { subdir
=> 'spiceproxy' },
1069 { subdir
=> 'sendkey' },
1070 { subdir
=> 'firewall' },
1076 __PACKAGE__-
>register_method ({
1077 subclass
=> "PVE::API2::Firewall::VM",
1078 path
=> '{vmid}/firewall',
1081 __PACKAGE__-
>register_method ({
1082 subclass
=> "PVE::API2::Qemu::Agent",
1083 path
=> '{vmid}/agent',
1086 __PACKAGE__-
>register_method({
1088 path
=> '{vmid}/rrd',
1090 protected
=> 1, # fixme: can we avoid that?
1092 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1094 description
=> "Read VM RRD statistics (returns PNG)",
1096 additionalProperties
=> 0,
1098 node
=> get_standard_option
('pve-node'),
1099 vmid
=> get_standard_option
('pve-vmid'),
1101 description
=> "Specify the time frame you are interested in.",
1103 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
1106 description
=> "The list of datasources you want to display.",
1107 type
=> 'string', format
=> 'pve-configid-list',
1110 description
=> "The RRD consolidation function",
1112 enum
=> [ 'AVERAGE', 'MAX' ],
1120 filename
=> { type
=> 'string' },
1126 return PVE
::RRD
::create_rrd_graph
(
1127 "pve2-vm/$param->{vmid}", $param->{timeframe
},
1128 $param->{ds
}, $param->{cf
});
1132 __PACKAGE__-
>register_method({
1134 path
=> '{vmid}/rrddata',
1136 protected
=> 1, # fixme: can we avoid that?
1138 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1140 description
=> "Read VM RRD statistics",
1142 additionalProperties
=> 0,
1144 node
=> get_standard_option
('pve-node'),
1145 vmid
=> get_standard_option
('pve-vmid'),
1147 description
=> "Specify the time frame you are interested in.",
1149 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
1152 description
=> "The RRD consolidation function",
1154 enum
=> [ 'AVERAGE', 'MAX' ],
1169 return PVE
::RRD
::create_rrd_data
(
1170 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
1174 __PACKAGE__-
>register_method({
1175 name
=> 'vm_config',
1176 path
=> '{vmid}/config',
1179 description
=> "Get the virtual machine configuration with pending configuration " .
1180 "changes applied. Set the 'current' parameter to get the current configuration instead.",
1182 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1185 additionalProperties
=> 0,
1187 node
=> get_standard_option
('pve-node'),
1188 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1190 description
=> "Get current values (instead of pending values).",
1195 snapshot
=> get_standard_option
('pve-snapshot-name', {
1196 description
=> "Fetch config values from given snapshot.",
1199 my ($cmd, $pname, $cur, $args) = @_;
1200 PVE
::QemuConfig-
>snapshot_list($args->[0]);
1206 description
=> "The VM configuration.",
1208 properties
=> PVE
::QemuServer
::json_config_properties
({
1211 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
1218 raise_param_exc
({ snapshot
=> "cannot use 'snapshot' parameter with 'current'",
1219 current
=> "cannot use 'snapshot' parameter with 'current'"})
1220 if ($param->{snapshot
} && $param->{current
});
1223 if ($param->{snapshot
}) {
1224 $conf = PVE
::QemuConfig-
>load_snapshot_config($param->{vmid
}, $param->{snapshot
});
1226 $conf = PVE
::QemuConfig-
>load_current_config($param->{vmid
}, $param->{current
});
1228 $conf->{cipassword
} = '**********' if $conf->{cipassword
};
1233 __PACKAGE__-
>register_method({
1234 name
=> 'vm_pending',
1235 path
=> '{vmid}/pending',
1238 description
=> "Get the virtual machine configuration with both current and pending values.",
1240 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1243 additionalProperties
=> 0,
1245 node
=> get_standard_option
('pve-node'),
1246 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1255 description
=> "Configuration option name.",
1259 description
=> "Current value.",
1264 description
=> "Pending value.",
1269 description
=> "Indicates a pending delete request if present and not 0. " .
1270 "The value 2 indicates a force-delete request.",
1282 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1284 my $pending_delete_hash = PVE
::QemuConfig-
>parse_pending_delete($conf->{pending
}->{delete});
1286 $conf->{cipassword
} = '**********' if defined($conf->{cipassword
});
1287 $conf->{pending
}->{cipassword
} = '********** ' if defined($conf->{pending
}->{cipassword
});
1289 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);
1292 # POST/PUT {vmid}/config implementation
1294 # The original API used PUT (idempotent) an we assumed that all operations
1295 # are fast. But it turned out that almost any configuration change can
1296 # involve hot-plug actions, or disk alloc/free. Such actions can take long
1297 # time to complete and have side effects (not idempotent).
1299 # The new implementation uses POST and forks a worker process. We added
1300 # a new option 'background_delay'. If specified we wait up to
1301 # 'background_delay' second for the worker task to complete. It returns null
1302 # if the task is finished within that time, else we return the UPID.
1304 my $update_vm_api = sub {
1305 my ($param, $sync) = @_;
1307 my $rpcenv = PVE
::RPCEnvironment
::get
();
1309 my $authuser = $rpcenv->get_user();
1311 my $node = extract_param
($param, 'node');
1313 my $vmid = extract_param
($param, 'vmid');
1315 my $digest = extract_param
($param, 'digest');
1317 my $background_delay = extract_param
($param, 'background_delay');
1319 if (defined(my $cipassword = $param->{cipassword
})) {
1320 # Same logic as in cloud-init (but with the regex fixed...)
1321 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
1322 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
1325 my @paramarr = (); # used for log message
1326 foreach my $key (sort keys %$param) {
1327 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
1328 push @paramarr, "-$key", $value;
1331 my $skiplock = extract_param
($param, 'skiplock');
1332 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1333 if $skiplock && $authuser ne 'root@pam';
1335 my $delete_str = extract_param
($param, 'delete');
1337 my $revert_str = extract_param
($param, 'revert');
1339 my $force = extract_param
($param, 'force');
1341 if (defined(my $ssh_keys = $param->{sshkeys
})) {
1342 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
1343 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
1346 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
1348 my $storecfg = PVE
::Storage
::config
();
1350 my $defaults = PVE
::QemuServer
::load_defaults
();
1352 &$resolve_cdrom_alias($param);
1354 # now try to verify all parameters
1357 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1358 if (!PVE
::QemuServer
::option_exists
($opt)) {
1359 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1362 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1363 "-revert $opt' at the same time" })
1364 if defined($param->{$opt});
1366 $revert->{$opt} = 1;
1370 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1371 $opt = 'ide2' if $opt eq 'cdrom';
1373 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1374 "-delete $opt' at the same time" })
1375 if defined($param->{$opt});
1377 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1378 "-revert $opt' at the same time" })
1381 if (!PVE
::QemuServer
::option_exists
($opt)) {
1382 raise_param_exc
({ delete => "unknown option '$opt'" });
1388 my $repl_conf = PVE
::ReplicationConfig-
>new();
1389 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1390 my $check_replication = sub {
1392 return if !$is_replicated;
1393 my $volid = $drive->{file
};
1394 return if !$volid || !($drive->{replicate
}//1);
1395 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1397 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1398 die "cannot add non-managed/pass-through volume to a replicated VM\n"
1399 if !defined($storeid);
1401 return if defined($volname) && $volname eq 'cloudinit';
1404 if ($volid =~ $NEW_DISK_RE) {
1406 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1408 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1410 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1411 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1412 return if $scfg->{shared
};
1413 die "cannot add non-replicatable volume to a replicated VM\n";
1416 $check_drive_param->($param, $storecfg, $check_replication);
1418 foreach my $opt (keys %$param) {
1419 if ($opt =~ m/^net(\d+)$/) {
1421 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1422 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1423 } elsif ($opt eq 'vmgenid') {
1424 if ($param->{$opt} eq '1') {
1425 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1427 } elsif ($opt eq 'hookscript') {
1428 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1429 raise_param_exc
({ $opt => $@ }) if $@;
1433 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1435 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1437 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1439 my $updatefn = sub {
1441 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1443 die "checksum missmatch (file change by other user?)\n"
1444 if $digest && $digest ne $conf->{digest
};
1446 &$check_cpu_model_access($rpcenv, $authuser, $param, $conf);
1448 # FIXME: 'suspended' lock should probabyl be a state or "weak" lock?!
1449 if (scalar(@delete) && grep { $_ eq 'vmstate'} @delete) {
1450 if (defined($conf->{lock}) && $conf->{lock} eq 'suspended') {
1451 delete $conf->{lock}; # for check lock check, not written out
1452 push @delete, 'lock'; # this is the real deal to write it out
1454 push @delete, 'runningmachine' if $conf->{runningmachine
};
1455 push @delete, 'runningcpu' if $conf->{runningcpu
};
1458 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1460 foreach my $opt (keys %$revert) {
1461 if (defined($conf->{$opt})) {
1462 $param->{$opt} = $conf->{$opt};
1463 } elsif (defined($conf->{pending
}->{$opt})) {
1468 if ($param->{memory
} || defined($param->{balloon
})) {
1469 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1470 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1472 die "balloon value too large (must be smaller than assigned memory)\n"
1473 if $balloon && $balloon > $maxmem;
1476 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1480 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1482 # write updates to pending section
1484 my $modified = {}; # record what $option we modify
1487 if (my $boot = $conf->{boot
}) {
1488 my $bootcfg = PVE
::JSONSchema
::parse_property_string
('pve-qm-boot', $boot);
1489 @bootorder = PVE
::Tools
::split_list
($bootcfg->{order
}) if $bootcfg && $bootcfg->{order
};
1491 my $bootorder_deleted = grep {$_ eq 'bootorder'} @delete;
1493 my $check_drive_perms = sub {
1494 my ($opt, $val) = @_;
1495 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val, 1);
1496 # FIXME: cloudinit: CDROM or Disk?
1497 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1498 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1500 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1504 foreach my $opt (@delete) {
1505 $modified->{$opt} = 1;
1506 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1508 # value of what we want to delete, independent if pending or not
1509 my $val = $conf->{$opt} // $conf->{pending
}->{$opt};
1510 if (!defined($val)) {
1511 warn "cannot delete '$opt' - not set in current configuration!\n";
1512 $modified->{$opt} = 0;
1515 my $is_pending_val = defined($conf->{pending
}->{$opt});
1516 delete $conf->{pending
}->{$opt};
1518 # remove from bootorder if necessary
1519 if (!$bootorder_deleted && @bootorder && grep {$_ eq $opt} @bootorder) {
1520 @bootorder = grep {$_ ne $opt} @bootorder;
1521 $conf->{pending
}->{boot
} = PVE
::QemuServer
::print_bootorder
(\
@bootorder);
1522 $modified->{boot
} = 1;
1525 if ($opt =~ m/^unused/) {
1526 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1527 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1528 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1529 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1530 delete $conf->{$opt};
1531 PVE
::QemuConfig-
>write_config($vmid, $conf);
1533 } elsif ($opt eq 'vmstate') {
1534 PVE
::QemuConfig-
>check_protection($conf, "can't remove vmstate '$val'");
1535 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, { file
=> $val }, $rpcenv, $authuser, 1)) {
1536 delete $conf->{$opt};
1537 PVE
::QemuConfig-
>write_config($vmid, $conf);
1539 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1540 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1541 $check_drive_perms->($opt, $val);
1542 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $val))
1544 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1545 PVE
::QemuConfig-
>write_config($vmid, $conf);
1546 } elsif ($opt =~ m/^serial\d+$/) {
1547 if ($val eq 'socket') {
1548 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1549 } elsif ($authuser ne 'root@pam') {
1550 die "only root can delete '$opt' config for real devices\n";
1552 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1553 PVE
::QemuConfig-
>write_config($vmid, $conf);
1554 } elsif ($opt =~ m/^usb\d+$/) {
1555 if ($val =~ m/spice/) {
1556 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1557 } elsif ($authuser ne 'root@pam') {
1558 die "only root can delete '$opt' config for real devices\n";
1560 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1561 PVE
::QemuConfig-
>write_config($vmid, $conf);
1563 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1564 PVE
::QemuConfig-
>write_config($vmid, $conf);
1568 foreach my $opt (keys %$param) { # add/change
1569 $modified->{$opt} = 1;
1570 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1571 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1573 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
1575 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1577 if ($conf->{$opt}) {
1578 $check_drive_perms->($opt, $conf->{$opt});
1582 $check_drive_perms->($opt, $param->{$opt});
1583 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1584 if defined($conf->{pending
}->{$opt});
1586 my (undef, $created_opts) = $create_disks->(
1594 {$opt => $param->{$opt}},
1596 $conf->{pending
}->{$_} = $created_opts->{$_} for keys $created_opts->%*;
1598 # default legacy boot order implies all cdroms anyway
1600 # append new CD drives to bootorder to mark them bootable
1601 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt}, 1);
1602 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1) && !grep(/^$opt$/, @bootorder)) {
1603 push @bootorder, $opt;
1604 $conf->{pending
}->{boot
} = PVE
::QemuServer
::print_bootorder
(\
@bootorder);
1605 $modified->{boot
} = 1;
1608 } elsif ($opt =~ m/^serial\d+/) {
1609 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1610 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1611 } elsif ($authuser ne 'root@pam') {
1612 die "only root can modify '$opt' config for real devices\n";
1614 $conf->{pending
}->{$opt} = $param->{$opt};
1615 } elsif ($opt =~ m/^usb\d+/) {
1616 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1617 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1618 } elsif ($authuser ne 'root@pam') {
1619 die "only root can modify '$opt' config for real devices\n";
1621 $conf->{pending
}->{$opt} = $param->{$opt};
1623 $conf->{pending
}->{$opt} = $param->{$opt};
1625 if ($opt eq 'boot') {
1626 my $new_bootcfg = PVE
::JSONSchema
::parse_property_string
('pve-qm-boot', $param->{$opt});
1627 if ($new_bootcfg->{order
}) {
1628 my @devs = PVE
::Tools
::split_list
($new_bootcfg->{order
});
1629 for my $dev (@devs) {
1630 my $exists = $conf->{$dev} || $conf->{pending
}->{$dev} || $param->{$dev};
1631 my $deleted = grep {$_ eq $dev} @delete;
1632 die "invalid bootorder: device '$dev' does not exist'\n"
1633 if !$exists || $deleted;
1636 # remove legacy boot order settings if new one set
1637 $conf->{pending
}->{$opt} = PVE
::QemuServer
::print_bootorder
(\
@devs);
1638 PVE
::QemuConfig-
>add_to_pending_delete($conf, "bootdisk")
1639 if $conf->{bootdisk
};
1643 PVE
::QemuConfig-
>remove_from_pending_delete($conf, $opt);
1644 PVE
::QemuConfig-
>write_config($vmid, $conf);
1647 # remove pending changes when nothing changed
1648 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1649 my $changes = PVE
::QemuConfig-
>cleanup_pending($conf);
1650 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1652 return if !scalar(keys %{$conf->{pending
}});
1654 my $running = PVE
::QemuServer
::check_running
($vmid);
1656 # apply pending changes
1658 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1662 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1664 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $errors);
1666 raise_param_exc
($errors) if scalar(keys %$errors);
1675 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1677 if ($background_delay) {
1679 # Note: It would be better to do that in the Event based HTTPServer
1680 # to avoid blocking call to sleep.
1682 my $end_time = time() + $background_delay;
1684 my $task = PVE
::Tools
::upid_decode
($upid);
1687 while (time() < $end_time) {
1688 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1690 sleep(1); # this gets interrupted when child process ends
1694 my $status = PVE
::Tools
::upid_read_status
($upid);
1695 return if !PVE
::Tools
::upid_status_is_error
($status);
1696 die "failed to update VM $vmid: $status\n";
1704 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1707 my $vm_config_perm_list = [
1712 'VM.Config.Network',
1714 'VM.Config.Options',
1715 'VM.Config.Cloudinit',
1718 __PACKAGE__-
>register_method({
1719 name
=> 'update_vm_async',
1720 path
=> '{vmid}/config',
1724 description
=> "Set virtual machine options (asynchrounous API).",
1726 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1729 additionalProperties
=> 0,
1730 properties
=> PVE
::QemuServer
::json_config_properties
(
1732 node
=> get_standard_option
('pve-node'),
1733 vmid
=> get_standard_option
('pve-vmid'),
1734 skiplock
=> get_standard_option
('skiplock'),
1736 type
=> 'string', format
=> 'pve-configid-list',
1737 description
=> "A list of settings you want to delete.",
1741 type
=> 'string', format
=> 'pve-configid-list',
1742 description
=> "Revert a pending change.",
1747 description
=> $opt_force_description,
1749 requires
=> 'delete',
1753 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1757 background_delay
=> {
1759 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1765 1, # with_disk_alloc
1772 code
=> $update_vm_api,
1775 __PACKAGE__-
>register_method({
1776 name
=> 'update_vm',
1777 path
=> '{vmid}/config',
1781 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1783 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1786 additionalProperties
=> 0,
1787 properties
=> PVE
::QemuServer
::json_config_properties
(
1789 node
=> get_standard_option
('pve-node'),
1790 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1791 skiplock
=> get_standard_option
('skiplock'),
1793 type
=> 'string', format
=> 'pve-configid-list',
1794 description
=> "A list of settings you want to delete.",
1798 type
=> 'string', format
=> 'pve-configid-list',
1799 description
=> "Revert a pending change.",
1804 description
=> $opt_force_description,
1806 requires
=> 'delete',
1810 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1815 1, # with_disk_alloc
1818 returns
=> { type
=> 'null' },
1821 &$update_vm_api($param, 1);
1826 __PACKAGE__-
>register_method({
1827 name
=> 'destroy_vm',
1832 description
=> "Destroy the VM and all used/owned volumes. Removes any VM specific permissions"
1833 ." and firewall rules",
1835 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1838 additionalProperties
=> 0,
1840 node
=> get_standard_option
('pve-node'),
1841 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1842 skiplock
=> get_standard_option
('skiplock'),
1845 description
=> "Remove VMID from configurations, like backup & replication jobs and HA.",
1848 'destroy-unreferenced-disks' => {
1850 description
=> "If set, destroy additionally all disks not referenced in the config"
1851 ." but with a matching VMID from all enabled storages.",
1863 my $rpcenv = PVE
::RPCEnvironment
::get
();
1864 my $authuser = $rpcenv->get_user();
1865 my $vmid = $param->{vmid
};
1867 my $skiplock = $param->{skiplock
};
1868 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1869 if $skiplock && $authuser ne 'root@pam';
1871 my $early_checks = sub {
1873 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1874 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1876 my $ha_managed = PVE
::HA
::Config
::service_is_configured
("vm:$vmid");
1878 if (!$param->{purge
}) {
1879 die "unable to remove VM $vmid - used in HA resources and purge parameter not set.\n"
1881 # don't allow destroy if with replication jobs but no purge param
1882 my $repl_conf = PVE
::ReplicationConfig-
>new();
1883 $repl_conf->check_for_existing_jobs($vmid);
1886 die "VM $vmid is running - destroy failed\n"
1887 if PVE
::QemuServer
::check_running
($vmid);
1897 my $storecfg = PVE
::Storage
::config
();
1899 syslog
('info', "destroy VM $vmid: $upid\n");
1900 PVE
::QemuConfig-
>lock_config($vmid, sub {
1901 # repeat, config might have changed
1902 my $ha_managed = $early_checks->();
1904 my $purge_unreferenced = $param->{'destroy-unreferenced-disks'};
1906 PVE
::QemuServer
::destroy_vm
(
1909 $skiplock, { lock => 'destroyed' },
1910 $purge_unreferenced,
1913 PVE
::AccessControl
::remove_vm_access
($vmid);
1914 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1915 if ($param->{purge
}) {
1916 print "purging VM $vmid from related configurations..\n";
1917 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
1918 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
1921 PVE
::HA
::Config
::delete_service_from_config
("vm:$vmid");
1922 print "NOTE: removed VM $vmid from HA resource configuration.\n";
1926 # only now remove the zombie config, else we can have reuse race
1927 PVE
::QemuConfig-
>destroy_config($vmid);
1931 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1934 __PACKAGE__-
>register_method({
1936 path
=> '{vmid}/unlink',
1940 description
=> "Unlink/delete disk images.",
1942 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1945 additionalProperties
=> 0,
1947 node
=> get_standard_option
('pve-node'),
1948 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1950 type
=> 'string', format
=> 'pve-configid-list',
1951 description
=> "A list of disk IDs you want to delete.",
1955 description
=> $opt_force_description,
1960 returns
=> { type
=> 'null'},
1964 $param->{delete} = extract_param
($param, 'idlist');
1966 __PACKAGE__-
>update_vm($param);
1971 # uses good entropy, each char is limited to 6 bit to get printable chars simply
1972 my $gen_rand_chars = sub {
1975 die "invalid length $length" if $length < 1;
1977 my $min = ord('!'); # first printable ascii
1979 my $rand_bytes = Crypt
::OpenSSL
::Random
::random_bytes
($length);
1980 die "failed to generate random bytes!\n"
1983 my $str = join('', map { chr((ord($_) & 0x3F) + $min) } split('', $rand_bytes));
1990 __PACKAGE__-
>register_method({
1992 path
=> '{vmid}/vncproxy',
1996 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1998 description
=> "Creates a TCP VNC proxy connections.",
2000 additionalProperties
=> 0,
2002 node
=> get_standard_option
('pve-node'),
2003 vmid
=> get_standard_option
('pve-vmid'),
2007 description
=> "starts websockify instead of vncproxy",
2009 'generate-password' => {
2013 description
=> "Generates a random password to be used as ticket instead of the API ticket.",
2018 additionalProperties
=> 0,
2020 user
=> { type
=> 'string' },
2021 ticket
=> { type
=> 'string' },
2024 description
=> "Returned if requested with 'generate-password' param."
2025 ." Consists of printable ASCII characters ('!' .. '~').",
2028 cert
=> { type
=> 'string' },
2029 port
=> { type
=> 'integer' },
2030 upid
=> { type
=> 'string' },
2036 my $rpcenv = PVE
::RPCEnvironment
::get
();
2038 my $authuser = $rpcenv->get_user();
2040 my $vmid = $param->{vmid
};
2041 my $node = $param->{node
};
2042 my $websocket = $param->{websocket
};
2044 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
2048 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
2049 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
2052 my $authpath = "/vms/$vmid";
2054 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
2055 my $password = $ticket;
2056 if ($param->{'generate-password'}) {
2057 $password = $gen_rand_chars->(8);
2060 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
2066 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
2067 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
2068 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
2069 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
2070 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, defined($serial) ?
'-t' : '-T');
2072 $family = PVE
::Tools
::get_host_address_family
($node);
2075 my $port = PVE
::Tools
::next_vnc_port
($family);
2082 syslog
('info', "starting vnc proxy $upid\n");
2086 if (defined($serial)) {
2088 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $serial, '-escape', '0' ];
2090 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
2091 '-timeout', $timeout, '-authpath', $authpath,
2092 '-perm', 'Sys.Console'];
2094 if ($param->{websocket
}) {
2095 $ENV{PVE_VNC_TICKET
} = $password; # pass ticket to vncterm
2096 push @$cmd, '-notls', '-listen', 'localhost';
2099 push @$cmd, '-c', @$remcmd, @$termcmd;
2101 PVE
::Tools
::run_command
($cmd);
2105 $ENV{LC_PVE_TICKET
} = $password if $websocket; # set ticket with "qm vncproxy"
2107 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
2109 my $sock = IO
::Socket
::IP-
>new(
2114 GetAddrInfoFlags
=> 0,
2115 ) or die "failed to create socket: $!\n";
2116 # Inside the worker we shouldn't have any previous alarms
2117 # running anyway...:
2119 local $SIG{ALRM
} = sub { die "connection timed out\n" };
2121 accept(my $cli, $sock) or die "connection failed: $!\n";
2124 if (PVE
::Tools
::run_command
($cmd,
2125 output
=> '>&'.fileno($cli),
2126 input
=> '<&'.fileno($cli),
2129 die "Failed to run vncproxy.\n";
2136 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
2138 PVE
::Tools
::wait_for_vnc_port
($port);
2147 $res->{password
} = $password if $param->{'generate-password'};
2152 __PACKAGE__-
>register_method({
2153 name
=> 'termproxy',
2154 path
=> '{vmid}/termproxy',
2158 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2160 description
=> "Creates a TCP proxy connections.",
2162 additionalProperties
=> 0,
2164 node
=> get_standard_option
('pve-node'),
2165 vmid
=> get_standard_option
('pve-vmid'),
2169 enum
=> [qw(serial0 serial1 serial2 serial3)],
2170 description
=> "opens a serial terminal (defaults to display)",
2175 additionalProperties
=> 0,
2177 user
=> { type
=> 'string' },
2178 ticket
=> { type
=> 'string' },
2179 port
=> { type
=> 'integer' },
2180 upid
=> { type
=> 'string' },
2186 my $rpcenv = PVE
::RPCEnvironment
::get
();
2188 my $authuser = $rpcenv->get_user();
2190 my $vmid = $param->{vmid
};
2191 my $node = $param->{node
};
2192 my $serial = $param->{serial
};
2194 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
2196 if (!defined($serial)) {
2198 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
2199 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
2203 my $authpath = "/vms/$vmid";
2205 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
2210 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
2211 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
2212 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
2213 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, '-t');
2214 push @$remcmd, '--';
2216 $family = PVE
::Tools
::get_host_address_family
($node);
2219 my $port = PVE
::Tools
::next_vnc_port
($family);
2221 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
2222 push @$termcmd, '-iface', $serial if $serial;
2227 syslog
('info', "starting qemu termproxy $upid\n");
2229 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
2230 '--perm', 'VM.Console', '--'];
2231 push @$cmd, @$remcmd, @$termcmd;
2233 PVE
::Tools
::run_command
($cmd);
2236 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
2238 PVE
::Tools
::wait_for_vnc_port
($port);
2248 __PACKAGE__-
>register_method({
2249 name
=> 'vncwebsocket',
2250 path
=> '{vmid}/vncwebsocket',
2253 description
=> "You also need to pass a valid ticket (vncticket).",
2254 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2256 description
=> "Opens a weksocket for VNC traffic.",
2258 additionalProperties
=> 0,
2260 node
=> get_standard_option
('pve-node'),
2261 vmid
=> get_standard_option
('pve-vmid'),
2263 description
=> "Ticket from previous call to vncproxy.",
2268 description
=> "Port number returned by previous vncproxy call.",
2278 port
=> { type
=> 'string' },
2284 my $rpcenv = PVE
::RPCEnvironment
::get
();
2286 my $authuser = $rpcenv->get_user();
2288 my $vmid = $param->{vmid
};
2289 my $node = $param->{node
};
2291 my $authpath = "/vms/$vmid";
2293 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
2295 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
2297 # Note: VNC ports are acessible from outside, so we do not gain any
2298 # security if we verify that $param->{port} belongs to VM $vmid. This
2299 # check is done by verifying the VNC ticket (inside VNC protocol).
2301 my $port = $param->{port
};
2303 return { port
=> $port };
2306 __PACKAGE__-
>register_method({
2307 name
=> 'spiceproxy',
2308 path
=> '{vmid}/spiceproxy',
2313 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2315 description
=> "Returns a SPICE configuration to connect to the VM.",
2317 additionalProperties
=> 0,
2319 node
=> get_standard_option
('pve-node'),
2320 vmid
=> get_standard_option
('pve-vmid'),
2321 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
2324 returns
=> get_standard_option
('remote-viewer-config'),
2328 my $rpcenv = PVE
::RPCEnvironment
::get
();
2330 my $authuser = $rpcenv->get_user();
2332 my $vmid = $param->{vmid
};
2333 my $node = $param->{node
};
2334 my $proxy = $param->{proxy
};
2336 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
2337 my $title = "VM $vmid";
2338 $title .= " - ". $conf->{name
} if $conf->{name
};
2340 my $port = PVE
::QemuServer
::spice_port
($vmid);
2342 my ($ticket, undef, $remote_viewer_config) =
2343 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
2345 mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
2346 mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
2348 return $remote_viewer_config;
2351 __PACKAGE__-
>register_method({
2353 path
=> '{vmid}/status',
2356 description
=> "Directory index",
2361 additionalProperties
=> 0,
2363 node
=> get_standard_option
('pve-node'),
2364 vmid
=> get_standard_option
('pve-vmid'),
2372 subdir
=> { type
=> 'string' },
2375 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
2381 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2384 { subdir
=> 'current' },
2385 { subdir
=> 'start' },
2386 { subdir
=> 'stop' },
2387 { subdir
=> 'reset' },
2388 { subdir
=> 'shutdown' },
2389 { subdir
=> 'suspend' },
2390 { subdir
=> 'reboot' },
2396 __PACKAGE__-
>register_method({
2397 name
=> 'vm_status',
2398 path
=> '{vmid}/status/current',
2401 protected
=> 1, # qemu pid files are only readable by root
2402 description
=> "Get virtual machine status.",
2404 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2407 additionalProperties
=> 0,
2409 node
=> get_standard_option
('pve-node'),
2410 vmid
=> get_standard_option
('pve-vmid'),
2416 %$PVE::QemuServer
::vmstatus_return_properties
,
2418 description
=> "HA manager service status.",
2422 description
=> "Qemu VGA configuration supports spice.",
2427 description
=> "Qemu GuestAgent enabled in config.",
2437 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2439 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
2440 my $status = $vmstatus->{$param->{vmid
}};
2442 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
2445 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
2446 my $spice = defined($vga->{type
}) && $vga->{type
} =~ /^virtio/;
2447 $spice ||= PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
2448 $status->{spice
} = 1 if $spice;
2450 $status->{agent
} = 1 if PVE
::QemuServer
::get_qga_key
($conf, 'enabled');
2455 __PACKAGE__-
>register_method({
2457 path
=> '{vmid}/status/start',
2461 description
=> "Start virtual machine.",
2463 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2466 additionalProperties
=> 0,
2468 node
=> get_standard_option
('pve-node'),
2469 vmid
=> get_standard_option
('pve-vmid',
2470 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
2471 skiplock
=> get_standard_option
('skiplock'),
2472 stateuri
=> get_standard_option
('pve-qm-stateuri'),
2473 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
2476 enum
=> ['secure', 'insecure'],
2477 description
=> "Migration traffic is encrypted using an SSH " .
2478 "tunnel by default. On secure, completely private networks " .
2479 "this can be disabled to increase performance.",
2482 migration_network
=> {
2483 type
=> 'string', format
=> 'CIDR',
2484 description
=> "CIDR of the (sub) network that is used for migration.",
2487 machine
=> get_standard_option
('pve-qemu-machine'),
2489 description
=> "Override QEMU's -cpu argument with the given string.",
2493 targetstorage
=> get_standard_option
('pve-targetstorage'),
2495 description
=> "Wait maximal timeout seconds.",
2498 default => 'max(30, vm memory in GiB)',
2509 my $rpcenv = PVE
::RPCEnvironment
::get
();
2510 my $authuser = $rpcenv->get_user();
2512 my $node = extract_param
($param, 'node');
2513 my $vmid = extract_param
($param, 'vmid');
2514 my $timeout = extract_param
($param, 'timeout');
2515 my $machine = extract_param
($param, 'machine');
2517 my $get_root_param = sub {
2518 my $value = extract_param
($param, $_[0]);
2519 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2520 if $value && $authuser ne 'root@pam';
2524 my $stateuri = $get_root_param->('stateuri');
2525 my $skiplock = $get_root_param->('skiplock');
2526 my $migratedfrom = $get_root_param->('migratedfrom');
2527 my $migration_type = $get_root_param->('migration_type');
2528 my $migration_network = $get_root_param->('migration_network');
2529 my $targetstorage = $get_root_param->('targetstorage');
2530 my $force_cpu = $get_root_param->('force-cpu');
2534 if ($targetstorage) {
2535 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2537 $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
2538 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
2542 # read spice ticket from STDIN
2544 my $nbd_protocol_version = 0;
2545 my $replicated_volumes = {};
2546 my $offline_volumes = {};
2547 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2548 while (defined(my $line = <STDIN
>)) {
2550 if ($line =~ m/^spice_ticket: (.+)$/) {
2552 } elsif ($line =~ m/^nbd_protocol_version: (\d+)$/) {
2553 $nbd_protocol_version = $1;
2554 } elsif ($line =~ m/^replicated_volume: (.*)$/) {
2555 $replicated_volumes->{$1} = 1;
2556 } elsif ($line =~ m/^tpmstate0: (.*)$/) { # Deprecated, use offline_volume instead
2557 $offline_volumes->{tpmstate0
} = $1;
2558 } elsif ($line =~ m/^offline_volume: ([^:]+): (.*)$/) {
2559 $offline_volumes->{$1} = $2;
2560 } elsif (!$spice_ticket) {
2561 # fallback for old source node
2562 $spice_ticket = $line;
2564 warn "unknown 'start' parameter on STDIN: '$line'\n";
2569 PVE
::Cluster
::check_cfs_quorum
();
2571 my $storecfg = PVE
::Storage
::config
();
2573 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2577 print "Requesting HA start for VM $vmid\n";
2579 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2580 PVE
::Tools
::run_command
($cmd);
2584 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2591 syslog
('info', "start VM $vmid: $upid\n");
2593 my $migrate_opts = {
2594 migratedfrom
=> $migratedfrom,
2595 spice_ticket
=> $spice_ticket,
2596 network
=> $migration_network,
2597 type
=> $migration_type,
2598 storagemap
=> $storagemap,
2599 nbd_proto_version
=> $nbd_protocol_version,
2600 replicated_volumes
=> $replicated_volumes,
2601 offline_volumes
=> $offline_volumes,
2605 statefile
=> $stateuri,
2606 skiplock
=> $skiplock,
2607 forcemachine
=> $machine,
2608 timeout
=> $timeout,
2609 forcecpu
=> $force_cpu,
2612 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $params, $migrate_opts);
2616 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2620 __PACKAGE__-
>register_method({
2622 path
=> '{vmid}/status/stop',
2626 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2627 "is akin to pulling the power plug of a running computer and may damage the VM data",
2629 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2632 additionalProperties
=> 0,
2634 node
=> get_standard_option
('pve-node'),
2635 vmid
=> get_standard_option
('pve-vmid',
2636 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2637 skiplock
=> get_standard_option
('skiplock'),
2638 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2640 description
=> "Wait maximal timeout seconds.",
2646 description
=> "Do not deactivate storage volumes.",
2659 my $rpcenv = PVE
::RPCEnvironment
::get
();
2660 my $authuser = $rpcenv->get_user();
2662 my $node = extract_param
($param, 'node');
2663 my $vmid = extract_param
($param, 'vmid');
2665 my $skiplock = extract_param
($param, 'skiplock');
2666 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2667 if $skiplock && $authuser ne 'root@pam';
2669 my $keepActive = extract_param
($param, 'keepActive');
2670 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2671 if $keepActive && $authuser ne 'root@pam';
2673 my $migratedfrom = extract_param
($param, 'migratedfrom');
2674 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2675 if $migratedfrom && $authuser ne 'root@pam';
2678 my $storecfg = PVE
::Storage
::config
();
2680 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2685 print "Requesting HA stop for VM $vmid\n";
2687 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
2688 PVE
::Tools
::run_command
($cmd);
2692 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2698 syslog
('info', "stop VM $vmid: $upid\n");
2700 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2701 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2705 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2709 __PACKAGE__-
>register_method({
2711 path
=> '{vmid}/status/reset',
2715 description
=> "Reset virtual machine.",
2717 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2720 additionalProperties
=> 0,
2722 node
=> get_standard_option
('pve-node'),
2723 vmid
=> get_standard_option
('pve-vmid',
2724 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2725 skiplock
=> get_standard_option
('skiplock'),
2734 my $rpcenv = PVE
::RPCEnvironment
::get
();
2736 my $authuser = $rpcenv->get_user();
2738 my $node = extract_param
($param, 'node');
2740 my $vmid = extract_param
($param, 'vmid');
2742 my $skiplock = extract_param
($param, 'skiplock');
2743 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2744 if $skiplock && $authuser ne 'root@pam';
2746 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2751 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2756 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2759 __PACKAGE__-
>register_method({
2760 name
=> 'vm_shutdown',
2761 path
=> '{vmid}/status/shutdown',
2765 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2766 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2768 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2771 additionalProperties
=> 0,
2773 node
=> get_standard_option
('pve-node'),
2774 vmid
=> get_standard_option
('pve-vmid',
2775 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2776 skiplock
=> get_standard_option
('skiplock'),
2778 description
=> "Wait maximal timeout seconds.",
2784 description
=> "Make sure the VM stops.",
2790 description
=> "Do not deactivate storage volumes.",
2803 my $rpcenv = PVE
::RPCEnvironment
::get
();
2804 my $authuser = $rpcenv->get_user();
2806 my $node = extract_param
($param, 'node');
2807 my $vmid = extract_param
($param, 'vmid');
2809 my $skiplock = extract_param
($param, 'skiplock');
2810 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2811 if $skiplock && $authuser ne 'root@pam';
2813 my $keepActive = extract_param
($param, 'keepActive');
2814 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2815 if $keepActive && $authuser ne 'root@pam';
2817 my $storecfg = PVE
::Storage
::config
();
2821 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2822 # otherwise, we will infer a shutdown command, but run into the timeout,
2823 # then when the vm is resumed, it will instantly shutdown
2825 # checking the qmp status here to get feedback to the gui/cli/api
2826 # and the status query should not take too long
2827 if (PVE
::QemuServer
::vm_is_paused
($vmid)) {
2828 if ($param->{forceStop
}) {
2829 warn "VM is paused - stop instead of shutdown\n";
2832 die "VM is paused - cannot shutdown\n";
2836 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2838 my $timeout = $param->{timeout
} // 60;
2842 print "Requesting HA stop for VM $vmid\n";
2844 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
2845 PVE
::Tools
::run_command
($cmd);
2849 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2856 syslog
('info', "shutdown VM $vmid: $upid\n");
2858 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2859 $shutdown, $param->{forceStop
}, $keepActive);
2863 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2867 __PACKAGE__-
>register_method({
2868 name
=> 'vm_reboot',
2869 path
=> '{vmid}/status/reboot',
2873 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2875 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2878 additionalProperties
=> 0,
2880 node
=> get_standard_option
('pve-node'),
2881 vmid
=> get_standard_option
('pve-vmid',
2882 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2884 description
=> "Wait maximal timeout seconds for the shutdown.",
2897 my $rpcenv = PVE
::RPCEnvironment
::get
();
2898 my $authuser = $rpcenv->get_user();
2900 my $node = extract_param
($param, 'node');
2901 my $vmid = extract_param
($param, 'vmid');
2903 die "VM is paused - cannot shutdown\n" if PVE
::QemuServer
::vm_is_paused
($vmid);
2905 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2910 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
2911 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
2915 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2918 __PACKAGE__-
>register_method({
2919 name
=> 'vm_suspend',
2920 path
=> '{vmid}/status/suspend',
2924 description
=> "Suspend virtual machine.",
2926 description
=> "You need 'VM.PowerMgmt' on /vms/{vmid}, and if you have set 'todisk',".
2927 " you need also 'VM.Config.Disk' on /vms/{vmid} and 'Datastore.AllocateSpace'".
2928 " on the storage for the vmstate.",
2929 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2932 additionalProperties
=> 0,
2934 node
=> get_standard_option
('pve-node'),
2935 vmid
=> get_standard_option
('pve-vmid',
2936 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2937 skiplock
=> get_standard_option
('skiplock'),
2942 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2944 statestorage
=> get_standard_option
('pve-storage-id', {
2945 description
=> "The storage for the VM state",
2946 requires
=> 'todisk',
2948 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2958 my $rpcenv = PVE
::RPCEnvironment
::get
();
2959 my $authuser = $rpcenv->get_user();
2961 my $node = extract_param
($param, 'node');
2962 my $vmid = extract_param
($param, 'vmid');
2964 my $todisk = extract_param
($param, 'todisk') // 0;
2966 my $statestorage = extract_param
($param, 'statestorage');
2968 my $skiplock = extract_param
($param, 'skiplock');
2969 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2970 if $skiplock && $authuser ne 'root@pam';
2972 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2974 die "Cannot suspend HA managed VM to disk\n"
2975 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2977 # early check for storage permission, for better user feedback
2979 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
2980 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2982 # check for hostpci devices (suspend will maybe work, resume won't),
2983 # so prevent users from suspending in the first place
2984 for my $key (keys %$conf) {
2985 next if $key !~ /^hostpci\d+/;
2986 die "Cannot suspend VM to disk with assigned PCI devices\n";
2989 if (!$statestorage) {
2990 # get statestorage from config if none is given
2991 my $storecfg = PVE
::Storage
::config
();
2992 $statestorage = PVE
::QemuServer
::find_vmstate_storage
($conf, $storecfg);
2995 $rpcenv->check($authuser, "/storage/$statestorage", ['Datastore.AllocateSpace']);
3001 syslog
('info', "suspend VM $vmid: $upid\n");
3003 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
3008 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
3009 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
3012 __PACKAGE__-
>register_method({
3013 name
=> 'vm_resume',
3014 path
=> '{vmid}/status/resume',
3018 description
=> "Resume virtual machine.",
3020 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
3023 additionalProperties
=> 0,
3025 node
=> get_standard_option
('pve-node'),
3026 vmid
=> get_standard_option
('pve-vmid',
3027 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
3028 skiplock
=> get_standard_option
('skiplock'),
3029 nocheck
=> { type
=> 'boolean', optional
=> 1 },
3039 my $rpcenv = PVE
::RPCEnvironment
::get
();
3041 my $authuser = $rpcenv->get_user();
3043 my $node = extract_param
($param, 'node');
3045 my $vmid = extract_param
($param, 'vmid');
3047 my $skiplock = extract_param
($param, 'skiplock');
3048 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3049 if $skiplock && $authuser ne 'root@pam';
3051 my $nocheck = extract_param
($param, 'nocheck');
3052 raise_param_exc
({ nocheck
=> "Only root may use this option." })
3053 if $nocheck && $authuser ne 'root@pam';
3055 my $to_disk_suspended;
3057 PVE
::QemuConfig-
>lock_config($vmid, sub {
3058 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3059 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
3063 die "VM $vmid not running\n"
3064 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
3069 syslog
('info', "resume VM $vmid: $upid\n");
3071 if (!$to_disk_suspended) {
3072 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
3074 my $storecfg = PVE
::Storage
::config
();
3075 PVE
::QemuServer
::vm_start
($storecfg, $vmid, { skiplock
=> $skiplock });
3081 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
3084 __PACKAGE__-
>register_method({
3085 name
=> 'vm_sendkey',
3086 path
=> '{vmid}/sendkey',
3090 description
=> "Send key event to virtual machine.",
3092 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
3095 additionalProperties
=> 0,
3097 node
=> get_standard_option
('pve-node'),
3098 vmid
=> get_standard_option
('pve-vmid',
3099 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
3100 skiplock
=> get_standard_option
('skiplock'),
3102 description
=> "The key (qemu monitor encoding).",
3107 returns
=> { type
=> 'null'},
3111 my $rpcenv = PVE
::RPCEnvironment
::get
();
3113 my $authuser = $rpcenv->get_user();
3115 my $node = extract_param
($param, 'node');
3117 my $vmid = extract_param
($param, 'vmid');
3119 my $skiplock = extract_param
($param, 'skiplock');
3120 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3121 if $skiplock && $authuser ne 'root@pam';
3123 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
3128 __PACKAGE__-
>register_method({
3129 name
=> 'vm_feature',
3130 path
=> '{vmid}/feature',
3134 description
=> "Check if feature for virtual machine is available.",
3136 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3139 additionalProperties
=> 0,
3141 node
=> get_standard_option
('pve-node'),
3142 vmid
=> get_standard_option
('pve-vmid'),
3144 description
=> "Feature to check.",
3146 enum
=> [ 'snapshot', 'clone', 'copy' ],
3148 snapname
=> get_standard_option
('pve-snapshot-name', {
3156 hasFeature
=> { type
=> 'boolean' },
3159 items
=> { type
=> 'string' },
3166 my $node = extract_param
($param, 'node');
3168 my $vmid = extract_param
($param, 'vmid');
3170 my $snapname = extract_param
($param, 'snapname');
3172 my $feature = extract_param
($param, 'feature');
3174 my $running = PVE
::QemuServer
::check_running
($vmid);
3176 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3179 my $snap = $conf->{snapshots
}->{$snapname};
3180 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3183 my $storecfg = PVE
::Storage
::config
();
3185 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
3186 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
3189 hasFeature
=> $hasFeature,
3190 nodes
=> [ keys %$nodelist ],
3194 __PACKAGE__-
>register_method({
3196 path
=> '{vmid}/clone',
3200 description
=> "Create a copy of virtual machine/template.",
3202 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
3203 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
3204 "'Datastore.AllocateSpace' on any used storage.",
3207 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
3209 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
3210 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
3215 additionalProperties
=> 0,
3217 node
=> get_standard_option
('pve-node'),
3218 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3219 newid
=> get_standard_option
('pve-vmid', {
3220 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
3221 description
=> 'VMID for the clone.' }),
3224 type
=> 'string', format
=> 'dns-name',
3225 description
=> "Set a name for the new VM.",
3230 description
=> "Description for the new VM.",
3234 type
=> 'string', format
=> 'pve-poolid',
3235 description
=> "Add the new VM to the specified pool.",
3237 snapname
=> get_standard_option
('pve-snapshot-name', {
3240 storage
=> get_standard_option
('pve-storage-id', {
3241 description
=> "Target storage for full clone.",
3245 description
=> "Target format for file storage. Only valid for full clone.",
3248 enum
=> [ 'raw', 'qcow2', 'vmdk'],
3253 description
=> "Create a full copy of all disks. This is always done when " .
3254 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
3256 target
=> get_standard_option
('pve-node', {
3257 description
=> "Target node. Only allowed if the original VM is on shared storage.",
3261 description
=> "Override I/O bandwidth limit (in KiB/s).",
3265 default => 'clone limit from datacenter or storage config',
3275 my $rpcenv = PVE
::RPCEnvironment
::get
();
3276 my $authuser = $rpcenv->get_user();
3278 my $node = extract_param
($param, 'node');
3279 my $vmid = extract_param
($param, 'vmid');
3280 my $newid = extract_param
($param, 'newid');
3281 my $pool = extract_param
($param, 'pool');
3283 my $snapname = extract_param
($param, 'snapname');
3284 my $storage = extract_param
($param, 'storage');
3285 my $format = extract_param
($param, 'format');
3286 my $target = extract_param
($param, 'target');
3288 my $localnode = PVE
::INotify
::nodename
();
3290 if ($target && ($target eq $localnode || $target eq 'localhost')) {
3294 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
3296 my $load_and_check = sub {
3297 $rpcenv->check_pool_exist($pool) if defined($pool);
3298 PVE
::Cluster
::check_node_exists
($target) if $target;
3300 my $storecfg = PVE
::Storage
::config
();
3303 # check if storage is enabled on local node
3304 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
3306 # check if storage is available on target node
3307 PVE
::Storage
::storage_check_enabled
($storecfg, $storage, $target);
3308 # clone only works if target storage is shared
3309 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
3310 die "can't clone to non-shared storage '$storage'\n"
3311 if !$scfg->{shared
};
3315 PVE
::Cluster
::check_cfs_quorum
();
3317 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3318 PVE
::QemuConfig-
>check_lock($conf);
3320 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
3321 die "unexpected state change\n" if $verify_running != $running;
3323 die "snapshot '$snapname' does not exist\n"
3324 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
3326 my $full = $param->{full
} // !PVE
::QemuConfig-
>is_template($conf);
3328 die "parameter 'storage' not allowed for linked clones\n"
3329 if defined($storage) && !$full;
3331 die "parameter 'format' not allowed for linked clones\n"
3332 if defined($format) && !$full;
3334 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
3336 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
3338 die "can't clone VM to node '$target' (VM uses local storage)\n"
3339 if $target && !$sharedvm;
3341 my $conffile = PVE
::QemuConfig-
>config_file($newid);
3342 die "unable to create VM $newid: config file already exists\n"
3345 my $newconf = { lock => 'clone' };
3350 foreach my $opt (keys %$oldconf) {
3351 my $value = $oldconf->{$opt};
3353 # do not copy snapshot related info
3354 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
3355 $opt eq 'vmstate' || $opt eq 'snapstate';
3357 # no need to copy unused images, because VMID(owner) changes anyways
3358 next if $opt =~ m/^unused\d+$/;
3360 die "cannot clone TPM state while VM is running\n"
3361 if $full && $running && !$snapname && $opt eq 'tpmstate0';
3363 # always change MAC! address
3364 if ($opt =~ m/^net(\d+)$/) {
3365 my $net = PVE
::QemuServer
::parse_net
($value);
3366 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
3367 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
3368 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
3369 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
3370 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
3371 die "unable to parse drive options for '$opt'\n" if !$drive;
3372 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
3373 $newconf->{$opt} = $value; # simply copy configuration
3375 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
3376 die "Full clone feature is not supported for drive '$opt'\n"
3377 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
3378 $fullclone->{$opt} = 1;
3380 # not full means clone instead of copy
3381 die "Linked clone feature is not supported for drive '$opt'\n"
3382 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
3384 $drives->{$opt} = $drive;
3385 next if PVE
::QemuServer
::drive_is_cloudinit
($drive);
3386 push @$vollist, $drive->{file
};
3389 # copy everything else
3390 $newconf->{$opt} = $value;
3394 return ($conffile, $newconf, $oldconf, $vollist, $drives, $fullclone);
3398 my ($conffile, $newconf, $oldconf, $vollist, $drives, $fullclone) = $load_and_check->();
3399 my $storecfg = PVE
::Storage
::config
();
3401 # auto generate a new uuid
3402 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
3403 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
3404 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
3405 # auto generate a new vmgenid only if the option was set for template
3406 if ($newconf->{vmgenid
}) {
3407 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
3410 delete $newconf->{template
};
3412 if ($param->{name
}) {
3413 $newconf->{name
} = $param->{name
};
3415 $newconf->{name
} = "Copy-of-VM-" . ($oldconf->{name
} // $vmid);
3418 if ($param->{description
}) {
3419 $newconf->{description
} = $param->{description
};
3422 # create empty/temp config - this fails if VM already exists on other node
3423 # FIXME use PVE::QemuConfig->create_and_lock_config and adapt code
3424 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
3426 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
3428 my $newvollist = [];
3435 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3437 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
3439 my $bwlimit = extract_param
($param, 'bwlimit');
3441 my $total_jobs = scalar(keys %{$drives});
3444 foreach my $opt (sort keys %$drives) {
3445 my $drive = $drives->{$opt};
3446 my $skipcomplete = ($total_jobs != $i); # finish after last drive
3447 my $completion = $skipcomplete ?
'skip' : 'complete';
3449 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
3450 my $storage_list = [ $src_sid ];
3451 push @$storage_list, $storage if defined($storage);
3452 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
3456 running
=> $running,
3459 snapname
=> $snapname,
3465 storage
=> $storage,
3469 $dest_info->{efisize
} = PVE
::QemuServer
::get_efivars_size
($oldconf)
3470 if $opt eq 'efidisk0';
3472 my $newdrive = PVE
::QemuServer
::clone_disk
(
3484 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($newdrive);
3486 PVE
::QemuConfig-
>write_config($newid, $newconf);
3490 delete $newconf->{lock};
3492 # do not write pending changes
3493 if (my @changes = keys %{$newconf->{pending
}}) {
3494 my $pending = join(',', @changes);
3495 warn "found pending changes for '$pending', discarding for clone\n";
3496 delete $newconf->{pending
};
3499 PVE
::QemuConfig-
>write_config($newid, $newconf);
3502 # always deactivate volumes - avoid lvm LVs to be active on several nodes
3503 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
3504 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
3506 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
3507 die "Failed to move config to node '$target' - rename failed: $!\n"
3508 if !rename($conffile, $newconffile);
3511 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
3514 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
3515 sleep 1; # some storage like rbd need to wait before release volume - really?
3517 foreach my $volid (@$newvollist) {
3518 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3522 PVE
::Firewall
::remove_vmfw_conf
($newid);
3524 unlink $conffile; # avoid races -> last thing before die
3526 die "clone failed: $err";
3532 # Aquire exclusive lock lock for $newid
3533 my $lock_target_vm = sub {
3534 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
3537 my $lock_source_vm = sub {
3538 # exclusive lock if VM is running - else shared lock is enough;
3540 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $lock_target_vm);
3542 return PVE
::QemuConfig-
>lock_config_shared($vmid, 1, $lock_target_vm);
3546 $load_and_check->(); # early checks before forking/locking
3548 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $lock_source_vm);
3551 __PACKAGE__-
>register_method({
3552 name
=> 'move_vm_disk',
3553 path
=> '{vmid}/move_disk',
3557 description
=> "Move volume to different storage or to a different VM.",
3559 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
3560 "and 'Datastore.AllocateSpace' permissions on the storage. To move ".
3561 "a disk to another VM, you need the permissions on the target VM as well.",
3562 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3565 additionalProperties
=> 0,
3567 node
=> get_standard_option
('pve-node'),
3568 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3569 'target-vmid' => get_standard_option
('pve-vmid', {
3570 completion
=> \
&PVE
::QemuServer
::complete_vmid
,
3575 description
=> "The disk you want to move.",
3576 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names_with_unused
()],
3578 storage
=> get_standard_option
('pve-storage-id', {
3579 description
=> "Target storage.",
3580 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3585 description
=> "Target Format.",
3586 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
3591 description
=> "Delete the original disk after successful copy. By default the"
3592 ." original disk is kept as unused disk.",
3598 description
=> 'Prevent changes if current configuration file has different SHA1"
3599 ." digest. This can be used to prevent concurrent modifications.',
3604 description
=> "Override I/O bandwidth limit (in KiB/s).",
3608 default => 'move limit from datacenter or storage config',
3612 description
=> "The config key the disk will be moved to on the target VM"
3613 ." (for example, ide0 or scsi1). Default is the source disk key.",
3614 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names_with_unused
()],
3617 'target-digest' => {
3619 description
=> 'Prevent changes if the current config file of the target VM has a"
3620 ." different SHA1 digest. This can be used to detect concurrent modifications.',
3628 description
=> "the task ID.",
3633 my $rpcenv = PVE
::RPCEnvironment
::get
();
3634 my $authuser = $rpcenv->get_user();
3636 my $node = extract_param
($param, 'node');
3637 my $vmid = extract_param
($param, 'vmid');
3638 my $target_vmid = extract_param
($param, 'target-vmid');
3639 my $digest = extract_param
($param, 'digest');
3640 my $target_digest = extract_param
($param, 'target-digest');
3641 my $disk = extract_param
($param, 'disk');
3642 my $target_disk = extract_param
($param, 'target-disk') // $disk;
3643 my $storeid = extract_param
($param, 'storage');
3644 my $format = extract_param
($param, 'format');
3646 my $storecfg = PVE
::Storage
::config
();
3648 my $load_and_check_move = sub {
3649 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3650 PVE
::QemuConfig-
>check_lock($conf);
3652 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
3654 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3656 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3658 die "disk '$disk' has no associated volume\n" if !$drive->{file
};
3659 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3661 my $old_volid = $drive->{file
};
3663 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3664 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3668 die "you can't move to the same storage with same format\n"
3669 if $oldstoreid eq $storeid && (!$format || !$oldfmt || $oldfmt eq $format);
3671 # this only checks snapshots because $disk is passed!
3672 my $snapshotted = PVE
::QemuServer
::Drive
::is_volume_in_use
(
3678 die "you can't move a disk with snapshots and delete the source\n"
3679 if $snapshotted && $param->{delete};
3681 return ($conf, $drive, $oldstoreid, $snapshotted);
3684 my $move_updatefn = sub {
3685 my ($conf, $drive, $oldstoreid, $snapshotted) = $load_and_check_move->();
3686 my $old_volid = $drive->{file
};
3688 PVE
::Cluster
::log_msg
(
3691 "move disk VM $vmid: move --disk $disk --storage $storeid"
3694 my $running = PVE
::QemuServer
::check_running
($vmid);
3696 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3698 my $newvollist = [];
3704 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3706 warn "moving disk with snapshots, snapshots will not be moved!\n"
3709 my $bwlimit = extract_param
($param, 'bwlimit');
3710 my $movelimit = PVE
::Storage
::get_bandwidth_limit
(
3712 [$oldstoreid, $storeid],
3718 running
=> $running,
3727 storage
=> $storeid,
3731 $dest_info->{efisize
} = PVE
::QemuServer
::get_efivars_size
($conf)
3732 if $disk eq 'efidisk0';
3734 my $newdrive = PVE
::QemuServer
::clone_disk
(
3745 $conf->{$disk} = PVE
::QemuServer
::print_drive
($newdrive);
3747 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3749 # convert moved disk to base if part of template
3750 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3751 if PVE
::QemuConfig-
>is_template($conf);
3753 PVE
::QemuConfig-
>write_config($vmid, $conf);
3755 my $do_trim = PVE
::QemuServer
::get_qga_key
($conf, 'fstrim_cloned_disks');
3756 if ($running && $do_trim && PVE
::QemuServer
::qga_check_running
($vmid)) {
3757 eval { mon_cmd
($vmid, "guest-fstrim") };
3761 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3762 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3768 foreach my $volid (@$newvollist) {
3769 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid) };
3772 die "storage migration failed: $err";
3775 if ($param->{delete}) {
3777 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3778 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3784 my $load_and_check_reassign_configs = sub {
3785 my $vmlist = PVE
::Cluster
::get_vmlist
()->{ids
};
3787 die "could not find VM ${vmid}\n" if !exists($vmlist->{$vmid});
3788 die "could not find target VM ${target_vmid}\n" if !exists($vmlist->{$target_vmid});
3790 my $source_node = $vmlist->{$vmid}->{node
};
3791 my $target_node = $vmlist->{$target_vmid}->{node
};
3793 die "Both VMs need to be on the same node ($source_node != $target_node)\n"
3794 if $source_node ne $target_node;
3796 my $source_conf = PVE
::QemuConfig-
>load_config($vmid);
3797 PVE
::QemuConfig-
>check_lock($source_conf);
3798 my $target_conf = PVE
::QemuConfig-
>load_config($target_vmid);
3799 PVE
::QemuConfig-
>check_lock($target_conf);
3801 die "Can't move disks from or to template VMs\n"
3802 if ($source_conf->{template
} || $target_conf->{template
});
3805 eval { PVE
::Tools
::assert_if_modified
($digest, $source_conf->{digest
}) };
3806 die "VM ${vmid}: $@" if $@;
3809 if ($target_digest) {
3810 eval { PVE
::Tools
::assert_if_modified
($target_digest, $target_conf->{digest
}) };
3811 die "VM ${target_vmid}: $@" if $@;
3814 die "Disk '${disk}' for VM '$vmid' does not exist\n" if !defined($source_conf->{$disk});
3816 die "Target disk key '${target_disk}' is already in use for VM '$target_vmid'\n"
3817 if $target_conf->{$target_disk};
3819 my $drive = PVE
::QemuServer
::parse_drive
(
3821 $source_conf->{$disk},
3823 die "failed to parse source disk - $@\n" if !$drive;
3825 my $source_volid = $drive->{file
};
3827 die "disk '${disk}' has no associated volume\n" if !$source_volid;
3828 die "CD drive contents can't be moved to another VM\n"
3829 if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3831 my $storeid = PVE
::Storage
::parse_volume_id
($source_volid, 1);
3832 die "Volume '$source_volid' not managed by PVE\n" if !defined($storeid);
3834 die "Can't move disk used by a snapshot to another VM\n"
3835 if PVE
::QemuServer
::Drive
::is_volume_in_use
($storecfg, $source_conf, $disk, $source_volid);
3836 die "Storage does not support moving of this disk to another VM\n"
3837 if (!PVE
::Storage
::volume_has_feature
($storecfg, 'rename', $source_volid));
3838 die "Cannot move disk to another VM while the source VM is running - detach first\n"
3839 if PVE
::QemuServer
::check_running
($vmid) && $disk !~ m/^unused\d+$/;
3841 # now re-parse using target disk slot format
3842 if ($target_disk =~ /^unused\d+$/) {
3843 $drive = PVE
::QemuServer
::parse_drive
(
3848 $drive = PVE
::QemuServer
::parse_drive
(
3850 $source_conf->{$disk},
3853 die "failed to parse source disk for target disk format - $@\n" if !$drive;
3855 my $repl_conf = PVE
::ReplicationConfig-
>new();
3856 if ($repl_conf->check_for_existing_jobs($target_vmid, 1)) {
3857 my $format = (PVE
::Storage
::parse_volname
($storecfg, $source_volid))[6];
3858 die "Cannot move disk to a replicated VM. Storage does not support replication!\n"
3859 if !PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
3862 return ($source_conf, $target_conf, $drive);
3867 print STDERR
"$msg\n";
3870 my $disk_reassignfn = sub {
3871 return PVE
::QemuConfig-
>lock_config($vmid, sub {
3872 return PVE
::QemuConfig-
>lock_config($target_vmid, sub {
3873 my ($source_conf, $target_conf, $drive) = &$load_and_check_reassign_configs();
3875 my $source_volid = $drive->{file
};
3877 print "moving disk '$disk' from VM '$vmid' to '$target_vmid'\n";
3878 my ($storeid, $source_volname) = PVE
::Storage
::parse_volume_id
($source_volid);
3880 my $fmt = (PVE
::Storage
::parse_volname
($storecfg, $source_volid))[6];
3882 my $new_volid = PVE
::Storage
::rename_volume
(
3888 $drive->{file
} = $new_volid;
3890 my $boot_order = PVE
::QemuServer
::device_bootorder
($source_conf);
3891 if (defined(delete $boot_order->{$disk})) {
3892 print "removing disk '$disk' from boot order config\n";
3893 my $boot_devs = [ sort { $boot_order->{$a} <=> $boot_order->{$b} } keys %$boot_order ];
3894 $source_conf->{boot
} = PVE
::QemuServer
::print_bootorder
($boot_devs);
3897 delete $source_conf->{$disk};
3898 print "removing disk '${disk}' from VM '${vmid}' config\n";
3899 PVE
::QemuConfig-
>write_config($vmid, $source_conf);
3901 my $drive_string = PVE
::QemuServer
::print_drive
($drive);
3903 if ($target_disk =~ /^unused\d+$/) {
3904 $target_conf->{$target_disk} = $drive_string;
3905 PVE
::QemuConfig-
>write_config($target_vmid, $target_conf);
3910 vmid
=> $target_vmid,
3911 digest
=> $target_digest,
3912 $target_disk => $drive_string,
3918 # remove possible replication snapshots
3919 if (PVE
::Storage
::volume_has_feature
(
3925 PVE
::Replication
::prepare
(
3935 print "Failed to remove replication snapshots on moved disk " .
3936 "'$target_disk'. Manual cleanup could be necessary.\n";
3943 if ($target_vmid && $storeid) {
3944 my $msg = "either set 'storage' or 'target-vmid', but not both";
3945 raise_param_exc
({ 'target-vmid' => $msg, 'storage' => $msg });
3946 } elsif ($target_vmid) {
3947 $rpcenv->check_vm_perm($authuser, $target_vmid, undef, ['VM.Config.Disk'])
3948 if $authuser ne 'root@pam';
3950 raise_param_exc
({ 'target-vmid' => "must be different than source VMID to reassign disk" })
3951 if $vmid eq $target_vmid;
3953 my (undef, undef, $drive) = &$load_and_check_reassign_configs();
3954 my $storage = PVE
::Storage
::parse_volume_id
($drive->{file
});
3955 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace']);
3957 return $rpcenv->fork_worker(
3959 "${vmid}-${disk}>${target_vmid}-${target_disk}",
3963 } elsif ($storeid) {
3964 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3966 die "cannot move disk '$disk', only configured disks can be moved to another storage\n"
3967 if $disk =~ m/^unused\d+$/;
3969 $load_and_check_move->(); # early checks before forking/locking
3972 PVE
::QemuConfig-
>lock_config($vmid, $move_updatefn);
3975 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3977 my $msg = "both 'storage' and 'target-vmid' missing, either needs to be set";
3978 raise_param_exc
({ 'target-vmid' => $msg, 'storage' => $msg });
3982 my $check_vm_disks_local = sub {
3983 my ($storecfg, $vmconf, $vmid) = @_;
3985 my $local_disks = {};
3987 # add some more information to the disks e.g. cdrom
3988 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3989 my ($volid, $attr) = @_;
3991 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3993 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3994 return if $scfg->{shared
};
3996 # The shared attr here is just a special case where the vdisk
3997 # is marked as shared manually
3998 return if $attr->{shared
};
3999 return if $attr->{cdrom
} and $volid eq "none";
4001 if (exists $local_disks->{$volid}) {
4002 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
4004 $local_disks->{$volid} = $attr;
4005 # ensure volid is present in case it's needed
4006 $local_disks->{$volid}->{volid
} = $volid;
4010 return $local_disks;
4013 __PACKAGE__-
>register_method({
4014 name
=> 'migrate_vm_precondition',
4015 path
=> '{vmid}/migrate',
4019 description
=> "Get preconditions for migration.",
4021 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
4024 additionalProperties
=> 0,
4026 node
=> get_standard_option
('pve-node'),
4027 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4028 target
=> get_standard_option
('pve-node', {
4029 description
=> "Target node.",
4030 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
4038 running
=> { type
=> 'boolean' },
4042 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
4044 not_allowed_nodes
=> {
4047 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
4051 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
4053 local_resources
=> {
4055 description
=> "List local resources e.g. pci, usb"
4062 my $rpcenv = PVE
::RPCEnvironment
::get
();
4064 my $authuser = $rpcenv->get_user();
4066 PVE
::Cluster
::check_cfs_quorum
();
4070 my $vmid = extract_param
($param, 'vmid');
4071 my $target = extract_param
($param, 'target');
4072 my $localnode = PVE
::INotify
::nodename
();
4076 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
4077 my $storecfg = PVE
::Storage
::config
();
4080 # try to detect errors early
4081 PVE
::QemuConfig-
>check_lock($vmconf);
4083 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
4085 # if vm is not running, return target nodes where local storage is available
4086 # for offline migration
4087 if (!$res->{running
}) {
4088 $res->{allowed_nodes
} = [];
4089 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
4090 delete $checked_nodes->{$localnode};
4092 foreach my $node (keys %$checked_nodes) {
4093 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
4094 push @{$res->{allowed_nodes
}}, $node;
4098 $res->{not_allowed_nodes
} = $checked_nodes;
4102 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
4103 $res->{local_disks
} = [ values %$local_disks ];;
4105 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
4107 $res->{local_resources
} = $local_resources;
4114 __PACKAGE__-
>register_method({
4115 name
=> 'migrate_vm',
4116 path
=> '{vmid}/migrate',
4120 description
=> "Migrate virtual machine. Creates a new migration task.",
4122 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
4125 additionalProperties
=> 0,
4127 node
=> get_standard_option
('pve-node'),
4128 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4129 target
=> get_standard_option
('pve-node', {
4130 description
=> "Target node.",
4131 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
4135 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
4140 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
4145 enum
=> ['secure', 'insecure'],
4146 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
4149 migration_network
=> {
4150 type
=> 'string', format
=> 'CIDR',
4151 description
=> "CIDR of the (sub) network that is used for migration.",
4154 "with-local-disks" => {
4156 description
=> "Enable live storage migration for local disk",
4159 targetstorage
=> get_standard_option
('pve-targetstorage', {
4160 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
4163 description
=> "Override I/O bandwidth limit (in KiB/s).",
4167 default => 'migrate limit from datacenter or storage config',
4173 description
=> "the task ID.",
4178 my $rpcenv = PVE
::RPCEnvironment
::get
();
4179 my $authuser = $rpcenv->get_user();
4181 my $target = extract_param
($param, 'target');
4183 my $localnode = PVE
::INotify
::nodename
();
4184 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
4186 PVE
::Cluster
::check_cfs_quorum
();
4188 PVE
::Cluster
::check_node_exists
($target);
4190 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
4192 my $vmid = extract_param
($param, 'vmid');
4194 raise_param_exc
({ force
=> "Only root may use this option." })
4195 if $param->{force
} && $authuser ne 'root@pam';
4197 raise_param_exc
({ migration_type
=> "Only root may use this option." })
4198 if $param->{migration_type
} && $authuser ne 'root@pam';
4200 # allow root only until better network permissions are available
4201 raise_param_exc
({ migration_network
=> "Only root may use this option." })
4202 if $param->{migration_network
} && $authuser ne 'root@pam';
4205 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4207 # try to detect errors early
4209 PVE
::QemuConfig-
>check_lock($conf);
4211 if (PVE
::QemuServer
::check_running
($vmid)) {
4212 die "can't migrate running VM without --online\n" if !$param->{online
};
4214 my $repl_conf = PVE
::ReplicationConfig-
>new();
4215 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
4216 my $is_replicated_to_target = defined($repl_conf->find_local_replication_job($vmid, $target));
4217 if (!$param->{force
} && $is_replicated && !$is_replicated_to_target) {
4218 die "Cannot live-migrate replicated VM to node '$target' - not a replication " .
4219 "target. Use 'force' to override.\n";
4222 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
4223 $param->{online
} = 0;
4226 my $storecfg = PVE
::Storage
::config
();
4227 if (my $targetstorage = $param->{targetstorage
}) {
4228 my $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
4229 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
4232 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk'])
4233 if !defined($storagemap->{identity
});
4235 foreach my $target_sid (values %{$storagemap->{entries
}}) {
4236 $check_storage_access_migrate->($rpcenv, $authuser, $storecfg, $target_sid, $target);
4239 $check_storage_access_migrate->($rpcenv, $authuser, $storecfg, $storagemap->{default}, $target)
4240 if $storagemap->{default};
4242 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target)
4243 if $storagemap->{identity
};
4245 $param->{storagemap
} = $storagemap;
4247 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
4250 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
4255 print "Requesting HA migration for VM $vmid to node $target\n";
4257 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
4258 PVE
::Tools
::run_command
($cmd);
4262 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
4267 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
4271 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
4274 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
4279 __PACKAGE__-
>register_method({
4281 path
=> '{vmid}/monitor',
4285 description
=> "Execute Qemu monitor commands.",
4287 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
4288 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
4291 additionalProperties
=> 0,
4293 node
=> get_standard_option
('pve-node'),
4294 vmid
=> get_standard_option
('pve-vmid'),
4297 description
=> "The monitor command.",
4301 returns
=> { type
=> 'string'},
4305 my $rpcenv = PVE
::RPCEnvironment
::get
();
4306 my $authuser = $rpcenv->get_user();
4309 my $command = shift;
4310 return $command =~ m/^\s*info(\s+|$)/
4311 || $command =~ m/^\s*help\s*$/;
4314 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
4315 if !&$is_ro($param->{command
});
4317 my $vmid = $param->{vmid
};
4319 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
4323 $res = PVE
::QemuServer
::Monitor
::hmp_cmd
($vmid, $param->{command
});
4325 $res = "ERROR: $@" if $@;
4330 __PACKAGE__-
>register_method({
4331 name
=> 'resize_vm',
4332 path
=> '{vmid}/resize',
4336 description
=> "Extend volume size.",
4338 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
4341 additionalProperties
=> 0,
4343 node
=> get_standard_option
('pve-node'),
4344 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4345 skiplock
=> get_standard_option
('skiplock'),
4348 description
=> "The disk you want to resize.",
4349 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
4353 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
4354 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.",
4358 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
4364 returns
=> { type
=> 'null'},
4368 my $rpcenv = PVE
::RPCEnvironment
::get
();
4370 my $authuser = $rpcenv->get_user();
4372 my $node = extract_param
($param, 'node');
4374 my $vmid = extract_param
($param, 'vmid');
4376 my $digest = extract_param
($param, 'digest');
4378 my $disk = extract_param
($param, 'disk');
4380 my $sizestr = extract_param
($param, 'size');
4382 my $skiplock = extract_param
($param, 'skiplock');
4383 raise_param_exc
({ skiplock
=> "Only root may use this option." })
4384 if $skiplock && $authuser ne 'root@pam';
4386 my $storecfg = PVE
::Storage
::config
();
4388 my $updatefn = sub {
4390 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4392 die "checksum missmatch (file change by other user?)\n"
4393 if $digest && $digest ne $conf->{digest
};
4394 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
4396 die "disk '$disk' does not exist\n" if !$conf->{$disk};
4398 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
4400 my (undef, undef, undef, undef, undef, undef, $format) =
4401 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
4403 die "can't resize volume: $disk if snapshot exists\n"
4404 if %{$conf->{snapshots
}} && $format eq 'qcow2';
4406 my $volid = $drive->{file
};
4408 die "disk '$disk' has no associated volume\n" if !$volid;
4410 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
4412 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
4414 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
4416 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
4417 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
4419 die "Could not determine current size of volume '$volid'\n" if !defined($size);
4421 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
4422 my ($ext, $newsize, $unit) = ($1, $2, $4);
4425 $newsize = $newsize * 1024;
4426 } elsif ($unit eq 'M') {
4427 $newsize = $newsize * 1024 * 1024;
4428 } elsif ($unit eq 'G') {
4429 $newsize = $newsize * 1024 * 1024 * 1024;
4430 } elsif ($unit eq 'T') {
4431 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
4434 $newsize += $size if $ext;
4435 $newsize = int($newsize);
4437 die "shrinking disks is not supported\n" if $newsize < $size;
4439 return if $size == $newsize;
4441 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
4443 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
4445 $drive->{size
} = $newsize;
4446 $conf->{$disk} = PVE
::QemuServer
::print_drive
($drive);
4448 PVE
::QemuConfig-
>write_config($vmid, $conf);
4451 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4455 __PACKAGE__-
>register_method({
4456 name
=> 'snapshot_list',
4457 path
=> '{vmid}/snapshot',
4459 description
=> "List all snapshots.",
4461 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4464 protected
=> 1, # qemu pid files are only readable by root
4466 additionalProperties
=> 0,
4468 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4469 node
=> get_standard_option
('pve-node'),
4478 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
4482 description
=> "Snapshot includes RAM.",
4487 description
=> "Snapshot description.",
4491 description
=> "Snapshot creation time",
4493 renderer
=> 'timestamp',
4497 description
=> "Parent snapshot identifier.",
4503 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
4508 my $vmid = $param->{vmid
};
4510 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4511 my $snaphash = $conf->{snapshots
} || {};
4515 foreach my $name (keys %$snaphash) {
4516 my $d = $snaphash->{$name};
4519 snaptime
=> $d->{snaptime
} || 0,
4520 vmstate
=> $d->{vmstate
} ?
1 : 0,
4521 description
=> $d->{description
} || '',
4523 $item->{parent
} = $d->{parent
} if $d->{parent
};
4524 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
4528 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
4531 digest
=> $conf->{digest
},
4532 running
=> $running,
4533 description
=> "You are here!",
4535 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
4537 push @$res, $current;
4542 __PACKAGE__-
>register_method({
4544 path
=> '{vmid}/snapshot',
4548 description
=> "Snapshot a VM.",
4550 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4553 additionalProperties
=> 0,
4555 node
=> get_standard_option
('pve-node'),
4556 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4557 snapname
=> get_standard_option
('pve-snapshot-name'),
4561 description
=> "Save the vmstate",
4566 description
=> "A textual description or comment.",
4572 description
=> "the task ID.",
4577 my $rpcenv = PVE
::RPCEnvironment
::get
();
4579 my $authuser = $rpcenv->get_user();
4581 my $node = extract_param
($param, 'node');
4583 my $vmid = extract_param
($param, 'vmid');
4585 my $snapname = extract_param
($param, 'snapname');
4587 die "unable to use snapshot name 'current' (reserved name)\n"
4588 if $snapname eq 'current';
4590 die "unable to use snapshot name 'pending' (reserved name)\n"
4591 if lc($snapname) eq 'pending';
4594 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
4595 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
4596 $param->{description
});
4599 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
4602 __PACKAGE__-
>register_method({
4603 name
=> 'snapshot_cmd_idx',
4604 path
=> '{vmid}/snapshot/{snapname}',
4611 additionalProperties
=> 0,
4613 vmid
=> get_standard_option
('pve-vmid'),
4614 node
=> get_standard_option
('pve-node'),
4615 snapname
=> get_standard_option
('pve-snapshot-name'),
4624 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
4631 push @$res, { cmd
=> 'rollback' };
4632 push @$res, { cmd
=> 'config' };
4637 __PACKAGE__-
>register_method({
4638 name
=> 'update_snapshot_config',
4639 path
=> '{vmid}/snapshot/{snapname}/config',
4643 description
=> "Update snapshot metadata.",
4645 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4648 additionalProperties
=> 0,
4650 node
=> get_standard_option
('pve-node'),
4651 vmid
=> get_standard_option
('pve-vmid'),
4652 snapname
=> get_standard_option
('pve-snapshot-name'),
4656 description
=> "A textual description or comment.",
4660 returns
=> { type
=> 'null' },
4664 my $rpcenv = PVE
::RPCEnvironment
::get
();
4666 my $authuser = $rpcenv->get_user();
4668 my $vmid = extract_param
($param, 'vmid');
4670 my $snapname = extract_param
($param, 'snapname');
4672 return if !defined($param->{description
});
4674 my $updatefn = sub {
4676 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4678 PVE
::QemuConfig-
>check_lock($conf);
4680 my $snap = $conf->{snapshots
}->{$snapname};
4682 die "snapshot '$snapname' does not exist\n" if !defined($snap);
4684 $snap->{description
} = $param->{description
} if defined($param->{description
});
4686 PVE
::QemuConfig-
>write_config($vmid, $conf);
4689 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4694 __PACKAGE__-
>register_method({
4695 name
=> 'get_snapshot_config',
4696 path
=> '{vmid}/snapshot/{snapname}/config',
4699 description
=> "Get snapshot configuration",
4701 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback', 'VM.Audit' ], any
=> 1],
4704 additionalProperties
=> 0,
4706 node
=> get_standard_option
('pve-node'),
4707 vmid
=> get_standard_option
('pve-vmid'),
4708 snapname
=> get_standard_option
('pve-snapshot-name'),
4711 returns
=> { type
=> "object" },
4715 my $rpcenv = PVE
::RPCEnvironment
::get
();
4717 my $authuser = $rpcenv->get_user();
4719 my $vmid = extract_param
($param, 'vmid');
4721 my $snapname = extract_param
($param, 'snapname');
4723 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4725 my $snap = $conf->{snapshots
}->{$snapname};
4727 die "snapshot '$snapname' does not exist\n" if !defined($snap);
4732 __PACKAGE__-
>register_method({
4734 path
=> '{vmid}/snapshot/{snapname}/rollback',
4738 description
=> "Rollback VM state to specified snapshot.",
4740 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
4743 additionalProperties
=> 0,
4745 node
=> get_standard_option
('pve-node'),
4746 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4747 snapname
=> get_standard_option
('pve-snapshot-name'),
4752 description
=> "the task ID.",
4757 my $rpcenv = PVE
::RPCEnvironment
::get
();
4759 my $authuser = $rpcenv->get_user();
4761 my $node = extract_param
($param, 'node');
4763 my $vmid = extract_param
($param, 'vmid');
4765 my $snapname = extract_param
($param, 'snapname');
4768 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
4769 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
4773 # hold migration lock, this makes sure that nobody create replication snapshots
4774 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
4777 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
4780 __PACKAGE__-
>register_method({
4781 name
=> 'delsnapshot',
4782 path
=> '{vmid}/snapshot/{snapname}',
4786 description
=> "Delete a VM snapshot.",
4788 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4791 additionalProperties
=> 0,
4793 node
=> get_standard_option
('pve-node'),
4794 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4795 snapname
=> get_standard_option
('pve-snapshot-name'),
4799 description
=> "For removal from config file, even if removing disk snapshots fails.",
4805 description
=> "the task ID.",
4810 my $rpcenv = PVE
::RPCEnvironment
::get
();
4812 my $authuser = $rpcenv->get_user();
4814 my $node = extract_param
($param, 'node');
4816 my $vmid = extract_param
($param, 'vmid');
4818 my $snapname = extract_param
($param, 'snapname');
4821 my $do_delete = sub {
4823 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
4824 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
4828 if ($param->{force
}) {
4831 eval { PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $do_delete); };
4833 die $err if $lock_obtained;
4834 die "Failed to obtain guest migration lock - replication running?\n";
4839 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
4842 __PACKAGE__-
>register_method({
4844 path
=> '{vmid}/template',
4848 description
=> "Create a Template.",
4850 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
4851 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
4854 additionalProperties
=> 0,
4856 node
=> get_standard_option
('pve-node'),
4857 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
4861 description
=> "If you want to convert only 1 disk to base image.",
4862 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
4869 description
=> "the task ID.",
4874 my $rpcenv = PVE
::RPCEnvironment
::get
();
4876 my $authuser = $rpcenv->get_user();
4878 my $node = extract_param
($param, 'node');
4880 my $vmid = extract_param
($param, 'vmid');
4882 my $disk = extract_param
($param, 'disk');
4884 my $load_and_check = sub {
4885 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4887 PVE
::QemuConfig-
>check_lock($conf);
4889 die "unable to create template, because VM contains snapshots\n"
4890 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
4892 die "you can't convert a template to a template\n"
4893 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
4895 die "you can't convert a VM to template if VM is running\n"
4896 if PVE
::QemuServer
::check_running
($vmid);
4901 $load_and_check->();
4904 PVE
::QemuConfig-
>lock_config($vmid, sub {
4905 my $conf = $load_and_check->();
4907 $conf->{template
} = 1;
4908 PVE
::QemuConfig-
>write_config($vmid, $conf);
4910 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
4914 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4917 __PACKAGE__-
>register_method({
4918 name
=> 'cloudinit_generated_config_dump',
4919 path
=> '{vmid}/cloudinit/dump',
4922 description
=> "Get automatically generated cloudinit config.",
4924 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4927 additionalProperties
=> 0,
4929 node
=> get_standard_option
('pve-node'),
4930 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4932 description
=> 'Config type.',
4934 enum
=> ['user', 'network', 'meta'],
4944 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4946 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});