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']);
2981 if (!$statestorage) {
2982 # get statestorage from config if none is given
2983 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2984 my $storecfg = PVE
::Storage
::config
();
2985 $statestorage = PVE
::QemuServer
::find_vmstate_storage
($conf, $storecfg);
2988 $rpcenv->check($authuser, "/storage/$statestorage", ['Datastore.AllocateSpace']);
2994 syslog
('info', "suspend VM $vmid: $upid\n");
2996 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
3001 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
3002 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
3005 __PACKAGE__-
>register_method({
3006 name
=> 'vm_resume',
3007 path
=> '{vmid}/status/resume',
3011 description
=> "Resume virtual machine.",
3013 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
3016 additionalProperties
=> 0,
3018 node
=> get_standard_option
('pve-node'),
3019 vmid
=> get_standard_option
('pve-vmid',
3020 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
3021 skiplock
=> get_standard_option
('skiplock'),
3022 nocheck
=> { type
=> 'boolean', optional
=> 1 },
3032 my $rpcenv = PVE
::RPCEnvironment
::get
();
3034 my $authuser = $rpcenv->get_user();
3036 my $node = extract_param
($param, 'node');
3038 my $vmid = extract_param
($param, 'vmid');
3040 my $skiplock = extract_param
($param, 'skiplock');
3041 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3042 if $skiplock && $authuser ne 'root@pam';
3044 my $nocheck = extract_param
($param, 'nocheck');
3045 raise_param_exc
({ nocheck
=> "Only root may use this option." })
3046 if $nocheck && $authuser ne 'root@pam';
3048 my $to_disk_suspended;
3050 PVE
::QemuConfig-
>lock_config($vmid, sub {
3051 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3052 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
3056 die "VM $vmid not running\n"
3057 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
3062 syslog
('info', "resume VM $vmid: $upid\n");
3064 if (!$to_disk_suspended) {
3065 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
3067 my $storecfg = PVE
::Storage
::config
();
3068 PVE
::QemuServer
::vm_start
($storecfg, $vmid, { skiplock
=> $skiplock });
3074 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
3077 __PACKAGE__-
>register_method({
3078 name
=> 'vm_sendkey',
3079 path
=> '{vmid}/sendkey',
3083 description
=> "Send key event to virtual machine.",
3085 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
3088 additionalProperties
=> 0,
3090 node
=> get_standard_option
('pve-node'),
3091 vmid
=> get_standard_option
('pve-vmid',
3092 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
3093 skiplock
=> get_standard_option
('skiplock'),
3095 description
=> "The key (qemu monitor encoding).",
3100 returns
=> { type
=> 'null'},
3104 my $rpcenv = PVE
::RPCEnvironment
::get
();
3106 my $authuser = $rpcenv->get_user();
3108 my $node = extract_param
($param, 'node');
3110 my $vmid = extract_param
($param, 'vmid');
3112 my $skiplock = extract_param
($param, 'skiplock');
3113 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3114 if $skiplock && $authuser ne 'root@pam';
3116 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
3121 __PACKAGE__-
>register_method({
3122 name
=> 'vm_feature',
3123 path
=> '{vmid}/feature',
3127 description
=> "Check if feature for virtual machine is available.",
3129 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3132 additionalProperties
=> 0,
3134 node
=> get_standard_option
('pve-node'),
3135 vmid
=> get_standard_option
('pve-vmid'),
3137 description
=> "Feature to check.",
3139 enum
=> [ 'snapshot', 'clone', 'copy' ],
3141 snapname
=> get_standard_option
('pve-snapshot-name', {
3149 hasFeature
=> { type
=> 'boolean' },
3152 items
=> { type
=> 'string' },
3159 my $node = extract_param
($param, 'node');
3161 my $vmid = extract_param
($param, 'vmid');
3163 my $snapname = extract_param
($param, 'snapname');
3165 my $feature = extract_param
($param, 'feature');
3167 my $running = PVE
::QemuServer
::check_running
($vmid);
3169 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3172 my $snap = $conf->{snapshots
}->{$snapname};
3173 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3176 my $storecfg = PVE
::Storage
::config
();
3178 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
3179 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
3182 hasFeature
=> $hasFeature,
3183 nodes
=> [ keys %$nodelist ],
3187 __PACKAGE__-
>register_method({
3189 path
=> '{vmid}/clone',
3193 description
=> "Create a copy of virtual machine/template.",
3195 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
3196 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
3197 "'Datastore.AllocateSpace' on any used storage.",
3200 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
3202 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
3203 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
3208 additionalProperties
=> 0,
3210 node
=> get_standard_option
('pve-node'),
3211 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3212 newid
=> get_standard_option
('pve-vmid', {
3213 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
3214 description
=> 'VMID for the clone.' }),
3217 type
=> 'string', format
=> 'dns-name',
3218 description
=> "Set a name for the new VM.",
3223 description
=> "Description for the new VM.",
3227 type
=> 'string', format
=> 'pve-poolid',
3228 description
=> "Add the new VM to the specified pool.",
3230 snapname
=> get_standard_option
('pve-snapshot-name', {
3233 storage
=> get_standard_option
('pve-storage-id', {
3234 description
=> "Target storage for full clone.",
3238 description
=> "Target format for file storage. Only valid for full clone.",
3241 enum
=> [ 'raw', 'qcow2', 'vmdk'],
3246 description
=> "Create a full copy of all disks. This is always done when " .
3247 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
3249 target
=> get_standard_option
('pve-node', {
3250 description
=> "Target node. Only allowed if the original VM is on shared storage.",
3254 description
=> "Override I/O bandwidth limit (in KiB/s).",
3258 default => 'clone limit from datacenter or storage config',
3268 my $rpcenv = PVE
::RPCEnvironment
::get
();
3269 my $authuser = $rpcenv->get_user();
3271 my $node = extract_param
($param, 'node');
3272 my $vmid = extract_param
($param, 'vmid');
3273 my $newid = extract_param
($param, 'newid');
3274 my $pool = extract_param
($param, 'pool');
3276 my $snapname = extract_param
($param, 'snapname');
3277 my $storage = extract_param
($param, 'storage');
3278 my $format = extract_param
($param, 'format');
3279 my $target = extract_param
($param, 'target');
3281 my $localnode = PVE
::INotify
::nodename
();
3283 if ($target && ($target eq $localnode || $target eq 'localhost')) {
3287 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
3289 my $load_and_check = sub {
3290 $rpcenv->check_pool_exist($pool) if defined($pool);
3291 PVE
::Cluster
::check_node_exists
($target) if $target;
3293 my $storecfg = PVE
::Storage
::config
();
3296 # check if storage is enabled on local node
3297 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
3299 # check if storage is available on target node
3300 PVE
::Storage
::storage_check_enabled
($storecfg, $storage, $target);
3301 # clone only works if target storage is shared
3302 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
3303 die "can't clone to non-shared storage '$storage'\n"
3304 if !$scfg->{shared
};
3308 PVE
::Cluster
::check_cfs_quorum
();
3310 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3311 PVE
::QemuConfig-
>check_lock($conf);
3313 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
3314 die "unexpected state change\n" if $verify_running != $running;
3316 die "snapshot '$snapname' does not exist\n"
3317 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
3319 my $full = $param->{full
} // !PVE
::QemuConfig-
>is_template($conf);
3321 die "parameter 'storage' not allowed for linked clones\n"
3322 if defined($storage) && !$full;
3324 die "parameter 'format' not allowed for linked clones\n"
3325 if defined($format) && !$full;
3327 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
3329 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
3331 die "can't clone VM to node '$target' (VM uses local storage)\n"
3332 if $target && !$sharedvm;
3334 my $conffile = PVE
::QemuConfig-
>config_file($newid);
3335 die "unable to create VM $newid: config file already exists\n"
3338 my $newconf = { lock => 'clone' };
3343 foreach my $opt (keys %$oldconf) {
3344 my $value = $oldconf->{$opt};
3346 # do not copy snapshot related info
3347 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
3348 $opt eq 'vmstate' || $opt eq 'snapstate';
3350 # no need to copy unused images, because VMID(owner) changes anyways
3351 next if $opt =~ m/^unused\d+$/;
3353 die "cannot clone TPM state while VM is running\n"
3354 if $full && $running && !$snapname && $opt eq 'tpmstate0';
3356 # always change MAC! address
3357 if ($opt =~ m/^net(\d+)$/) {
3358 my $net = PVE
::QemuServer
::parse_net
($value);
3359 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
3360 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
3361 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
3362 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
3363 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
3364 die "unable to parse drive options for '$opt'\n" if !$drive;
3365 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
3366 $newconf->{$opt} = $value; # simply copy configuration
3368 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
3369 die "Full clone feature is not supported for drive '$opt'\n"
3370 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
3371 $fullclone->{$opt} = 1;
3373 # not full means clone instead of copy
3374 die "Linked clone feature is not supported for drive '$opt'\n"
3375 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
3377 $drives->{$opt} = $drive;
3378 next if PVE
::QemuServer
::drive_is_cloudinit
($drive);
3379 push @$vollist, $drive->{file
};
3382 # copy everything else
3383 $newconf->{$opt} = $value;
3387 return ($conffile, $newconf, $oldconf, $vollist, $drives, $fullclone);
3391 my ($conffile, $newconf, $oldconf, $vollist, $drives, $fullclone) = $load_and_check->();
3392 my $storecfg = PVE
::Storage
::config
();
3394 # auto generate a new uuid
3395 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
3396 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
3397 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
3398 # auto generate a new vmgenid only if the option was set for template
3399 if ($newconf->{vmgenid
}) {
3400 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
3403 delete $newconf->{template
};
3405 if ($param->{name
}) {
3406 $newconf->{name
} = $param->{name
};
3408 $newconf->{name
} = "Copy-of-VM-" . ($oldconf->{name
} // $vmid);
3411 if ($param->{description
}) {
3412 $newconf->{description
} = $param->{description
};
3415 # create empty/temp config - this fails if VM already exists on other node
3416 # FIXME use PVE::QemuConfig->create_and_lock_config and adapt code
3417 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
3419 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
3421 my $newvollist = [];
3428 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3430 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
3432 my $bwlimit = extract_param
($param, 'bwlimit');
3434 my $total_jobs = scalar(keys %{$drives});
3437 foreach my $opt (sort keys %$drives) {
3438 my $drive = $drives->{$opt};
3439 my $skipcomplete = ($total_jobs != $i); # finish after last drive
3440 my $completion = $skipcomplete ?
'skip' : 'complete';
3442 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
3443 my $storage_list = [ $src_sid ];
3444 push @$storage_list, $storage if defined($storage);
3445 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
3449 running
=> $running,
3452 snapname
=> $snapname,
3458 storage
=> $storage,
3462 $dest_info->{efisize
} = PVE
::QemuServer
::get_efivars_size
($oldconf)
3463 if $opt eq 'efidisk0';
3465 my $newdrive = PVE
::QemuServer
::clone_disk
(
3477 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($newdrive);
3479 PVE
::QemuConfig-
>write_config($newid, $newconf);
3483 delete $newconf->{lock};
3485 # do not write pending changes
3486 if (my @changes = keys %{$newconf->{pending
}}) {
3487 my $pending = join(',', @changes);
3488 warn "found pending changes for '$pending', discarding for clone\n";
3489 delete $newconf->{pending
};
3492 PVE
::QemuConfig-
>write_config($newid, $newconf);
3495 # always deactivate volumes - avoid lvm LVs to be active on several nodes
3496 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
3497 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
3499 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
3500 die "Failed to move config to node '$target' - rename failed: $!\n"
3501 if !rename($conffile, $newconffile);
3504 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
3507 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
3508 sleep 1; # some storage like rbd need to wait before release volume - really?
3510 foreach my $volid (@$newvollist) {
3511 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3515 PVE
::Firewall
::remove_vmfw_conf
($newid);
3517 unlink $conffile; # avoid races -> last thing before die
3519 die "clone failed: $err";
3525 # Aquire exclusive lock lock for $newid
3526 my $lock_target_vm = sub {
3527 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
3530 my $lock_source_vm = sub {
3531 # exclusive lock if VM is running - else shared lock is enough;
3533 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $lock_target_vm);
3535 return PVE
::QemuConfig-
>lock_config_shared($vmid, 1, $lock_target_vm);
3539 $load_and_check->(); # early checks before forking/locking
3541 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $lock_source_vm);
3544 __PACKAGE__-
>register_method({
3545 name
=> 'move_vm_disk',
3546 path
=> '{vmid}/move_disk',
3550 description
=> "Move volume to different storage or to a different VM.",
3552 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
3553 "and 'Datastore.AllocateSpace' permissions on the storage. To move ".
3554 "a disk to another VM, you need the permissions on the target VM as well.",
3555 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3558 additionalProperties
=> 0,
3560 node
=> get_standard_option
('pve-node'),
3561 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3562 'target-vmid' => get_standard_option
('pve-vmid', {
3563 completion
=> \
&PVE
::QemuServer
::complete_vmid
,
3568 description
=> "The disk you want to move.",
3569 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names_with_unused
()],
3571 storage
=> get_standard_option
('pve-storage-id', {
3572 description
=> "Target storage.",
3573 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3578 description
=> "Target Format.",
3579 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
3584 description
=> "Delete the original disk after successful copy. By default the"
3585 ." original disk is kept as unused disk.",
3591 description
=> 'Prevent changes if current configuration file has different SHA1"
3592 ." digest. This can be used to prevent concurrent modifications.',
3597 description
=> "Override I/O bandwidth limit (in KiB/s).",
3601 default => 'move limit from datacenter or storage config',
3605 description
=> "The config key the disk will be moved to on the target VM"
3606 ." (for example, ide0 or scsi1). Default is the source disk key.",
3607 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names_with_unused
()],
3610 'target-digest' => {
3612 description
=> 'Prevent changes if the current config file of the target VM has a"
3613 ." different SHA1 digest. This can be used to detect concurrent modifications.',
3621 description
=> "the task ID.",
3626 my $rpcenv = PVE
::RPCEnvironment
::get
();
3627 my $authuser = $rpcenv->get_user();
3629 my $node = extract_param
($param, 'node');
3630 my $vmid = extract_param
($param, 'vmid');
3631 my $target_vmid = extract_param
($param, 'target-vmid');
3632 my $digest = extract_param
($param, 'digest');
3633 my $target_digest = extract_param
($param, 'target-digest');
3634 my $disk = extract_param
($param, 'disk');
3635 my $target_disk = extract_param
($param, 'target-disk') // $disk;
3636 my $storeid = extract_param
($param, 'storage');
3637 my $format = extract_param
($param, 'format');
3639 my $storecfg = PVE
::Storage
::config
();
3641 my $load_and_check_move = sub {
3642 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3643 PVE
::QemuConfig-
>check_lock($conf);
3645 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
3647 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3649 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3651 die "disk '$disk' has no associated volume\n" if !$drive->{file
};
3652 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3654 my $old_volid = $drive->{file
};
3656 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3657 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3661 die "you can't move to the same storage with same format\n"
3662 if $oldstoreid eq $storeid && (!$format || !$oldfmt || $oldfmt eq $format);
3664 # this only checks snapshots because $disk is passed!
3665 my $snapshotted = PVE
::QemuServer
::Drive
::is_volume_in_use
(
3671 die "you can't move a disk with snapshots and delete the source\n"
3672 if $snapshotted && $param->{delete};
3674 return ($conf, $drive, $oldstoreid, $snapshotted);
3677 my $move_updatefn = sub {
3678 my ($conf, $drive, $oldstoreid, $snapshotted) = $load_and_check_move->();
3679 my $old_volid = $drive->{file
};
3681 PVE
::Cluster
::log_msg
(
3684 "move disk VM $vmid: move --disk $disk --storage $storeid"
3687 my $running = PVE
::QemuServer
::check_running
($vmid);
3689 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3691 my $newvollist = [];
3697 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3699 warn "moving disk with snapshots, snapshots will not be moved!\n"
3702 my $bwlimit = extract_param
($param, 'bwlimit');
3703 my $movelimit = PVE
::Storage
::get_bandwidth_limit
(
3705 [$oldstoreid, $storeid],
3711 running
=> $running,
3720 storage
=> $storeid,
3724 $dest_info->{efisize
} = PVE
::QemuServer
::get_efivars_size
($conf)
3725 if $disk eq 'efidisk0';
3727 my $newdrive = PVE
::QemuServer
::clone_disk
(
3738 $conf->{$disk} = PVE
::QemuServer
::print_drive
($newdrive);
3740 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3742 # convert moved disk to base if part of template
3743 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3744 if PVE
::QemuConfig-
>is_template($conf);
3746 PVE
::QemuConfig-
>write_config($vmid, $conf);
3748 my $do_trim = PVE
::QemuServer
::get_qga_key
($conf, 'fstrim_cloned_disks');
3749 if ($running && $do_trim && PVE
::QemuServer
::qga_check_running
($vmid)) {
3750 eval { mon_cmd
($vmid, "guest-fstrim") };
3754 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3755 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3761 foreach my $volid (@$newvollist) {
3762 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid) };
3765 die "storage migration failed: $err";
3768 if ($param->{delete}) {
3770 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3771 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3777 my $load_and_check_reassign_configs = sub {
3778 my $vmlist = PVE
::Cluster
::get_vmlist
()->{ids
};
3780 die "could not find VM ${vmid}\n" if !exists($vmlist->{$vmid});
3781 die "could not find target VM ${target_vmid}\n" if !exists($vmlist->{$target_vmid});
3783 my $source_node = $vmlist->{$vmid}->{node
};
3784 my $target_node = $vmlist->{$target_vmid}->{node
};
3786 die "Both VMs need to be on the same node ($source_node != $target_node)\n"
3787 if $source_node ne $target_node;
3789 my $source_conf = PVE
::QemuConfig-
>load_config($vmid);
3790 PVE
::QemuConfig-
>check_lock($source_conf);
3791 my $target_conf = PVE
::QemuConfig-
>load_config($target_vmid);
3792 PVE
::QemuConfig-
>check_lock($target_conf);
3794 die "Can't move disks from or to template VMs\n"
3795 if ($source_conf->{template
} || $target_conf->{template
});
3798 eval { PVE
::Tools
::assert_if_modified
($digest, $source_conf->{digest
}) };
3799 die "VM ${vmid}: $@" if $@;
3802 if ($target_digest) {
3803 eval { PVE
::Tools
::assert_if_modified
($target_digest, $target_conf->{digest
}) };
3804 die "VM ${target_vmid}: $@" if $@;
3807 die "Disk '${disk}' for VM '$vmid' does not exist\n" if !defined($source_conf->{$disk});
3809 die "Target disk key '${target_disk}' is already in use for VM '$target_vmid'\n"
3810 if $target_conf->{$target_disk};
3812 my $drive = PVE
::QemuServer
::parse_drive
(
3814 $source_conf->{$disk},
3816 die "failed to parse source disk - $@\n" if !$drive;
3818 my $source_volid = $drive->{file
};
3820 die "disk '${disk}' has no associated volume\n" if !$source_volid;
3821 die "CD drive contents can't be moved to another VM\n"
3822 if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3824 my $storeid = PVE
::Storage
::parse_volume_id
($source_volid, 1);
3825 die "Volume '$source_volid' not managed by PVE\n" if !defined($storeid);
3827 die "Can't move disk used by a snapshot to another VM\n"
3828 if PVE
::QemuServer
::Drive
::is_volume_in_use
($storecfg, $source_conf, $disk, $source_volid);
3829 die "Storage does not support moving of this disk to another VM\n"
3830 if (!PVE
::Storage
::volume_has_feature
($storecfg, 'rename', $source_volid));
3831 die "Cannot move disk to another VM while the source VM is running - detach first\n"
3832 if PVE
::QemuServer
::check_running
($vmid) && $disk !~ m/^unused\d+$/;
3834 # now re-parse using target disk slot format
3835 if ($target_disk =~ /^unused\d+$/) {
3836 $drive = PVE
::QemuServer
::parse_drive
(
3841 $drive = PVE
::QemuServer
::parse_drive
(
3843 $source_conf->{$disk},
3846 die "failed to parse source disk for target disk format - $@\n" if !$drive;
3848 my $repl_conf = PVE
::ReplicationConfig-
>new();
3849 if ($repl_conf->check_for_existing_jobs($target_vmid, 1)) {
3850 my $format = (PVE
::Storage
::parse_volname
($storecfg, $source_volid))[6];
3851 die "Cannot move disk to a replicated VM. Storage does not support replication!\n"
3852 if !PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
3855 return ($source_conf, $target_conf, $drive);
3860 print STDERR
"$msg\n";
3863 my $disk_reassignfn = sub {
3864 return PVE
::QemuConfig-
>lock_config($vmid, sub {
3865 return PVE
::QemuConfig-
>lock_config($target_vmid, sub {
3866 my ($source_conf, $target_conf, $drive) = &$load_and_check_reassign_configs();
3868 my $source_volid = $drive->{file
};
3870 print "moving disk '$disk' from VM '$vmid' to '$target_vmid'\n";
3871 my ($storeid, $source_volname) = PVE
::Storage
::parse_volume_id
($source_volid);
3873 my $fmt = (PVE
::Storage
::parse_volname
($storecfg, $source_volid))[6];
3875 my $new_volid = PVE
::Storage
::rename_volume
(
3881 $drive->{file
} = $new_volid;
3883 my $boot_order = PVE
::QemuServer
::device_bootorder
($source_conf);
3884 if (defined(delete $boot_order->{$disk})) {
3885 print "removing disk '$disk' from boot order config\n";
3886 my $boot_devs = [ sort { $boot_order->{$a} <=> $boot_order->{$b} } keys %$boot_order ];
3887 $source_conf->{boot
} = PVE
::QemuServer
::print_bootorder
($boot_devs);
3890 delete $source_conf->{$disk};
3891 print "removing disk '${disk}' from VM '${vmid}' config\n";
3892 PVE
::QemuConfig-
>write_config($vmid, $source_conf);
3894 my $drive_string = PVE
::QemuServer
::print_drive
($drive);
3896 if ($target_disk =~ /^unused\d+$/) {
3897 $target_conf->{$target_disk} = $drive_string;
3898 PVE
::QemuConfig-
>write_config($target_vmid, $target_conf);
3903 vmid
=> $target_vmid,
3904 digest
=> $target_digest,
3905 $target_disk => $drive_string,
3911 # remove possible replication snapshots
3912 if (PVE
::Storage
::volume_has_feature
(
3918 PVE
::Replication
::prepare
(
3928 print "Failed to remove replication snapshots on moved disk " .
3929 "'$target_disk'. Manual cleanup could be necessary.\n";
3936 if ($target_vmid && $storeid) {
3937 my $msg = "either set 'storage' or 'target-vmid', but not both";
3938 raise_param_exc
({ 'target-vmid' => $msg, 'storage' => $msg });
3939 } elsif ($target_vmid) {
3940 $rpcenv->check_vm_perm($authuser, $target_vmid, undef, ['VM.Config.Disk'])
3941 if $authuser ne 'root@pam';
3943 raise_param_exc
({ 'target-vmid' => "must be different than source VMID to reassign disk" })
3944 if $vmid eq $target_vmid;
3946 my (undef, undef, $drive) = &$load_and_check_reassign_configs();
3947 my $storage = PVE
::Storage
::parse_volume_id
($drive->{file
});
3948 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace']);
3950 return $rpcenv->fork_worker(
3952 "${vmid}-${disk}>${target_vmid}-${target_disk}",
3956 } elsif ($storeid) {
3957 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3959 die "cannot move disk '$disk', only configured disks can be moved to another storage\n"
3960 if $disk =~ m/^unused\d+$/;
3962 $load_and_check_move->(); # early checks before forking/locking
3965 PVE
::QemuConfig-
>lock_config($vmid, $move_updatefn);
3968 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3970 my $msg = "both 'storage' and 'target-vmid' missing, either needs to be set";
3971 raise_param_exc
({ 'target-vmid' => $msg, 'storage' => $msg });
3975 my $check_vm_disks_local = sub {
3976 my ($storecfg, $vmconf, $vmid) = @_;
3978 my $local_disks = {};
3980 # add some more information to the disks e.g. cdrom
3981 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3982 my ($volid, $attr) = @_;
3984 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3986 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3987 return if $scfg->{shared
};
3989 # The shared attr here is just a special case where the vdisk
3990 # is marked as shared manually
3991 return if $attr->{shared
};
3992 return if $attr->{cdrom
} and $volid eq "none";
3994 if (exists $local_disks->{$volid}) {
3995 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3997 $local_disks->{$volid} = $attr;
3998 # ensure volid is present in case it's needed
3999 $local_disks->{$volid}->{volid
} = $volid;
4003 return $local_disks;
4006 __PACKAGE__-
>register_method({
4007 name
=> 'migrate_vm_precondition',
4008 path
=> '{vmid}/migrate',
4012 description
=> "Get preconditions for migration.",
4014 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
4017 additionalProperties
=> 0,
4019 node
=> get_standard_option
('pve-node'),
4020 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4021 target
=> get_standard_option
('pve-node', {
4022 description
=> "Target node.",
4023 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
4031 running
=> { type
=> 'boolean' },
4035 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
4037 not_allowed_nodes
=> {
4040 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
4044 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
4046 local_resources
=> {
4048 description
=> "List local resources e.g. pci, usb"
4055 my $rpcenv = PVE
::RPCEnvironment
::get
();
4057 my $authuser = $rpcenv->get_user();
4059 PVE
::Cluster
::check_cfs_quorum
();
4063 my $vmid = extract_param
($param, 'vmid');
4064 my $target = extract_param
($param, 'target');
4065 my $localnode = PVE
::INotify
::nodename
();
4069 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
4070 my $storecfg = PVE
::Storage
::config
();
4073 # try to detect errors early
4074 PVE
::QemuConfig-
>check_lock($vmconf);
4076 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
4078 # if vm is not running, return target nodes where local storage is available
4079 # for offline migration
4080 if (!$res->{running
}) {
4081 $res->{allowed_nodes
} = [];
4082 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
4083 delete $checked_nodes->{$localnode};
4085 foreach my $node (keys %$checked_nodes) {
4086 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
4087 push @{$res->{allowed_nodes
}}, $node;
4091 $res->{not_allowed_nodes
} = $checked_nodes;
4095 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
4096 $res->{local_disks
} = [ values %$local_disks ];;
4098 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
4100 $res->{local_resources
} = $local_resources;
4107 __PACKAGE__-
>register_method({
4108 name
=> 'migrate_vm',
4109 path
=> '{vmid}/migrate',
4113 description
=> "Migrate virtual machine. Creates a new migration task.",
4115 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
4118 additionalProperties
=> 0,
4120 node
=> get_standard_option
('pve-node'),
4121 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4122 target
=> get_standard_option
('pve-node', {
4123 description
=> "Target node.",
4124 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
4128 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
4133 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
4138 enum
=> ['secure', 'insecure'],
4139 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
4142 migration_network
=> {
4143 type
=> 'string', format
=> 'CIDR',
4144 description
=> "CIDR of the (sub) network that is used for migration.",
4147 "with-local-disks" => {
4149 description
=> "Enable live storage migration for local disk",
4152 targetstorage
=> get_standard_option
('pve-targetstorage', {
4153 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
4156 description
=> "Override I/O bandwidth limit (in KiB/s).",
4160 default => 'migrate limit from datacenter or storage config',
4166 description
=> "the task ID.",
4171 my $rpcenv = PVE
::RPCEnvironment
::get
();
4172 my $authuser = $rpcenv->get_user();
4174 my $target = extract_param
($param, 'target');
4176 my $localnode = PVE
::INotify
::nodename
();
4177 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
4179 PVE
::Cluster
::check_cfs_quorum
();
4181 PVE
::Cluster
::check_node_exists
($target);
4183 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
4185 my $vmid = extract_param
($param, 'vmid');
4187 raise_param_exc
({ force
=> "Only root may use this option." })
4188 if $param->{force
} && $authuser ne 'root@pam';
4190 raise_param_exc
({ migration_type
=> "Only root may use this option." })
4191 if $param->{migration_type
} && $authuser ne 'root@pam';
4193 # allow root only until better network permissions are available
4194 raise_param_exc
({ migration_network
=> "Only root may use this option." })
4195 if $param->{migration_network
} && $authuser ne 'root@pam';
4198 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4200 # try to detect errors early
4202 PVE
::QemuConfig-
>check_lock($conf);
4204 if (PVE
::QemuServer
::check_running
($vmid)) {
4205 die "can't migrate running VM without --online\n" if !$param->{online
};
4207 my $repl_conf = PVE
::ReplicationConfig-
>new();
4208 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
4209 my $is_replicated_to_target = defined($repl_conf->find_local_replication_job($vmid, $target));
4210 if (!$param->{force
} && $is_replicated && !$is_replicated_to_target) {
4211 die "Cannot live-migrate replicated VM to node '$target' - not a replication " .
4212 "target. Use 'force' to override.\n";
4215 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
4216 $param->{online
} = 0;
4219 my $storecfg = PVE
::Storage
::config
();
4220 if (my $targetstorage = $param->{targetstorage
}) {
4221 my $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
4222 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
4225 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk'])
4226 if !defined($storagemap->{identity
});
4228 foreach my $target_sid (values %{$storagemap->{entries
}}) {
4229 $check_storage_access_migrate->($rpcenv, $authuser, $storecfg, $target_sid, $target);
4232 $check_storage_access_migrate->($rpcenv, $authuser, $storecfg, $storagemap->{default}, $target)
4233 if $storagemap->{default};
4235 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target)
4236 if $storagemap->{identity
};
4238 $param->{storagemap
} = $storagemap;
4240 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
4243 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
4248 print "Requesting HA migration for VM $vmid to node $target\n";
4250 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
4251 PVE
::Tools
::run_command
($cmd);
4255 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
4260 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
4264 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
4267 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
4272 __PACKAGE__-
>register_method({
4274 path
=> '{vmid}/monitor',
4278 description
=> "Execute Qemu monitor commands.",
4280 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
4281 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
4284 additionalProperties
=> 0,
4286 node
=> get_standard_option
('pve-node'),
4287 vmid
=> get_standard_option
('pve-vmid'),
4290 description
=> "The monitor command.",
4294 returns
=> { type
=> 'string'},
4298 my $rpcenv = PVE
::RPCEnvironment
::get
();
4299 my $authuser = $rpcenv->get_user();
4302 my $command = shift;
4303 return $command =~ m/^\s*info(\s+|$)/
4304 || $command =~ m/^\s*help\s*$/;
4307 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
4308 if !&$is_ro($param->{command
});
4310 my $vmid = $param->{vmid
};
4312 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
4316 $res = PVE
::QemuServer
::Monitor
::hmp_cmd
($vmid, $param->{command
});
4318 $res = "ERROR: $@" if $@;
4323 __PACKAGE__-
>register_method({
4324 name
=> 'resize_vm',
4325 path
=> '{vmid}/resize',
4329 description
=> "Extend volume size.",
4331 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
4334 additionalProperties
=> 0,
4336 node
=> get_standard_option
('pve-node'),
4337 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4338 skiplock
=> get_standard_option
('skiplock'),
4341 description
=> "The disk you want to resize.",
4342 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
4346 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
4347 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.",
4351 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
4357 returns
=> { type
=> 'null'},
4361 my $rpcenv = PVE
::RPCEnvironment
::get
();
4363 my $authuser = $rpcenv->get_user();
4365 my $node = extract_param
($param, 'node');
4367 my $vmid = extract_param
($param, 'vmid');
4369 my $digest = extract_param
($param, 'digest');
4371 my $disk = extract_param
($param, 'disk');
4373 my $sizestr = extract_param
($param, 'size');
4375 my $skiplock = extract_param
($param, 'skiplock');
4376 raise_param_exc
({ skiplock
=> "Only root may use this option." })
4377 if $skiplock && $authuser ne 'root@pam';
4379 my $storecfg = PVE
::Storage
::config
();
4381 my $updatefn = sub {
4383 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4385 die "checksum missmatch (file change by other user?)\n"
4386 if $digest && $digest ne $conf->{digest
};
4387 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
4389 die "disk '$disk' does not exist\n" if !$conf->{$disk};
4391 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
4393 my (undef, undef, undef, undef, undef, undef, $format) =
4394 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
4396 die "can't resize volume: $disk if snapshot exists\n"
4397 if %{$conf->{snapshots
}} && $format eq 'qcow2';
4399 my $volid = $drive->{file
};
4401 die "disk '$disk' has no associated volume\n" if !$volid;
4403 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
4405 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
4407 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
4409 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
4410 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
4412 die "Could not determine current size of volume '$volid'\n" if !defined($size);
4414 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
4415 my ($ext, $newsize, $unit) = ($1, $2, $4);
4418 $newsize = $newsize * 1024;
4419 } elsif ($unit eq 'M') {
4420 $newsize = $newsize * 1024 * 1024;
4421 } elsif ($unit eq 'G') {
4422 $newsize = $newsize * 1024 * 1024 * 1024;
4423 } elsif ($unit eq 'T') {
4424 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
4427 $newsize += $size if $ext;
4428 $newsize = int($newsize);
4430 die "shrinking disks is not supported\n" if $newsize < $size;
4432 return if $size == $newsize;
4434 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
4436 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
4438 $drive->{size
} = $newsize;
4439 $conf->{$disk} = PVE
::QemuServer
::print_drive
($drive);
4441 PVE
::QemuConfig-
>write_config($vmid, $conf);
4444 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4448 __PACKAGE__-
>register_method({
4449 name
=> 'snapshot_list',
4450 path
=> '{vmid}/snapshot',
4452 description
=> "List all snapshots.",
4454 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4457 protected
=> 1, # qemu pid files are only readable by root
4459 additionalProperties
=> 0,
4461 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4462 node
=> get_standard_option
('pve-node'),
4471 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
4475 description
=> "Snapshot includes RAM.",
4480 description
=> "Snapshot description.",
4484 description
=> "Snapshot creation time",
4486 renderer
=> 'timestamp',
4490 description
=> "Parent snapshot identifier.",
4496 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
4501 my $vmid = $param->{vmid
};
4503 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4504 my $snaphash = $conf->{snapshots
} || {};
4508 foreach my $name (keys %$snaphash) {
4509 my $d = $snaphash->{$name};
4512 snaptime
=> $d->{snaptime
} || 0,
4513 vmstate
=> $d->{vmstate
} ?
1 : 0,
4514 description
=> $d->{description
} || '',
4516 $item->{parent
} = $d->{parent
} if $d->{parent
};
4517 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
4521 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
4524 digest
=> $conf->{digest
},
4525 running
=> $running,
4526 description
=> "You are here!",
4528 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
4530 push @$res, $current;
4535 __PACKAGE__-
>register_method({
4537 path
=> '{vmid}/snapshot',
4541 description
=> "Snapshot a VM.",
4543 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4546 additionalProperties
=> 0,
4548 node
=> get_standard_option
('pve-node'),
4549 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4550 snapname
=> get_standard_option
('pve-snapshot-name'),
4554 description
=> "Save the vmstate",
4559 description
=> "A textual description or comment.",
4565 description
=> "the task ID.",
4570 my $rpcenv = PVE
::RPCEnvironment
::get
();
4572 my $authuser = $rpcenv->get_user();
4574 my $node = extract_param
($param, 'node');
4576 my $vmid = extract_param
($param, 'vmid');
4578 my $snapname = extract_param
($param, 'snapname');
4580 die "unable to use snapshot name 'current' (reserved name)\n"
4581 if $snapname eq 'current';
4583 die "unable to use snapshot name 'pending' (reserved name)\n"
4584 if lc($snapname) eq 'pending';
4587 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
4588 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
4589 $param->{description
});
4592 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
4595 __PACKAGE__-
>register_method({
4596 name
=> 'snapshot_cmd_idx',
4597 path
=> '{vmid}/snapshot/{snapname}',
4604 additionalProperties
=> 0,
4606 vmid
=> get_standard_option
('pve-vmid'),
4607 node
=> get_standard_option
('pve-node'),
4608 snapname
=> get_standard_option
('pve-snapshot-name'),
4617 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
4624 push @$res, { cmd
=> 'rollback' };
4625 push @$res, { cmd
=> 'config' };
4630 __PACKAGE__-
>register_method({
4631 name
=> 'update_snapshot_config',
4632 path
=> '{vmid}/snapshot/{snapname}/config',
4636 description
=> "Update snapshot metadata.",
4638 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4641 additionalProperties
=> 0,
4643 node
=> get_standard_option
('pve-node'),
4644 vmid
=> get_standard_option
('pve-vmid'),
4645 snapname
=> get_standard_option
('pve-snapshot-name'),
4649 description
=> "A textual description or comment.",
4653 returns
=> { type
=> 'null' },
4657 my $rpcenv = PVE
::RPCEnvironment
::get
();
4659 my $authuser = $rpcenv->get_user();
4661 my $vmid = extract_param
($param, 'vmid');
4663 my $snapname = extract_param
($param, 'snapname');
4665 return if !defined($param->{description
});
4667 my $updatefn = sub {
4669 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4671 PVE
::QemuConfig-
>check_lock($conf);
4673 my $snap = $conf->{snapshots
}->{$snapname};
4675 die "snapshot '$snapname' does not exist\n" if !defined($snap);
4677 $snap->{description
} = $param->{description
} if defined($param->{description
});
4679 PVE
::QemuConfig-
>write_config($vmid, $conf);
4682 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4687 __PACKAGE__-
>register_method({
4688 name
=> 'get_snapshot_config',
4689 path
=> '{vmid}/snapshot/{snapname}/config',
4692 description
=> "Get snapshot configuration",
4694 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback', 'VM.Audit' ], any
=> 1],
4697 additionalProperties
=> 0,
4699 node
=> get_standard_option
('pve-node'),
4700 vmid
=> get_standard_option
('pve-vmid'),
4701 snapname
=> get_standard_option
('pve-snapshot-name'),
4704 returns
=> { type
=> "object" },
4708 my $rpcenv = PVE
::RPCEnvironment
::get
();
4710 my $authuser = $rpcenv->get_user();
4712 my $vmid = extract_param
($param, 'vmid');
4714 my $snapname = extract_param
($param, 'snapname');
4716 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4718 my $snap = $conf->{snapshots
}->{$snapname};
4720 die "snapshot '$snapname' does not exist\n" if !defined($snap);
4725 __PACKAGE__-
>register_method({
4727 path
=> '{vmid}/snapshot/{snapname}/rollback',
4731 description
=> "Rollback VM state to specified snapshot.",
4733 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
4736 additionalProperties
=> 0,
4738 node
=> get_standard_option
('pve-node'),
4739 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4740 snapname
=> get_standard_option
('pve-snapshot-name'),
4745 description
=> "the task ID.",
4750 my $rpcenv = PVE
::RPCEnvironment
::get
();
4752 my $authuser = $rpcenv->get_user();
4754 my $node = extract_param
($param, 'node');
4756 my $vmid = extract_param
($param, 'vmid');
4758 my $snapname = extract_param
($param, 'snapname');
4761 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
4762 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
4766 # hold migration lock, this makes sure that nobody create replication snapshots
4767 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
4770 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
4773 __PACKAGE__-
>register_method({
4774 name
=> 'delsnapshot',
4775 path
=> '{vmid}/snapshot/{snapname}',
4779 description
=> "Delete a VM snapshot.",
4781 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4784 additionalProperties
=> 0,
4786 node
=> get_standard_option
('pve-node'),
4787 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4788 snapname
=> get_standard_option
('pve-snapshot-name'),
4792 description
=> "For removal from config file, even if removing disk snapshots fails.",
4798 description
=> "the task ID.",
4803 my $rpcenv = PVE
::RPCEnvironment
::get
();
4805 my $authuser = $rpcenv->get_user();
4807 my $node = extract_param
($param, 'node');
4809 my $vmid = extract_param
($param, 'vmid');
4811 my $snapname = extract_param
($param, 'snapname');
4814 my $do_delete = sub {
4816 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
4817 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
4821 if ($param->{force
}) {
4824 eval { PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $do_delete); };
4826 die $err if $lock_obtained;
4827 die "Failed to obtain guest migration lock - replication running?\n";
4832 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
4835 __PACKAGE__-
>register_method({
4837 path
=> '{vmid}/template',
4841 description
=> "Create a Template.",
4843 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
4844 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
4847 additionalProperties
=> 0,
4849 node
=> get_standard_option
('pve-node'),
4850 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
4854 description
=> "If you want to convert only 1 disk to base image.",
4855 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
4862 description
=> "the task ID.",
4867 my $rpcenv = PVE
::RPCEnvironment
::get
();
4869 my $authuser = $rpcenv->get_user();
4871 my $node = extract_param
($param, 'node');
4873 my $vmid = extract_param
($param, 'vmid');
4875 my $disk = extract_param
($param, 'disk');
4877 my $load_and_check = sub {
4878 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4880 PVE
::QemuConfig-
>check_lock($conf);
4882 die "unable to create template, because VM contains snapshots\n"
4883 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
4885 die "you can't convert a template to a template\n"
4886 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
4888 die "you can't convert a VM to template if VM is running\n"
4889 if PVE
::QemuServer
::check_running
($vmid);
4894 $load_and_check->();
4897 PVE
::QemuConfig-
>lock_config($vmid, sub {
4898 my $conf = $load_and_check->();
4900 $conf->{template
} = 1;
4901 PVE
::QemuConfig-
>write_config($vmid, $conf);
4903 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
4907 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4910 __PACKAGE__-
>register_method({
4911 name
=> 'cloudinit_generated_config_dump',
4912 path
=> '{vmid}/cloudinit/dump',
4915 description
=> "Get automatically generated cloudinit config.",
4917 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4920 additionalProperties
=> 0,
4922 node
=> get_standard_option
('pve-node'),
4923 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4925 description
=> 'Config type.',
4927 enum
=> ['user', 'network', 'meta'],
4937 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4939 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});