1 package PVE
::API2
::Qemu
;
12 use Crypt
::OpenSSL
::Random
;
13 use Socket
qw(SOCK_STREAM);
15 use PVE
::APIClient
::LWP
;
17 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
20 use PVE
::Tools
qw(extract_param);
21 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
23 use PVE
::JSONSchema
qw(get_standard_option);
25 use PVE
::ReplicationConfig
;
26 use PVE
::GuestHelpers
qw(assert_tag_permissions);
29 use PVE
::QemuServer
::Cloudinit
;
30 use PVE
::QemuServer
::CPUConfig
;
31 use PVE
::QemuServer
::Drive
;
32 use PVE
::QemuServer
::ImportDisk
;
33 use PVE
::QemuServer
::Monitor
qw(mon_cmd);
34 use PVE
::QemuServer
::Machine
;
35 use PVE
::QemuServer
::Memory
qw(get_current_memory);
36 use PVE
::QemuServer
::PCI
;
37 use PVE
::QemuServer
::USB
;
39 use PVE
::RPCEnvironment
;
40 use PVE
::AccessControl
;
44 use PVE
::API2
::Firewall
::VM
;
45 use PVE
::API2
::Qemu
::Agent
;
46 use PVE
::VZDump
::Plugin
;
47 use PVE
::DataCenterConfig
;
50 use PVE
::StorageTunnel
;
53 if (!$ENV{PVE_GENERATING_DOCS
}) {
54 require PVE
::HA
::Env
::PVE2
;
55 import PVE
::HA
::Env
::PVE2
;
56 require PVE
::HA
::Config
;
57 import PVE
::HA
::Config
;
61 use base
qw(PVE::RESTHandler);
63 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.";
65 my $resolve_cdrom_alias = sub {
68 if (my $value = $param->{cdrom
}) {
69 $value .= ",media=cdrom" if $value !~ m/media=/;
70 $param->{ide2
} = $value;
71 delete $param->{cdrom
};
75 # Used in import-enabled API endpoints. Parses drives using the extended '_with_alloc' schema.
76 my $foreach_volume_with_alloc = sub {
77 my ($param, $func) = @_;
79 for my $opt (sort keys $param->%*) {
80 next if !PVE
::QemuServer
::is_valid_drivename
($opt);
82 my $drive = PVE
::QemuServer
::Drive
::parse_drive
($opt, $param->{$opt}, 1);
85 $func->($opt, $drive);
89 my $check_drive_param = sub {
90 my ($param, $storecfg, $extra_checks) = @_;
92 for my $opt (sort keys $param->%*) {
93 next if !PVE
::QemuServer
::is_valid_drivename
($opt);
95 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt}, 1);
96 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
98 if ($drive->{'import-from'}) {
99 if ($drive->{file
} !~ $PVE::QemuServer
::Drive
::NEW_DISK_RE
|| $3 != 0) {
101 $opt => "'import-from' requires special syntax - ".
102 "use <storage ID>:0,import-from=<source>",
106 if ($opt eq 'efidisk0') {
107 for my $required (qw(efitype pre-enrolled-keys)) {
108 if (!defined($drive->{$required})) {
110 $opt => "need to specify '$required' when using 'import-from'",
114 } elsif ($opt eq 'tpmstate0') {
115 raise_param_exc
({ $opt => "need to specify 'version' when using 'import-from'" })
116 if !defined($drive->{version
});
120 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
122 $extra_checks->($drive) if $extra_checks;
124 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive, 1);
128 my $check_storage_access = sub {
129 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
131 $foreach_volume_with_alloc->($settings, sub {
132 my ($ds, $drive) = @_;
134 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
136 my $volid = $drive->{file
};
137 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
139 if (!$volid || ($volid eq 'none' || $volid eq 'cloudinit' || (defined($volname) && $volname eq 'cloudinit'))) {
141 } elsif ($isCDROM && ($volid eq 'cdrom')) {
142 $rpcenv->check($authuser, "/", ['Sys.Console']);
143 } elsif (!$isCDROM && ($volid =~ $PVE::QemuServer
::Drive
::NEW_DISK_RE
)) {
144 my ($storeid, $size) = ($2 || $default_storage, $3);
145 die "no storage ID specified (and no default storage)\n" if !$storeid;
146 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
147 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
148 raise_param_exc
({ storage
=> "storage '$storeid' does not support vm images"})
149 if !$scfg->{content
}->{images
};
151 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
153 my ($vtype) = PVE
::Storage
::parse_volname
($storecfg, $volid);
154 raise_param_exc
({ $ds => "content type needs to be 'images' or 'iso'" })
155 if $vtype ne 'images' && $vtype ne 'iso';
159 if (my $src_image = $drive->{'import-from'}) {
161 if (PVE
::Storage
::parse_volume_id
($src_image, 1)) { # PVE-managed volume
162 (my $vtype, undef, $src_vmid) = PVE
::Storage
::parse_volname
($storecfg, $src_image);
163 raise_param_exc
({ $ds => "$src_image has wrong type '$vtype' - not an image" })
164 if $vtype ne 'images';
167 if ($src_vmid) { # might be actively used by VM and will be copied via clone_disk()
168 $rpcenv->check($authuser, "/vms/${src_vmid}", ['VM.Clone']);
170 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $src_image);
175 $rpcenv->check($authuser, "/storage/$settings->{vmstatestorage}", ['Datastore.AllocateSpace'])
176 if defined($settings->{vmstatestorage
});
179 my $check_storage_access_clone = sub {
180 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
184 PVE
::QemuConfig-
>foreach_volume($conf, sub {
185 my ($ds, $drive) = @_;
187 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
189 my $volid = $drive->{file
};
191 return if !$volid || $volid eq 'none';
194 if ($volid eq 'cdrom') {
195 $rpcenv->check($authuser, "/", ['Sys.Console']);
197 # we simply allow access
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
};
204 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
205 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
206 $sharedvm = 0 if !$scfg->{shared
};
208 $sid = $storage if $storage;
209 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
213 $rpcenv->check($authuser, "/storage/$conf->{vmstatestorage}", ['Datastore.AllocateSpace'])
214 if defined($conf->{vmstatestorage
});
219 my $check_storage_access_migrate = sub {
220 my ($rpcenv, $authuser, $storecfg, $storage, $node) = @_;
222 PVE
::Storage
::storage_check_enabled
($storecfg, $storage, $node);
224 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace']);
226 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
227 die "storage '$storage' does not support vm images\n"
228 if !$scfg->{content
}->{images
};
231 my $import_from_volid = sub {
232 my ($storecfg, $src_volid, $dest_info, $vollist) = @_;
234 die "could not get size of $src_volid\n"
235 if !PVE
::Storage
::volume_size_info
($storecfg, $src_volid, 10);
237 die "cannot import from cloudinit disk\n"
238 if PVE
::QemuServer
::Drive
::drive_is_cloudinit
({ file
=> $src_volid });
240 my $src_vmid = (PVE
::Storage
::parse_volname
($storecfg, $src_volid))[2];
242 my $src_vm_state = sub {
243 my $exists = $src_vmid && PVE
::Cluster
::get_vmlist
()->{ids
}->{$src_vmid} ?
1 : 0;
247 eval { PVE
::QemuConfig
::assert_config_exists_on_node
($src_vmid); };
248 die "owner VM $src_vmid not on local node\n" if $@;
249 $runs = PVE
::QemuServer
::Helpers
::vm_running_locally
($src_vmid) || 0;
252 return ($exists, $runs);
255 my ($src_vm_exists, $running) = $src_vm_state->();
257 die "cannot import from '$src_volid' - full clone feature is not supported\n"
258 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $src_volid, undef, $running);
261 my ($src_vm_exists_now, $running_now) = $src_vm_state->();
263 die "owner VM $src_vmid changed state unexpectedly\n"
264 if $src_vm_exists_now != $src_vm_exists || $running_now != $running;
266 my $src_conf = $src_vm_exists_now ? PVE
::QemuConfig-
>load_config($src_vmid) : {};
268 my $src_drive = { file
=> $src_volid };
270 PVE
::QemuConfig-
>foreach_volume($src_conf, sub {
271 my ($ds, $drive) = @_;
273 return if $src_drivename;
275 if ($drive->{file
} eq $src_volid) {
277 $src_drivename = $ds;
283 running
=> $running_now,
284 drivename
=> $src_drivename,
289 my ($src_storeid) = PVE
::Storage
::parse_volume_id
($src_volid);
291 return PVE
::QemuServer
::clone_disk
(
300 PVE
::Storage
::get_bandwidth_limit
('clone', [$src_storeid, $dest_info->{storage
}]),
306 $cloned = PVE
::QemuConfig-
>lock_config_full($src_vmid, 30, $clonefn);
307 } elsif ($src_vmid) {
308 $cloned = PVE
::QemuConfig-
>lock_config_shared($src_vmid, 30, $clonefn);
310 $cloned = $clonefn->();
313 return $cloned->@{qw(file size)};
316 # Note: $pool is only needed when creating a VM, because pool permissions
317 # are automatically inherited if VM already exists inside a pool.
318 my $create_disks = sub {
319 my ($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
326 my ($ds, $disk) = @_;
328 my $volid = $disk->{file
};
329 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
331 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
332 delete $disk->{size
};
333 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
334 } elsif (defined($volname) && $volname eq 'cloudinit') {
335 $storeid = $storeid // $default_storage;
336 die "no storage ID specified (and no default storage)\n" if !$storeid;
339 my $ci_key = PVE
::QemuConfig-
>has_cloudinit($conf, $ds)
340 || PVE
::QemuConfig-
>has_cloudinit($conf->{pending
} || {}, $ds)
341 || PVE
::QemuConfig-
>has_cloudinit($res, $ds)
343 die "$ds - cloud-init drive is already attached at '$ci_key'\n";
346 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
347 my $name = "vm-$vmid-cloudinit";
351 $fmt = $disk->{format
} // "qcow2";
354 $fmt = $disk->{format
} // "raw";
357 # Initial disk created with 4 MB and aligned to 4MB on regeneration
358 my $ci_size = PVE
::QemuServer
::Cloudinit
::CLOUDINIT_DISK_SIZE
;
359 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, $name, $ci_size/1024);
360 $disk->{file
} = $volid;
361 $disk->{media
} = 'cdrom';
362 push @$vollist, $volid;
363 delete $disk->{format
}; # no longer needed
364 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
365 print "$ds: successfully created disk '$res->{$ds}'\n";
366 } elsif ($volid =~ $PVE::QemuServer
::Drive
::NEW_DISK_RE
) {
367 my ($storeid, $size) = ($2 || $default_storage, $3);
368 die "no storage ID specified (and no default storage)\n" if !$storeid;
370 if (my $source = delete $disk->{'import-from'}) {
373 if (PVE
::Storage
::parse_volume_id
($source, 1)) { # PVE-managed volume
378 format
=> $disk->{format
},
381 $dest_info->{efisize
} = PVE
::QemuServer
::get_efivars_size
($conf, $disk)
382 if $ds eq 'efidisk0';
384 ($dst_volid, $size) = eval {
385 $import_from_volid->($storecfg, $source, $dest_info, $vollist);
387 die "cannot import from '$source' - $@" if $@;
389 $source = PVE
::Storage
::abs_filesystem_path
($storecfg, $source, 1);
390 $size = PVE
::Storage
::file_size_info
($source);
391 die "could not get file size of $source\n" if !$size;
393 (undef, $dst_volid) = PVE
::QemuServer
::ImportDisk
::do_import
(
399 format
=> $disk->{format
},
400 'skip-config-update' => 1,
403 push @$vollist, $dst_volid;
406 $disk->{file
} = $dst_volid;
407 $disk->{size
} = $size;
408 delete $disk->{format
}; # no longer needed
409 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
411 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
412 my $fmt = $disk->{format
} || $defformat;
414 $size = PVE
::Tools
::convert_size
($size, 'gb' => 'kb'); # vdisk_alloc uses kb
417 if ($ds eq 'efidisk0') {
418 my $smm = PVE
::QemuServer
::Machine
::machine_type_is_q35
($conf);
419 ($volid, $size) = PVE
::QemuServer
::create_efidisk
(
420 $storecfg, $storeid, $vmid, $fmt, $arch, $disk, $smm);
421 } elsif ($ds eq 'tpmstate0') {
422 # swtpm can only use raw volumes, and uses a fixed size
423 $size = PVE
::Tools
::convert_size
(PVE
::QemuServer
::Drive
::TPMSTATE_DISK_SIZE
, 'b' => 'kb');
424 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, "raw", undef, $size);
426 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, undef, $size);
428 push @$vollist, $volid;
429 $disk->{file
} = $volid;
430 $disk->{size
} = PVE
::Tools
::convert_size
($size, 'kb' => 'b');
431 delete $disk->{format
}; # no longer needed
432 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
435 print "$ds: successfully created disk '$res->{$ds}'\n";
437 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
439 my ($vtype) = PVE
::Storage
::parse_volname
($storecfg, $volid);
440 die "cannot use volume $volid - content type needs to be 'images' or 'iso'"
441 if $vtype ne 'images' && $vtype ne 'iso';
443 if (PVE
::QemuServer
::Drive
::drive_is_cloudinit
($disk)) {
445 my $ci_key = PVE
::QemuConfig-
>has_cloudinit($conf, $ds)
446 || PVE
::QemuConfig-
>has_cloudinit($conf->{pending
} || {}, $ds)
447 || PVE
::QemuConfig-
>has_cloudinit($res, $ds)
449 die "$ds - cloud-init drive is already attached at '$ci_key'\n";
454 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
456 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
457 die "volume $volid does not exist\n" if !$size;
458 $disk->{size
} = $size;
460 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
464 eval { $foreach_volume_with_alloc->($settings, $code); };
466 # free allocated images on error
468 syslog
('err', "VM $vmid creating disks failed");
469 foreach my $volid (@$vollist) {
470 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
476 return ($vollist, $res);
479 my $check_cpu_model_access = sub {
480 my ($rpcenv, $authuser, $new, $existing) = @_;
482 return if !defined($new->{cpu
});
484 my $cpu = PVE
::JSONSchema
::check_format
('pve-vm-cpu-conf', $new->{cpu
});
485 return if !$cpu || !$cpu->{cputype
}; # always allow default
486 my $cputype = $cpu->{cputype
};
488 if ($existing && $existing->{cpu
}) {
489 # changing only other settings doesn't require permissions for CPU model
490 my $existingCpu = PVE
::JSONSchema
::check_format
('pve-vm-cpu-conf', $existing->{cpu
});
491 return if $existingCpu->{cputype
} eq $cputype;
494 if (PVE
::QemuServer
::CPUConfig
::is_custom_model
($cputype)) {
495 $rpcenv->check($authuser, "/nodes", ['Sys.Audit']);
510 my $memoryoptions = {
516 my $hwtypeoptions = {
529 my $generaloptions = {
536 'migrate_downtime' => 1,
537 'migrate_speed' => 1,
549 my $vmpoweroptions = {
556 'vmstatestorage' => 1,
559 my $cloudinitoptions = {
570 my $check_vm_create_serial_perm = sub {
571 my ($rpcenv, $authuser, $vmid, $pool, $param) = @_;
573 return 1 if $authuser eq 'root@pam';
575 foreach my $opt (keys %{$param}) {
576 next if $opt !~ m/^serial\d+$/;
578 if ($param->{$opt} eq 'socket') {
579 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
581 die "only root can set '$opt' config for real devices\n";
588 my sub check_usb_perm
{
589 my ($rpcenv, $authuser, $vmid, $pool, $opt, $value) = @_;
591 return 1 if $authuser eq 'root@pam';
593 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
595 my $device = PVE
::JSONSchema
::parse_property_string
('pve-qm-usb', $value);
596 if ($device->{host
} && $device->{host
} !~ m/^spice$/i) {
597 die "only root can set '$opt' config for real devices\n";
598 } elsif ($device->{mapping
}) {
599 $rpcenv->check_full($authuser, "/mapping/usb/$device->{mapping}", ['Mapping.Use']);
601 die "either 'host' or 'mapping' must be set.\n";
607 my sub check_vm_create_usb_perm
{
608 my ($rpcenv, $authuser, $vmid, $pool, $param) = @_;
610 return 1 if $authuser eq 'root@pam';
612 foreach my $opt (keys %{$param}) {
613 next if $opt !~ m/^usb\d+$/;
614 check_usb_perm
($rpcenv, $authuser, $vmid, $pool, $opt, $param->{$opt});
620 my sub check_hostpci_perm
{
621 my ($rpcenv, $authuser, $vmid, $pool, $opt, $value) = @_;
623 return 1 if $authuser eq 'root@pam';
625 my $device = PVE
::JSONSchema
::parse_property_string
('pve-qm-hostpci', $value);
626 if ($device->{host
}) {
627 die "only root can set '$opt' config for non-mapped devices\n";
628 } elsif ($device->{mapping
}) {
629 $rpcenv->check_full($authuser, "/mapping/pci/$device->{mapping}", ['Mapping.Use']);
630 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
632 die "either 'host' or 'mapping' must be set.\n";
638 my sub check_vm_create_hostpci_perm
{
639 my ($rpcenv, $authuser, $vmid, $pool, $param) = @_;
641 return 1 if $authuser eq 'root@pam';
643 foreach my $opt (keys %{$param}) {
644 next if $opt !~ m/^hostpci\d+$/;
645 check_hostpci_perm
($rpcenv, $authuser, $vmid, $pool, $opt, $param->{$opt});
651 my $check_vm_modify_config_perm = sub {
652 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
654 return 1 if $authuser eq 'root@pam';
656 foreach my $opt (@$key_list) {
657 # some checks (e.g., disk, serial port, usb) need to be done somewhere
658 # else, as there the permission can be value dependend
659 next if PVE
::QemuServer
::is_valid_drivename
($opt);
660 next if $opt eq 'cdrom';
661 next if $opt =~ m/^(?:unused|serial|usb|hostpci)\d+$/;
662 next if $opt eq 'tags';
665 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
666 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
667 } elsif ($memoryoptions->{$opt}) {
668 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
669 } elsif ($hwtypeoptions->{$opt}) {
670 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
671 } elsif ($generaloptions->{$opt}) {
672 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
673 # special case for startup since it changes host behaviour
674 if ($opt eq 'startup') {
675 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
677 } elsif ($vmpoweroptions->{$opt}) {
678 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
679 } elsif ($diskoptions->{$opt}) {
680 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
681 } elsif ($opt =~ m/^net\d+$/) {
682 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
683 } elsif ($cloudinitoptions->{$opt} || $opt =~ m/^ipconfig\d+$/) {
684 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Cloudinit', 'VM.Config.Network'], 1);
685 } elsif ($opt eq 'vmstate') {
686 # the user needs Disk and PowerMgmt privileges to change the vmstate
687 # also needs privileges on the storage, that will be checked later
688 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk', 'VM.PowerMgmt' ]);
690 # catches args, lock, etc.
691 # new options will be checked here
692 die "only root can set '$opt' config\n";
699 sub assert_scsi_feature_compatibility
{
700 my ($opt, $conf, $storecfg, $drive_attributes) = @_;
702 my $drive = PVE
::QemuServer
::Drive
::parse_drive
($opt, $drive_attributes);
704 my $machine_type = PVE
::QemuServer
::get_vm_machine
($conf, undef, $conf->{arch
});
705 my $machine_version = PVE
::QemuServer
::Machine
::extract_version
(
706 $machine_type, PVE
::QemuServer
::kvm_user_version
());
707 my $drivetype = PVE
::QemuServer
::Drive
::get_scsi_devicetype
(
708 $drive, $storecfg, $machine_version);
710 if ($drivetype ne 'hd' && $drivetype ne 'cd') {
711 if ($drive->{product
}) {
713 $opt => "Passing of product information is only supported for 'scsi-hd' and "
714 ."'scsi-cd' devices (e.g. not pass-through).",
717 if ($drive->{vendor
}) {
719 $opt => "Passing of vendor information is only supported for 'scsi-hd' and "
720 ."'scsi-cd' devices (e.g. not pass-through).",
726 __PACKAGE__-
>register_method({
730 description
=> "Virtual machine index (per node).",
732 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
736 protected
=> 1, # qemu pid files are only readable by root
738 additionalProperties
=> 0,
740 node
=> get_standard_option
('pve-node'),
744 description
=> "Determine the full status of active VMs.",
752 properties
=> $PVE::QemuServer
::vmstatus_return_properties
,
754 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
759 my $rpcenv = PVE
::RPCEnvironment
::get
();
760 my $authuser = $rpcenv->get_user();
762 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
765 foreach my $vmid (keys %$vmstatus) {
766 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
768 my $data = $vmstatus->{$vmid};
775 my $parse_restore_archive = sub {
776 my ($storecfg, $archive) = @_;
778 my ($archive_storeid, $archive_volname) = PVE
::Storage
::parse_volume_id
($archive, 1);
782 if (defined($archive_storeid)) {
783 my $scfg = PVE
::Storage
::storage_config
($storecfg, $archive_storeid);
784 $res->{volid
} = $archive;
785 if ($scfg->{type
} eq 'pbs') {
786 $res->{type
} = 'pbs';
790 my $path = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
791 $res->{type
} = 'file';
792 $res->{path
} = $path;
797 __PACKAGE__-
>register_method({
801 description
=> "Create or restore a virtual machine.",
803 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
804 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
805 "If you create disks you need 'Datastore.AllocateSpace' on any used storage." .
806 "If you use a bridge/vlan, you need 'SDN.Use' on any used bridge/vlan.",
807 user
=> 'all', # check inside
812 additionalProperties
=> 0,
813 properties
=> PVE
::QemuServer
::json_config_properties
(
815 node
=> get_standard_option
('pve-node'),
816 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
818 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.",
822 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
824 storage
=> get_standard_option
('pve-storage-id', {
825 description
=> "Default storage.",
827 completion
=> \
&PVE
::QemuServer
::complete_storage
,
832 description
=> "Allow to overwrite existing VM.",
833 requires
=> 'archive',
838 description
=> "Assign a unique random ethernet address.",
839 requires
=> 'archive',
844 description
=> "Start the VM immediately from the backup and restore in background. PBS only.",
845 requires
=> 'archive',
849 type
=> 'string', format
=> 'pve-poolid',
850 description
=> "Add the VM to the specified pool.",
853 description
=> "Override I/O bandwidth limit (in KiB/s).",
857 default => 'restore limit from datacenter or storage config',
863 description
=> "Start VM after it was created successfully.",
875 my $rpcenv = PVE
::RPCEnvironment
::get
();
876 my $authuser = $rpcenv->get_user();
878 my $node = extract_param
($param, 'node');
879 my $vmid = extract_param
($param, 'vmid');
881 my $archive = extract_param
($param, 'archive');
882 my $is_restore = !!$archive;
884 my $bwlimit = extract_param
($param, 'bwlimit');
885 my $force = extract_param
($param, 'force');
886 my $pool = extract_param
($param, 'pool');
887 my $start_after_create = extract_param
($param, 'start');
888 my $storage = extract_param
($param, 'storage');
889 my $unique = extract_param
($param, 'unique');
890 my $live_restore = extract_param
($param, 'live-restore');
892 if (defined(my $ssh_keys = $param->{sshkeys
})) {
893 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
894 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
897 $param->{cpuunits
} = PVE
::CGroup
::clamp_cpu_shares
($param->{cpuunits
})
898 if defined($param->{cpuunits
}); # clamp value depending on cgroup version
900 PVE
::Cluster
::check_cfs_quorum
();
902 my $filename = PVE
::QemuConfig-
>config_file($vmid);
903 my $storecfg = PVE
::Storage
::config
();
905 if (defined($pool)) {
906 $rpcenv->check_pool_exist($pool);
909 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
910 if defined($storage);
912 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
914 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
916 } elsif ($archive && $force && (-f
$filename) &&
917 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
918 # OK: user has VM.Backup permissions and wants to restore an existing VM
924 for my $opt (sort keys $param->%*) {
925 if (PVE
::QemuServer
::Drive
::is_valid_drivename
($opt)) {
926 raise_param_exc
({ $opt => "option conflicts with option 'archive'" });
930 if ($archive eq '-') {
931 die "pipe requires cli environment\n" if $rpcenv->{type
} ne 'cli';
932 $archive = { type
=> 'pipe' };
934 PVE
::Storage
::check_volume_access
(
943 $archive = $parse_restore_archive->($storecfg, $archive);
947 if (scalar(keys $param->%*) > 0) {
948 &$resolve_cdrom_alias($param);
950 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
952 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
954 &$check_vm_create_serial_perm($rpcenv, $authuser, $vmid, $pool, $param);
955 check_vm_create_usb_perm
($rpcenv, $authuser, $vmid, $pool, $param);
956 check_vm_create_hostpci_perm
($rpcenv, $authuser, $vmid, $pool, $param);
958 PVE
::QemuServer
::check_bridge_access
($rpcenv, $authuser, $param);
959 &$check_cpu_model_access($rpcenv, $authuser, $param);
961 $check_drive_param->($param, $storecfg);
963 PVE
::QemuServer
::add_random_macs
($param);
966 my $emsg = $is_restore ?
"unable to restore VM $vmid -" : "unable to create VM $vmid -";
968 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, $force) };
969 die "$emsg $@" if $@;
971 my $restored_data = 0;
972 my $restorefn = sub {
973 my $conf = PVE
::QemuConfig-
>load_config($vmid);
975 PVE
::QemuConfig-
>check_protection($conf, $emsg);
977 die "$emsg vm is running\n" if PVE
::QemuServer
::check_running
($vmid);
980 my $restore_options = {
985 live
=> $live_restore,
986 override_conf
=> $param,
988 if (my $volid = $archive->{volid
}) {
989 # best effort, real check is after restoring!
991 my $old_conf = PVE
::Storage
::extract_vzdump_config
($storecfg, $volid);
992 PVE
::QemuServer
::restore_merge_config
("backup/qemu-server/$vmid.conf", $old_conf, $param);
995 warn "Could not extract backed up config: $@\n";
996 warn "Skipping early checks!\n";
998 PVE
::QemuServer
::check_restore_permissions
($rpcenv, $authuser, $merged);
1001 if ($archive->{type
} eq 'file' || $archive->{type
} eq 'pipe') {
1002 die "live-restore is only compatible with backup images from a Proxmox Backup Server\n"
1004 PVE
::QemuServer
::restore_file_archive
($archive->{path
} // '-', $vmid, $authuser, $restore_options);
1005 } elsif ($archive->{type
} eq 'pbs') {
1006 PVE
::QemuServer
::restore_proxmox_backup_archive
($archive->{volid
}, $vmid, $authuser, $restore_options);
1008 die "unknown backup archive type\n";
1012 my $restored_conf = PVE
::QemuConfig-
>load_config($vmid);
1013 # Convert restored VM to template if backup was VM template
1014 if (PVE
::QemuConfig-
>is_template($restored_conf)) {
1015 warn "Convert to template.\n";
1016 eval { PVE
::QemuServer
::template_create
($vmid, $restored_conf) };
1020 PVE
::QemuServer
::create_ifaces_ipams_ips
($restored_conf, $vmid) if $unique;
1023 # ensure no old replication state are exists
1024 PVE
::ReplicationState
::delete_guest_states
($vmid);
1026 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
1028 if ($start_after_create && !$live_restore) {
1029 print "Execute autostart\n";
1030 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
1035 my $createfn = sub {
1036 # ensure no old replication state are exists
1037 PVE
::ReplicationState
::delete_guest_states
($vmid);
1041 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
1044 for my $opt (sort keys $param->%*) {
1045 next if $opt !~ m/^scsi\d+$/;
1046 assert_scsi_feature_compatibility
($opt, $conf, $storecfg, $param->{$opt});
1049 $conf->{meta
} = PVE
::QemuServer
::new_meta_info_string
();
1053 ($vollist, my $created_opts) = $create_disks->(
1064 $conf->{$_} = $created_opts->{$_} for keys $created_opts->%*;
1066 if (!$conf->{boot
}) {
1067 my $devs = PVE
::QemuServer
::get_default_bootdevices
($conf);
1068 $conf->{boot
} = PVE
::QemuServer
::print_bootorder
($devs);
1071 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
1072 PVE
::QemuServer
::assert_clipboard_config
($vga);
1074 # auto generate uuid if user did not specify smbios1 option
1075 if (!$conf->{smbios1
}) {
1076 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
1079 if ((!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') && $arch ne 'aarch64') {
1080 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
1083 my $machine = $conf->{machine
};
1084 if (!$machine || $machine =~ m/^(?:pc|q35|virt)$/) {
1085 # always pin Windows' machine version on create, they get to easily confused
1086 if (PVE
::QemuServer
::Helpers
::windows_version
($conf->{ostype
})) {
1087 $conf->{machine
} = PVE
::QemuServer
::windows_get_pinned_machine_version
($machine);
1091 PVE
::QemuConfig-
>write_config($vmid, $conf);
1097 foreach my $volid (@$vollist) {
1098 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
1104 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
1106 PVE
::QemuServer
::create_ifaces_ipams_ips
($conf, $vmid);
1109 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
1111 if ($start_after_create) {
1112 print "Execute autostart\n";
1113 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
1118 my ($code, $worker_name);
1120 $worker_name = 'qmrestore';
1122 eval { $restorefn->() };
1124 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
1126 if ($restored_data) {
1127 warn "error after data was restored, VM disks should be OK but config may "
1128 ."require adaptions. VM $vmid state is NOT cleaned up.\n";
1130 warn "error before or during data restore, some or all disks were not "
1131 ."completely restored. VM $vmid state is NOT cleaned up.\n";
1137 $worker_name = 'qmcreate';
1139 eval { $createfn->() };
1142 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
1143 unlink($conffile) or die "failed to remove config file: $!\n";
1151 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
1154 __PACKAGE__-
>register_method({
1159 description
=> "Directory index",
1164 additionalProperties
=> 0,
1166 node
=> get_standard_option
('pve-node'),
1167 vmid
=> get_standard_option
('pve-vmid'),
1175 subdir
=> { type
=> 'string' },
1178 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1184 { subdir
=> 'config' },
1185 { subdir
=> 'cloudinit' },
1186 { subdir
=> 'pending' },
1187 { subdir
=> 'status' },
1188 { subdir
=> 'unlink' },
1189 { subdir
=> 'vncproxy' },
1190 { subdir
=> 'termproxy' },
1191 { subdir
=> 'migrate' },
1192 { subdir
=> 'resize' },
1193 { subdir
=> 'move' },
1194 { subdir
=> 'rrd' },
1195 { subdir
=> 'rrddata' },
1196 { subdir
=> 'monitor' },
1197 { subdir
=> 'agent' },
1198 { subdir
=> 'snapshot' },
1199 { subdir
=> 'spiceproxy' },
1200 { subdir
=> 'sendkey' },
1201 { subdir
=> 'firewall' },
1202 { subdir
=> 'mtunnel' },
1203 { subdir
=> 'remote_migrate' },
1209 __PACKAGE__-
>register_method ({
1210 subclass
=> "PVE::API2::Firewall::VM",
1211 path
=> '{vmid}/firewall',
1214 __PACKAGE__-
>register_method ({
1215 subclass
=> "PVE::API2::Qemu::Agent",
1216 path
=> '{vmid}/agent',
1219 __PACKAGE__-
>register_method({
1221 path
=> '{vmid}/rrd',
1223 protected
=> 1, # fixme: can we avoid that?
1225 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1227 description
=> "Read VM RRD statistics (returns PNG)",
1229 additionalProperties
=> 0,
1231 node
=> get_standard_option
('pve-node'),
1232 vmid
=> get_standard_option
('pve-vmid'),
1234 description
=> "Specify the time frame you are interested in.",
1236 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
1239 description
=> "The list of datasources you want to display.",
1240 type
=> 'string', format
=> 'pve-configid-list',
1243 description
=> "The RRD consolidation function",
1245 enum
=> [ 'AVERAGE', 'MAX' ],
1253 filename
=> { type
=> 'string' },
1259 return PVE
::RRD
::create_rrd_graph
(
1260 "pve2-vm/$param->{vmid}", $param->{timeframe
},
1261 $param->{ds
}, $param->{cf
});
1265 __PACKAGE__-
>register_method({
1267 path
=> '{vmid}/rrddata',
1269 protected
=> 1, # fixme: can we avoid that?
1271 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1273 description
=> "Read VM RRD statistics",
1275 additionalProperties
=> 0,
1277 node
=> get_standard_option
('pve-node'),
1278 vmid
=> get_standard_option
('pve-vmid'),
1280 description
=> "Specify the time frame you are interested in.",
1282 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
1285 description
=> "The RRD consolidation function",
1287 enum
=> [ 'AVERAGE', 'MAX' ],
1302 return PVE
::RRD
::create_rrd_data
(
1303 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
1307 __PACKAGE__-
>register_method({
1308 name
=> 'vm_config',
1309 path
=> '{vmid}/config',
1312 description
=> "Get the virtual machine configuration with pending configuration " .
1313 "changes applied. Set the 'current' parameter to get the current configuration instead.",
1315 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1318 additionalProperties
=> 0,
1320 node
=> get_standard_option
('pve-node'),
1321 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1323 description
=> "Get current values (instead of pending values).",
1328 snapshot
=> get_standard_option
('pve-snapshot-name', {
1329 description
=> "Fetch config values from given snapshot.",
1332 my ($cmd, $pname, $cur, $args) = @_;
1333 PVE
::QemuConfig-
>snapshot_list($args->[0]);
1339 description
=> "The VM configuration.",
1341 properties
=> PVE
::QemuServer
::json_config_properties
({
1344 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
1351 raise_param_exc
({ snapshot
=> "cannot use 'snapshot' parameter with 'current'",
1352 current
=> "cannot use 'snapshot' parameter with 'current'"})
1353 if ($param->{snapshot
} && $param->{current
});
1356 if ($param->{snapshot
}) {
1357 $conf = PVE
::QemuConfig-
>load_snapshot_config($param->{vmid
}, $param->{snapshot
});
1359 $conf = PVE
::QemuConfig-
>load_current_config($param->{vmid
}, $param->{current
});
1361 $conf->{cipassword
} = '**********' if $conf->{cipassword
};
1366 __PACKAGE__-
>register_method({
1367 name
=> 'vm_pending',
1368 path
=> '{vmid}/pending',
1371 description
=> "Get the virtual machine configuration with both current and pending values.",
1373 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1376 additionalProperties
=> 0,
1378 node
=> get_standard_option
('pve-node'),
1379 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1388 description
=> "Configuration option name.",
1392 description
=> "Current value.",
1397 description
=> "Pending value.",
1402 description
=> "Indicates a pending delete request if present and not 0. " .
1403 "The value 2 indicates a force-delete request.",
1415 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1417 my $pending_delete_hash = PVE
::QemuConfig-
>parse_pending_delete($conf->{pending
}->{delete});
1419 $conf->{cipassword
} = '**********' if defined($conf->{cipassword
});
1420 $conf->{pending
}->{cipassword
} = '********** ' if defined($conf->{pending
}->{cipassword
});
1422 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);
1425 __PACKAGE__-
>register_method({
1426 name
=> 'cloudinit_pending',
1427 path
=> '{vmid}/cloudinit',
1430 description
=> "Get the cloudinit configuration with both current and pending values.",
1432 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1435 additionalProperties
=> 0,
1437 node
=> get_standard_option
('pve-node'),
1438 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1447 description
=> "Configuration option name.",
1451 description
=> "Value as it was used to generate the current cloudinit image.",
1456 description
=> "The new pending value.",
1461 description
=> "Indicates a pending delete request if present and not 0. ",
1473 my $vmid = $param->{vmid
};
1474 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1476 my $ci = $conf->{cloudinit
};
1478 $conf->{cipassword
} = '**********' if exists $conf->{cipassword
};
1479 $ci->{cipassword
} = '**********' if exists $ci->{cipassword
};
1483 # All the values that got added
1484 my $added = delete($ci->{added
}) // '';
1485 for my $key (PVE
::Tools
::split_list
($added)) {
1486 push @$res, { key
=> $key, pending
=> $conf->{$key} };
1489 # All already existing values (+ their new value, if it exists)
1490 for my $opt (keys %$cloudinitoptions) {
1491 next if !$conf->{$opt};
1492 next if $added =~ m/$opt/;
1497 if (my $pending = $ci->{$opt}) {
1498 $item->{value
} = $pending;
1499 $item->{pending
} = $conf->{$opt};
1501 $item->{value
} = $conf->{$opt},
1507 # Now, we'll find the deleted ones
1508 for my $opt (keys %$ci) {
1509 next if $conf->{$opt};
1510 push @$res, { key
=> $opt, delete => 1 };
1516 __PACKAGE__-
>register_method({
1517 name
=> 'cloudinit_update',
1518 path
=> '{vmid}/cloudinit',
1522 description
=> "Regenerate and change cloudinit config drive.",
1524 check
=> ['perm', '/vms/{vmid}', ['VM.Config.Cloudinit']],
1527 additionalProperties
=> 0,
1529 node
=> get_standard_option
('pve-node'),
1530 vmid
=> get_standard_option
('pve-vmid'),
1533 returns
=> { type
=> 'null' },
1537 my $rpcenv = PVE
::RPCEnvironment
::get
();
1538 my $authuser = $rpcenv->get_user();
1540 my $vmid = extract_param
($param, 'vmid');
1542 PVE
::QemuConfig-
>lock_config($vmid, sub {
1543 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1544 PVE
::QemuConfig-
>check_lock($conf);
1546 my $storecfg = PVE
::Storage
::config
();
1547 PVE
::QemuServer
::vmconfig_update_cloudinit_drive
($storecfg, $conf, $vmid);
1552 # POST/PUT {vmid}/config implementation
1554 # The original API used PUT (idempotent) an we assumed that all operations
1555 # are fast. But it turned out that almost any configuration change can
1556 # involve hot-plug actions, or disk alloc/free. Such actions can take long
1557 # time to complete and have side effects (not idempotent).
1559 # The new implementation uses POST and forks a worker process. We added
1560 # a new option 'background_delay'. If specified we wait up to
1561 # 'background_delay' second for the worker task to complete. It returns null
1562 # if the task is finished within that time, else we return the UPID.
1564 my $update_vm_api = sub {
1565 my ($param, $sync) = @_;
1567 my $rpcenv = PVE
::RPCEnvironment
::get
();
1569 my $authuser = $rpcenv->get_user();
1571 my $node = extract_param
($param, 'node');
1573 my $vmid = extract_param
($param, 'vmid');
1575 my $digest = extract_param
($param, 'digest');
1577 my $background_delay = extract_param
($param, 'background_delay');
1579 my $skip_cloud_init = extract_param
($param, 'skip_cloud_init');
1581 if (defined(my $cipassword = $param->{cipassword
})) {
1582 # Same logic as in cloud-init (but with the regex fixed...)
1583 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
1584 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
1587 my @paramarr = (); # used for log message
1588 foreach my $key (sort keys %$param) {
1589 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
1590 push @paramarr, "-$key", $value;
1593 my $skiplock = extract_param
($param, 'skiplock');
1594 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1595 if $skiplock && $authuser ne 'root@pam';
1597 my $delete_str = extract_param
($param, 'delete');
1599 my $revert_str = extract_param
($param, 'revert');
1601 my $force = extract_param
($param, 'force');
1603 if (defined(my $ssh_keys = $param->{sshkeys
})) {
1604 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
1605 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
1608 $param->{cpuunits
} = PVE
::CGroup
::clamp_cpu_shares
($param->{cpuunits
})
1609 if defined($param->{cpuunits
}); # clamp value depending on cgroup version
1611 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
1613 my $storecfg = PVE
::Storage
::config
();
1615 &$resolve_cdrom_alias($param);
1617 # now try to verify all parameters
1620 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1621 if (!PVE
::QemuServer
::option_exists
($opt)) {
1622 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1625 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1626 "-revert $opt' at the same time" })
1627 if defined($param->{$opt});
1629 $revert->{$opt} = 1;
1633 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1634 $opt = 'ide2' if $opt eq 'cdrom';
1636 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1637 "-delete $opt' at the same time" })
1638 if defined($param->{$opt});
1640 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1641 "-revert $opt' at the same time" })
1644 if (!PVE
::QemuServer
::option_exists
($opt)) {
1645 raise_param_exc
({ delete => "unknown option '$opt'" });
1651 my $repl_conf = PVE
::ReplicationConfig-
>new();
1652 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1653 my $check_replication = sub {
1655 return if !$is_replicated;
1656 my $volid = $drive->{file
};
1657 return if !$volid || !($drive->{replicate
}//1);
1658 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1660 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1661 die "cannot add non-managed/pass-through volume to a replicated VM\n"
1662 if !defined($storeid);
1664 return if defined($volname) && $volname eq 'cloudinit';
1667 if ($volid =~ $PVE::QemuServer
::Drive
::NEW_DISK_RE
) {
1669 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1671 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1673 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1674 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1675 return if $scfg->{shared
};
1676 die "cannot add non-replicatable volume to a replicated VM\n";
1679 $check_drive_param->($param, $storecfg, $check_replication);
1681 foreach my $opt (keys %$param) {
1682 if ($opt =~ m/^net(\d+)$/) {
1684 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1685 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1686 } elsif ($opt eq 'vmgenid') {
1687 if ($param->{$opt} eq '1') {
1688 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1690 } elsif ($opt eq 'hookscript') {
1691 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1692 raise_param_exc
({ $opt => $@ }) if $@;
1696 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1698 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1700 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1702 PVE
::QemuServer
::check_bridge_access
($rpcenv, $authuser, $param);
1704 my $updatefn = sub {
1706 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1708 die "checksum missmatch (file change by other user?)\n"
1709 if $digest && $digest ne $conf->{digest
};
1711 &$check_cpu_model_access($rpcenv, $authuser, $param, $conf);
1713 # FIXME: 'suspended' lock should probabyl be a state or "weak" lock?!
1714 if (scalar(@delete) && grep { $_ eq 'vmstate'} @delete) {
1715 if (defined($conf->{lock}) && $conf->{lock} eq 'suspended') {
1716 delete $conf->{lock}; # for check lock check, not written out
1717 push @delete, 'lock'; # this is the real deal to write it out
1719 push @delete, 'runningmachine' if $conf->{runningmachine
};
1720 push @delete, 'runningcpu' if $conf->{runningcpu
};
1723 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1725 foreach my $opt (keys %$revert) {
1726 if (defined($conf->{$opt})) {
1727 $param->{$opt} = $conf->{$opt};
1728 } elsif (defined($conf->{pending
}->{$opt})) {
1733 if ($param->{memory
} || defined($param->{balloon
})) {
1735 my $memory = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
};
1736 my $maxmem = get_current_memory
($memory);
1737 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1739 die "balloon value too large (must be smaller than assigned memory)\n"
1740 if $balloon && $balloon > $maxmem;
1743 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1747 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1749 # write updates to pending section
1751 my $modified = {}; # record what $option we modify
1754 if (my $boot = $conf->{boot
}) {
1755 my $bootcfg = PVE
::JSONSchema
::parse_property_string
('pve-qm-boot', $boot);
1756 @bootorder = PVE
::Tools
::split_list
($bootcfg->{order
}) if $bootcfg && $bootcfg->{order
};
1758 my $bootorder_deleted = grep {$_ eq 'bootorder'} @delete;
1760 my $check_drive_perms = sub {
1761 my ($opt, $val) = @_;
1762 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val, 1);
1763 if (PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
1764 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Cloudinit', 'VM.Config.CDROM']);
1765 } elsif (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) { # CDROM
1766 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1768 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1773 foreach my $opt (@delete) {
1774 $modified->{$opt} = 1;
1775 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1777 # value of what we want to delete, independent if pending or not
1778 my $val = $conf->{$opt} // $conf->{pending
}->{$opt};
1779 if (!defined($val)) {
1780 warn "cannot delete '$opt' - not set in current configuration!\n";
1781 $modified->{$opt} = 0;
1784 my $is_pending_val = defined($conf->{pending
}->{$opt});
1785 delete $conf->{pending
}->{$opt};
1787 # remove from bootorder if necessary
1788 if (!$bootorder_deleted && @bootorder && grep {$_ eq $opt} @bootorder) {
1789 @bootorder = grep {$_ ne $opt} @bootorder;
1790 $conf->{pending
}->{boot
} = PVE
::QemuServer
::print_bootorder
(\
@bootorder);
1791 $modified->{boot
} = 1;
1794 if ($opt =~ m/^unused/) {
1795 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1796 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1797 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1798 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1799 delete $conf->{$opt};
1800 PVE
::QemuConfig-
>write_config($vmid, $conf);
1802 } elsif ($opt eq 'vmstate') {
1803 PVE
::QemuConfig-
>check_protection($conf, "can't remove vmstate '$val'");
1804 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, { file
=> $val }, $rpcenv, $authuser, 1)) {
1805 delete $conf->{$opt};
1806 PVE
::QemuConfig-
>write_config($vmid, $conf);
1808 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1809 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1810 $check_drive_perms->($opt, $val);
1811 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $val))
1813 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1814 PVE
::QemuConfig-
>write_config($vmid, $conf);
1815 } elsif ($opt =~ m/^serial\d+$/) {
1816 if ($val eq 'socket') {
1817 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1818 } elsif ($authuser ne 'root@pam') {
1819 die "only root can delete '$opt' config for real devices\n";
1821 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1822 PVE
::QemuConfig-
>write_config($vmid, $conf);
1823 } elsif ($opt =~ m/^usb\d+$/) {
1824 check_usb_perm
($rpcenv, $authuser, $vmid, undef, $opt, $val);
1825 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1826 PVE
::QemuConfig-
>write_config($vmid, $conf);
1827 } elsif ($opt =~ m/^hostpci\d+$/) {
1828 check_hostpci_perm
($rpcenv, $authuser, $vmid, undef, $opt, $val);
1829 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1830 PVE
::QemuConfig-
>write_config($vmid, $conf);
1831 } elsif ($opt eq 'tags') {
1832 assert_tag_permissions
($vmid, $val, '', $rpcenv, $authuser);
1833 delete $conf->{$opt};
1834 PVE
::QemuConfig-
>write_config($vmid, $conf);
1835 } elsif ($opt =~ m/^net\d+$/) {
1836 if ($conf->{$opt}) {
1837 PVE
::QemuServer
::check_bridge_access
(
1840 { $opt => $conf->{$opt} },
1843 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1844 PVE
::QemuConfig-
>write_config($vmid, $conf);
1846 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1847 PVE
::QemuConfig-
>write_config($vmid, $conf);
1851 foreach my $opt (keys %$param) { # add/change
1852 $modified->{$opt} = 1;
1853 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1854 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1856 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
1858 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1860 if ($conf->{$opt}) {
1861 $check_drive_perms->($opt, $conf->{$opt});
1865 $check_drive_perms->($opt, $param->{$opt});
1866 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1867 if defined($conf->{pending
}->{$opt});
1869 assert_scsi_feature_compatibility
($opt, $conf, $storecfg, $param->{$opt})
1870 if $opt =~ m/^scsi\d+$/;
1872 my (undef, $created_opts) = $create_disks->(
1880 {$opt => $param->{$opt}},
1882 $conf->{pending
}->{$_} = $created_opts->{$_} for keys $created_opts->%*;
1884 # default legacy boot order implies all cdroms anyway
1886 # append new CD drives to bootorder to mark them bootable
1887 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt}, 1);
1888 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1) && !grep(/^$opt$/, @bootorder)) {
1889 push @bootorder, $opt;
1890 $conf->{pending
}->{boot
} = PVE
::QemuServer
::print_bootorder
(\
@bootorder);
1891 $modified->{boot
} = 1;
1894 } elsif ($opt =~ m/^serial\d+/) {
1895 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1896 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1897 } elsif ($authuser ne 'root@pam') {
1898 die "only root can modify '$opt' config for real devices\n";
1900 $conf->{pending
}->{$opt} = $param->{$opt};
1901 } elsif ($opt eq 'vga') {
1902 my $vga = PVE
::QemuServer
::parse_vga
($param->{$opt});
1903 PVE
::QemuServer
::assert_clipboard_config
($vga);
1904 $conf->{pending
}->{$opt} = $param->{$opt};
1905 } elsif ($opt =~ m/^usb\d+/) {
1906 if (my $olddevice = $conf->{$opt}) {
1907 check_usb_perm
($rpcenv, $authuser, $vmid, undef, $opt, $conf->{$opt});
1909 check_usb_perm
($rpcenv, $authuser, $vmid, undef, $opt, $param->{$opt});
1910 $conf->{pending
}->{$opt} = $param->{$opt};
1911 } elsif ($opt =~ m/^hostpci\d+$/) {
1912 if (my $oldvalue = $conf->{$opt}) {
1913 check_hostpci_perm
($rpcenv, $authuser, $vmid, undef, $opt, $oldvalue);
1915 check_hostpci_perm
($rpcenv, $authuser, $vmid, undef, $opt, $param->{$opt});
1916 $conf->{pending
}->{$opt} = $param->{$opt};
1917 } elsif ($opt eq 'tags') {
1918 assert_tag_permissions
($vmid, $conf->{$opt}, $param->{$opt}, $rpcenv, $authuser);
1919 $conf->{pending
}->{$opt} = PVE
::GuestHelpers
::get_unique_tags
($param->{$opt});
1920 } elsif ($opt =~ m/^net\d+$/) {
1921 if ($conf->{$opt}) {
1922 PVE
::QemuServer
::check_bridge_access
(
1925 { $opt => $conf->{$opt} },
1928 $conf->{pending
}->{$opt} = $param->{$opt};
1930 $conf->{pending
}->{$opt} = $param->{$opt};
1932 if ($opt eq 'boot') {
1933 my $new_bootcfg = PVE
::JSONSchema
::parse_property_string
('pve-qm-boot', $param->{$opt});
1934 if ($new_bootcfg->{order
}) {
1935 my @devs = PVE
::Tools
::split_list
($new_bootcfg->{order
});
1936 for my $dev (@devs) {
1937 my $exists = $conf->{$dev} || $conf->{pending
}->{$dev} || $param->{$dev};
1938 my $deleted = grep {$_ eq $dev} @delete;
1939 die "invalid bootorder: device '$dev' does not exist'\n"
1940 if !$exists || $deleted;
1943 # remove legacy boot order settings if new one set
1944 $conf->{pending
}->{$opt} = PVE
::QemuServer
::print_bootorder
(\
@devs);
1945 PVE
::QemuConfig-
>add_to_pending_delete($conf, "bootdisk")
1946 if $conf->{bootdisk
};
1950 PVE
::QemuConfig-
>remove_from_pending_delete($conf, $opt);
1951 PVE
::QemuConfig-
>write_config($vmid, $conf);
1954 # remove pending changes when nothing changed
1955 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1956 my $changes = PVE
::QemuConfig-
>cleanup_pending($conf);
1957 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1959 return if !scalar(keys %{$conf->{pending
}});
1961 my $running = PVE
::QemuServer
::check_running
($vmid);
1963 # apply pending changes
1965 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1969 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1971 # cloud_init must be skipped if we are in an incoming, remote live migration
1972 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $errors, $skip_cloud_init);
1974 raise_param_exc
($errors) if scalar(keys %$errors);
1983 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1985 if ($background_delay) {
1987 # Note: It would be better to do that in the Event based HTTPServer
1988 # to avoid blocking call to sleep.
1990 my $end_time = time() + $background_delay;
1992 my $task = PVE
::Tools
::upid_decode
($upid);
1995 while (time() < $end_time) {
1996 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1998 sleep(1); # this gets interrupted when child process ends
2002 my $status = PVE
::Tools
::upid_read_status
($upid);
2003 return if !PVE
::Tools
::upid_status_is_error
($status);
2004 die "failed to update VM $vmid: $status\n";
2012 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2015 my $vm_config_perm_list = [
2020 'VM.Config.Network',
2022 'VM.Config.Options',
2023 'VM.Config.Cloudinit',
2026 __PACKAGE__-
>register_method({
2027 name
=> 'update_vm_async',
2028 path
=> '{vmid}/config',
2032 description
=> "Set virtual machine options (asynchrounous API).",
2034 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
2037 additionalProperties
=> 0,
2038 properties
=> PVE
::QemuServer
::json_config_properties
(
2040 node
=> get_standard_option
('pve-node'),
2041 vmid
=> get_standard_option
('pve-vmid'),
2042 skiplock
=> get_standard_option
('skiplock'),
2044 type
=> 'string', format
=> 'pve-configid-list',
2045 description
=> "A list of settings you want to delete.",
2049 type
=> 'string', format
=> 'pve-configid-list',
2050 description
=> "Revert a pending change.",
2055 description
=> $opt_force_description,
2057 requires
=> 'delete',
2061 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2065 background_delay
=> {
2067 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
2073 1, # with_disk_alloc
2080 code
=> $update_vm_api,
2083 __PACKAGE__-
>register_method({
2084 name
=> 'update_vm',
2085 path
=> '{vmid}/config',
2089 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
2091 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
2094 additionalProperties
=> 0,
2095 properties
=> PVE
::QemuServer
::json_config_properties
(
2097 node
=> get_standard_option
('pve-node'),
2098 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2099 skiplock
=> get_standard_option
('skiplock'),
2101 type
=> 'string', format
=> 'pve-configid-list',
2102 description
=> "A list of settings you want to delete.",
2106 type
=> 'string', format
=> 'pve-configid-list',
2107 description
=> "Revert a pending change.",
2112 description
=> $opt_force_description,
2114 requires
=> 'delete',
2118 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2123 1, # with_disk_alloc
2126 returns
=> { type
=> 'null' },
2129 &$update_vm_api($param, 1);
2134 __PACKAGE__-
>register_method({
2135 name
=> 'destroy_vm',
2140 description
=> "Destroy the VM and all used/owned volumes. Removes any VM specific permissions"
2141 ." and firewall rules",
2143 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
2146 additionalProperties
=> 0,
2148 node
=> get_standard_option
('pve-node'),
2149 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
2150 skiplock
=> get_standard_option
('skiplock'),
2153 description
=> "Remove VMID from configurations, like backup & replication jobs and HA.",
2156 'destroy-unreferenced-disks' => {
2158 description
=> "If set, destroy additionally all disks not referenced in the config"
2159 ." but with a matching VMID from all enabled storages.",
2171 my $rpcenv = PVE
::RPCEnvironment
::get
();
2172 my $authuser = $rpcenv->get_user();
2173 my $vmid = $param->{vmid
};
2175 my $skiplock = $param->{skiplock
};
2176 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2177 if $skiplock && $authuser ne 'root@pam';
2179 my $early_checks = sub {
2181 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2182 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
2184 my $ha_managed = PVE
::HA
::Config
::service_is_configured
("vm:$vmid");
2186 if (!$param->{purge
}) {
2187 die "unable to remove VM $vmid - used in HA resources and purge parameter not set.\n"
2189 # don't allow destroy if with replication jobs but no purge param
2190 my $repl_conf = PVE
::ReplicationConfig-
>new();
2191 $repl_conf->check_for_existing_jobs($vmid);
2194 die "VM $vmid is running - destroy failed\n"
2195 if PVE
::QemuServer
::check_running
($vmid);
2205 my $storecfg = PVE
::Storage
::config
();
2207 syslog
('info', "destroy VM $vmid: $upid\n");
2208 PVE
::QemuConfig-
>lock_config($vmid, sub {
2209 # repeat, config might have changed
2210 my $ha_managed = $early_checks->();
2212 my $purge_unreferenced = $param->{'destroy-unreferenced-disks'};
2214 PVE
::QemuServer
::destroy_vm
(
2217 $skiplock, { lock => 'destroyed' },
2218 $purge_unreferenced,
2221 PVE
::AccessControl
::remove_vm_access
($vmid);
2222 PVE
::Firewall
::remove_vmfw_conf
($vmid);
2223 if ($param->{purge
}) {
2224 print "purging VM $vmid from related configurations..\n";
2225 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
2226 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
2229 PVE
::HA
::Config
::delete_service_from_config
("vm:$vmid");
2230 print "NOTE: removed VM $vmid from HA resource configuration.\n";
2234 # only now remove the zombie config, else we can have reuse race
2235 PVE
::QemuConfig-
>destroy_config($vmid);
2239 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
2242 __PACKAGE__-
>register_method({
2244 path
=> '{vmid}/unlink',
2248 description
=> "Unlink/delete disk images.",
2250 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
2253 additionalProperties
=> 0,
2255 node
=> get_standard_option
('pve-node'),
2256 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2258 type
=> 'string', format
=> 'pve-configid-list',
2259 description
=> "A list of disk IDs you want to delete.",
2263 description
=> $opt_force_description,
2268 returns
=> { type
=> 'null'},
2272 $param->{delete} = extract_param
($param, 'idlist');
2274 __PACKAGE__-
>update_vm($param);
2279 # uses good entropy, each char is limited to 6 bit to get printable chars simply
2280 my $gen_rand_chars = sub {
2283 die "invalid length $length" if $length < 1;
2285 my $min = ord('!'); # first printable ascii
2287 my $rand_bytes = Crypt
::OpenSSL
::Random
::random_bytes
($length);
2288 die "failed to generate random bytes!\n"
2291 my $str = join('', map { chr((ord($_) & 0x3F) + $min) } split('', $rand_bytes));
2298 __PACKAGE__-
>register_method({
2300 path
=> '{vmid}/vncproxy',
2304 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2306 description
=> "Creates a TCP VNC proxy connections.",
2308 additionalProperties
=> 0,
2310 node
=> get_standard_option
('pve-node'),
2311 vmid
=> get_standard_option
('pve-vmid'),
2315 description
=> "Prepare for websocket upgrade (only required when using "
2316 ."serial terminal, otherwise upgrade is always possible).",
2318 'generate-password' => {
2322 description
=> "Generates a random password to be used as ticket instead of the API ticket.",
2327 additionalProperties
=> 0,
2329 user
=> { type
=> 'string' },
2330 ticket
=> { type
=> 'string' },
2333 description
=> "Returned if requested with 'generate-password' param."
2334 ." Consists of printable ASCII characters ('!' .. '~').",
2337 cert
=> { type
=> 'string' },
2338 port
=> { type
=> 'integer' },
2339 upid
=> { type
=> 'string' },
2345 my $rpcenv = PVE
::RPCEnvironment
::get
();
2347 my $authuser = $rpcenv->get_user();
2349 my $vmid = $param->{vmid
};
2350 my $node = $param->{node
};
2351 my $websocket = $param->{websocket
};
2353 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
2357 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
2358 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
2361 my $authpath = "/vms/$vmid";
2363 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
2364 my $password = $ticket;
2365 if ($param->{'generate-password'}) {
2366 $password = $gen_rand_chars->(8);
2369 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
2375 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
2376 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
2377 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
2378 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
2379 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, defined($serial) ?
'-t' : '-T');
2381 $family = PVE
::Tools
::get_host_address_family
($node);
2384 my $port = PVE
::Tools
::next_vnc_port
($family);
2391 syslog
('info', "starting vnc proxy $upid\n");
2395 if (defined($serial)) {
2397 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $serial, '-escape', '0' ];
2399 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
2400 '-timeout', $timeout, '-authpath', $authpath,
2401 '-perm', 'Sys.Console'];
2403 if ($param->{websocket
}) {
2404 $ENV{PVE_VNC_TICKET
} = $password; # pass ticket to vncterm
2405 push @$cmd, '-notls', '-listen', 'localhost';
2408 push @$cmd, '-c', @$remcmd, @$termcmd;
2410 PVE
::Tools
::run_command
($cmd);
2414 $ENV{LC_PVE_TICKET
} = $password; # set ticket with "qm vncproxy"
2416 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
2418 my $sock = IO
::Socket
::IP-
>new(
2423 GetAddrInfoFlags
=> 0,
2424 ) or die "failed to create socket: $!\n";
2425 # Inside the worker we shouldn't have any previous alarms
2426 # running anyway...:
2428 local $SIG{ALRM
} = sub { die "connection timed out\n" };
2430 accept(my $cli, $sock) or die "connection failed: $!\n";
2433 if (PVE
::Tools
::run_command
($cmd,
2434 output
=> '>&'.fileno($cli),
2435 input
=> '<&'.fileno($cli),
2438 die "Failed to run vncproxy.\n";
2445 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
2447 PVE
::Tools
::wait_for_vnc_port
($port);
2456 $res->{password
} = $password if $param->{'generate-password'};
2461 __PACKAGE__-
>register_method({
2462 name
=> 'termproxy',
2463 path
=> '{vmid}/termproxy',
2467 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2469 description
=> "Creates a TCP proxy connections.",
2471 additionalProperties
=> 0,
2473 node
=> get_standard_option
('pve-node'),
2474 vmid
=> get_standard_option
('pve-vmid'),
2478 enum
=> [qw(serial0 serial1 serial2 serial3)],
2479 description
=> "opens a serial terminal (defaults to display)",
2484 additionalProperties
=> 0,
2486 user
=> { type
=> 'string' },
2487 ticket
=> { type
=> 'string' },
2488 port
=> { type
=> 'integer' },
2489 upid
=> { type
=> 'string' },
2495 my $rpcenv = PVE
::RPCEnvironment
::get
();
2497 my $authuser = $rpcenv->get_user();
2499 my $vmid = $param->{vmid
};
2500 my $node = $param->{node
};
2501 my $serial = $param->{serial
};
2503 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
2505 if (!defined($serial)) {
2507 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
2508 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
2512 my $authpath = "/vms/$vmid";
2514 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
2519 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
2520 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
2521 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
2522 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, '-t');
2523 push @$remcmd, '--';
2525 $family = PVE
::Tools
::get_host_address_family
($node);
2528 my $port = PVE
::Tools
::next_vnc_port
($family);
2530 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
2531 push @$termcmd, '-iface', $serial if $serial;
2536 syslog
('info', "starting qemu termproxy $upid\n");
2538 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
2539 '--perm', 'VM.Console', '--'];
2540 push @$cmd, @$remcmd, @$termcmd;
2542 PVE
::Tools
::run_command
($cmd);
2545 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
2547 PVE
::Tools
::wait_for_vnc_port
($port);
2557 __PACKAGE__-
>register_method({
2558 name
=> 'vncwebsocket',
2559 path
=> '{vmid}/vncwebsocket',
2562 description
=> "You also need to pass a valid ticket (vncticket).",
2563 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2565 description
=> "Opens a weksocket for VNC traffic.",
2567 additionalProperties
=> 0,
2569 node
=> get_standard_option
('pve-node'),
2570 vmid
=> get_standard_option
('pve-vmid'),
2572 description
=> "Ticket from previous call to vncproxy.",
2577 description
=> "Port number returned by previous vncproxy call.",
2587 port
=> { type
=> 'string' },
2593 my $rpcenv = PVE
::RPCEnvironment
::get
();
2595 my $authuser = $rpcenv->get_user();
2597 my $vmid = $param->{vmid
};
2598 my $node = $param->{node
};
2600 my $authpath = "/vms/$vmid";
2602 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
2604 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
2606 # Note: VNC ports are acessible from outside, so we do not gain any
2607 # security if we verify that $param->{port} belongs to VM $vmid. This
2608 # check is done by verifying the VNC ticket (inside VNC protocol).
2610 my $port = $param->{port
};
2612 return { port
=> $port };
2615 __PACKAGE__-
>register_method({
2616 name
=> 'spiceproxy',
2617 path
=> '{vmid}/spiceproxy',
2622 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2624 description
=> "Returns a SPICE configuration to connect to the VM.",
2626 additionalProperties
=> 0,
2628 node
=> get_standard_option
('pve-node'),
2629 vmid
=> get_standard_option
('pve-vmid'),
2630 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
2633 returns
=> get_standard_option
('remote-viewer-config'),
2637 my $rpcenv = PVE
::RPCEnvironment
::get
();
2639 my $authuser = $rpcenv->get_user();
2641 my $vmid = $param->{vmid
};
2642 my $node = $param->{node
};
2643 my $proxy = $param->{proxy
};
2645 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
2646 my $title = "VM $vmid";
2647 $title .= " - ". $conf->{name
} if $conf->{name
};
2649 my $port = PVE
::QemuServer
::spice_port
($vmid);
2651 my ($ticket, undef, $remote_viewer_config) =
2652 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
2654 mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
2655 mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
2657 return $remote_viewer_config;
2660 __PACKAGE__-
>register_method({
2662 path
=> '{vmid}/status',
2665 description
=> "Directory index",
2670 additionalProperties
=> 0,
2672 node
=> get_standard_option
('pve-node'),
2673 vmid
=> get_standard_option
('pve-vmid'),
2681 subdir
=> { type
=> 'string' },
2684 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
2690 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2693 { subdir
=> 'current' },
2694 { subdir
=> 'start' },
2695 { subdir
=> 'stop' },
2696 { subdir
=> 'reset' },
2697 { subdir
=> 'shutdown' },
2698 { subdir
=> 'suspend' },
2699 { subdir
=> 'reboot' },
2705 __PACKAGE__-
>register_method({
2706 name
=> 'vm_status',
2707 path
=> '{vmid}/status/current',
2710 protected
=> 1, # qemu pid files are only readable by root
2711 description
=> "Get virtual machine status.",
2713 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2716 additionalProperties
=> 0,
2718 node
=> get_standard_option
('pve-node'),
2719 vmid
=> get_standard_option
('pve-vmid'),
2725 %$PVE::QemuServer
::vmstatus_return_properties
,
2727 description
=> "HA manager service status.",
2731 description
=> "QEMU VGA configuration supports spice.",
2736 description
=> "QEMU Guest Agent is enabled in config.",
2741 description
=> 'Enable a specific clipboard. If not set, depending on'
2742 .' the display type the SPICE one will be added.',
2753 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2755 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
2756 my $status = $vmstatus->{$param->{vmid
}};
2758 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
2761 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
2762 my $spice = defined($vga->{type
}) && $vga->{type
} =~ /^virtio/;
2763 $spice ||= PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
2764 $status->{spice
} = 1 if $spice;
2765 $status->{clipboard
} = $vga->{clipboard
};
2767 $status->{agent
} = 1 if PVE
::QemuServer
::get_qga_key
($conf, 'enabled');
2772 __PACKAGE__-
>register_method({
2774 path
=> '{vmid}/status/start',
2778 description
=> "Start virtual machine.",
2780 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2783 additionalProperties
=> 0,
2785 node
=> get_standard_option
('pve-node'),
2786 vmid
=> get_standard_option
('pve-vmid',
2787 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
2788 skiplock
=> get_standard_option
('skiplock'),
2789 stateuri
=> get_standard_option
('pve-qm-stateuri'),
2790 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
2793 enum
=> ['secure', 'insecure'],
2794 description
=> "Migration traffic is encrypted using an SSH " .
2795 "tunnel by default. On secure, completely private networks " .
2796 "this can be disabled to increase performance.",
2799 migration_network
=> {
2800 type
=> 'string', format
=> 'CIDR',
2801 description
=> "CIDR of the (sub) network that is used for migration.",
2804 machine
=> get_standard_option
('pve-qemu-machine'),
2806 description
=> "Override QEMU's -cpu argument with the given string.",
2810 targetstorage
=> get_standard_option
('pve-targetstorage'),
2812 description
=> "Wait maximal timeout seconds.",
2815 default => 'max(30, vm memory in GiB)',
2826 my $rpcenv = PVE
::RPCEnvironment
::get
();
2827 my $authuser = $rpcenv->get_user();
2829 my $node = extract_param
($param, 'node');
2830 my $vmid = extract_param
($param, 'vmid');
2831 my $timeout = extract_param
($param, 'timeout');
2832 my $machine = extract_param
($param, 'machine');
2834 my $get_root_param = sub {
2835 my $value = extract_param
($param, $_[0]);
2836 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2837 if $value && $authuser ne 'root@pam';
2841 my $stateuri = $get_root_param->('stateuri');
2842 my $skiplock = $get_root_param->('skiplock');
2843 my $migratedfrom = $get_root_param->('migratedfrom');
2844 my $migration_type = $get_root_param->('migration_type');
2845 my $migration_network = $get_root_param->('migration_network');
2846 my $targetstorage = $get_root_param->('targetstorage');
2847 my $force_cpu = $get_root_param->('force-cpu');
2851 if ($targetstorage) {
2852 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2854 $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
2855 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
2859 # read spice ticket from STDIN
2861 my $nbd_protocol_version = 0;
2862 my $replicated_volumes = {};
2863 my $offline_volumes = {};
2864 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2865 while (defined(my $line = <STDIN
>)) {
2867 if ($line =~ m/^spice_ticket: (.+)$/) {
2869 } elsif ($line =~ m/^nbd_protocol_version: (\d+)$/) {
2870 $nbd_protocol_version = $1;
2871 } elsif ($line =~ m/^replicated_volume: (.*)$/) {
2872 $replicated_volumes->{$1} = 1;
2873 } elsif ($line =~ m/^tpmstate0: (.*)$/) { # Deprecated, use offline_volume instead
2874 $offline_volumes->{tpmstate0
} = $1;
2875 } elsif ($line =~ m/^offline_volume: ([^:]+): (.*)$/) {
2876 $offline_volumes->{$1} = $2;
2877 } elsif (!$spice_ticket) {
2878 # fallback for old source node
2879 $spice_ticket = $line;
2881 warn "unknown 'start' parameter on STDIN: '$line'\n";
2886 PVE
::Cluster
::check_cfs_quorum
();
2888 my $storecfg = PVE
::Storage
::config
();
2890 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2894 print "Requesting HA start for VM $vmid\n";
2896 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2897 PVE
::Tools
::run_command
($cmd);
2901 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2908 syslog
('info', "start VM $vmid: $upid\n");
2910 my $migrate_opts = {
2911 migratedfrom
=> $migratedfrom,
2912 spice_ticket
=> $spice_ticket,
2913 network
=> $migration_network,
2914 type
=> $migration_type,
2915 storagemap
=> $storagemap,
2916 nbd_proto_version
=> $nbd_protocol_version,
2917 replicated_volumes
=> $replicated_volumes,
2918 offline_volumes
=> $offline_volumes,
2922 statefile
=> $stateuri,
2923 skiplock
=> $skiplock,
2924 forcemachine
=> $machine,
2925 timeout
=> $timeout,
2926 forcecpu
=> $force_cpu,
2929 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $params, $migrate_opts);
2933 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2937 __PACKAGE__-
>register_method({
2939 path
=> '{vmid}/status/stop',
2943 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2944 "is akin to pulling the power plug of a running computer and may damage the VM data",
2946 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2949 additionalProperties
=> 0,
2951 node
=> get_standard_option
('pve-node'),
2952 vmid
=> get_standard_option
('pve-vmid',
2953 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2954 skiplock
=> get_standard_option
('skiplock'),
2955 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2957 description
=> "Wait maximal timeout seconds.",
2963 description
=> "Do not deactivate storage volumes.",
2976 my $rpcenv = PVE
::RPCEnvironment
::get
();
2977 my $authuser = $rpcenv->get_user();
2979 my $node = extract_param
($param, 'node');
2980 my $vmid = extract_param
($param, 'vmid');
2982 my $skiplock = extract_param
($param, 'skiplock');
2983 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2984 if $skiplock && $authuser ne 'root@pam';
2986 my $keepActive = extract_param
($param, 'keepActive');
2987 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2988 if $keepActive && $authuser ne 'root@pam';
2990 my $migratedfrom = extract_param
($param, 'migratedfrom');
2991 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2992 if $migratedfrom && $authuser ne 'root@pam';
2995 my $storecfg = PVE
::Storage
::config
();
2997 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
3002 print "Requesting HA stop for VM $vmid\n";
3004 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
3005 PVE
::Tools
::run_command
($cmd);
3009 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
3015 syslog
('info', "stop VM $vmid: $upid\n");
3017 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
3018 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
3022 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
3026 __PACKAGE__-
>register_method({
3028 path
=> '{vmid}/status/reset',
3032 description
=> "Reset virtual machine.",
3034 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
3037 additionalProperties
=> 0,
3039 node
=> get_standard_option
('pve-node'),
3040 vmid
=> get_standard_option
('pve-vmid',
3041 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
3042 skiplock
=> get_standard_option
('skiplock'),
3051 my $rpcenv = PVE
::RPCEnvironment
::get
();
3053 my $authuser = $rpcenv->get_user();
3055 my $node = extract_param
($param, 'node');
3057 my $vmid = extract_param
($param, 'vmid');
3059 my $skiplock = extract_param
($param, 'skiplock');
3060 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3061 if $skiplock && $authuser ne 'root@pam';
3063 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
3068 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
3073 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
3076 __PACKAGE__-
>register_method({
3077 name
=> 'vm_shutdown',
3078 path
=> '{vmid}/status/shutdown',
3082 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
3083 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
3085 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
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
=> "Wait maximal timeout seconds.",
3101 description
=> "Make sure the VM stops.",
3107 description
=> "Do not deactivate storage volumes.",
3120 my $rpcenv = PVE
::RPCEnvironment
::get
();
3121 my $authuser = $rpcenv->get_user();
3123 my $node = extract_param
($param, 'node');
3124 my $vmid = extract_param
($param, 'vmid');
3126 my $skiplock = extract_param
($param, 'skiplock');
3127 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3128 if $skiplock && $authuser ne 'root@pam';
3130 my $keepActive = extract_param
($param, 'keepActive');
3131 raise_param_exc
({ keepActive
=> "Only root may use this option." })
3132 if $keepActive && $authuser ne 'root@pam';
3134 my $storecfg = PVE
::Storage
::config
();
3138 # sending a graceful shutdown command to paused VMs runs into timeouts, and even worse, when
3139 # the VM gets resumed later, it still gets the request delivered and powers off
3140 if (PVE
::QemuServer
::vm_is_paused
($vmid, 1)) {
3141 if ($param->{forceStop
}) {
3142 warn "VM is paused - stop instead of shutdown\n";
3145 die "VM is paused - cannot shutdown\n";
3149 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3151 my $timeout = $param->{timeout
} // 60;
3155 print "Requesting HA stop for VM $vmid\n";
3157 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
3158 PVE
::Tools
::run_command
($cmd);
3162 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
3169 syslog
('info', "shutdown VM $vmid: $upid\n");
3171 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
3172 $shutdown, $param->{forceStop
}, $keepActive);
3176 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
3180 __PACKAGE__-
>register_method({
3181 name
=> 'vm_reboot',
3182 path
=> '{vmid}/status/reboot',
3186 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
3188 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
3191 additionalProperties
=> 0,
3193 node
=> get_standard_option
('pve-node'),
3194 vmid
=> get_standard_option
('pve-vmid',
3195 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
3197 description
=> "Wait maximal timeout seconds for the shutdown.",
3210 my $rpcenv = PVE
::RPCEnvironment
::get
();
3211 my $authuser = $rpcenv->get_user();
3213 my $node = extract_param
($param, 'node');
3214 my $vmid = extract_param
($param, 'vmid');
3216 die "VM is paused - cannot shutdown\n" if PVE
::QemuServer
::vm_is_paused
($vmid, 1);
3218 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
3223 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
3224 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
3228 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
3231 __PACKAGE__-
>register_method({
3232 name
=> 'vm_suspend',
3233 path
=> '{vmid}/status/suspend',
3237 description
=> "Suspend virtual machine.",
3239 description
=> "You need 'VM.PowerMgmt' on /vms/{vmid}, and if you have set 'todisk',".
3240 " you need also 'VM.Config.Disk' on /vms/{vmid} and 'Datastore.AllocateSpace'".
3241 " on the storage for the vmstate.",
3242 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
3245 additionalProperties
=> 0,
3247 node
=> get_standard_option
('pve-node'),
3248 vmid
=> get_standard_option
('pve-vmid',
3249 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
3250 skiplock
=> get_standard_option
('skiplock'),
3255 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
3257 statestorage
=> get_standard_option
('pve-storage-id', {
3258 description
=> "The storage for the VM state",
3259 requires
=> 'todisk',
3261 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
3271 my $rpcenv = PVE
::RPCEnvironment
::get
();
3272 my $authuser = $rpcenv->get_user();
3274 my $node = extract_param
($param, 'node');
3275 my $vmid = extract_param
($param, 'vmid');
3277 my $todisk = extract_param
($param, 'todisk') // 0;
3279 my $statestorage = extract_param
($param, 'statestorage');
3281 my $skiplock = extract_param
($param, 'skiplock');
3282 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3283 if $skiplock && $authuser ne 'root@pam';
3285 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
3287 die "Cannot suspend HA managed VM to disk\n"
3288 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
3290 # early check for storage permission, for better user feedback
3292 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
3293 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3295 # cannot save the state of a non-virtualized PCIe device, so resume cannot really work
3296 for my $key (keys %$conf) {
3297 next if $key !~ /^hostpci\d+/;
3298 die "cannot suspend VM to disk due to passed-through PCI device(s), which lack the"
3299 ." possibility to save/restore their internal state\n";
3302 if (!$statestorage) {
3303 # get statestorage from config if none is given
3304 my $storecfg = PVE
::Storage
::config
();
3305 $statestorage = PVE
::QemuServer
::find_vmstate_storage
($conf, $storecfg);
3308 $rpcenv->check($authuser, "/storage/$statestorage", ['Datastore.AllocateSpace']);
3314 syslog
('info', "suspend VM $vmid: $upid\n");
3316 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
3321 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
3322 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
3325 __PACKAGE__-
>register_method({
3326 name
=> 'vm_resume',
3327 path
=> '{vmid}/status/resume',
3331 description
=> "Resume virtual machine.",
3333 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
3336 additionalProperties
=> 0,
3338 node
=> get_standard_option
('pve-node'),
3339 vmid
=> get_standard_option
('pve-vmid',
3340 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
3341 skiplock
=> get_standard_option
('skiplock'),
3342 nocheck
=> { type
=> 'boolean', optional
=> 1 },
3352 my $rpcenv = PVE
::RPCEnvironment
::get
();
3354 my $authuser = $rpcenv->get_user();
3356 my $node = extract_param
($param, 'node');
3358 my $vmid = extract_param
($param, 'vmid');
3360 my $skiplock = extract_param
($param, 'skiplock');
3361 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3362 if $skiplock && $authuser ne 'root@pam';
3364 # nocheck is used as part of migration when config file might be still
3366 my $nocheck = extract_param
($param, 'nocheck');
3367 raise_param_exc
({ nocheck
=> "Only root may use this option." })
3368 if $nocheck && $authuser ne 'root@pam';
3370 my $to_disk_suspended;
3372 PVE
::QemuConfig-
>lock_config($vmid, sub {
3373 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3374 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
3378 die "VM $vmid not running\n"
3379 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
3384 syslog
('info', "resume VM $vmid: $upid\n");
3386 if (!$to_disk_suspended) {
3387 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
3389 my $storecfg = PVE
::Storage
::config
();
3390 PVE
::QemuServer
::vm_start
($storecfg, $vmid, { skiplock
=> $skiplock });
3396 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
3399 __PACKAGE__-
>register_method({
3400 name
=> 'vm_sendkey',
3401 path
=> '{vmid}/sendkey',
3405 description
=> "Send key event to virtual machine.",
3407 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
3410 additionalProperties
=> 0,
3412 node
=> get_standard_option
('pve-node'),
3413 vmid
=> get_standard_option
('pve-vmid',
3414 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
3415 skiplock
=> get_standard_option
('skiplock'),
3417 description
=> "The key (qemu monitor encoding).",
3422 returns
=> { type
=> 'null'},
3426 my $rpcenv = PVE
::RPCEnvironment
::get
();
3428 my $authuser = $rpcenv->get_user();
3430 my $node = extract_param
($param, 'node');
3432 my $vmid = extract_param
($param, 'vmid');
3434 my $skiplock = extract_param
($param, 'skiplock');
3435 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3436 if $skiplock && $authuser ne 'root@pam';
3438 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
3443 __PACKAGE__-
>register_method({
3444 name
=> 'vm_feature',
3445 path
=> '{vmid}/feature',
3449 description
=> "Check if feature for virtual machine is available.",
3451 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3454 additionalProperties
=> 0,
3456 node
=> get_standard_option
('pve-node'),
3457 vmid
=> get_standard_option
('pve-vmid'),
3459 description
=> "Feature to check.",
3461 enum
=> [ 'snapshot', 'clone', 'copy' ],
3463 snapname
=> get_standard_option
('pve-snapshot-name', {
3471 hasFeature
=> { type
=> 'boolean' },
3474 items
=> { type
=> 'string' },
3481 my $node = extract_param
($param, 'node');
3483 my $vmid = extract_param
($param, 'vmid');
3485 my $snapname = extract_param
($param, 'snapname');
3487 my $feature = extract_param
($param, 'feature');
3489 my $running = PVE
::QemuServer
::check_running
($vmid);
3491 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3494 my $snap = $conf->{snapshots
}->{$snapname};
3495 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3498 my $storecfg = PVE
::Storage
::config
();
3500 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
3501 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
3504 hasFeature
=> $hasFeature,
3505 nodes
=> [ keys %$nodelist ],
3509 __PACKAGE__-
>register_method({
3511 path
=> '{vmid}/clone',
3515 description
=> "Create a copy of virtual machine/template.",
3517 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
3518 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
3519 "'Datastore.AllocateSpace' on any used storage and 'SDN.Use' on any used bridge/vnet",
3522 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
3524 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
3525 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
3530 additionalProperties
=> 0,
3532 node
=> get_standard_option
('pve-node'),
3533 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3534 newid
=> get_standard_option
('pve-vmid', {
3535 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
3536 description
=> 'VMID for the clone.' }),
3539 type
=> 'string', format
=> 'dns-name',
3540 description
=> "Set a name for the new VM.",
3545 description
=> "Description for the new VM.",
3549 type
=> 'string', format
=> 'pve-poolid',
3550 description
=> "Add the new VM to the specified pool.",
3552 snapname
=> get_standard_option
('pve-snapshot-name', {
3555 storage
=> get_standard_option
('pve-storage-id', {
3556 description
=> "Target storage for full clone.",
3560 description
=> "Target format for file storage. Only valid for full clone.",
3563 enum
=> [ 'raw', 'qcow2', 'vmdk'],
3568 description
=> "Create a full copy of all disks. This is always done when " .
3569 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
3571 target
=> get_standard_option
('pve-node', {
3572 description
=> "Target node. Only allowed if the original VM is on shared storage.",
3576 description
=> "Override I/O bandwidth limit (in KiB/s).",
3580 default => 'clone limit from datacenter or storage config',
3590 my $rpcenv = PVE
::RPCEnvironment
::get
();
3591 my $authuser = $rpcenv->get_user();
3593 my $node = extract_param
($param, 'node');
3594 my $vmid = extract_param
($param, 'vmid');
3595 my $newid = extract_param
($param, 'newid');
3596 my $pool = extract_param
($param, 'pool');
3598 my $snapname = extract_param
($param, 'snapname');
3599 my $storage = extract_param
($param, 'storage');
3600 my $format = extract_param
($param, 'format');
3601 my $target = extract_param
($param, 'target');
3603 my $localnode = PVE
::INotify
::nodename
();
3605 if ($target && ($target eq $localnode || $target eq 'localhost')) {
3609 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
3611 my $load_and_check = sub {
3612 $rpcenv->check_pool_exist($pool) if defined($pool);
3613 PVE
::Cluster
::check_node_exists
($target) if $target;
3615 my $storecfg = PVE
::Storage
::config
();
3618 # check if storage is enabled on local node
3619 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
3621 # check if storage is available on target node
3622 PVE
::Storage
::storage_check_enabled
($storecfg, $storage, $target);
3623 # clone only works if target storage is shared
3624 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
3625 die "can't clone to non-shared storage '$storage'\n"
3626 if !$scfg->{shared
};
3630 PVE
::Cluster
::check_cfs_quorum
();
3632 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3633 PVE
::QemuConfig-
>check_lock($conf);
3635 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
3636 die "unexpected state change\n" if $verify_running != $running;
3638 die "snapshot '$snapname' does not exist\n"
3639 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
3641 my $full = $param->{full
} // !PVE
::QemuConfig-
>is_template($conf);
3643 die "parameter 'storage' not allowed for linked clones\n"
3644 if defined($storage) && !$full;
3646 die "parameter 'format' not allowed for linked clones\n"
3647 if defined($format) && !$full;
3649 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
3651 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
3652 PVE
::QemuServer
::check_mapping_access
($rpcenv, $authuser, $oldconf);
3654 PVE
::QemuServer
::check_bridge_access
($rpcenv, $authuser, $oldconf);
3656 die "can't clone VM to node '$target' (VM uses local storage)\n"
3657 if $target && !$sharedvm;
3659 my $conffile = PVE
::QemuConfig-
>config_file($newid);
3660 die "unable to create VM $newid: config file already exists\n"
3663 my $newconf = { lock => 'clone' };
3668 foreach my $opt (keys %$oldconf) {
3669 my $value = $oldconf->{$opt};
3671 # do not copy snapshot related info
3672 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
3673 $opt eq 'vmstate' || $opt eq 'snapstate';
3675 # no need to copy unused images, because VMID(owner) changes anyways
3676 next if $opt =~ m/^unused\d+$/;
3678 die "cannot clone TPM state while VM is running\n"
3679 if $full && $running && !$snapname && $opt eq 'tpmstate0';
3681 # always change MAC! address
3682 if ($opt =~ m/^net(\d+)$/) {
3683 my $net = PVE
::QemuServer
::parse_net
($value);
3684 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
3685 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
3686 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
3687 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
3688 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
3689 die "unable to parse drive options for '$opt'\n" if !$drive;
3690 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
3691 $newconf->{$opt} = $value; # simply copy configuration
3693 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
3694 die "Full clone feature is not supported for drive '$opt'\n"
3695 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
3696 $fullclone->{$opt} = 1;
3698 # not full means clone instead of copy
3699 die "Linked clone feature is not supported for drive '$opt'\n"
3700 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
3702 $drives->{$opt} = $drive;
3703 next if PVE
::QemuServer
::drive_is_cloudinit
($drive);
3704 push @$vollist, $drive->{file
};
3707 # copy everything else
3708 $newconf->{$opt} = $value;
3712 return ($conffile, $newconf, $oldconf, $vollist, $drives, $fullclone);
3716 my ($conffile, $newconf, $oldconf, $vollist, $drives, $fullclone) = $load_and_check->();
3717 my $storecfg = PVE
::Storage
::config
();
3719 # auto generate a new uuid
3720 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
3721 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
3722 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
3723 # auto generate a new vmgenid only if the option was set for template
3724 if ($newconf->{vmgenid
}) {
3725 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
3728 delete $newconf->{template
};
3730 if ($param->{name
}) {
3731 $newconf->{name
} = $param->{name
};
3733 $newconf->{name
} = "Copy-of-VM-" . ($oldconf->{name
} // $vmid);
3736 if ($param->{description
}) {
3737 $newconf->{description
} = $param->{description
};
3740 # create empty/temp config - this fails if VM already exists on other node
3741 # FIXME use PVE::QemuConfig->create_and_lock_config and adapt code
3742 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
3744 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
3746 my $newvollist = [];
3753 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3755 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
3757 my $bwlimit = extract_param
($param, 'bwlimit');
3759 my $total_jobs = scalar(keys %{$drives});
3762 foreach my $opt (sort keys %$drives) {
3763 my $drive = $drives->{$opt};
3764 my $skipcomplete = ($total_jobs != $i); # finish after last drive
3765 my $completion = $skipcomplete ?
'skip' : 'complete';
3767 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
3768 my $storage_list = [ $src_sid ];
3769 push @$storage_list, $storage if defined($storage);
3770 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
3774 running
=> $running,
3777 snapname
=> $snapname,
3783 storage
=> $storage,
3787 $dest_info->{efisize
} = PVE
::QemuServer
::get_efivars_size
($oldconf)
3788 if $opt eq 'efidisk0';
3790 my $newdrive = PVE
::QemuServer
::clone_disk
(
3802 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($newdrive);
3804 PVE
::QemuConfig-
>write_config($newid, $newconf);
3808 delete $newconf->{lock};
3810 # do not write pending changes
3811 if (my @changes = keys %{$newconf->{pending
}}) {
3812 my $pending = join(',', @changes);
3813 warn "found pending changes for '$pending', discarding for clone\n";
3814 delete $newconf->{pending
};
3817 PVE
::QemuConfig-
>write_config($newid, $newconf);
3819 PVE
::QemuServer
::create_ifaces_ipams_ips
($newconf, $newid);
3822 # always deactivate volumes - avoid lvm LVs to be active on several nodes
3823 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
3824 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
3826 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
3827 die "Failed to move config to node '$target' - rename failed: $!\n"
3828 if !rename($conffile, $newconffile);
3831 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
3834 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
3835 sleep 1; # some storage like rbd need to wait before release volume - really?
3837 foreach my $volid (@$newvollist) {
3838 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3842 PVE
::Firewall
::remove_vmfw_conf
($newid);
3844 unlink $conffile; # avoid races -> last thing before die
3846 die "clone failed: $err";
3852 # Aquire exclusive lock lock for $newid
3853 my $lock_target_vm = sub {
3854 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
3857 my $lock_source_vm = sub {
3858 # exclusive lock if VM is running - else shared lock is enough;
3860 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $lock_target_vm);
3862 return PVE
::QemuConfig-
>lock_config_shared($vmid, 1, $lock_target_vm);
3866 $load_and_check->(); # early checks before forking/locking
3868 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $lock_source_vm);
3871 __PACKAGE__-
>register_method({
3872 name
=> 'move_vm_disk',
3873 path
=> '{vmid}/move_disk',
3877 description
=> "Move volume to different storage or to a different VM.",
3879 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
3880 "and 'Datastore.AllocateSpace' permissions on the storage. To move ".
3881 "a disk to another VM, you need the permissions on the target VM as well.",
3882 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3885 additionalProperties
=> 0,
3887 node
=> get_standard_option
('pve-node'),
3888 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3889 'target-vmid' => get_standard_option
('pve-vmid', {
3890 completion
=> \
&PVE
::QemuServer
::complete_vmid
,
3895 description
=> "The disk you want to move.",
3896 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names_with_unused
()],
3898 storage
=> get_standard_option
('pve-storage-id', {
3899 description
=> "Target storage.",
3900 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3905 description
=> "Target Format.",
3906 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
3911 description
=> "Delete the original disk after successful copy. By default the"
3912 ." original disk is kept as unused disk.",
3918 description
=> 'Prevent changes if current configuration file has different SHA1"
3919 ." digest. This can be used to prevent concurrent modifications.',
3924 description
=> "Override I/O bandwidth limit (in KiB/s).",
3928 default => 'move limit from datacenter or storage config',
3932 description
=> "The config key the disk will be moved to on the target VM"
3933 ." (for example, ide0 or scsi1). Default is the source disk key.",
3934 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names_with_unused
()],
3937 'target-digest' => {
3939 description
=> 'Prevent changes if the current config file of the target VM has a"
3940 ." different SHA1 digest. This can be used to detect concurrent modifications.',
3948 description
=> "the task ID.",
3953 my $rpcenv = PVE
::RPCEnvironment
::get
();
3954 my $authuser = $rpcenv->get_user();
3956 my $node = extract_param
($param, 'node');
3957 my $vmid = extract_param
($param, 'vmid');
3958 my $target_vmid = extract_param
($param, 'target-vmid');
3959 my $digest = extract_param
($param, 'digest');
3960 my $target_digest = extract_param
($param, 'target-digest');
3961 my $disk = extract_param
($param, 'disk');
3962 my $target_disk = extract_param
($param, 'target-disk') // $disk;
3963 my $storeid = extract_param
($param, 'storage');
3964 my $format = extract_param
($param, 'format');
3966 my $storecfg = PVE
::Storage
::config
();
3968 my $load_and_check_move = sub {
3969 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3970 PVE
::QemuConfig-
>check_lock($conf);
3972 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
3974 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3976 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3978 die "disk '$disk' has no associated volume\n" if !$drive->{file
};
3979 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3981 my $old_volid = $drive->{file
};
3983 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3984 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3988 die "you can't move to the same storage with same format\n"
3989 if $oldstoreid eq $storeid && (!$format || !$oldfmt || $oldfmt eq $format);
3991 # this only checks snapshots because $disk is passed!
3992 my $snapshotted = PVE
::QemuServer
::Drive
::is_volume_in_use
(
3998 die "you can't move a disk with snapshots and delete the source\n"
3999 if $snapshotted && $param->{delete};
4001 return ($conf, $drive, $oldstoreid, $snapshotted);
4004 my $move_updatefn = sub {
4005 my ($conf, $drive, $oldstoreid, $snapshotted) = $load_and_check_move->();
4006 my $old_volid = $drive->{file
};
4008 PVE
::Cluster
::log_msg
(
4011 "move disk VM $vmid: move --disk $disk --storage $storeid"
4014 my $running = PVE
::QemuServer
::check_running
($vmid);
4016 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
4018 my $newvollist = [];
4024 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
4026 warn "moving disk with snapshots, snapshots will not be moved!\n"
4029 my $bwlimit = extract_param
($param, 'bwlimit');
4030 my $movelimit = PVE
::Storage
::get_bandwidth_limit
(
4032 [$oldstoreid, $storeid],
4038 running
=> $running,
4047 storage
=> $storeid,
4051 $dest_info->{efisize
} = PVE
::QemuServer
::get_efivars_size
($conf)
4052 if $disk eq 'efidisk0';
4054 my $newdrive = PVE
::QemuServer
::clone_disk
(
4065 $conf->{$disk} = PVE
::QemuServer
::print_drive
($newdrive);
4067 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
4069 # convert moved disk to base if part of template
4070 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
4071 if PVE
::QemuConfig-
>is_template($conf);
4073 PVE
::QemuConfig-
>write_config($vmid, $conf);
4075 my $do_trim = PVE
::QemuServer
::get_qga_key
($conf, 'fstrim_cloned_disks');
4076 if ($running && $do_trim && PVE
::QemuServer
::qga_check_running
($vmid)) {
4077 eval { mon_cmd
($vmid, "guest-fstrim") };
4081 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
4082 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
4088 foreach my $volid (@$newvollist) {
4089 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid) };
4092 die "storage migration failed: $err";
4095 if ($param->{delete}) {
4097 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
4098 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
4104 my $load_and_check_reassign_configs = sub {
4105 my $vmlist = PVE
::Cluster
::get_vmlist
()->{ids
};
4107 die "could not find VM ${vmid}\n" if !exists($vmlist->{$vmid});
4108 die "could not find target VM ${target_vmid}\n" if !exists($vmlist->{$target_vmid});
4110 my $source_node = $vmlist->{$vmid}->{node
};
4111 my $target_node = $vmlist->{$target_vmid}->{node
};
4113 die "Both VMs need to be on the same node ($source_node != $target_node)\n"
4114 if $source_node ne $target_node;
4116 my $source_conf = PVE
::QemuConfig-
>load_config($vmid);
4117 PVE
::QemuConfig-
>check_lock($source_conf);
4118 my $target_conf = PVE
::QemuConfig-
>load_config($target_vmid);
4119 PVE
::QemuConfig-
>check_lock($target_conf);
4121 die "Can't move disks from or to template VMs\n"
4122 if ($source_conf->{template
} || $target_conf->{template
});
4125 eval { PVE
::Tools
::assert_if_modified
($digest, $source_conf->{digest
}) };
4126 die "VM ${vmid}: $@" if $@;
4129 if ($target_digest) {
4130 eval { PVE
::Tools
::assert_if_modified
($target_digest, $target_conf->{digest
}) };
4131 die "VM ${target_vmid}: $@" if $@;
4134 die "Disk '${disk}' for VM '$vmid' does not exist\n" if !defined($source_conf->{$disk});
4136 die "Target disk key '${target_disk}' is already in use for VM '$target_vmid'\n"
4137 if $target_conf->{$target_disk};
4139 my $drive = PVE
::QemuServer
::parse_drive
(
4141 $source_conf->{$disk},
4143 die "failed to parse source disk - $@\n" if !$drive;
4145 my $source_volid = $drive->{file
};
4147 die "disk '${disk}' has no associated volume\n" if !$source_volid;
4148 die "CD drive contents can't be moved to another VM\n"
4149 if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
4151 my $storeid = PVE
::Storage
::parse_volume_id
($source_volid, 1);
4152 die "Volume '$source_volid' not managed by PVE\n" if !defined($storeid);
4154 die "Can't move disk used by a snapshot to another VM\n"
4155 if PVE
::QemuServer
::Drive
::is_volume_in_use
($storecfg, $source_conf, $disk, $source_volid);
4156 die "Storage does not support moving of this disk to another VM\n"
4157 if (!PVE
::Storage
::volume_has_feature
($storecfg, 'rename', $source_volid));
4158 die "Cannot move disk to another VM while the source VM is running - detach first\n"
4159 if PVE
::QemuServer
::check_running
($vmid) && $disk !~ m/^unused\d+$/;
4161 # now re-parse using target disk slot format
4162 if ($target_disk =~ /^unused\d+$/) {
4163 $drive = PVE
::QemuServer
::parse_drive
(
4168 $drive = PVE
::QemuServer
::parse_drive
(
4170 $source_conf->{$disk},
4173 die "failed to parse source disk for target disk format - $@\n" if !$drive;
4175 my $repl_conf = PVE
::ReplicationConfig-
>new();
4176 if ($repl_conf->check_for_existing_jobs($target_vmid, 1)) {
4177 my $format = (PVE
::Storage
::parse_volname
($storecfg, $source_volid))[6];
4178 die "Cannot move disk to a replicated VM. Storage does not support replication!\n"
4179 if !PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
4182 return ($source_conf, $target_conf, $drive);
4187 print STDERR
"$msg\n";
4190 my $disk_reassignfn = sub {
4191 return PVE
::QemuConfig-
>lock_config($vmid, sub {
4192 return PVE
::QemuConfig-
>lock_config($target_vmid, sub {
4193 my ($source_conf, $target_conf, $drive) = &$load_and_check_reassign_configs();
4195 my $source_volid = $drive->{file
};
4197 print "moving disk '$disk' from VM '$vmid' to '$target_vmid'\n";
4198 my ($storeid, $source_volname) = PVE
::Storage
::parse_volume_id
($source_volid);
4200 my $fmt = (PVE
::Storage
::parse_volname
($storecfg, $source_volid))[6];
4202 my $new_volid = PVE
::Storage
::rename_volume
(
4208 $drive->{file
} = $new_volid;
4210 my $boot_order = PVE
::QemuServer
::device_bootorder
($source_conf);
4211 if (defined(delete $boot_order->{$disk})) {
4212 print "removing disk '$disk' from boot order config\n";
4213 my $boot_devs = [ sort { $boot_order->{$a} <=> $boot_order->{$b} } keys %$boot_order ];
4214 $source_conf->{boot
} = PVE
::QemuServer
::print_bootorder
($boot_devs);
4217 delete $source_conf->{$disk};
4218 print "removing disk '${disk}' from VM '${vmid}' config\n";
4219 PVE
::QemuConfig-
>write_config($vmid, $source_conf);
4221 my $drive_string = PVE
::QemuServer
::print_drive
($drive);
4223 if ($target_disk =~ /^unused\d+$/) {
4224 $target_conf->{$target_disk} = $drive_string;
4225 PVE
::QemuConfig-
>write_config($target_vmid, $target_conf);
4230 vmid
=> $target_vmid,
4231 digest
=> $target_digest,
4232 $target_disk => $drive_string,
4238 # remove possible replication snapshots
4239 if (PVE
::Storage
::volume_has_feature
(
4245 PVE
::Replication
::prepare
(
4255 print "Failed to remove replication snapshots on moved disk " .
4256 "'$target_disk'. Manual cleanup could be necessary.\n";
4263 if ($target_vmid && $storeid) {
4264 my $msg = "either set 'storage' or 'target-vmid', but not both";
4265 raise_param_exc
({ 'target-vmid' => $msg, 'storage' => $msg });
4266 } elsif ($target_vmid) {
4267 $rpcenv->check_vm_perm($authuser, $target_vmid, undef, ['VM.Config.Disk'])
4268 if $authuser ne 'root@pam';
4270 raise_param_exc
({ 'target-vmid' => "must be different than source VMID to reassign disk" })
4271 if $vmid eq $target_vmid;
4273 my (undef, undef, $drive) = &$load_and_check_reassign_configs();
4274 my $storage = PVE
::Storage
::parse_volume_id
($drive->{file
});
4275 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace']);
4277 return $rpcenv->fork_worker(
4279 "${vmid}-${disk}>${target_vmid}-${target_disk}",
4283 } elsif ($storeid) {
4284 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
4286 die "cannot move disk '$disk', only configured disks can be moved to another storage\n"
4287 if $disk =~ m/^unused\d+$/;
4289 $load_and_check_move->(); # early checks before forking/locking
4292 PVE
::QemuConfig-
>lock_config($vmid, $move_updatefn);
4295 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
4297 my $msg = "both 'storage' and 'target-vmid' missing, either needs to be set";
4298 raise_param_exc
({ 'target-vmid' => $msg, 'storage' => $msg });
4302 my $check_vm_disks_local = sub {
4303 my ($storecfg, $vmconf, $vmid) = @_;
4305 my $local_disks = {};
4307 # add some more information to the disks e.g. cdrom
4308 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
4309 my ($volid, $attr) = @_;
4311 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
4313 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
4314 return if $scfg->{shared
};
4316 # The shared attr here is just a special case where the vdisk
4317 # is marked as shared manually
4318 return if $attr->{shared
};
4319 return if $attr->{cdrom
} and $volid eq "none";
4321 if (exists $local_disks->{$volid}) {
4322 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
4324 $local_disks->{$volid} = $attr;
4325 # ensure volid is present in case it's needed
4326 $local_disks->{$volid}->{volid
} = $volid;
4330 return $local_disks;
4333 __PACKAGE__-
>register_method({
4334 name
=> 'migrate_vm_precondition',
4335 path
=> '{vmid}/migrate',
4339 description
=> "Get preconditions for migration.",
4341 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
4344 additionalProperties
=> 0,
4346 node
=> get_standard_option
('pve-node'),
4347 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4348 target
=> get_standard_option
('pve-node', {
4349 description
=> "Target node.",
4350 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
4358 running
=> { type
=> 'boolean' },
4362 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
4364 not_allowed_nodes
=> {
4367 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
4371 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
4373 local_resources
=> {
4375 description
=> "List local resources e.g. pci, usb"
4377 'mapped-resources' => {
4379 description
=> "List of mapped resources e.g. pci, usb"
4386 my $rpcenv = PVE
::RPCEnvironment
::get
();
4388 my $authuser = $rpcenv->get_user();
4390 PVE
::Cluster
::check_cfs_quorum
();
4394 my $vmid = extract_param
($param, 'vmid');
4395 my $target = extract_param
($param, 'target');
4396 my $localnode = PVE
::INotify
::nodename
();
4400 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
4401 my $storecfg = PVE
::Storage
::config
();
4404 # try to detect errors early
4405 PVE
::QemuConfig-
>check_lock($vmconf);
4407 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
4409 my ($local_resources, $mapped_resources, $missing_mappings_by_node) =
4410 PVE
::QemuServer
::check_local_resources
($vmconf, 1);
4411 delete $missing_mappings_by_node->{$localnode};
4413 my $vga = PVE
::QemuServer
::parse_vga
($vmconf->{vga
});
4414 if ($res->{running
} && $vga->{'clipboard'} && $vga->{'clipboard'} eq 'vnc') {
4415 push $local_resources->@*, "clipboard=vnc";
4418 # if vm is not running, return target nodes where local storage/mapped devices are available
4419 # for offline migration
4420 if (!$res->{running
}) {
4421 $res->{allowed_nodes
} = [];
4422 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
4423 delete $checked_nodes->{$localnode};
4425 foreach my $node (keys %$checked_nodes) {
4426 my $missing_mappings = $missing_mappings_by_node->{$node};
4427 if (scalar($missing_mappings->@*)) {
4428 $checked_nodes->{$node}->{'unavailable-resources'} = $missing_mappings;
4432 if (!defined($checked_nodes->{$node}->{unavailable_storages
})) {
4433 push @{$res->{allowed_nodes
}}, $node;
4437 $res->{not_allowed_nodes
} = $checked_nodes;
4440 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
4441 $res->{local_disks
} = [ values %$local_disks ];;
4443 $res->{local_resources
} = $local_resources;
4444 $res->{'mapped-resources'} = $mapped_resources;
4451 __PACKAGE__-
>register_method({
4452 name
=> 'migrate_vm',
4453 path
=> '{vmid}/migrate',
4457 description
=> "Migrate virtual machine. Creates a new migration task.",
4459 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
4462 additionalProperties
=> 0,
4464 node
=> get_standard_option
('pve-node'),
4465 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4466 target
=> get_standard_option
('pve-node', {
4467 description
=> "Target node.",
4468 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
4472 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
4477 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
4482 enum
=> ['secure', 'insecure'],
4483 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
4486 migration_network
=> {
4487 type
=> 'string', format
=> 'CIDR',
4488 description
=> "CIDR of the (sub) network that is used for migration.",
4491 "with-local-disks" => {
4493 description
=> "Enable live storage migration for local disk",
4496 targetstorage
=> get_standard_option
('pve-targetstorage', {
4497 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
4500 description
=> "Override I/O bandwidth limit (in KiB/s).",
4504 default => 'migrate limit from datacenter or storage config',
4510 description
=> "the task ID.",
4515 my $rpcenv = PVE
::RPCEnvironment
::get
();
4516 my $authuser = $rpcenv->get_user();
4518 my $target = extract_param
($param, 'target');
4520 my $localnode = PVE
::INotify
::nodename
();
4521 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
4523 PVE
::Cluster
::check_cfs_quorum
();
4525 PVE
::Cluster
::check_node_exists
($target);
4527 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
4529 my $vmid = extract_param
($param, 'vmid');
4531 raise_param_exc
({ force
=> "Only root may use this option." })
4532 if $param->{force
} && $authuser ne 'root@pam';
4534 raise_param_exc
({ migration_type
=> "Only root may use this option." })
4535 if $param->{migration_type
} && $authuser ne 'root@pam';
4537 # allow root only until better network permissions are available
4538 raise_param_exc
({ migration_network
=> "Only root may use this option." })
4539 if $param->{migration_network
} && $authuser ne 'root@pam';
4542 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4544 # try to detect errors early
4546 PVE
::QemuConfig-
>check_lock($conf);
4548 if (PVE
::QemuServer
::check_running
($vmid)) {
4549 die "can't migrate running VM without --online\n" if !$param->{online
};
4551 my $repl_conf = PVE
::ReplicationConfig-
>new();
4552 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
4553 my $is_replicated_to_target = defined($repl_conf->find_local_replication_job($vmid, $target));
4554 if (!$param->{force
} && $is_replicated && !$is_replicated_to_target) {
4555 die "Cannot live-migrate replicated VM to node '$target' - not a replication " .
4556 "target. Use 'force' to override.\n";
4559 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
4560 $param->{online
} = 0;
4563 my $storecfg = PVE
::Storage
::config
();
4564 if (my $targetstorage = $param->{targetstorage
}) {
4565 my $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
4566 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
4569 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk'])
4570 if !defined($storagemap->{identity
});
4572 foreach my $target_sid (values %{$storagemap->{entries
}}) {
4573 $check_storage_access_migrate->($rpcenv, $authuser, $storecfg, $target_sid, $target);
4576 $check_storage_access_migrate->($rpcenv, $authuser, $storecfg, $storagemap->{default}, $target)
4577 if $storagemap->{default};
4579 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target)
4580 if $storagemap->{identity
};
4582 $param->{storagemap
} = $storagemap;
4584 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
4587 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
4592 print "Requesting HA migration for VM $vmid to node $target\n";
4594 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
4595 PVE
::Tools
::run_command
($cmd);
4599 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
4604 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
4608 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
4611 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
4616 __PACKAGE__-
>register_method({
4617 name
=> 'remote_migrate_vm',
4618 path
=> '{vmid}/remote_migrate',
4622 description
=> "Migrate virtual machine to a remote cluster. Creates a new migration task. EXPERIMENTAL feature!",
4624 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
4627 additionalProperties
=> 0,
4629 node
=> get_standard_option
('pve-node'),
4630 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4631 'target-vmid' => get_standard_option
('pve-vmid', { optional
=> 1 }),
4632 'target-endpoint' => get_standard_option
('proxmox-remote', {
4633 description
=> "Remote target endpoint",
4637 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
4642 description
=> "Delete the original VM and related data after successful migration. By default the original VM is kept on the source cluster in a stopped state.",
4646 'target-storage' => get_standard_option
('pve-targetstorage', {
4647 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
4650 'target-bridge' => {
4652 description
=> "Mapping from source to target bridges. Providing only a single bridge ID maps all source bridges to that bridge. Providing the special value '1' will map each source bridge to itself.",
4653 format
=> 'bridge-pair-list',
4656 description
=> "Override I/O bandwidth limit (in KiB/s).",
4660 default => 'migrate limit from datacenter or storage config',
4666 description
=> "the task ID.",
4671 my $rpcenv = PVE
::RPCEnvironment
::get
();
4672 my $authuser = $rpcenv->get_user();
4674 my $source_vmid = extract_param
($param, 'vmid');
4675 my $target_endpoint = extract_param
($param, 'target-endpoint');
4676 my $target_vmid = extract_param
($param, 'target-vmid') // $source_vmid;
4678 my $delete = extract_param
($param, 'delete') // 0;
4680 PVE
::Cluster
::check_cfs_quorum
();
4683 my $conf = PVE
::QemuConfig-
>load_config($source_vmid);
4685 PVE
::QemuConfig-
>check_lock($conf);
4687 raise_param_exc
({ vmid
=> "cannot migrate HA-managed VM to remote cluster" })
4688 if PVE
::HA
::Config
::vm_is_ha_managed
($source_vmid);
4690 my $remote = PVE
::JSONSchema
::parse_property_string
('proxmox-remote', $target_endpoint);
4692 # TODO: move this as helper somewhere appropriate?
4694 protocol
=> 'https',
4695 host
=> $remote->{host
},
4696 port
=> $remote->{port
} // 8006,
4697 apitoken
=> $remote->{apitoken
},
4701 if ($fp = $remote->{fingerprint
}) {
4702 $conn_args->{cached_fingerprints
} = { uc($fp) => 1 };
4705 print "Establishing API connection with remote at '$remote->{host}'\n";
4707 my $api_client = PVE
::APIClient
::LWP-
>new(%$conn_args);
4709 if (!defined($fp)) {
4710 my $cert_info = $api_client->get("/nodes/localhost/certificates/info");
4711 foreach my $cert (@$cert_info) {
4712 my $filename = $cert->{filename
};
4713 next if $filename ne 'pveproxy-ssl.pem' && $filename ne 'pve-ssl.pem';
4714 $fp = $cert->{fingerprint
} if !$fp || $filename eq 'pveproxy-ssl.pem';
4716 $conn_args->{cached_fingerprints
} = { uc($fp) => 1 }
4720 my $repl_conf = PVE
::ReplicationConfig-
>new();
4721 my $is_replicated = $repl_conf->check_for_existing_jobs($source_vmid, 1);
4722 die "cannot remote-migrate replicated VM\n" if $is_replicated;
4724 if (PVE
::QemuServer
::check_running
($source_vmid)) {
4725 die "can't migrate running VM without --online\n" if !$param->{online
};
4728 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
4729 $param->{online
} = 0;
4732 my $storecfg = PVE
::Storage
::config
();
4733 my $target_storage = extract_param
($param, 'target-storage');
4734 my $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($target_storage, 'pve-storage-id') };
4735 raise_param_exc
({ 'target-storage' => "failed to parse storage map: $@" })
4738 my $target_bridge = extract_param
($param, 'target-bridge');
4739 my $bridgemap = eval { PVE
::JSONSchema
::parse_idmap
($target_bridge, 'pve-bridge-id') };
4740 raise_param_exc
({ 'target-bridge' => "failed to parse bridge map: $@" })
4743 die "remote migration requires explicit storage mapping!\n"
4744 if $storagemap->{identity
};
4746 $param->{storagemap
} = $storagemap;
4747 $param->{bridgemap
} = $bridgemap;
4748 $param->{remote
} = {
4749 conn
=> $conn_args, # re-use fingerprint for tunnel
4750 client
=> $api_client,
4751 vmid
=> $target_vmid,
4753 $param->{migration_type
} = 'websocket';
4754 $param->{'with-local-disks'} = 1;
4755 $param->{delete} = $delete if $delete;
4757 my $cluster_status = $api_client->get("/cluster/status");
4759 foreach my $entry (@$cluster_status) {
4760 next if $entry->{type
} ne 'node';
4761 if ($entry->{local}) {
4762 $target_node = $entry->{name
};
4767 die "couldn't determine endpoint's node name\n"
4768 if !defined($target_node);
4771 PVE
::QemuMigrate-
>migrate($target_node, $remote->{host
}, $source_vmid, $param);
4775 return PVE
::GuestHelpers
::guest_migration_lock
($source_vmid, 10, $realcmd);
4778 return $rpcenv->fork_worker('qmigrate', $source_vmid, $authuser, $worker);
4781 __PACKAGE__-
>register_method({
4783 path
=> '{vmid}/monitor',
4787 description
=> "Execute QEMU monitor commands.",
4789 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
4790 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
4793 additionalProperties
=> 0,
4795 node
=> get_standard_option
('pve-node'),
4796 vmid
=> get_standard_option
('pve-vmid'),
4799 description
=> "The monitor command.",
4803 returns
=> { type
=> 'string'},
4807 my $rpcenv = PVE
::RPCEnvironment
::get
();
4808 my $authuser = $rpcenv->get_user();
4811 my $command = shift;
4812 return $command =~ m/^\s*info(\s+|$)/
4813 || $command =~ m/^\s*help\s*$/;
4816 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
4817 if !&$is_ro($param->{command
});
4819 my $vmid = $param->{vmid
};
4821 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
4825 $res = PVE
::QemuServer
::Monitor
::hmp_cmd
($vmid, $param->{command
});
4827 $res = "ERROR: $@" if $@;
4832 __PACKAGE__-
>register_method({
4833 name
=> 'resize_vm',
4834 path
=> '{vmid}/resize',
4838 description
=> "Extend volume size.",
4840 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
4843 additionalProperties
=> 0,
4845 node
=> get_standard_option
('pve-node'),
4846 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4847 skiplock
=> get_standard_option
('skiplock'),
4850 description
=> "The disk you want to resize.",
4851 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
4855 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
4856 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.",
4860 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
4868 description
=> "the task ID.",
4873 my $rpcenv = PVE
::RPCEnvironment
::get
();
4875 my $authuser = $rpcenv->get_user();
4877 my $node = extract_param
($param, 'node');
4879 my $vmid = extract_param
($param, 'vmid');
4881 my $digest = extract_param
($param, 'digest');
4883 my $disk = extract_param
($param, 'disk');
4885 my $sizestr = extract_param
($param, 'size');
4887 my $skiplock = extract_param
($param, 'skiplock');
4888 raise_param_exc
({ skiplock
=> "Only root may use this option." })
4889 if $skiplock && $authuser ne 'root@pam';
4891 my $storecfg = PVE
::Storage
::config
();
4893 my $updatefn = sub {
4895 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4897 die "checksum missmatch (file change by other user?)\n"
4898 if $digest && $digest ne $conf->{digest
};
4899 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
4901 die "disk '$disk' does not exist\n" if !$conf->{$disk};
4903 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
4905 my (undef, undef, undef, undef, undef, undef, $format) =
4906 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
4908 my $volid = $drive->{file
};
4910 die "disk '$disk' has no associated volume\n" if !$volid;
4912 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
4914 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
4916 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
4918 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
4919 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
4921 die "Could not determine current size of volume '$volid'\n" if !defined($size);
4923 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
4924 my ($ext, $newsize, $unit) = ($1, $2, $4);
4927 $newsize = $newsize * 1024;
4928 } elsif ($unit eq 'M') {
4929 $newsize = $newsize * 1024 * 1024;
4930 } elsif ($unit eq 'G') {
4931 $newsize = $newsize * 1024 * 1024 * 1024;
4932 } elsif ($unit eq 'T') {
4933 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
4936 $newsize += $size if $ext;
4937 $newsize = int($newsize);
4939 die "shrinking disks is not supported\n" if $newsize < $size;
4941 return if $size == $newsize;
4943 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
4945 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
4947 $drive->{size
} = $newsize;
4948 $conf->{$disk} = PVE
::QemuServer
::print_drive
($drive);
4950 PVE
::QemuConfig-
>write_config($vmid, $conf);
4954 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4957 return $rpcenv->fork_worker('resize', $vmid, $authuser, $worker);
4960 __PACKAGE__-
>register_method({
4961 name
=> 'snapshot_list',
4962 path
=> '{vmid}/snapshot',
4964 description
=> "List all snapshots.",
4966 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4969 protected
=> 1, # qemu pid files are only readable by root
4971 additionalProperties
=> 0,
4973 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4974 node
=> get_standard_option
('pve-node'),
4983 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
4987 description
=> "Snapshot includes RAM.",
4992 description
=> "Snapshot description.",
4996 description
=> "Snapshot creation time",
4998 renderer
=> 'timestamp',
5002 description
=> "Parent snapshot identifier.",
5008 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
5013 my $vmid = $param->{vmid
};
5015 my $conf = PVE
::QemuConfig-
>load_config($vmid);
5016 my $snaphash = $conf->{snapshots
} || {};
5020 foreach my $name (keys %$snaphash) {
5021 my $d = $snaphash->{$name};
5024 snaptime
=> $d->{snaptime
} || 0,
5025 vmstate
=> $d->{vmstate
} ?
1 : 0,
5026 description
=> $d->{description
} || '',
5028 $item->{parent
} = $d->{parent
} if $d->{parent
};
5029 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
5033 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
5036 digest
=> $conf->{digest
},
5037 running
=> $running,
5038 description
=> "You are here!",
5040 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
5042 push @$res, $current;
5047 __PACKAGE__-
>register_method({
5049 path
=> '{vmid}/snapshot',
5053 description
=> "Snapshot a VM.",
5055 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
5058 additionalProperties
=> 0,
5060 node
=> get_standard_option
('pve-node'),
5061 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
5062 snapname
=> get_standard_option
('pve-snapshot-name'),
5066 description
=> "Save the vmstate",
5071 description
=> "A textual description or comment.",
5077 description
=> "the task ID.",
5082 my $rpcenv = PVE
::RPCEnvironment
::get
();
5084 my $authuser = $rpcenv->get_user();
5086 my $node = extract_param
($param, 'node');
5088 my $vmid = extract_param
($param, 'vmid');
5090 my $snapname = extract_param
($param, 'snapname');
5092 die "unable to use snapshot name 'current' (reserved name)\n"
5093 if $snapname eq 'current';
5095 die "unable to use snapshot name 'pending' (reserved name)\n"
5096 if lc($snapname) eq 'pending';
5099 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
5100 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
5101 $param->{description
});
5104 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
5107 __PACKAGE__-
>register_method({
5108 name
=> 'snapshot_cmd_idx',
5109 path
=> '{vmid}/snapshot/{snapname}',
5116 additionalProperties
=> 0,
5118 vmid
=> get_standard_option
('pve-vmid'),
5119 node
=> get_standard_option
('pve-node'),
5120 snapname
=> get_standard_option
('pve-snapshot-name'),
5129 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
5136 push @$res, { cmd
=> 'rollback' };
5137 push @$res, { cmd
=> 'config' };
5142 __PACKAGE__-
>register_method({
5143 name
=> 'update_snapshot_config',
5144 path
=> '{vmid}/snapshot/{snapname}/config',
5148 description
=> "Update snapshot metadata.",
5150 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
5153 additionalProperties
=> 0,
5155 node
=> get_standard_option
('pve-node'),
5156 vmid
=> get_standard_option
('pve-vmid'),
5157 snapname
=> get_standard_option
('pve-snapshot-name'),
5161 description
=> "A textual description or comment.",
5165 returns
=> { type
=> 'null' },
5169 my $rpcenv = PVE
::RPCEnvironment
::get
();
5171 my $authuser = $rpcenv->get_user();
5173 my $vmid = extract_param
($param, 'vmid');
5175 my $snapname = extract_param
($param, 'snapname');
5177 return if !defined($param->{description
});
5179 my $updatefn = sub {
5181 my $conf = PVE
::QemuConfig-
>load_config($vmid);
5183 PVE
::QemuConfig-
>check_lock($conf);
5185 my $snap = $conf->{snapshots
}->{$snapname};
5187 die "snapshot '$snapname' does not exist\n" if !defined($snap);
5189 $snap->{description
} = $param->{description
} if defined($param->{description
});
5191 PVE
::QemuConfig-
>write_config($vmid, $conf);
5194 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
5199 __PACKAGE__-
>register_method({
5200 name
=> 'get_snapshot_config',
5201 path
=> '{vmid}/snapshot/{snapname}/config',
5204 description
=> "Get snapshot configuration",
5206 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback', 'VM.Audit' ], any
=> 1],
5209 additionalProperties
=> 0,
5211 node
=> get_standard_option
('pve-node'),
5212 vmid
=> get_standard_option
('pve-vmid'),
5213 snapname
=> get_standard_option
('pve-snapshot-name'),
5216 returns
=> { type
=> "object" },
5220 my $rpcenv = PVE
::RPCEnvironment
::get
();
5222 my $authuser = $rpcenv->get_user();
5224 my $vmid = extract_param
($param, 'vmid');
5226 my $snapname = extract_param
($param, 'snapname');
5228 my $conf = PVE
::QemuConfig-
>load_config($vmid);
5230 my $snap = $conf->{snapshots
}->{$snapname};
5232 die "snapshot '$snapname' does not exist\n" if !defined($snap);
5237 __PACKAGE__-
>register_method({
5239 path
=> '{vmid}/snapshot/{snapname}/rollback',
5243 description
=> "Rollback VM state to specified snapshot.",
5245 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
5248 additionalProperties
=> 0,
5250 node
=> get_standard_option
('pve-node'),
5251 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
5252 snapname
=> get_standard_option
('pve-snapshot-name'),
5255 description
=> "Whether the VM should get started after rolling back successfully."
5256 . " (Note: VMs will be automatically started if the snapshot includes RAM.)",
5264 description
=> "the task ID.",
5269 my $rpcenv = PVE
::RPCEnvironment
::get
();
5271 my $authuser = $rpcenv->get_user();
5273 my $node = extract_param
($param, 'node');
5275 my $vmid = extract_param
($param, 'vmid');
5277 my $snapname = extract_param
($param, 'snapname');
5280 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
5281 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
5283 if ($param->{start
} && !PVE
::QemuServer
::Helpers
::vm_running_locally
($vmid)) {
5284 PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node });
5289 # hold migration lock, this makes sure that nobody create replication snapshots
5290 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
5293 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
5296 __PACKAGE__-
>register_method({
5297 name
=> 'delsnapshot',
5298 path
=> '{vmid}/snapshot/{snapname}',
5302 description
=> "Delete a VM snapshot.",
5304 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
5307 additionalProperties
=> 0,
5309 node
=> get_standard_option
('pve-node'),
5310 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
5311 snapname
=> get_standard_option
('pve-snapshot-name'),
5315 description
=> "For removal from config file, even if removing disk snapshots fails.",
5321 description
=> "the task ID.",
5326 my $rpcenv = PVE
::RPCEnvironment
::get
();
5328 my $authuser = $rpcenv->get_user();
5330 my $node = extract_param
($param, 'node');
5332 my $vmid = extract_param
($param, 'vmid');
5334 my $snapname = extract_param
($param, 'snapname');
5337 my $do_delete = sub {
5339 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
5340 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
5344 if ($param->{force
}) {
5347 eval { PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $do_delete); };
5349 die $err if $lock_obtained;
5350 die "Failed to obtain guest migration lock - replication running?\n";
5355 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
5358 __PACKAGE__-
>register_method({
5360 path
=> '{vmid}/template',
5364 description
=> "Create a Template.",
5366 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
5367 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
5370 additionalProperties
=> 0,
5372 node
=> get_standard_option
('pve-node'),
5373 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
5377 description
=> "If you want to convert only 1 disk to base image.",
5378 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
5385 description
=> "the task ID.",
5390 my $rpcenv = PVE
::RPCEnvironment
::get
();
5392 my $authuser = $rpcenv->get_user();
5394 my $node = extract_param
($param, 'node');
5396 my $vmid = extract_param
($param, 'vmid');
5398 my $disk = extract_param
($param, 'disk');
5400 my $load_and_check = sub {
5401 my $conf = PVE
::QemuConfig-
>load_config($vmid);
5403 PVE
::QemuConfig-
>check_lock($conf);
5405 die "unable to create template, because VM contains snapshots\n"
5406 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
5408 die "you can't convert a template to a template\n"
5409 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
5411 die "you can't convert a VM to template if VM is running\n"
5412 if PVE
::QemuServer
::check_running
($vmid);
5417 $load_and_check->();
5420 PVE
::QemuConfig-
>lock_config($vmid, sub {
5421 my $conf = $load_and_check->();
5423 $conf->{template
} = 1;
5424 PVE
::QemuConfig-
>write_config($vmid, $conf);
5426 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
5430 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
5433 __PACKAGE__-
>register_method({
5434 name
=> 'cloudinit_generated_config_dump',
5435 path
=> '{vmid}/cloudinit/dump',
5438 description
=> "Get automatically generated cloudinit config.",
5440 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
5443 additionalProperties
=> 0,
5445 node
=> get_standard_option
('pve-node'),
5446 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
5448 description
=> 'Config type.',
5450 enum
=> ['user', 'network', 'meta'],
5460 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
5462 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});
5465 __PACKAGE__-
>register_method({
5467 path
=> '{vmid}/mtunnel',
5470 description
=> 'Migration tunnel endpoint - only for internal use by VM migration.',
5474 ['perm', '/vms/{vmid}', [ 'VM.Allocate' ]],
5475 ['perm', '/', [ 'Sys.Incoming' ]],
5477 description
=> "You need 'VM.Allocate' permissions on '/vms/{vmid}' and Sys.Incoming" .
5478 " on '/'. Further permission checks happen during the actual migration.",
5481 additionalProperties
=> 0,
5483 node
=> get_standard_option
('pve-node'),
5484 vmid
=> get_standard_option
('pve-vmid'),
5487 format
=> 'pve-storage-id-list',
5489 description
=> 'List of storages to check permission and availability. Will be checked again for all actually used storages during migration.',
5493 format
=> 'pve-bridge-id-list',
5495 description
=> 'List of network bridges to check availability. Will be checked again for actually used bridges during migration.',
5500 additionalProperties
=> 0,
5502 upid
=> { type
=> 'string' },
5503 ticket
=> { type
=> 'string' },
5504 socket => { type
=> 'string' },
5510 my $rpcenv = PVE
::RPCEnvironment
::get
();
5511 my $authuser = $rpcenv->get_user();
5513 my $node = extract_param
($param, 'node');
5514 my $vmid = extract_param
($param, 'vmid');
5516 my $storages = extract_param
($param, 'storages');
5517 my $bridges = extract_param
($param, 'bridges');
5519 my $nodename = PVE
::INotify
::nodename
();
5521 raise_param_exc
({ node
=> "node needs to be 'localhost' or local hostname '$nodename'" })
5522 if $node ne 'localhost' && $node ne $nodename;
5526 my $storecfg = PVE
::Storage
::config
();
5527 foreach my $storeid (PVE
::Tools
::split_list
($storages)) {
5528 $check_storage_access_migrate->($rpcenv, $authuser, $storecfg, $storeid, $node);
5531 foreach my $bridge (PVE
::Tools
::split_list
($bridges)) {
5532 PVE
::Network
::read_bridge_mtu
($bridge);
5535 PVE
::Cluster
::check_cfs_quorum
();
5537 my $lock = 'create';
5538 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, 0, $lock); };
5540 raise_param_exc
({ vmid
=> "unable to create empty VM config - $@"})
5545 storecfg
=> PVE
::Storage
::config
(),
5550 my $run_locked = sub {
5551 my ($code, $params) = @_;
5552 return PVE
::QemuConfig-
>lock_config($state->{vmid
}, sub {
5553 my $conf = PVE
::QemuConfig-
>load_config($state->{vmid
});
5555 $state->{conf
} = $conf;
5557 die "Encountered wrong lock - aborting mtunnel command handling.\n"
5558 if $state->{lock} && !PVE
::QemuConfig-
>has_lock($conf, $state->{lock});
5560 return $code->($params);
5568 description
=> 'Full VM config, adapted for target cluster/node',
5570 'firewall-config' => {
5572 description
=> 'VM firewall config',
5577 format
=> PVE
::JSONSchema
::get_standard_option
('pve-qm-image-format'),
5580 format
=> 'pve-storage-id',
5584 description
=> 'parsed drive information without volid and format',
5590 description
=> 'params passed to vm_start_nolock',
5594 description
=> 'migrate_opts passed to vm_start_nolock',
5600 description
=> 'socket path for which the ticket should be valid. must be known to current mtunnel instance.',
5606 description
=> 'remove VM config and disks, aborting migration',
5610 'disk-import' => $PVE::StorageTunnel
::cmd_schema-
>{'disk-import'},
5611 'query-disk-import' => $PVE::StorageTunnel
::cmd_schema-
>{'query-disk-import'},
5612 bwlimit
=> $PVE::StorageTunnel
::cmd_schema-
>{bwlimit
},
5615 my $cmd_handlers = {
5617 # compared against other end's version
5618 # bump/reset for breaking changes
5619 # bump/bump for opt-in changes
5621 api
=> $PVE::QemuMigrate
::WS_TUNNEL_VERSION
,
5628 # parse and write out VM FW config if given
5629 if (my $fw_conf = $params->{'firewall-config'}) {
5630 my ($path, $fh) = PVE
::Tools
::tempfile_contents
($fw_conf, 700);
5637 ipset_comments
=> {},
5639 my $cluster_fw_conf = PVE
::Firewall
::load_clusterfw_conf
();
5641 # TODO: add flag for strict parsing?
5642 # TODO: add import sub that does all this given raw content?
5643 my $vmfw_conf = PVE
::Firewall
::generic_fw_config_parser
($path, $cluster_fw_conf, $empty_conf, 'vm');
5644 $vmfw_conf->{vmid
} = $state->{vmid
};
5645 PVE
::Firewall
::save_vmfw_conf
($state->{vmid
}, $vmfw_conf);
5647 $state->{cleanup
}->{fw
} = 1;
5650 my $conf_fn = "incoming/qemu-server/$state->{vmid}.conf";
5651 my $new_conf = PVE
::QemuServer
::parse_vm_config
($conf_fn, $params->{conf
}, 1);
5652 delete $new_conf->{lock};
5653 delete $new_conf->{digest
};
5655 # TODO handle properly?
5656 delete $new_conf->{snapshots
};
5657 delete $new_conf->{parent
};
5658 delete $new_conf->{pending
};
5660 # not handled by update_vm_api
5661 my $vmgenid = delete $new_conf->{vmgenid
};
5662 my $meta = delete $new_conf->{meta
};
5663 my $cloudinit = delete $new_conf->{cloudinit
}; # this is informational only
5664 $new_conf->{skip_cloud_init
} = 1; # re-use image from source side
5666 $new_conf->{vmid
} = $state->{vmid
};
5667 $new_conf->{node
} = $node;
5669 PVE
::QemuConfig-
>remove_lock($state->{vmid
}, 'create');
5672 $update_vm_api->($new_conf, 1);
5675 # revert to locked previous config
5676 my $conf = PVE
::QemuConfig-
>load_config($state->{vmid
});
5677 $conf->{lock} = 'create';
5678 PVE
::QemuConfig-
>write_config($state->{vmid
}, $conf);
5683 my $conf = PVE
::QemuConfig-
>load_config($state->{vmid
});
5684 $conf->{lock} = 'migrate';
5685 $conf->{vmgenid
} = $vmgenid if defined($vmgenid);
5686 $conf->{meta
} = $meta if defined($meta);
5687 $conf->{cloudinit
} = $cloudinit if defined($cloudinit);
5688 PVE
::QemuConfig-
>write_config($state->{vmid
}, $conf);
5690 $state->{lock} = 'migrate';
5696 return PVE
::StorageTunnel
::handle_bwlimit
($params);
5701 my $format = $params->{format
};
5702 my $storeid = $params->{storage
};
5703 my $drive = $params->{drive
};
5705 $check_storage_access_migrate->($rpcenv, $authuser, $state->{storecfg
}, $storeid, $node);
5708 default => $storeid,
5711 my $source_volumes = {
5721 my $res = PVE
::QemuServer
::vm_migrate_alloc_nbd_disks
($state->{storecfg
}, $state->{vmid
}, $source_volumes, $storagemap);
5722 if (defined($res->{disk
})) {
5723 $state->{cleanup
}->{volumes
}->{$res->{disk
}->{volid
}} = 1;
5724 return $res->{disk
};
5726 die "failed to allocate NBD disk..\n";
5729 'disk-import' => sub {
5732 $check_storage_access_migrate->(
5740 $params->{unix
} = "/run/qemu-server/$state->{vmid}.storage";
5742 return PVE
::StorageTunnel
::handle_disk_import
($state, $params);
5744 'query-disk-import' => sub {
5747 return PVE
::StorageTunnel
::handle_query_disk_import
($state, $params);
5752 my $info = PVE
::QemuServer
::vm_start_nolock
(
5756 $params->{start_params
},
5757 $params->{migrate_opts
},
5761 if ($info->{migrate
}->{proto
} ne 'unix') {
5762 PVE
::QemuServer
::vm_stop
(undef, $state->{vmid
}, 1, 1);
5763 die "migration over non-UNIX sockets not possible\n";
5766 my $socket = $info->{migrate
}->{addr
};
5767 chown $state->{socket_uid
}, -1, $socket;
5768 $state->{sockets
}->{$socket} = 1;
5770 my $unix_sockets = $info->{migrate
}->{unix_sockets
};
5771 foreach my $socket (@$unix_sockets) {
5772 chown $state->{socket_uid
}, -1, $socket;
5773 $state->{sockets
}->{$socket} = 1;
5778 if (PVE
::QemuServer
::qga_check_running
($state->{vmid
})) {
5779 eval { mon_cmd
($state->{vmid
}, "guest-fstrim") };
5780 warn "fstrim failed: $@\n" if $@;
5785 PVE
::QemuServer
::vm_stop
(undef, $state->{vmid
}, 1, 1);
5789 PVE
::QemuServer
::nbd_stop
($state->{vmid
});
5793 if (PVE
::QemuServer
::Helpers
::vm_running_locally
($state->{vmid
})) {
5794 PVE
::QemuServer
::vm_resume
($state->{vmid
}, 1, 1);
5796 die "VM $state->{vmid} not running\n";
5801 PVE
::QemuConfig-
>remove_lock($state->{vmid
}, $state->{lock});
5802 delete $state->{lock};
5808 my $path = $params->{path
};
5810 die "Not allowed to generate ticket for unknown socket '$path'\n"
5811 if !defined($state->{sockets
}->{$path});
5813 return { ticket
=> PVE
::AccessControl
::assemble_tunnel_ticket
($authuser, "/socket/$path") };
5818 if ($params->{cleanup
}) {
5819 if ($state->{cleanup
}->{fw
}) {
5820 PVE
::Firewall
::remove_vmfw_conf
($state->{vmid
});
5823 for my $volid (keys $state->{cleanup
}->{volumes
}->%*) {
5824 print "freeing volume '$volid' as part of cleanup\n";
5825 eval { PVE
::Storage
::vdisk_free
($state->{storecfg
}, $volid) };
5829 PVE
::QemuServer
::destroy_vm
($state->{storecfg
}, $state->{vmid
}, 1);
5832 print "switching to exit-mode, waiting for client to disconnect\n";
5839 my $socket_addr = "/run/qemu-server/$state->{vmid}.mtunnel";
5840 unlink $socket_addr;
5842 $state->{socket} = IO
::Socket
::UNIX-
>new(
5843 Type
=> SOCK_STREAM
(),
5844 Local
=> $socket_addr,
5848 $state->{socket_uid
} = getpwnam('www-data')
5849 or die "Failed to resolve user 'www-data' to numeric UID\n";
5850 chown $state->{socket_uid
}, -1, $socket_addr;
5853 print "mtunnel started\n";
5855 my $conn = eval { PVE
::Tools
::run_with_timeout
(300, sub { $state->{socket}->accept() }) };
5857 warn "Failed to accept tunnel connection - $@\n";
5859 warn "Removing tunnel socket..\n";
5860 unlink $state->{socket};
5862 warn "Removing temporary VM config..\n";
5864 PVE
::QemuServer
::destroy_vm
($state->{storecfg
}, $state->{vmid
}, 1);
5867 die "Exiting mtunnel\n";
5870 $state->{conn
} = $conn;
5872 my $reply_err = sub {
5875 my $reply = JSON
::encode_json
({
5876 success
=> JSON
::false
,
5879 $conn->print("$reply\n");
5883 my $reply_ok = sub {
5886 $res->{success
} = JSON
::true
;
5887 my $reply = JSON
::encode_json
($res);
5888 $conn->print("$reply\n");
5892 while (my $line = <$conn>) {
5895 # untaint, we validate below if needed
5896 ($line) = $line =~ /^(.*)$/;
5897 my $parsed = eval { JSON
::decode_json
($line) };
5899 $reply_err->("failed to parse command - $@");
5903 my $cmd = delete $parsed->{cmd
};
5904 if (!defined($cmd)) {
5905 $reply_err->("'cmd' missing");
5906 } elsif ($state->{exit}) {
5907 $reply_err->("tunnel is in exit-mode, processing '$cmd' cmd not possible");
5909 } elsif (my $handler = $cmd_handlers->{$cmd}) {
5910 print "received command '$cmd'\n";
5912 if ($cmd_desc->{$cmd}) {
5913 PVE
::JSONSchema
::validate
($parsed, $cmd_desc->{$cmd});
5917 my $res = $run_locked->($handler, $parsed);
5920 $reply_err->("failed to handle '$cmd' command - $@")
5923 $reply_err->("unknown command '$cmd' given");
5927 if ($state->{exit}) {
5928 print "mtunnel exited\n";
5930 die "mtunnel exited unexpectedly\n";
5934 my $socket_addr = "/run/qemu-server/$vmid.mtunnel";
5935 my $ticket = PVE
::AccessControl
::assemble_tunnel_ticket
($authuser, "/socket/$socket_addr");
5936 my $upid = $rpcenv->fork_worker('qmtunnel', $vmid, $authuser, $realcmd);
5941 socket => $socket_addr,
5945 __PACKAGE__-
>register_method({
5946 name
=> 'mtunnelwebsocket',
5947 path
=> '{vmid}/mtunnelwebsocket',
5950 description
=> "You need to pass a ticket valid for the selected socket. Tickets can be created via the mtunnel API call, which will check permissions accordingly.",
5951 user
=> 'all', # check inside
5953 description
=> 'Migration tunnel endpoint for websocket upgrade - only for internal use by VM migration.',
5955 additionalProperties
=> 0,
5957 node
=> get_standard_option
('pve-node'),
5958 vmid
=> get_standard_option
('pve-vmid'),
5961 description
=> "unix socket to forward to",
5965 description
=> "ticket return by initial 'mtunnel' API call, or retrieved via 'ticket' tunnel command",
5972 port
=> { type
=> 'string', optional
=> 1 },
5973 socket => { type
=> 'string', optional
=> 1 },
5979 my $rpcenv = PVE
::RPCEnvironment
::get
();
5980 my $authuser = $rpcenv->get_user();
5982 my $nodename = PVE
::INotify
::nodename
();
5983 my $node = extract_param
($param, 'node');
5985 raise_param_exc
({ node
=> "node needs to be 'localhost' or local hostname '$nodename'" })
5986 if $node ne 'localhost' && $node ne $nodename;
5988 my $vmid = $param->{vmid
};
5990 PVE
::QemuConfig-
>load_config($vmid);
5992 my $socket = $param->{socket};
5993 PVE
::AccessControl
::verify_tunnel_ticket
($param->{ticket
}, $authuser, "/socket/$socket");
5995 return { socket => $socket };