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
;
51 use PVE
::RESTEnvironment
qw(log_warn);
54 if (!$ENV{PVE_GENERATING_DOCS
}) {
55 require PVE
::HA
::Env
::PVE2
;
56 import PVE
::HA
::Env
::PVE2
;
57 require PVE
::HA
::Config
;
58 import PVE
::HA
::Config
;
62 use base
qw(PVE::RESTHandler);
64 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.";
66 my $resolve_cdrom_alias = sub {
69 if (my $value = $param->{cdrom
}) {
70 $value .= ",media=cdrom" if $value !~ m/media=/;
71 $param->{ide2
} = $value;
72 delete $param->{cdrom
};
76 # Used in import-enabled API endpoints. Parses drives using the extended '_with_alloc' schema.
77 my $foreach_volume_with_alloc = sub {
78 my ($param, $func) = @_;
80 for my $opt (sort keys $param->%*) {
81 next if !PVE
::QemuServer
::is_valid_drivename
($opt);
83 my $drive = PVE
::QemuServer
::Drive
::parse_drive
($opt, $param->{$opt}, 1);
86 $func->($opt, $drive);
90 my $check_drive_param = sub {
91 my ($param, $storecfg, $extra_checks) = @_;
93 for my $opt (sort keys $param->%*) {
94 next if !PVE
::QemuServer
::is_valid_drivename
($opt);
96 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt}, 1);
97 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
99 if ($drive->{'import-from'}) {
100 if ($drive->{file
} !~ $PVE::QemuServer
::Drive
::NEW_DISK_RE
|| $3 != 0) {
102 $opt => "'import-from' requires special syntax - ".
103 "use <storage ID>:0,import-from=<source>",
107 if ($opt eq 'efidisk0') {
108 for my $required (qw(efitype pre-enrolled-keys)) {
109 if (!defined($drive->{$required})) {
111 $opt => "need to specify '$required' when using 'import-from'",
115 } elsif ($opt eq 'tpmstate0') {
116 raise_param_exc
({ $opt => "need to specify 'version' when using 'import-from'" })
117 if !defined($drive->{version
});
121 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
123 $extra_checks->($drive) if $extra_checks;
125 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive, 1);
129 my $check_storage_access = sub {
130 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
132 $foreach_volume_with_alloc->($settings, sub {
133 my ($ds, $drive) = @_;
135 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
137 my $volid = $drive->{file
};
138 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
140 if (!$volid || ($volid eq 'none' || $volid eq 'cloudinit' || (defined($volname) && $volname eq 'cloudinit'))) {
142 } elsif ($isCDROM && ($volid eq 'cdrom')) {
143 $rpcenv->check($authuser, "/", ['Sys.Console']);
144 } elsif (!$isCDROM && ($volid =~ $PVE::QemuServer
::Drive
::NEW_DISK_RE
)) {
145 my ($storeid, $size) = ($2 || $default_storage, $3);
146 die "no storage ID specified (and no default storage)\n" if !$storeid;
147 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
148 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
149 raise_param_exc
({ storage
=> "storage '$storeid' does not support vm images"})
150 if !$scfg->{content
}->{images
};
152 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
154 my ($vtype) = PVE
::Storage
::parse_volname
($storecfg, $volid);
155 raise_param_exc
({ $ds => "content type needs to be 'images' or 'iso'" })
156 if $vtype ne 'images' && $vtype ne 'iso';
160 if (my $src_image = $drive->{'import-from'}) {
162 if (PVE
::Storage
::parse_volume_id
($src_image, 1)) { # PVE-managed volume
163 (my $vtype, undef, $src_vmid) = PVE
::Storage
::parse_volname
($storecfg, $src_image);
164 raise_param_exc
({ $ds => "$src_image has wrong type '$vtype' - not an image" })
165 if $vtype ne 'images';
168 if ($src_vmid) { # might be actively used by VM and will be copied via clone_disk()
169 $rpcenv->check($authuser, "/vms/${src_vmid}", ['VM.Clone']);
171 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $src_image);
176 $rpcenv->check($authuser, "/storage/$settings->{vmstatestorage}", ['Datastore.AllocateSpace'])
177 if defined($settings->{vmstatestorage
});
180 my $check_storage_access_clone = sub {
181 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
185 PVE
::QemuConfig-
>foreach_volume($conf, sub {
186 my ($ds, $drive) = @_;
188 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
190 my $volid = $drive->{file
};
192 return if !$volid || $volid eq 'none';
195 if ($volid eq 'cdrom') {
196 $rpcenv->check($authuser, "/", ['Sys.Console']);
198 # we simply allow access
199 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
200 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
201 $sharedvm = 0 if !$scfg->{shared
};
205 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
206 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
207 $sharedvm = 0 if !$scfg->{shared
};
209 $sid = $storage if $storage;
210 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
214 $rpcenv->check($authuser, "/storage/$conf->{vmstatestorage}", ['Datastore.AllocateSpace'])
215 if defined($conf->{vmstatestorage
});
220 my $check_storage_access_migrate = sub {
221 my ($rpcenv, $authuser, $storecfg, $storage, $node) = @_;
223 PVE
::Storage
::storage_check_enabled
($storecfg, $storage, $node);
225 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace']);
227 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
228 die "storage '$storage' does not support vm images\n"
229 if !$scfg->{content
}->{images
};
232 my $import_from_volid = sub {
233 my ($storecfg, $src_volid, $dest_info, $vollist) = @_;
235 die "could not get size of $src_volid\n"
236 if !PVE
::Storage
::volume_size_info
($storecfg, $src_volid, 10);
238 die "cannot import from cloudinit disk\n"
239 if PVE
::QemuServer
::Drive
::drive_is_cloudinit
({ file
=> $src_volid });
241 my $src_vmid = (PVE
::Storage
::parse_volname
($storecfg, $src_volid))[2];
243 my $src_vm_state = sub {
244 my $exists = $src_vmid && PVE
::Cluster
::get_vmlist
()->{ids
}->{$src_vmid} ?
1 : 0;
248 eval { PVE
::QemuConfig
::assert_config_exists_on_node
($src_vmid); };
249 die "owner VM $src_vmid not on local node\n" if $@;
250 $runs = PVE
::QemuServer
::Helpers
::vm_running_locally
($src_vmid) || 0;
253 return ($exists, $runs);
256 my ($src_vm_exists, $running) = $src_vm_state->();
258 die "cannot import from '$src_volid' - full clone feature is not supported\n"
259 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $src_volid, undef, $running);
262 my ($src_vm_exists_now, $running_now) = $src_vm_state->();
264 die "owner VM $src_vmid changed state unexpectedly\n"
265 if $src_vm_exists_now != $src_vm_exists || $running_now != $running;
267 my $src_conf = $src_vm_exists_now ? PVE
::QemuConfig-
>load_config($src_vmid) : {};
269 my $src_drive = { file
=> $src_volid };
271 PVE
::QemuConfig-
>foreach_volume($src_conf, sub {
272 my ($ds, $drive) = @_;
274 return if $src_drivename;
276 if ($drive->{file
} eq $src_volid) {
278 $src_drivename = $ds;
284 running
=> $running_now,
285 drivename
=> $src_drivename,
290 my ($src_storeid) = PVE
::Storage
::parse_volume_id
($src_volid);
292 return PVE
::QemuServer
::clone_disk
(
301 PVE
::Storage
::get_bandwidth_limit
('clone', [$src_storeid, $dest_info->{storage
}]),
307 $cloned = PVE
::QemuConfig-
>lock_config_full($src_vmid, 30, $clonefn);
308 } elsif ($src_vmid) {
309 $cloned = PVE
::QemuConfig-
>lock_config_shared($src_vmid, 30, $clonefn);
311 $cloned = $clonefn->();
314 return $cloned->@{qw(file size)};
317 # Note: $pool is only needed when creating a VM, because pool permissions
318 # are automatically inherited if VM already exists inside a pool.
319 my $create_disks = sub {
320 my ($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
327 my ($ds, $disk) = @_;
329 my $volid = $disk->{file
};
330 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
332 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
333 delete $disk->{size
};
334 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
335 } elsif (defined($volname) && $volname eq 'cloudinit') {
336 $storeid = $storeid // $default_storage;
337 die "no storage ID specified (and no default storage)\n" if !$storeid;
340 my $ci_key = PVE
::QemuConfig-
>has_cloudinit($conf, $ds)
341 || PVE
::QemuConfig-
>has_cloudinit($conf->{pending
} || {}, $ds)
342 || PVE
::QemuConfig-
>has_cloudinit($res, $ds)
344 die "$ds - cloud-init drive is already attached at '$ci_key'\n";
347 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
348 my $name = "vm-$vmid-cloudinit";
352 $fmt = $disk->{format
} // "qcow2";
355 $fmt = $disk->{format
} // "raw";
358 # Initial disk created with 4 MB and aligned to 4MB on regeneration
359 my $ci_size = PVE
::QemuServer
::Cloudinit
::CLOUDINIT_DISK_SIZE
;
360 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, $name, $ci_size/1024);
361 $disk->{file
} = $volid;
362 $disk->{media
} = 'cdrom';
363 push @$vollist, $volid;
364 delete $disk->{format
}; # no longer needed
365 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
366 print "$ds: successfully created disk '$res->{$ds}'\n";
367 } elsif ($volid =~ $PVE::QemuServer
::Drive
::NEW_DISK_RE
) {
368 my ($storeid, $size) = ($2 || $default_storage, $3);
369 die "no storage ID specified (and no default storage)\n" if !$storeid;
371 if (my $source = delete $disk->{'import-from'}) {
374 if (PVE
::Storage
::parse_volume_id
($source, 1)) { # PVE-managed volume
379 format
=> $disk->{format
},
382 $dest_info->{efisize
} = PVE
::QemuServer
::get_efivars_size
($conf, $disk)
383 if $ds eq 'efidisk0';
385 ($dst_volid, $size) = eval {
386 $import_from_volid->($storecfg, $source, $dest_info, $vollist);
388 die "cannot import from '$source' - $@" if $@;
390 $source = PVE
::Storage
::abs_filesystem_path
($storecfg, $source, 1);
391 $size = PVE
::Storage
::file_size_info
($source);
392 die "could not get file size of $source\n" if !$size;
394 (undef, $dst_volid) = PVE
::QemuServer
::ImportDisk
::do_import
(
400 format
=> $disk->{format
},
401 'skip-config-update' => 1,
404 push @$vollist, $dst_volid;
407 $disk->{file
} = $dst_volid;
408 $disk->{size
} = $size;
409 delete $disk->{format
}; # no longer needed
410 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
412 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
413 my $fmt = $disk->{format
} || $defformat;
415 $size = PVE
::Tools
::convert_size
($size, 'gb' => 'kb'); # vdisk_alloc uses kb
418 if ($ds eq 'efidisk0') {
419 my $smm = PVE
::QemuServer
::Machine
::machine_type_is_q35
($conf);
420 ($volid, $size) = PVE
::QemuServer
::create_efidisk
(
421 $storecfg, $storeid, $vmid, $fmt, $arch, $disk, $smm);
422 } elsif ($ds eq 'tpmstate0') {
423 # swtpm can only use raw volumes, and uses a fixed size
424 $size = PVE
::Tools
::convert_size
(PVE
::QemuServer
::Drive
::TPMSTATE_DISK_SIZE
, 'b' => 'kb');
425 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, "raw", undef, $size);
427 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, undef, $size);
429 push @$vollist, $volid;
430 $disk->{file
} = $volid;
431 $disk->{size
} = PVE
::Tools
::convert_size
($size, 'kb' => 'b');
432 delete $disk->{format
}; # no longer needed
433 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
436 print "$ds: successfully created disk '$res->{$ds}'\n";
438 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
440 my ($vtype) = PVE
::Storage
::parse_volname
($storecfg, $volid);
441 die "cannot use volume $volid - content type needs to be 'images' or 'iso'"
442 if $vtype ne 'images' && $vtype ne 'iso';
444 if (PVE
::QemuServer
::Drive
::drive_is_cloudinit
($disk)) {
446 my $ci_key = PVE
::QemuConfig-
>has_cloudinit($conf, $ds)
447 || PVE
::QemuConfig-
>has_cloudinit($conf->{pending
} || {}, $ds)
448 || PVE
::QemuConfig-
>has_cloudinit($res, $ds)
450 die "$ds - cloud-init drive is already attached at '$ci_key'\n";
455 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
457 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
458 die "volume $volid does not exist\n" if !$size;
459 $disk->{size
} = $size;
461 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
465 eval { $foreach_volume_with_alloc->($settings, $code); };
467 # free allocated images on error
469 syslog
('err', "VM $vmid creating disks failed");
470 foreach my $volid (@$vollist) {
471 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
477 return ($vollist, $res);
480 my $check_cpu_model_access = sub {
481 my ($rpcenv, $authuser, $new, $existing) = @_;
483 return if !defined($new->{cpu
});
485 my $cpu = PVE
::JSONSchema
::check_format
('pve-vm-cpu-conf', $new->{cpu
});
486 return if !$cpu || !$cpu->{cputype
}; # always allow default
487 my $cputype = $cpu->{cputype
};
489 if ($existing && $existing->{cpu
}) {
490 # changing only other settings doesn't require permissions for CPU model
491 my $existingCpu = PVE
::JSONSchema
::check_format
('pve-vm-cpu-conf', $existing->{cpu
});
492 return if $existingCpu->{cputype
} eq $cputype;
495 if (PVE
::QemuServer
::CPUConfig
::is_custom_model
($cputype)) {
496 $rpcenv->check($authuser, "/nodes", ['Sys.Audit']);
511 my $memoryoptions = {
517 my $hwtypeoptions = {
530 my $generaloptions = {
537 'migrate_downtime' => 1,
538 'migrate_speed' => 1,
550 my $vmpoweroptions = {
557 'vmstatestorage' => 1,
560 my $cloudinitoptions = {
571 my $check_vm_create_serial_perm = sub {
572 my ($rpcenv, $authuser, $vmid, $pool, $param) = @_;
574 return 1 if $authuser eq 'root@pam';
576 foreach my $opt (keys %{$param}) {
577 next if $opt !~ m/^serial\d+$/;
579 if ($param->{$opt} eq 'socket') {
580 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
582 die "only root can set '$opt' config for real devices\n";
589 my sub check_usb_perm
{
590 my ($rpcenv, $authuser, $vmid, $pool, $opt, $value) = @_;
592 return 1 if $authuser eq 'root@pam';
594 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
596 my $device = PVE
::JSONSchema
::parse_property_string
('pve-qm-usb', $value);
597 if ($device->{host
} && $device->{host
} !~ m/^spice$/i) {
598 die "only root can set '$opt' config for real devices\n";
599 } elsif ($device->{mapping
}) {
600 $rpcenv->check_full($authuser, "/mapping/usb/$device->{mapping}", ['Mapping.Use']);
602 die "either 'host' or 'mapping' must be set.\n";
608 my sub check_vm_create_usb_perm
{
609 my ($rpcenv, $authuser, $vmid, $pool, $param) = @_;
611 return 1 if $authuser eq 'root@pam';
613 foreach my $opt (keys %{$param}) {
614 next if $opt !~ m/^usb\d+$/;
615 check_usb_perm
($rpcenv, $authuser, $vmid, $pool, $opt, $param->{$opt});
621 my sub check_hostpci_perm
{
622 my ($rpcenv, $authuser, $vmid, $pool, $opt, $value) = @_;
624 return 1 if $authuser eq 'root@pam';
626 my $device = PVE
::JSONSchema
::parse_property_string
('pve-qm-hostpci', $value);
627 if ($device->{host
}) {
628 die "only root can set '$opt' config for non-mapped devices\n";
629 } elsif ($device->{mapping
}) {
630 $rpcenv->check_full($authuser, "/mapping/pci/$device->{mapping}", ['Mapping.Use']);
631 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
633 die "either 'host' or 'mapping' must be set.\n";
639 my sub check_vm_create_hostpci_perm
{
640 my ($rpcenv, $authuser, $vmid, $pool, $param) = @_;
642 return 1 if $authuser eq 'root@pam';
644 foreach my $opt (keys %{$param}) {
645 next if $opt !~ m/^hostpci\d+$/;
646 check_hostpci_perm
($rpcenv, $authuser, $vmid, $pool, $opt, $param->{$opt});
652 my $check_vm_modify_config_perm = sub {
653 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
655 return 1 if $authuser eq 'root@pam';
657 foreach my $opt (@$key_list) {
658 # some checks (e.g., disk, serial port, usb) need to be done somewhere
659 # else, as there the permission can be value dependend
660 next if PVE
::QemuServer
::is_valid_drivename
($opt);
661 next if $opt eq 'cdrom';
662 next if $opt =~ m/^(?:unused|serial|usb|hostpci)\d+$/;
663 next if $opt eq 'tags';
666 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
667 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
668 } elsif ($memoryoptions->{$opt}) {
669 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
670 } elsif ($hwtypeoptions->{$opt}) {
671 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
672 } elsif ($generaloptions->{$opt}) {
673 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
674 # special case for startup since it changes host behaviour
675 if ($opt eq 'startup') {
676 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
678 } elsif ($vmpoweroptions->{$opt}) {
679 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
680 } elsif ($diskoptions->{$opt}) {
681 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
682 } elsif ($opt =~ m/^net\d+$/) {
683 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
684 } elsif ($cloudinitoptions->{$opt} || $opt =~ m/^ipconfig\d+$/) {
685 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Cloudinit', 'VM.Config.Network'], 1);
686 } elsif ($opt eq 'vmstate') {
687 # the user needs Disk and PowerMgmt privileges to change the vmstate
688 # also needs privileges on the storage, that will be checked later
689 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk', 'VM.PowerMgmt' ]);
691 # catches args, lock, etc.
692 # new options will be checked here
693 die "only root can set '$opt' config\n";
700 sub assert_scsi_feature_compatibility
{
701 my ($opt, $conf, $storecfg, $drive_attributes) = @_;
703 my $drive = PVE
::QemuServer
::Drive
::parse_drive
($opt, $drive_attributes, 1);
705 my $machine_type = PVE
::QemuServer
::get_vm_machine
($conf, undef, $conf->{arch
});
706 my $machine_version = PVE
::QemuServer
::Machine
::extract_version
(
707 $machine_type, PVE
::QemuServer
::kvm_user_version
());
708 my $drivetype = PVE
::QemuServer
::Drive
::get_scsi_devicetype
(
709 $drive, $storecfg, $machine_version);
711 if ($drivetype ne 'hd' && $drivetype ne 'cd') {
712 if ($drive->{product
}) {
714 $opt => "Passing of product information is only supported for 'scsi-hd' and "
715 ."'scsi-cd' devices (e.g. not pass-through).",
718 if ($drive->{vendor
}) {
720 $opt => "Passing of vendor information is only supported for 'scsi-hd' and "
721 ."'scsi-cd' devices (e.g. not pass-through).",
727 __PACKAGE__-
>register_method({
731 description
=> "Virtual machine index (per node).",
733 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
737 protected
=> 1, # qemu pid files are only readable by root
739 additionalProperties
=> 0,
741 node
=> get_standard_option
('pve-node'),
745 description
=> "Determine the full status of active VMs.",
753 properties
=> $PVE::QemuServer
::vmstatus_return_properties
,
755 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
760 my $rpcenv = PVE
::RPCEnvironment
::get
();
761 my $authuser = $rpcenv->get_user();
763 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
766 foreach my $vmid (keys %$vmstatus) {
767 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
769 my $data = $vmstatus->{$vmid};
776 my $parse_restore_archive = sub {
777 my ($storecfg, $archive) = @_;
779 my ($archive_storeid, $archive_volname) = PVE
::Storage
::parse_volume_id
($archive, 1);
783 if (defined($archive_storeid)) {
784 my $scfg = PVE
::Storage
::storage_config
($storecfg, $archive_storeid);
785 $res->{volid
} = $archive;
786 if ($scfg->{type
} eq 'pbs') {
787 $res->{type
} = 'pbs';
791 my $path = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
792 $res->{type
} = 'file';
793 $res->{path
} = $path;
798 __PACKAGE__-
>register_method({
802 description
=> "Create or restore a virtual machine.",
804 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
805 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
806 "If you create disks you need 'Datastore.AllocateSpace' on any used storage." .
807 "If you use a bridge/vlan, you need 'SDN.Use' on any used bridge/vlan.",
808 user
=> 'all', # check inside
813 additionalProperties
=> 0,
814 properties
=> PVE
::QemuServer
::json_config_properties
(
816 node
=> get_standard_option
('pve-node'),
817 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
819 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.",
823 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
825 storage
=> get_standard_option
('pve-storage-id', {
826 description
=> "Default storage.",
828 completion
=> \
&PVE
::QemuServer
::complete_storage
,
833 description
=> "Allow to overwrite existing VM.",
834 requires
=> 'archive',
839 description
=> "Assign a unique random ethernet address.",
840 requires
=> 'archive',
845 description
=> "Start the VM immediately from the backup and restore in background. PBS only.",
846 requires
=> 'archive',
850 type
=> 'string', format
=> 'pve-poolid',
851 description
=> "Add the VM to the specified pool.",
854 description
=> "Override I/O bandwidth limit (in KiB/s).",
858 default => 'restore limit from datacenter or storage config',
864 description
=> "Start VM after it was created successfully.",
876 my $rpcenv = PVE
::RPCEnvironment
::get
();
877 my $authuser = $rpcenv->get_user();
879 my $node = extract_param
($param, 'node');
880 my $vmid = extract_param
($param, 'vmid');
882 my $archive = extract_param
($param, 'archive');
883 my $is_restore = !!$archive;
885 my $bwlimit = extract_param
($param, 'bwlimit');
886 my $force = extract_param
($param, 'force');
887 my $pool = extract_param
($param, 'pool');
888 my $start_after_create = extract_param
($param, 'start');
889 my $storage = extract_param
($param, 'storage');
890 my $unique = extract_param
($param, 'unique');
891 my $live_restore = extract_param
($param, 'live-restore');
893 if (defined(my $ssh_keys = $param->{sshkeys
})) {
894 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
895 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
898 $param->{cpuunits
} = PVE
::CGroup
::clamp_cpu_shares
($param->{cpuunits
})
899 if defined($param->{cpuunits
}); # clamp value depending on cgroup version
901 PVE
::Cluster
::check_cfs_quorum
();
903 my $filename = PVE
::QemuConfig-
>config_file($vmid);
904 my $storecfg = PVE
::Storage
::config
();
906 if (defined($pool)) {
907 $rpcenv->check_pool_exist($pool);
910 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
911 if defined($storage);
913 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
915 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
917 } elsif ($archive && $force && (-f
$filename) &&
918 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
919 # OK: user has VM.Backup permissions and wants to restore an existing VM
925 for my $opt (sort keys $param->%*) {
926 if (PVE
::QemuServer
::Drive
::is_valid_drivename
($opt)) {
927 raise_param_exc
({ $opt => "option conflicts with option 'archive'" });
931 if ($archive eq '-') {
932 die "pipe requires cli environment\n" if $rpcenv->{type
} ne 'cli';
933 $archive = { type
=> 'pipe' };
935 PVE
::Storage
::check_volume_access
(
944 $archive = $parse_restore_archive->($storecfg, $archive);
948 if (scalar(keys $param->%*) > 0) {
949 &$resolve_cdrom_alias($param);
951 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
953 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
955 &$check_vm_create_serial_perm($rpcenv, $authuser, $vmid, $pool, $param);
956 check_vm_create_usb_perm
($rpcenv, $authuser, $vmid, $pool, $param);
957 check_vm_create_hostpci_perm
($rpcenv, $authuser, $vmid, $pool, $param);
959 PVE
::QemuServer
::check_bridge_access
($rpcenv, $authuser, $param);
960 &$check_cpu_model_access($rpcenv, $authuser, $param);
962 $check_drive_param->($param, $storecfg);
964 PVE
::QemuServer
::add_random_macs
($param);
967 my $emsg = $is_restore ?
"unable to restore VM $vmid -" : "unable to create VM $vmid -";
969 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, $force) };
970 die "$emsg $@" if $@;
972 my $restored_data = 0;
973 my $restorefn = sub {
974 my $conf = PVE
::QemuConfig-
>load_config($vmid);
976 PVE
::QemuConfig-
>check_protection($conf, $emsg);
978 die "$emsg vm is running\n" if PVE
::QemuServer
::check_running
($vmid);
981 my $restore_options = {
986 live
=> $live_restore,
987 override_conf
=> $param,
989 if (my $volid = $archive->{volid
}) {
990 # best effort, real check is after restoring!
992 my $old_conf = PVE
::Storage
::extract_vzdump_config
($storecfg, $volid);
993 PVE
::QemuServer
::restore_merge_config
("backup/qemu-server/$vmid.conf", $old_conf, $param);
996 warn "Could not extract backed up config: $@\n";
997 warn "Skipping early checks!\n";
999 PVE
::QemuServer
::check_restore_permissions
($rpcenv, $authuser, $merged);
1002 if ($archive->{type
} eq 'file' || $archive->{type
} eq 'pipe') {
1003 die "live-restore is only compatible with backup images from a Proxmox Backup Server\n"
1005 PVE
::QemuServer
::restore_file_archive
($archive->{path
} // '-', $vmid, $authuser, $restore_options);
1006 } elsif ($archive->{type
} eq 'pbs') {
1007 PVE
::QemuServer
::restore_proxmox_backup_archive
($archive->{volid
}, $vmid, $authuser, $restore_options);
1009 die "unknown backup archive type\n";
1013 my $restored_conf = PVE
::QemuConfig-
>load_config($vmid);
1014 # Convert restored VM to template if backup was VM template
1015 if (PVE
::QemuConfig-
>is_template($restored_conf)) {
1016 warn "Convert to template.\n";
1017 eval { PVE
::QemuServer
::template_create
($vmid, $restored_conf) };
1021 PVE
::QemuServer
::create_ifaces_ipams_ips
($restored_conf, $vmid) if $unique;
1024 # ensure no old replication state are exists
1025 PVE
::ReplicationState
::delete_guest_states
($vmid);
1027 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
1029 if ($start_after_create && !$live_restore) {
1030 print "Execute autostart\n";
1031 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
1036 my $createfn = sub {
1037 # ensure no old replication state are exists
1038 PVE
::ReplicationState
::delete_guest_states
($vmid);
1042 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
1045 for my $opt (sort keys $param->%*) {
1046 next if $opt !~ m/^scsi\d+$/;
1047 assert_scsi_feature_compatibility
($opt, $conf, $storecfg, $param->{$opt});
1050 $conf->{meta
} = PVE
::QemuServer
::new_meta_info_string
();
1054 ($vollist, my $created_opts) = $create_disks->(
1065 $conf->{$_} = $created_opts->{$_} for keys $created_opts->%*;
1067 if (!$conf->{boot
}) {
1068 my $devs = PVE
::QemuServer
::get_default_bootdevices
($conf);
1069 $conf->{boot
} = PVE
::QemuServer
::print_bootorder
($devs);
1072 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
1073 PVE
::QemuServer
::assert_clipboard_config
($vga);
1075 # auto generate uuid if user did not specify smbios1 option
1076 if (!$conf->{smbios1
}) {
1077 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
1080 if ((!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') && $arch ne 'aarch64') {
1081 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
1084 my $machine = $conf->{machine
};
1085 if (!$machine || $machine =~ m/^(?:pc|q35|virt)$/) {
1086 # always pin Windows' machine version on create, they get to easily confused
1087 if (PVE
::QemuServer
::Helpers
::windows_version
($conf->{ostype
})) {
1088 $conf->{machine
} = PVE
::QemuServer
::windows_get_pinned_machine_version
($machine);
1092 PVE
::QemuConfig-
>write_config($vmid, $conf);
1098 foreach my $volid (@$vollist) {
1099 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
1105 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
1107 PVE
::QemuServer
::create_ifaces_ipams_ips
($conf, $vmid);
1110 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
1112 if ($start_after_create) {
1113 print "Execute autostart\n";
1114 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
1119 my ($code, $worker_name);
1121 $worker_name = 'qmrestore';
1123 eval { $restorefn->() };
1125 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
1127 if ($restored_data) {
1128 warn "error after data was restored, VM disks should be OK but config may "
1129 ."require adaptions. VM $vmid state is NOT cleaned up.\n";
1131 warn "error before or during data restore, some or all disks were not "
1132 ."completely restored. VM $vmid state is NOT cleaned up.\n";
1138 $worker_name = 'qmcreate';
1140 eval { $createfn->() };
1143 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
1144 unlink($conffile) or die "failed to remove config file: $!\n";
1152 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
1155 __PACKAGE__-
>register_method({
1160 description
=> "Directory index",
1165 additionalProperties
=> 0,
1167 node
=> get_standard_option
('pve-node'),
1168 vmid
=> get_standard_option
('pve-vmid'),
1176 subdir
=> { type
=> 'string' },
1179 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1185 { subdir
=> 'config' },
1186 { subdir
=> 'cloudinit' },
1187 { subdir
=> 'pending' },
1188 { subdir
=> 'status' },
1189 { subdir
=> 'unlink' },
1190 { subdir
=> 'vncproxy' },
1191 { subdir
=> 'termproxy' },
1192 { subdir
=> 'migrate' },
1193 { subdir
=> 'resize' },
1194 { subdir
=> 'move' },
1195 { subdir
=> 'rrd' },
1196 { subdir
=> 'rrddata' },
1197 { subdir
=> 'monitor' },
1198 { subdir
=> 'agent' },
1199 { subdir
=> 'snapshot' },
1200 { subdir
=> 'spiceproxy' },
1201 { subdir
=> 'sendkey' },
1202 { subdir
=> 'firewall' },
1203 { subdir
=> 'mtunnel' },
1204 { subdir
=> 'remote_migrate' },
1210 __PACKAGE__-
>register_method ({
1211 subclass
=> "PVE::API2::Firewall::VM",
1212 path
=> '{vmid}/firewall',
1215 __PACKAGE__-
>register_method ({
1216 subclass
=> "PVE::API2::Qemu::Agent",
1217 path
=> '{vmid}/agent',
1220 __PACKAGE__-
>register_method({
1222 path
=> '{vmid}/rrd',
1224 protected
=> 1, # fixme: can we avoid that?
1226 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1228 description
=> "Read VM RRD statistics (returns PNG)",
1230 additionalProperties
=> 0,
1232 node
=> get_standard_option
('pve-node'),
1233 vmid
=> get_standard_option
('pve-vmid'),
1235 description
=> "Specify the time frame you are interested in.",
1237 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
1240 description
=> "The list of datasources you want to display.",
1241 type
=> 'string', format
=> 'pve-configid-list',
1244 description
=> "The RRD consolidation function",
1246 enum
=> [ 'AVERAGE', 'MAX' ],
1254 filename
=> { type
=> 'string' },
1260 return PVE
::RRD
::create_rrd_graph
(
1261 "pve2-vm/$param->{vmid}", $param->{timeframe
},
1262 $param->{ds
}, $param->{cf
});
1266 __PACKAGE__-
>register_method({
1268 path
=> '{vmid}/rrddata',
1270 protected
=> 1, # fixme: can we avoid that?
1272 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1274 description
=> "Read VM RRD statistics",
1276 additionalProperties
=> 0,
1278 node
=> get_standard_option
('pve-node'),
1279 vmid
=> get_standard_option
('pve-vmid'),
1281 description
=> "Specify the time frame you are interested in.",
1283 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
1286 description
=> "The RRD consolidation function",
1288 enum
=> [ 'AVERAGE', 'MAX' ],
1303 return PVE
::RRD
::create_rrd_data
(
1304 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
1308 __PACKAGE__-
>register_method({
1309 name
=> 'vm_config',
1310 path
=> '{vmid}/config',
1313 description
=> "Get the virtual machine configuration with pending configuration " .
1314 "changes applied. Set the 'current' parameter to get the current configuration instead.",
1316 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1319 additionalProperties
=> 0,
1321 node
=> get_standard_option
('pve-node'),
1322 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1324 description
=> "Get current values (instead of pending values).",
1329 snapshot
=> get_standard_option
('pve-snapshot-name', {
1330 description
=> "Fetch config values from given snapshot.",
1333 my ($cmd, $pname, $cur, $args) = @_;
1334 PVE
::QemuConfig-
>snapshot_list($args->[0]);
1340 description
=> "The VM configuration.",
1342 properties
=> PVE
::QemuServer
::json_config_properties
({
1345 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
1352 raise_param_exc
({ snapshot
=> "cannot use 'snapshot' parameter with 'current'",
1353 current
=> "cannot use 'snapshot' parameter with 'current'"})
1354 if ($param->{snapshot
} && $param->{current
});
1357 if ($param->{snapshot
}) {
1358 $conf = PVE
::QemuConfig-
>load_snapshot_config($param->{vmid
}, $param->{snapshot
});
1360 $conf = PVE
::QemuConfig-
>load_current_config($param->{vmid
}, $param->{current
});
1362 $conf->{cipassword
} = '**********' if $conf->{cipassword
};
1367 __PACKAGE__-
>register_method({
1368 name
=> 'vm_pending',
1369 path
=> '{vmid}/pending',
1372 description
=> "Get the virtual machine configuration with both current and pending values.",
1374 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1377 additionalProperties
=> 0,
1379 node
=> get_standard_option
('pve-node'),
1380 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1389 description
=> "Configuration option name.",
1393 description
=> "Current value.",
1398 description
=> "Pending value.",
1403 description
=> "Indicates a pending delete request if present and not 0. " .
1404 "The value 2 indicates a force-delete request.",
1416 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1418 my $pending_delete_hash = PVE
::QemuConfig-
>parse_pending_delete($conf->{pending
}->{delete});
1420 $conf->{cipassword
} = '**********' if defined($conf->{cipassword
});
1421 $conf->{pending
}->{cipassword
} = '********** ' if defined($conf->{pending
}->{cipassword
});
1423 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);
1426 __PACKAGE__-
>register_method({
1427 name
=> 'cloudinit_pending',
1428 path
=> '{vmid}/cloudinit',
1431 description
=> "Get the cloudinit configuration with both current and pending values.",
1433 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1436 additionalProperties
=> 0,
1438 node
=> get_standard_option
('pve-node'),
1439 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1448 description
=> "Configuration option name.",
1452 description
=> "Value as it was used to generate the current cloudinit image.",
1457 description
=> "The new pending value.",
1462 description
=> "Indicates a pending delete request if present and not 0. ",
1474 my $vmid = $param->{vmid
};
1475 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1477 my $ci = $conf->{cloudinit
};
1479 $conf->{cipassword
} = '**********' if exists $conf->{cipassword
};
1480 $ci->{cipassword
} = '**********' if exists $ci->{cipassword
};
1484 # All the values that got added
1485 my $added = delete($ci->{added
}) // '';
1486 for my $key (PVE
::Tools
::split_list
($added)) {
1487 push @$res, { key
=> $key, pending
=> $conf->{$key} };
1490 # All already existing values (+ their new value, if it exists)
1491 for my $opt (keys %$cloudinitoptions) {
1492 next if !$conf->{$opt};
1493 next if $added =~ m/$opt/;
1498 if (my $pending = $ci->{$opt}) {
1499 $item->{value
} = $pending;
1500 $item->{pending
} = $conf->{$opt};
1502 $item->{value
} = $conf->{$opt},
1508 # Now, we'll find the deleted ones
1509 for my $opt (keys %$ci) {
1510 next if $conf->{$opt};
1511 push @$res, { key
=> $opt, delete => 1 };
1517 __PACKAGE__-
>register_method({
1518 name
=> 'cloudinit_update',
1519 path
=> '{vmid}/cloudinit',
1523 description
=> "Regenerate and change cloudinit config drive.",
1525 check
=> ['perm', '/vms/{vmid}', ['VM.Config.Cloudinit']],
1528 additionalProperties
=> 0,
1530 node
=> get_standard_option
('pve-node'),
1531 vmid
=> get_standard_option
('pve-vmid'),
1534 returns
=> { type
=> 'null' },
1538 my $rpcenv = PVE
::RPCEnvironment
::get
();
1539 my $authuser = $rpcenv->get_user();
1541 my $vmid = extract_param
($param, 'vmid');
1543 PVE
::QemuConfig-
>lock_config($vmid, sub {
1544 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1545 PVE
::QemuConfig-
>check_lock($conf);
1547 my $storecfg = PVE
::Storage
::config
();
1548 PVE
::QemuServer
::vmconfig_update_cloudinit_drive
($storecfg, $conf, $vmid);
1553 # POST/PUT {vmid}/config implementation
1555 # The original API used PUT (idempotent) an we assumed that all operations
1556 # are fast. But it turned out that almost any configuration change can
1557 # involve hot-plug actions, or disk alloc/free. Such actions can take long
1558 # time to complete and have side effects (not idempotent).
1560 # The new implementation uses POST and forks a worker process. We added
1561 # a new option 'background_delay'. If specified we wait up to
1562 # 'background_delay' second for the worker task to complete. It returns null
1563 # if the task is finished within that time, else we return the UPID.
1565 my $update_vm_api = sub {
1566 my ($param, $sync) = @_;
1568 my $rpcenv = PVE
::RPCEnvironment
::get
();
1570 my $authuser = $rpcenv->get_user();
1572 my $node = extract_param
($param, 'node');
1574 my $vmid = extract_param
($param, 'vmid');
1576 my $digest = extract_param
($param, 'digest');
1578 my $background_delay = extract_param
($param, 'background_delay');
1580 my $skip_cloud_init = extract_param
($param, 'skip_cloud_init');
1582 if (defined(my $cipassword = $param->{cipassword
})) {
1583 # Same logic as in cloud-init (but with the regex fixed...)
1584 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
1585 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
1588 my @paramarr = (); # used for log message
1589 foreach my $key (sort keys %$param) {
1590 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
1591 push @paramarr, "-$key", $value;
1594 my $skiplock = extract_param
($param, 'skiplock');
1595 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1596 if $skiplock && $authuser ne 'root@pam';
1598 my $delete_str = extract_param
($param, 'delete');
1600 my $revert_str = extract_param
($param, 'revert');
1602 my $force = extract_param
($param, 'force');
1604 if (defined(my $ssh_keys = $param->{sshkeys
})) {
1605 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
1606 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
1609 $param->{cpuunits
} = PVE
::CGroup
::clamp_cpu_shares
($param->{cpuunits
})
1610 if defined($param->{cpuunits
}); # clamp value depending on cgroup version
1612 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
1614 my $storecfg = PVE
::Storage
::config
();
1616 &$resolve_cdrom_alias($param);
1618 # now try to verify all parameters
1621 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1622 if (!PVE
::QemuServer
::option_exists
($opt)) {
1623 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1626 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1627 "-revert $opt' at the same time" })
1628 if defined($param->{$opt});
1630 $revert->{$opt} = 1;
1634 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1635 $opt = 'ide2' if $opt eq 'cdrom';
1637 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1638 "-delete $opt' at the same time" })
1639 if defined($param->{$opt});
1641 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1642 "-revert $opt' at the same time" })
1645 if (!PVE
::QemuServer
::option_exists
($opt)) {
1646 raise_param_exc
({ delete => "unknown option '$opt'" });
1652 my $repl_conf = PVE
::ReplicationConfig-
>new();
1653 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1654 my $check_replication = sub {
1656 return if !$is_replicated;
1657 my $volid = $drive->{file
};
1658 return if !$volid || !($drive->{replicate
}//1);
1659 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1661 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1662 die "cannot add non-managed/pass-through volume to a replicated VM\n"
1663 if !defined($storeid);
1665 return if defined($volname) && $volname eq 'cloudinit';
1668 if ($volid =~ $PVE::QemuServer
::Drive
::NEW_DISK_RE
) {
1670 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1672 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1674 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1675 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1676 return if $scfg->{shared
};
1677 die "cannot add non-replicatable volume to a replicated VM\n";
1680 $check_drive_param->($param, $storecfg, $check_replication);
1682 foreach my $opt (keys %$param) {
1683 if ($opt =~ m/^net(\d+)$/) {
1685 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1686 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1687 } elsif ($opt eq 'vmgenid') {
1688 if ($param->{$opt} eq '1') {
1689 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1691 } elsif ($opt eq 'hookscript') {
1692 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1693 raise_param_exc
({ $opt => $@ }) if $@;
1697 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1699 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1701 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1703 PVE
::QemuServer
::check_bridge_access
($rpcenv, $authuser, $param);
1705 my $updatefn = sub {
1707 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1709 die "checksum missmatch (file change by other user?)\n"
1710 if $digest && $digest ne $conf->{digest
};
1712 &$check_cpu_model_access($rpcenv, $authuser, $param, $conf);
1714 # FIXME: 'suspended' lock should probabyl be a state or "weak" lock?!
1715 if (scalar(@delete) && grep { $_ eq 'vmstate'} @delete) {
1716 if (defined($conf->{lock}) && $conf->{lock} eq 'suspended') {
1717 delete $conf->{lock}; # for check lock check, not written out
1718 push @delete, 'lock'; # this is the real deal to write it out
1720 push @delete, 'runningmachine' if $conf->{runningmachine
};
1721 push @delete, 'runningcpu' if $conf->{runningcpu
};
1724 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1726 foreach my $opt (keys %$revert) {
1727 if (defined($conf->{$opt})) {
1728 $param->{$opt} = $conf->{$opt};
1729 } elsif (defined($conf->{pending
}->{$opt})) {
1734 if ($param->{memory
} || defined($param->{balloon
})) {
1736 my $memory = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
};
1737 my $maxmem = get_current_memory
($memory);
1738 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1740 die "balloon value too large (must be smaller than assigned memory)\n"
1741 if $balloon && $balloon > $maxmem;
1744 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1748 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1750 # write updates to pending section
1752 my $modified = {}; # record what $option we modify
1755 if (my $boot = $conf->{boot
}) {
1756 my $bootcfg = PVE
::JSONSchema
::parse_property_string
('pve-qm-boot', $boot);
1757 @bootorder = PVE
::Tools
::split_list
($bootcfg->{order
}) if $bootcfg && $bootcfg->{order
};
1759 my $bootorder_deleted = grep {$_ eq 'bootorder'} @delete;
1761 my $check_drive_perms = sub {
1762 my ($opt, $val) = @_;
1763 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val, 1);
1764 if (PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
1765 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Cloudinit', 'VM.Config.CDROM']);
1766 } elsif (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) { # CDROM
1767 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1769 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1774 foreach my $opt (@delete) {
1775 $modified->{$opt} = 1;
1776 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1778 # value of what we want to delete, independent if pending or not
1779 my $val = $conf->{$opt} // $conf->{pending
}->{$opt};
1780 if (!defined($val)) {
1781 warn "cannot delete '$opt' - not set in current configuration!\n";
1782 $modified->{$opt} = 0;
1785 my $is_pending_val = defined($conf->{pending
}->{$opt});
1786 delete $conf->{pending
}->{$opt};
1788 # remove from bootorder if necessary
1789 if (!$bootorder_deleted && @bootorder && grep {$_ eq $opt} @bootorder) {
1790 @bootorder = grep {$_ ne $opt} @bootorder;
1791 $conf->{pending
}->{boot
} = PVE
::QemuServer
::print_bootorder
(\
@bootorder);
1792 $modified->{boot
} = 1;
1795 if ($opt =~ m/^unused/) {
1796 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1797 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1798 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1799 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1800 delete $conf->{$opt};
1801 PVE
::QemuConfig-
>write_config($vmid, $conf);
1803 } elsif ($opt eq 'vmstate') {
1804 PVE
::QemuConfig-
>check_protection($conf, "can't remove vmstate '$val'");
1805 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, { file
=> $val }, $rpcenv, $authuser, 1)) {
1806 delete $conf->{$opt};
1807 PVE
::QemuConfig-
>write_config($vmid, $conf);
1809 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1810 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1811 $check_drive_perms->($opt, $val);
1812 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $val))
1814 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1815 PVE
::QemuConfig-
>write_config($vmid, $conf);
1816 } elsif ($opt =~ m/^serial\d+$/) {
1817 if ($val eq 'socket') {
1818 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1819 } elsif ($authuser ne 'root@pam') {
1820 die "only root can delete '$opt' config for real devices\n";
1822 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1823 PVE
::QemuConfig-
>write_config($vmid, $conf);
1824 } elsif ($opt =~ m/^usb\d+$/) {
1825 check_usb_perm
($rpcenv, $authuser, $vmid, undef, $opt, $val);
1826 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1827 PVE
::QemuConfig-
>write_config($vmid, $conf);
1828 } elsif ($opt =~ m/^hostpci\d+$/) {
1829 check_hostpci_perm
($rpcenv, $authuser, $vmid, undef, $opt, $val);
1830 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1831 PVE
::QemuConfig-
>write_config($vmid, $conf);
1832 } elsif ($opt eq 'tags') {
1833 assert_tag_permissions
($vmid, $val, '', $rpcenv, $authuser);
1834 delete $conf->{$opt};
1835 PVE
::QemuConfig-
>write_config($vmid, $conf);
1836 } elsif ($opt =~ m/^net\d+$/) {
1837 if ($conf->{$opt}) {
1838 PVE
::QemuServer
::check_bridge_access
(
1841 { $opt => $conf->{$opt} },
1844 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1845 PVE
::QemuConfig-
>write_config($vmid, $conf);
1847 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1848 PVE
::QemuConfig-
>write_config($vmid, $conf);
1852 foreach my $opt (keys %$param) { # add/change
1853 $modified->{$opt} = 1;
1854 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1855 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1857 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
1859 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1861 if ($conf->{$opt}) {
1862 $check_drive_perms->($opt, $conf->{$opt});
1866 $check_drive_perms->($opt, $param->{$opt});
1867 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1868 if defined($conf->{pending
}->{$opt});
1870 assert_scsi_feature_compatibility
($opt, $conf, $storecfg, $param->{$opt})
1871 if $opt =~ m/^scsi\d+$/;
1873 my (undef, $created_opts) = $create_disks->(
1881 {$opt => $param->{$opt}},
1883 $conf->{pending
}->{$_} = $created_opts->{$_} for keys $created_opts->%*;
1885 # default legacy boot order implies all cdroms anyway
1887 # append new CD drives to bootorder to mark them bootable
1888 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt}, 1);
1889 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1) && !grep(/^$opt$/, @bootorder)) {
1890 push @bootorder, $opt;
1891 $conf->{pending
}->{boot
} = PVE
::QemuServer
::print_bootorder
(\
@bootorder);
1892 $modified->{boot
} = 1;
1895 } elsif ($opt =~ m/^serial\d+/) {
1896 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1897 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1898 } elsif ($authuser ne 'root@pam') {
1899 die "only root can modify '$opt' config for real devices\n";
1901 $conf->{pending
}->{$opt} = $param->{$opt};
1902 } elsif ($opt eq 'vga') {
1903 my $vga = PVE
::QemuServer
::parse_vga
($param->{$opt});
1904 PVE
::QemuServer
::assert_clipboard_config
($vga);
1905 $conf->{pending
}->{$opt} = $param->{$opt};
1906 } elsif ($opt =~ m/^usb\d+/) {
1907 if (my $olddevice = $conf->{$opt}) {
1908 check_usb_perm
($rpcenv, $authuser, $vmid, undef, $opt, $conf->{$opt});
1910 check_usb_perm
($rpcenv, $authuser, $vmid, undef, $opt, $param->{$opt});
1911 $conf->{pending
}->{$opt} = $param->{$opt};
1912 } elsif ($opt =~ m/^hostpci\d+$/) {
1913 if (my $oldvalue = $conf->{$opt}) {
1914 check_hostpci_perm
($rpcenv, $authuser, $vmid, undef, $opt, $oldvalue);
1916 check_hostpci_perm
($rpcenv, $authuser, $vmid, undef, $opt, $param->{$opt});
1917 $conf->{pending
}->{$opt} = $param->{$opt};
1918 } elsif ($opt eq 'tags') {
1919 assert_tag_permissions
($vmid, $conf->{$opt}, $param->{$opt}, $rpcenv, $authuser);
1920 $conf->{pending
}->{$opt} = PVE
::GuestHelpers
::get_unique_tags
($param->{$opt});
1921 } elsif ($opt =~ m/^net\d+$/) {
1922 if ($conf->{$opt}) {
1923 PVE
::QemuServer
::check_bridge_access
(
1926 { $opt => $conf->{$opt} },
1929 $conf->{pending
}->{$opt} = $param->{$opt};
1931 $conf->{pending
}->{$opt} = $param->{$opt};
1933 if ($opt eq 'boot') {
1934 my $new_bootcfg = PVE
::JSONSchema
::parse_property_string
('pve-qm-boot', $param->{$opt});
1935 if ($new_bootcfg->{order
}) {
1936 my @devs = PVE
::Tools
::split_list
($new_bootcfg->{order
});
1937 for my $dev (@devs) {
1938 my $exists = $conf->{$dev} || $conf->{pending
}->{$dev} || $param->{$dev};
1939 my $deleted = grep {$_ eq $dev} @delete;
1940 die "invalid bootorder: device '$dev' does not exist'\n"
1941 if !$exists || $deleted;
1944 # remove legacy boot order settings if new one set
1945 $conf->{pending
}->{$opt} = PVE
::QemuServer
::print_bootorder
(\
@devs);
1946 PVE
::QemuConfig-
>add_to_pending_delete($conf, "bootdisk")
1947 if $conf->{bootdisk
};
1951 PVE
::QemuConfig-
>remove_from_pending_delete($conf, $opt);
1952 PVE
::QemuConfig-
>write_config($vmid, $conf);
1955 # remove pending changes when nothing changed
1956 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1957 my $changes = PVE
::QemuConfig-
>cleanup_pending($conf);
1958 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1960 return if !scalar(keys %{$conf->{pending
}});
1962 my $running = PVE
::QemuServer
::check_running
($vmid);
1964 # apply pending changes
1966 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1970 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1972 # cloud_init must be skipped if we are in an incoming, remote live migration
1973 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $errors, $skip_cloud_init);
1975 raise_param_exc
($errors) if scalar(keys %$errors);
1984 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1986 if ($background_delay) {
1988 # Note: It would be better to do that in the Event based HTTPServer
1989 # to avoid blocking call to sleep.
1991 my $end_time = time() + $background_delay;
1993 my $task = PVE
::Tools
::upid_decode
($upid);
1996 while (time() < $end_time) {
1997 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1999 sleep(1); # this gets interrupted when child process ends
2003 my $status = PVE
::Tools
::upid_read_status
($upid);
2004 return if !PVE
::Tools
::upid_status_is_error
($status);
2005 die "failed to update VM $vmid: $status\n";
2013 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2016 my $vm_config_perm_list = [
2021 'VM.Config.Network',
2023 'VM.Config.Options',
2024 'VM.Config.Cloudinit',
2027 __PACKAGE__-
>register_method({
2028 name
=> 'update_vm_async',
2029 path
=> '{vmid}/config',
2033 description
=> "Set virtual machine options (asynchrounous API).",
2035 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
2038 additionalProperties
=> 0,
2039 properties
=> PVE
::QemuServer
::json_config_properties
(
2041 node
=> get_standard_option
('pve-node'),
2042 vmid
=> get_standard_option
('pve-vmid'),
2043 skiplock
=> get_standard_option
('skiplock'),
2045 type
=> 'string', format
=> 'pve-configid-list',
2046 description
=> "A list of settings you want to delete.",
2050 type
=> 'string', format
=> 'pve-configid-list',
2051 description
=> "Revert a pending change.",
2056 description
=> $opt_force_description,
2058 requires
=> 'delete',
2062 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2066 background_delay
=> {
2068 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
2074 1, # with_disk_alloc
2081 code
=> $update_vm_api,
2084 __PACKAGE__-
>register_method({
2085 name
=> 'update_vm',
2086 path
=> '{vmid}/config',
2090 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
2092 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
2095 additionalProperties
=> 0,
2096 properties
=> PVE
::QemuServer
::json_config_properties
(
2098 node
=> get_standard_option
('pve-node'),
2099 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2100 skiplock
=> get_standard_option
('skiplock'),
2102 type
=> 'string', format
=> 'pve-configid-list',
2103 description
=> "A list of settings you want to delete.",
2107 type
=> 'string', format
=> 'pve-configid-list',
2108 description
=> "Revert a pending change.",
2113 description
=> $opt_force_description,
2115 requires
=> 'delete',
2119 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2124 1, # with_disk_alloc
2127 returns
=> { type
=> 'null' },
2130 &$update_vm_api($param, 1);
2135 __PACKAGE__-
>register_method({
2136 name
=> 'destroy_vm',
2141 description
=> "Destroy the VM and all used/owned volumes. Removes any VM specific permissions"
2142 ." and firewall rules",
2144 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
2147 additionalProperties
=> 0,
2149 node
=> get_standard_option
('pve-node'),
2150 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
2151 skiplock
=> get_standard_option
('skiplock'),
2154 description
=> "Remove VMID from configurations, like backup & replication jobs and HA.",
2157 'destroy-unreferenced-disks' => {
2159 description
=> "If set, destroy additionally all disks not referenced in the config"
2160 ." but with a matching VMID from all enabled storages.",
2172 my $rpcenv = PVE
::RPCEnvironment
::get
();
2173 my $authuser = $rpcenv->get_user();
2174 my $vmid = $param->{vmid
};
2176 my $skiplock = $param->{skiplock
};
2177 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2178 if $skiplock && $authuser ne 'root@pam';
2180 my $early_checks = sub {
2182 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2183 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
2185 my $ha_managed = PVE
::HA
::Config
::service_is_configured
("vm:$vmid");
2187 if (!$param->{purge
}) {
2188 die "unable to remove VM $vmid - used in HA resources and purge parameter not set.\n"
2190 # don't allow destroy if with replication jobs but no purge param
2191 my $repl_conf = PVE
::ReplicationConfig-
>new();
2192 $repl_conf->check_for_existing_jobs($vmid);
2195 die "VM $vmid is running - destroy failed\n"
2196 if PVE
::QemuServer
::check_running
($vmid);
2206 my $storecfg = PVE
::Storage
::config
();
2208 syslog
('info', "destroy VM $vmid: $upid\n");
2209 PVE
::QemuConfig-
>lock_config($vmid, sub {
2210 # repeat, config might have changed
2211 my $ha_managed = $early_checks->();
2213 my $purge_unreferenced = $param->{'destroy-unreferenced-disks'};
2215 PVE
::QemuServer
::destroy_vm
(
2218 $skiplock, { lock => 'destroyed' },
2219 $purge_unreferenced,
2222 PVE
::AccessControl
::remove_vm_access
($vmid);
2223 PVE
::Firewall
::remove_vmfw_conf
($vmid);
2224 if ($param->{purge
}) {
2225 print "purging VM $vmid from related configurations..\n";
2226 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
2227 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
2230 PVE
::HA
::Config
::delete_service_from_config
("vm:$vmid");
2231 print "NOTE: removed VM $vmid from HA resource configuration.\n";
2235 # only now remove the zombie config, else we can have reuse race
2236 PVE
::QemuConfig-
>destroy_config($vmid);
2240 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
2243 __PACKAGE__-
>register_method({
2245 path
=> '{vmid}/unlink',
2249 description
=> "Unlink/delete disk images.",
2251 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
2254 additionalProperties
=> 0,
2256 node
=> get_standard_option
('pve-node'),
2257 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2259 type
=> 'string', format
=> 'pve-configid-list',
2260 description
=> "A list of disk IDs you want to delete.",
2264 description
=> $opt_force_description,
2269 returns
=> { type
=> 'null'},
2273 $param->{delete} = extract_param
($param, 'idlist');
2275 __PACKAGE__-
>update_vm($param);
2280 # uses good entropy, each char is limited to 6 bit to get printable chars simply
2281 my $gen_rand_chars = sub {
2284 die "invalid length $length" if $length < 1;
2286 my $min = ord('!'); # first printable ascii
2288 my $rand_bytes = Crypt
::OpenSSL
::Random
::random_bytes
($length);
2289 die "failed to generate random bytes!\n"
2292 my $str = join('', map { chr((ord($_) & 0x3F) + $min) } split('', $rand_bytes));
2299 __PACKAGE__-
>register_method({
2301 path
=> '{vmid}/vncproxy',
2305 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2307 description
=> "Creates a TCP VNC proxy connections.",
2309 additionalProperties
=> 0,
2311 node
=> get_standard_option
('pve-node'),
2312 vmid
=> get_standard_option
('pve-vmid'),
2316 description
=> "Prepare for websocket upgrade (only required when using "
2317 ."serial terminal, otherwise upgrade is always possible).",
2319 'generate-password' => {
2323 description
=> "Generates a random password to be used as ticket instead of the API ticket.",
2328 additionalProperties
=> 0,
2330 user
=> { type
=> 'string' },
2331 ticket
=> { type
=> 'string' },
2334 description
=> "Returned if requested with 'generate-password' param."
2335 ." Consists of printable ASCII characters ('!' .. '~').",
2338 cert
=> { type
=> 'string' },
2339 port
=> { type
=> 'integer' },
2340 upid
=> { type
=> 'string' },
2346 my $rpcenv = PVE
::RPCEnvironment
::get
();
2348 my $authuser = $rpcenv->get_user();
2350 my $vmid = $param->{vmid
};
2351 my $node = $param->{node
};
2352 my $websocket = $param->{websocket
};
2354 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
2358 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
2359 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
2362 my $authpath = "/vms/$vmid";
2364 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
2365 my $password = $ticket;
2366 if ($param->{'generate-password'}) {
2367 $password = $gen_rand_chars->(8);
2370 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
2376 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
2377 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
2378 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
2379 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
2380 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, defined($serial) ?
'-t' : '-T');
2382 $family = PVE
::Tools
::get_host_address_family
($node);
2385 my $port = PVE
::Tools
::next_vnc_port
($family);
2392 syslog
('info', "starting vnc proxy $upid\n");
2396 if (defined($serial)) {
2398 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $serial, '-escape', '0' ];
2400 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
2401 '-timeout', $timeout, '-authpath', $authpath,
2402 '-perm', 'Sys.Console'];
2404 if ($param->{websocket
}) {
2405 $ENV{PVE_VNC_TICKET
} = $password; # pass ticket to vncterm
2406 push @$cmd, '-notls', '-listen', 'localhost';
2409 push @$cmd, '-c', @$remcmd, @$termcmd;
2411 PVE
::Tools
::run_command
($cmd);
2415 $ENV{LC_PVE_TICKET
} = $password; # set ticket with "qm vncproxy"
2417 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
2419 my $sock = IO
::Socket
::IP-
>new(
2424 GetAddrInfoFlags
=> 0,
2425 ) or die "failed to create socket: $!\n";
2426 # Inside the worker we shouldn't have any previous alarms
2427 # running anyway...:
2429 local $SIG{ALRM
} = sub { die "connection timed out\n" };
2431 accept(my $cli, $sock) or die "connection failed: $!\n";
2434 if (PVE
::Tools
::run_command
($cmd,
2435 output
=> '>&'.fileno($cli),
2436 input
=> '<&'.fileno($cli),
2439 die "Failed to run vncproxy.\n";
2446 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
2448 PVE
::Tools
::wait_for_vnc_port
($port);
2457 $res->{password
} = $password if $param->{'generate-password'};
2462 __PACKAGE__-
>register_method({
2463 name
=> 'termproxy',
2464 path
=> '{vmid}/termproxy',
2468 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2470 description
=> "Creates a TCP proxy connections.",
2472 additionalProperties
=> 0,
2474 node
=> get_standard_option
('pve-node'),
2475 vmid
=> get_standard_option
('pve-vmid'),
2479 enum
=> [qw(serial0 serial1 serial2 serial3)],
2480 description
=> "opens a serial terminal (defaults to display)",
2485 additionalProperties
=> 0,
2487 user
=> { type
=> 'string' },
2488 ticket
=> { type
=> 'string' },
2489 port
=> { type
=> 'integer' },
2490 upid
=> { type
=> 'string' },
2496 my $rpcenv = PVE
::RPCEnvironment
::get
();
2498 my $authuser = $rpcenv->get_user();
2500 my $vmid = $param->{vmid
};
2501 my $node = $param->{node
};
2502 my $serial = $param->{serial
};
2504 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
2506 if (!defined($serial)) {
2508 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
2509 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
2513 my $authpath = "/vms/$vmid";
2515 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
2520 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
2521 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
2522 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
2523 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, '-t');
2524 push @$remcmd, '--';
2526 $family = PVE
::Tools
::get_host_address_family
($node);
2529 my $port = PVE
::Tools
::next_vnc_port
($family);
2531 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
2532 push @$termcmd, '-iface', $serial if $serial;
2537 syslog
('info', "starting qemu termproxy $upid\n");
2539 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
2540 '--perm', 'VM.Console', '--'];
2541 push @$cmd, @$remcmd, @$termcmd;
2543 PVE
::Tools
::run_command
($cmd);
2546 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
2548 PVE
::Tools
::wait_for_vnc_port
($port);
2558 __PACKAGE__-
>register_method({
2559 name
=> 'vncwebsocket',
2560 path
=> '{vmid}/vncwebsocket',
2563 description
=> "You also need to pass a valid ticket (vncticket).",
2564 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2566 description
=> "Opens a weksocket for VNC traffic.",
2568 additionalProperties
=> 0,
2570 node
=> get_standard_option
('pve-node'),
2571 vmid
=> get_standard_option
('pve-vmid'),
2573 description
=> "Ticket from previous call to vncproxy.",
2578 description
=> "Port number returned by previous vncproxy call.",
2588 port
=> { type
=> 'string' },
2594 my $rpcenv = PVE
::RPCEnvironment
::get
();
2596 my $authuser = $rpcenv->get_user();
2598 my $vmid = $param->{vmid
};
2599 my $node = $param->{node
};
2601 my $authpath = "/vms/$vmid";
2603 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
2605 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
2607 # Note: VNC ports are acessible from outside, so we do not gain any
2608 # security if we verify that $param->{port} belongs to VM $vmid. This
2609 # check is done by verifying the VNC ticket (inside VNC protocol).
2611 my $port = $param->{port
};
2613 return { port
=> $port };
2616 __PACKAGE__-
>register_method({
2617 name
=> 'spiceproxy',
2618 path
=> '{vmid}/spiceproxy',
2623 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2625 description
=> "Returns a SPICE configuration to connect to the VM.",
2627 additionalProperties
=> 0,
2629 node
=> get_standard_option
('pve-node'),
2630 vmid
=> get_standard_option
('pve-vmid'),
2631 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
2634 returns
=> get_standard_option
('remote-viewer-config'),
2638 my $rpcenv = PVE
::RPCEnvironment
::get
();
2640 my $authuser = $rpcenv->get_user();
2642 my $vmid = $param->{vmid
};
2643 my $node = $param->{node
};
2644 my $proxy = $param->{proxy
};
2646 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
2647 my $title = "VM $vmid";
2648 $title .= " - ". $conf->{name
} if $conf->{name
};
2650 my $port = PVE
::QemuServer
::spice_port
($vmid);
2652 my ($ticket, undef, $remote_viewer_config) =
2653 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
2655 mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
2656 mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
2658 return $remote_viewer_config;
2661 __PACKAGE__-
>register_method({
2663 path
=> '{vmid}/status',
2666 description
=> "Directory index",
2671 additionalProperties
=> 0,
2673 node
=> get_standard_option
('pve-node'),
2674 vmid
=> get_standard_option
('pve-vmid'),
2682 subdir
=> { type
=> 'string' },
2685 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
2691 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2694 { subdir
=> 'current' },
2695 { subdir
=> 'start' },
2696 { subdir
=> 'stop' },
2697 { subdir
=> 'reset' },
2698 { subdir
=> 'shutdown' },
2699 { subdir
=> 'suspend' },
2700 { subdir
=> 'reboot' },
2706 __PACKAGE__-
>register_method({
2707 name
=> 'vm_status',
2708 path
=> '{vmid}/status/current',
2711 protected
=> 1, # qemu pid files are only readable by root
2712 description
=> "Get virtual machine status.",
2714 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2717 additionalProperties
=> 0,
2719 node
=> get_standard_option
('pve-node'),
2720 vmid
=> get_standard_option
('pve-vmid'),
2726 %$PVE::QemuServer
::vmstatus_return_properties
,
2728 description
=> "HA manager service status.",
2732 description
=> "QEMU VGA configuration supports spice.",
2737 description
=> "QEMU Guest Agent is enabled in config.",
2742 description
=> 'Enable a specific clipboard. If not set, depending on'
2743 .' the display type the SPICE one will be added.',
2754 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2756 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
2757 my $status = $vmstatus->{$param->{vmid
}};
2759 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
2762 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
2763 my $spice = defined($vga->{type
}) && $vga->{type
} =~ /^virtio/;
2764 $spice ||= PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
2765 $status->{spice
} = 1 if $spice;
2766 $status->{clipboard
} = $vga->{clipboard
};
2768 $status->{agent
} = 1 if PVE
::QemuServer
::get_qga_key
($conf, 'enabled');
2773 __PACKAGE__-
>register_method({
2775 path
=> '{vmid}/status/start',
2779 description
=> "Start virtual machine.",
2781 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2784 additionalProperties
=> 0,
2786 node
=> get_standard_option
('pve-node'),
2787 vmid
=> get_standard_option
('pve-vmid',
2788 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
2789 skiplock
=> get_standard_option
('skiplock'),
2790 stateuri
=> get_standard_option
('pve-qm-stateuri'),
2791 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
2794 enum
=> ['secure', 'insecure'],
2795 description
=> "Migration traffic is encrypted using an SSH " .
2796 "tunnel by default. On secure, completely private networks " .
2797 "this can be disabled to increase performance.",
2800 migration_network
=> {
2801 type
=> 'string', format
=> 'CIDR',
2802 description
=> "CIDR of the (sub) network that is used for migration.",
2805 machine
=> get_standard_option
('pve-qemu-machine'),
2807 description
=> "Override QEMU's -cpu argument with the given string.",
2811 targetstorage
=> get_standard_option
('pve-targetstorage'),
2813 description
=> "Wait maximal timeout seconds.",
2816 default => 'max(30, vm memory in GiB)',
2827 my $rpcenv = PVE
::RPCEnvironment
::get
();
2828 my $authuser = $rpcenv->get_user();
2830 my $node = extract_param
($param, 'node');
2831 my $vmid = extract_param
($param, 'vmid');
2832 my $timeout = extract_param
($param, 'timeout');
2833 my $machine = extract_param
($param, 'machine');
2835 my $get_root_param = sub {
2836 my $value = extract_param
($param, $_[0]);
2837 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2838 if $value && $authuser ne 'root@pam';
2842 my $stateuri = $get_root_param->('stateuri');
2843 my $skiplock = $get_root_param->('skiplock');
2844 my $migratedfrom = $get_root_param->('migratedfrom');
2845 my $migration_type = $get_root_param->('migration_type');
2846 my $migration_network = $get_root_param->('migration_network');
2847 my $targetstorage = $get_root_param->('targetstorage');
2848 my $force_cpu = $get_root_param->('force-cpu');
2852 if ($targetstorage) {
2853 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2855 $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
2856 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
2860 # read spice ticket from STDIN
2862 my $nbd_protocol_version = 0;
2863 my $replicated_volumes = {};
2864 my $offline_volumes = {};
2865 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2866 while (defined(my $line = <STDIN
>)) {
2868 if ($line =~ m/^spice_ticket: (.+)$/) {
2870 } elsif ($line =~ m/^nbd_protocol_version: (\d+)$/) {
2871 $nbd_protocol_version = $1;
2872 } elsif ($line =~ m/^replicated_volume: (.*)$/) {
2873 $replicated_volumes->{$1} = 1;
2874 } elsif ($line =~ m/^tpmstate0: (.*)$/) { # Deprecated, use offline_volume instead
2875 $offline_volumes->{tpmstate0
} = $1;
2876 } elsif ($line =~ m/^offline_volume: ([^:]+): (.*)$/) {
2877 $offline_volumes->{$1} = $2;
2878 } elsif (!$spice_ticket) {
2879 # fallback for old source node
2880 $spice_ticket = $line;
2882 warn "unknown 'start' parameter on STDIN: '$line'\n";
2887 PVE
::Cluster
::check_cfs_quorum
();
2889 my $storecfg = PVE
::Storage
::config
();
2891 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2895 print "Requesting HA start for VM $vmid\n";
2897 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2898 PVE
::Tools
::run_command
($cmd);
2902 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2909 syslog
('info', "start VM $vmid: $upid\n");
2911 my $migrate_opts = {
2912 migratedfrom
=> $migratedfrom,
2913 spice_ticket
=> $spice_ticket,
2914 network
=> $migration_network,
2915 type
=> $migration_type,
2916 storagemap
=> $storagemap,
2917 nbd_proto_version
=> $nbd_protocol_version,
2918 replicated_volumes
=> $replicated_volumes,
2919 offline_volumes
=> $offline_volumes,
2923 statefile
=> $stateuri,
2924 skiplock
=> $skiplock,
2925 forcemachine
=> $machine,
2926 timeout
=> $timeout,
2927 forcecpu
=> $force_cpu,
2930 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $params, $migrate_opts);
2934 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2938 __PACKAGE__-
>register_method({
2940 path
=> '{vmid}/status/stop',
2944 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2945 "is akin to pulling the power plug of a running computer and may damage the VM data",
2947 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2950 additionalProperties
=> 0,
2952 node
=> get_standard_option
('pve-node'),
2953 vmid
=> get_standard_option
('pve-vmid',
2954 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2955 skiplock
=> get_standard_option
('skiplock'),
2956 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2958 description
=> "Wait maximal timeout seconds.",
2964 description
=> "Do not deactivate storage volumes.",
2977 my $rpcenv = PVE
::RPCEnvironment
::get
();
2978 my $authuser = $rpcenv->get_user();
2980 my $node = extract_param
($param, 'node');
2981 my $vmid = extract_param
($param, 'vmid');
2983 my $skiplock = extract_param
($param, 'skiplock');
2984 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2985 if $skiplock && $authuser ne 'root@pam';
2987 my $keepActive = extract_param
($param, 'keepActive');
2988 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2989 if $keepActive && $authuser ne 'root@pam';
2991 my $migratedfrom = extract_param
($param, 'migratedfrom');
2992 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2993 if $migratedfrom && $authuser ne 'root@pam';
2996 my $storecfg = PVE
::Storage
::config
();
2998 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
3003 print "Requesting HA stop for VM $vmid\n";
3005 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
3006 PVE
::Tools
::run_command
($cmd);
3010 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
3016 syslog
('info', "stop VM $vmid: $upid\n");
3018 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
3019 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
3023 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
3027 __PACKAGE__-
>register_method({
3029 path
=> '{vmid}/status/reset',
3033 description
=> "Reset virtual machine.",
3035 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
3038 additionalProperties
=> 0,
3040 node
=> get_standard_option
('pve-node'),
3041 vmid
=> get_standard_option
('pve-vmid',
3042 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
3043 skiplock
=> get_standard_option
('skiplock'),
3052 my $rpcenv = PVE
::RPCEnvironment
::get
();
3054 my $authuser = $rpcenv->get_user();
3056 my $node = extract_param
($param, 'node');
3058 my $vmid = extract_param
($param, 'vmid');
3060 my $skiplock = extract_param
($param, 'skiplock');
3061 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3062 if $skiplock && $authuser ne 'root@pam';
3064 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
3069 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
3074 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
3077 __PACKAGE__-
>register_method({
3078 name
=> 'vm_shutdown',
3079 path
=> '{vmid}/status/shutdown',
3083 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
3084 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
3086 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
3089 additionalProperties
=> 0,
3091 node
=> get_standard_option
('pve-node'),
3092 vmid
=> get_standard_option
('pve-vmid',
3093 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
3094 skiplock
=> get_standard_option
('skiplock'),
3096 description
=> "Wait maximal timeout seconds.",
3102 description
=> "Make sure the VM stops.",
3108 description
=> "Do not deactivate storage volumes.",
3121 my $rpcenv = PVE
::RPCEnvironment
::get
();
3122 my $authuser = $rpcenv->get_user();
3124 my $node = extract_param
($param, 'node');
3125 my $vmid = extract_param
($param, 'vmid');
3127 my $skiplock = extract_param
($param, 'skiplock');
3128 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3129 if $skiplock && $authuser ne 'root@pam';
3131 my $keepActive = extract_param
($param, 'keepActive');
3132 raise_param_exc
({ keepActive
=> "Only root may use this option." })
3133 if $keepActive && $authuser ne 'root@pam';
3135 my $storecfg = PVE
::Storage
::config
();
3139 # sending a graceful shutdown command to paused VMs runs into timeouts, and even worse, when
3140 # the VM gets resumed later, it still gets the request delivered and powers off
3141 if (PVE
::QemuServer
::vm_is_paused
($vmid, 1)) {
3142 if ($param->{forceStop
}) {
3143 warn "VM is paused - stop instead of shutdown\n";
3146 die "VM is paused - cannot shutdown\n";
3150 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3152 my $timeout = $param->{timeout
} // 60;
3156 print "Requesting HA stop for VM $vmid\n";
3158 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
3159 PVE
::Tools
::run_command
($cmd);
3163 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
3170 syslog
('info', "shutdown VM $vmid: $upid\n");
3172 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
3173 $shutdown, $param->{forceStop
}, $keepActive);
3177 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
3181 __PACKAGE__-
>register_method({
3182 name
=> 'vm_reboot',
3183 path
=> '{vmid}/status/reboot',
3187 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
3189 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
3192 additionalProperties
=> 0,
3194 node
=> get_standard_option
('pve-node'),
3195 vmid
=> get_standard_option
('pve-vmid',
3196 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
3198 description
=> "Wait maximal timeout seconds for the shutdown.",
3211 my $rpcenv = PVE
::RPCEnvironment
::get
();
3212 my $authuser = $rpcenv->get_user();
3214 my $node = extract_param
($param, 'node');
3215 my $vmid = extract_param
($param, 'vmid');
3217 die "VM is paused - cannot shutdown\n" if PVE
::QemuServer
::vm_is_paused
($vmid, 1);
3219 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
3224 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
3225 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
3229 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
3232 __PACKAGE__-
>register_method({
3233 name
=> 'vm_suspend',
3234 path
=> '{vmid}/status/suspend',
3238 description
=> "Suspend virtual machine.",
3240 description
=> "You need 'VM.PowerMgmt' on /vms/{vmid}, and if you have set 'todisk',".
3241 " you need also 'VM.Config.Disk' on /vms/{vmid} and 'Datastore.AllocateSpace'".
3242 " on the storage for the vmstate.",
3243 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
3246 additionalProperties
=> 0,
3248 node
=> get_standard_option
('pve-node'),
3249 vmid
=> get_standard_option
('pve-vmid',
3250 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
3251 skiplock
=> get_standard_option
('skiplock'),
3256 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
3258 statestorage
=> get_standard_option
('pve-storage-id', {
3259 description
=> "The storage for the VM state",
3260 requires
=> 'todisk',
3262 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
3272 my $rpcenv = PVE
::RPCEnvironment
::get
();
3273 my $authuser = $rpcenv->get_user();
3275 my $node = extract_param
($param, 'node');
3276 my $vmid = extract_param
($param, 'vmid');
3278 my $todisk = extract_param
($param, 'todisk') // 0;
3280 my $statestorage = extract_param
($param, 'statestorage');
3282 my $skiplock = extract_param
($param, 'skiplock');
3283 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3284 if $skiplock && $authuser ne 'root@pam';
3286 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
3288 die "Cannot suspend HA managed VM to disk\n"
3289 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
3291 # early check for storage permission, for better user feedback
3293 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
3294 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3296 # cannot save the state of a non-virtualized PCIe device, so resume cannot really work
3297 for my $key (keys %$conf) {
3298 next if $key !~ /^hostpci\d+/;
3299 die "cannot suspend VM to disk due to passed-through PCI device(s), which lack the"
3300 ." possibility to save/restore their internal state\n";
3303 if (!$statestorage) {
3304 # get statestorage from config if none is given
3305 my $storecfg = PVE
::Storage
::config
();
3306 $statestorage = PVE
::QemuServer
::find_vmstate_storage
($conf, $storecfg);
3309 $rpcenv->check($authuser, "/storage/$statestorage", ['Datastore.AllocateSpace']);
3315 syslog
('info', "suspend VM $vmid: $upid\n");
3317 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
3322 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
3323 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
3326 __PACKAGE__-
>register_method({
3327 name
=> 'vm_resume',
3328 path
=> '{vmid}/status/resume',
3332 description
=> "Resume virtual machine.",
3334 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
3337 additionalProperties
=> 0,
3339 node
=> get_standard_option
('pve-node'),
3340 vmid
=> get_standard_option
('pve-vmid',
3341 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
3342 skiplock
=> get_standard_option
('skiplock'),
3343 nocheck
=> { type
=> 'boolean', optional
=> 1 },
3353 my $rpcenv = PVE
::RPCEnvironment
::get
();
3355 my $authuser = $rpcenv->get_user();
3357 my $node = extract_param
($param, 'node');
3359 my $vmid = extract_param
($param, 'vmid');
3361 my $skiplock = extract_param
($param, 'skiplock');
3362 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3363 if $skiplock && $authuser ne 'root@pam';
3365 # nocheck is used as part of migration when config file might be still
3367 my $nocheck = extract_param
($param, 'nocheck');
3368 raise_param_exc
({ nocheck
=> "Only root may use this option." })
3369 if $nocheck && $authuser ne 'root@pam';
3371 my $to_disk_suspended;
3373 PVE
::QemuConfig-
>lock_config($vmid, sub {
3374 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3375 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
3379 die "VM $vmid not running\n"
3380 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
3385 syslog
('info', "resume VM $vmid: $upid\n");
3387 if (!$to_disk_suspended) {
3388 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
3390 my $storecfg = PVE
::Storage
::config
();
3391 PVE
::QemuServer
::vm_start
($storecfg, $vmid, { skiplock
=> $skiplock });
3397 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
3400 __PACKAGE__-
>register_method({
3401 name
=> 'vm_sendkey',
3402 path
=> '{vmid}/sendkey',
3406 description
=> "Send key event to virtual machine.",
3408 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
3411 additionalProperties
=> 0,
3413 node
=> get_standard_option
('pve-node'),
3414 vmid
=> get_standard_option
('pve-vmid',
3415 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
3416 skiplock
=> get_standard_option
('skiplock'),
3418 description
=> "The key (qemu monitor encoding).",
3423 returns
=> { type
=> 'null'},
3427 my $rpcenv = PVE
::RPCEnvironment
::get
();
3429 my $authuser = $rpcenv->get_user();
3431 my $node = extract_param
($param, 'node');
3433 my $vmid = extract_param
($param, 'vmid');
3435 my $skiplock = extract_param
($param, 'skiplock');
3436 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3437 if $skiplock && $authuser ne 'root@pam';
3439 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
3444 __PACKAGE__-
>register_method({
3445 name
=> 'vm_feature',
3446 path
=> '{vmid}/feature',
3450 description
=> "Check if feature for virtual machine is available.",
3452 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3455 additionalProperties
=> 0,
3457 node
=> get_standard_option
('pve-node'),
3458 vmid
=> get_standard_option
('pve-vmid'),
3460 description
=> "Feature to check.",
3462 enum
=> [ 'snapshot', 'clone', 'copy' ],
3464 snapname
=> get_standard_option
('pve-snapshot-name', {
3472 hasFeature
=> { type
=> 'boolean' },
3475 items
=> { type
=> 'string' },
3482 my $node = extract_param
($param, 'node');
3484 my $vmid = extract_param
($param, 'vmid');
3486 my $snapname = extract_param
($param, 'snapname');
3488 my $feature = extract_param
($param, 'feature');
3490 my $running = PVE
::QemuServer
::check_running
($vmid);
3492 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3495 my $snap = $conf->{snapshots
}->{$snapname};
3496 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3499 my $storecfg = PVE
::Storage
::config
();
3501 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
3502 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
3505 hasFeature
=> $hasFeature,
3506 nodes
=> [ keys %$nodelist ],
3510 __PACKAGE__-
>register_method({
3512 path
=> '{vmid}/clone',
3516 description
=> "Create a copy of virtual machine/template.",
3518 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
3519 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
3520 "'Datastore.AllocateSpace' on any used storage and 'SDN.Use' on any used bridge/vnet",
3523 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
3525 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
3526 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
3531 additionalProperties
=> 0,
3533 node
=> get_standard_option
('pve-node'),
3534 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3535 newid
=> get_standard_option
('pve-vmid', {
3536 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
3537 description
=> 'VMID for the clone.' }),
3540 type
=> 'string', format
=> 'dns-name',
3541 description
=> "Set a name for the new VM.",
3546 description
=> "Description for the new VM.",
3550 type
=> 'string', format
=> 'pve-poolid',
3551 description
=> "Add the new VM to the specified pool.",
3553 snapname
=> get_standard_option
('pve-snapshot-name', {
3556 storage
=> get_standard_option
('pve-storage-id', {
3557 description
=> "Target storage for full clone.",
3561 description
=> "Target format for file storage. Only valid for full clone.",
3564 enum
=> [ 'raw', 'qcow2', 'vmdk'],
3569 description
=> "Create a full copy of all disks. This is always done when " .
3570 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
3572 target
=> get_standard_option
('pve-node', {
3573 description
=> "Target node. Only allowed if the original VM is on shared storage.",
3577 description
=> "Override I/O bandwidth limit (in KiB/s).",
3581 default => 'clone limit from datacenter or storage config',
3591 my $rpcenv = PVE
::RPCEnvironment
::get
();
3592 my $authuser = $rpcenv->get_user();
3594 my $node = extract_param
($param, 'node');
3595 my $vmid = extract_param
($param, 'vmid');
3596 my $newid = extract_param
($param, 'newid');
3597 my $pool = extract_param
($param, 'pool');
3599 my $snapname = extract_param
($param, 'snapname');
3600 my $storage = extract_param
($param, 'storage');
3601 my $format = extract_param
($param, 'format');
3602 my $target = extract_param
($param, 'target');
3604 my $localnode = PVE
::INotify
::nodename
();
3606 if ($target && ($target eq $localnode || $target eq 'localhost')) {
3610 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
3612 my $load_and_check = sub {
3613 $rpcenv->check_pool_exist($pool) if defined($pool);
3614 PVE
::Cluster
::check_node_exists
($target) if $target;
3616 my $storecfg = PVE
::Storage
::config
();
3619 # check if storage is enabled on local node
3620 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
3622 # check if storage is available on target node
3623 PVE
::Storage
::storage_check_enabled
($storecfg, $storage, $target);
3624 # clone only works if target storage is shared
3625 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
3626 die "can't clone to non-shared storage '$storage'\n"
3627 if !$scfg->{shared
};
3631 PVE
::Cluster
::check_cfs_quorum
();
3633 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3634 PVE
::QemuConfig-
>check_lock($conf);
3636 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
3637 die "unexpected state change\n" if $verify_running != $running;
3639 die "snapshot '$snapname' does not exist\n"
3640 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
3642 my $full = $param->{full
} // !PVE
::QemuConfig-
>is_template($conf);
3644 die "parameter 'storage' not allowed for linked clones\n"
3645 if defined($storage) && !$full;
3647 die "parameter 'format' not allowed for linked clones\n"
3648 if defined($format) && !$full;
3650 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
3652 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
3653 PVE
::QemuServer
::check_mapping_access
($rpcenv, $authuser, $oldconf);
3655 PVE
::QemuServer
::check_bridge_access
($rpcenv, $authuser, $oldconf);
3657 die "can't clone VM to node '$target' (VM uses local storage)\n"
3658 if $target && !$sharedvm;
3660 my $conffile = PVE
::QemuConfig-
>config_file($newid);
3661 die "unable to create VM $newid: config file already exists\n"
3664 my $newconf = { lock => 'clone' };
3669 foreach my $opt (keys %$oldconf) {
3670 my $value = $oldconf->{$opt};
3672 # do not copy snapshot related info
3673 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
3674 $opt eq 'vmstate' || $opt eq 'snapstate';
3676 # no need to copy unused images, because VMID(owner) changes anyways
3677 next if $opt =~ m/^unused\d+$/;
3679 die "cannot clone TPM state while VM is running\n"
3680 if $full && $running && !$snapname && $opt eq 'tpmstate0';
3682 # always change MAC! address
3683 if ($opt =~ m/^net(\d+)$/) {
3684 my $net = PVE
::QemuServer
::parse_net
($value);
3685 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
3686 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
3687 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
3688 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
3689 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
3690 die "unable to parse drive options for '$opt'\n" if !$drive;
3691 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
3692 $newconf->{$opt} = $value; # simply copy configuration
3694 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
3695 die "Full clone feature is not supported for drive '$opt'\n"
3696 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
3697 $fullclone->{$opt} = 1;
3699 # not full means clone instead of copy
3700 die "Linked clone feature is not supported for drive '$opt'\n"
3701 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
3703 $drives->{$opt} = $drive;
3704 next if PVE
::QemuServer
::drive_is_cloudinit
($drive);
3705 push @$vollist, $drive->{file
};
3708 # copy everything else
3709 $newconf->{$opt} = $value;
3713 return ($conffile, $newconf, $oldconf, $vollist, $drives, $fullclone);
3717 my ($conffile, $newconf, $oldconf, $vollist, $drives, $fullclone) = $load_and_check->();
3718 my $storecfg = PVE
::Storage
::config
();
3720 # auto generate a new uuid
3721 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
3722 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
3723 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
3724 # auto generate a new vmgenid only if the option was set for template
3725 if ($newconf->{vmgenid
}) {
3726 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
3729 delete $newconf->{template
};
3731 if ($param->{name
}) {
3732 $newconf->{name
} = $param->{name
};
3734 $newconf->{name
} = "Copy-of-VM-" . ($oldconf->{name
} // $vmid);
3737 if ($param->{description
}) {
3738 $newconf->{description
} = $param->{description
};
3741 # create empty/temp config - this fails if VM already exists on other node
3742 # FIXME use PVE::QemuConfig->create_and_lock_config and adapt code
3743 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
3745 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
3747 my $newvollist = [];
3754 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3756 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
3758 my $bwlimit = extract_param
($param, 'bwlimit');
3760 my $total_jobs = scalar(keys %{$drives});
3763 foreach my $opt (sort keys %$drives) {
3764 my $drive = $drives->{$opt};
3765 my $skipcomplete = ($total_jobs != $i); # finish after last drive
3766 my $completion = $skipcomplete ?
'skip' : 'complete';
3768 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
3769 my $storage_list = [ $src_sid ];
3770 push @$storage_list, $storage if defined($storage);
3771 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
3775 running
=> $running,
3778 snapname
=> $snapname,
3784 storage
=> $storage,
3788 $dest_info->{efisize
} = PVE
::QemuServer
::get_efivars_size
($oldconf)
3789 if $opt eq 'efidisk0';
3791 my $newdrive = PVE
::QemuServer
::clone_disk
(
3803 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($newdrive);
3805 PVE
::QemuConfig-
>write_config($newid, $newconf);
3809 delete $newconf->{lock};
3811 # do not write pending changes
3812 if (my @changes = keys %{$newconf->{pending
}}) {
3813 my $pending = join(',', @changes);
3814 warn "found pending changes for '$pending', discarding for clone\n";
3815 delete $newconf->{pending
};
3818 PVE
::QemuConfig-
>write_config($newid, $newconf);
3820 PVE
::QemuServer
::create_ifaces_ipams_ips
($newconf, $newid);
3823 # always deactivate volumes - avoid lvm LVs to be active on several nodes
3825 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
3827 log_warn
($@) if ($@);
3829 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
3831 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
3832 die "Failed to move config to node '$target' - rename failed: $!\n"
3833 if !rename($conffile, $newconffile);
3836 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
3839 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
3840 sleep 1; # some storage like rbd need to wait before release volume - really?
3842 foreach my $volid (@$newvollist) {
3843 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3847 PVE
::Firewall
::remove_vmfw_conf
($newid);
3849 unlink $conffile; # avoid races -> last thing before die
3851 die "clone failed: $err";
3857 # Aquire exclusive lock lock for $newid
3858 my $lock_target_vm = sub {
3859 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
3862 my $lock_source_vm = sub {
3863 # exclusive lock if VM is running - else shared lock is enough;
3865 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $lock_target_vm);
3867 return PVE
::QemuConfig-
>lock_config_shared($vmid, 1, $lock_target_vm);
3871 $load_and_check->(); # early checks before forking/locking
3873 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $lock_source_vm);
3876 __PACKAGE__-
>register_method({
3877 name
=> 'move_vm_disk',
3878 path
=> '{vmid}/move_disk',
3882 description
=> "Move volume to different storage or to a different VM.",
3884 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
3885 "and 'Datastore.AllocateSpace' permissions on the storage. To move ".
3886 "a disk to another VM, you need the permissions on the target VM as well.",
3887 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3890 additionalProperties
=> 0,
3892 node
=> get_standard_option
('pve-node'),
3893 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3894 'target-vmid' => get_standard_option
('pve-vmid', {
3895 completion
=> \
&PVE
::QemuServer
::complete_vmid
,
3900 description
=> "The disk you want to move.",
3901 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names_with_unused
()],
3903 storage
=> get_standard_option
('pve-storage-id', {
3904 description
=> "Target storage.",
3905 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3910 description
=> "Target Format.",
3911 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
3916 description
=> "Delete the original disk after successful copy. By default the"
3917 ." original disk is kept as unused disk.",
3923 description
=> 'Prevent changes if current configuration file has different SHA1"
3924 ." digest. This can be used to prevent concurrent modifications.',
3929 description
=> "Override I/O bandwidth limit (in KiB/s).",
3933 default => 'move limit from datacenter or storage config',
3937 description
=> "The config key the disk will be moved to on the target VM"
3938 ." (for example, ide0 or scsi1). Default is the source disk key.",
3939 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names_with_unused
()],
3942 'target-digest' => {
3944 description
=> 'Prevent changes if the current config file of the target VM has a"
3945 ." different SHA1 digest. This can be used to detect concurrent modifications.',
3953 description
=> "the task ID.",
3958 my $rpcenv = PVE
::RPCEnvironment
::get
();
3959 my $authuser = $rpcenv->get_user();
3961 my $node = extract_param
($param, 'node');
3962 my $vmid = extract_param
($param, 'vmid');
3963 my $target_vmid = extract_param
($param, 'target-vmid');
3964 my $digest = extract_param
($param, 'digest');
3965 my $target_digest = extract_param
($param, 'target-digest');
3966 my $disk = extract_param
($param, 'disk');
3967 my $target_disk = extract_param
($param, 'target-disk') // $disk;
3968 my $storeid = extract_param
($param, 'storage');
3969 my $format = extract_param
($param, 'format');
3971 my $storecfg = PVE
::Storage
::config
();
3973 my $load_and_check_move = sub {
3974 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3975 PVE
::QemuConfig-
>check_lock($conf);
3977 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
3979 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3981 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3983 die "disk '$disk' has no associated volume\n" if !$drive->{file
};
3984 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3986 my $old_volid = $drive->{file
};
3988 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3989 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3993 die "you can't move to the same storage with same format\n"
3994 if $oldstoreid eq $storeid && (!$format || !$oldfmt || $oldfmt eq $format);
3996 # this only checks snapshots because $disk is passed!
3997 my $snapshotted = PVE
::QemuServer
::Drive
::is_volume_in_use
(
4003 die "you can't move a disk with snapshots and delete the source\n"
4004 if $snapshotted && $param->{delete};
4006 return ($conf, $drive, $oldstoreid, $snapshotted);
4009 my $move_updatefn = sub {
4010 my ($conf, $drive, $oldstoreid, $snapshotted) = $load_and_check_move->();
4011 my $old_volid = $drive->{file
};
4013 PVE
::Cluster
::log_msg
(
4016 "move disk VM $vmid: move --disk $disk --storage $storeid"
4019 my $running = PVE
::QemuServer
::check_running
($vmid);
4021 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
4023 my $newvollist = [];
4029 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
4031 warn "moving disk with snapshots, snapshots will not be moved!\n"
4034 my $bwlimit = extract_param
($param, 'bwlimit');
4035 my $movelimit = PVE
::Storage
::get_bandwidth_limit
(
4037 [$oldstoreid, $storeid],
4043 running
=> $running,
4052 storage
=> $storeid,
4056 $dest_info->{efisize
} = PVE
::QemuServer
::get_efivars_size
($conf)
4057 if $disk eq 'efidisk0';
4059 my $newdrive = PVE
::QemuServer
::clone_disk
(
4070 $conf->{$disk} = PVE
::QemuServer
::print_drive
($newdrive);
4072 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
4074 # convert moved disk to base if part of template
4075 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
4076 if PVE
::QemuConfig-
>is_template($conf);
4078 PVE
::QemuConfig-
>write_config($vmid, $conf);
4080 my $do_trim = PVE
::QemuServer
::get_qga_key
($conf, 'fstrim_cloned_disks');
4081 if ($running && $do_trim && PVE
::QemuServer
::qga_check_running
($vmid)) {
4082 eval { mon_cmd
($vmid, "guest-fstrim") };
4086 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
4087 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
4093 foreach my $volid (@$newvollist) {
4094 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid) };
4097 die "storage migration failed: $err";
4100 if ($param->{delete}) {
4102 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
4103 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
4109 my $load_and_check_reassign_configs = sub {
4110 my $vmlist = PVE
::Cluster
::get_vmlist
()->{ids
};
4112 die "could not find VM ${vmid}\n" if !exists($vmlist->{$vmid});
4113 die "could not find target VM ${target_vmid}\n" if !exists($vmlist->{$target_vmid});
4115 my $source_node = $vmlist->{$vmid}->{node
};
4116 my $target_node = $vmlist->{$target_vmid}->{node
};
4118 die "Both VMs need to be on the same node ($source_node != $target_node)\n"
4119 if $source_node ne $target_node;
4121 my $source_conf = PVE
::QemuConfig-
>load_config($vmid);
4122 PVE
::QemuConfig-
>check_lock($source_conf);
4123 my $target_conf = PVE
::QemuConfig-
>load_config($target_vmid);
4124 PVE
::QemuConfig-
>check_lock($target_conf);
4126 die "Can't move disks from or to template VMs\n"
4127 if ($source_conf->{template
} || $target_conf->{template
});
4130 eval { PVE
::Tools
::assert_if_modified
($digest, $source_conf->{digest
}) };
4131 die "VM ${vmid}: $@" if $@;
4134 if ($target_digest) {
4135 eval { PVE
::Tools
::assert_if_modified
($target_digest, $target_conf->{digest
}) };
4136 die "VM ${target_vmid}: $@" if $@;
4139 die "Disk '${disk}' for VM '$vmid' does not exist\n" if !defined($source_conf->{$disk});
4141 die "Target disk key '${target_disk}' is already in use for VM '$target_vmid'\n"
4142 if $target_conf->{$target_disk};
4144 my $drive = PVE
::QemuServer
::parse_drive
(
4146 $source_conf->{$disk},
4148 die "failed to parse source disk - $@\n" if !$drive;
4150 my $source_volid = $drive->{file
};
4152 die "disk '${disk}' has no associated volume\n" if !$source_volid;
4153 die "CD drive contents can't be moved to another VM\n"
4154 if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
4156 my $storeid = PVE
::Storage
::parse_volume_id
($source_volid, 1);
4157 die "Volume '$source_volid' not managed by PVE\n" if !defined($storeid);
4159 die "Can't move disk used by a snapshot to another VM\n"
4160 if PVE
::QemuServer
::Drive
::is_volume_in_use
($storecfg, $source_conf, $disk, $source_volid);
4161 die "Storage does not support moving of this disk to another VM\n"
4162 if (!PVE
::Storage
::volume_has_feature
($storecfg, 'rename', $source_volid));
4163 die "Cannot move disk to another VM while the source VM is running - detach first\n"
4164 if PVE
::QemuServer
::check_running
($vmid) && $disk !~ m/^unused\d+$/;
4166 # now re-parse using target disk slot format
4167 if ($target_disk =~ /^unused\d+$/) {
4168 $drive = PVE
::QemuServer
::parse_drive
(
4173 $drive = PVE
::QemuServer
::parse_drive
(
4175 $source_conf->{$disk},
4178 die "failed to parse source disk for target disk format - $@\n" if !$drive;
4180 my $repl_conf = PVE
::ReplicationConfig-
>new();
4181 if ($repl_conf->check_for_existing_jobs($target_vmid, 1)) {
4182 my $format = (PVE
::Storage
::parse_volname
($storecfg, $source_volid))[6];
4183 die "Cannot move disk to a replicated VM. Storage does not support replication!\n"
4184 if !PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
4187 return ($source_conf, $target_conf, $drive);
4192 print STDERR
"$msg\n";
4195 my $disk_reassignfn = sub {
4196 return PVE
::QemuConfig-
>lock_config($vmid, sub {
4197 return PVE
::QemuConfig-
>lock_config($target_vmid, sub {
4198 my ($source_conf, $target_conf, $drive) = &$load_and_check_reassign_configs();
4200 my $source_volid = $drive->{file
};
4202 print "moving disk '$disk' from VM '$vmid' to '$target_vmid'\n";
4203 my ($storeid, $source_volname) = PVE
::Storage
::parse_volume_id
($source_volid);
4205 my $fmt = (PVE
::Storage
::parse_volname
($storecfg, $source_volid))[6];
4207 my $new_volid = PVE
::Storage
::rename_volume
(
4213 $drive->{file
} = $new_volid;
4215 my $boot_order = PVE
::QemuServer
::device_bootorder
($source_conf);
4216 if (defined(delete $boot_order->{$disk})) {
4217 print "removing disk '$disk' from boot order config\n";
4218 my $boot_devs = [ sort { $boot_order->{$a} <=> $boot_order->{$b} } keys %$boot_order ];
4219 $source_conf->{boot
} = PVE
::QemuServer
::print_bootorder
($boot_devs);
4222 delete $source_conf->{$disk};
4223 print "removing disk '${disk}' from VM '${vmid}' config\n";
4224 PVE
::QemuConfig-
>write_config($vmid, $source_conf);
4226 my $drive_string = PVE
::QemuServer
::print_drive
($drive);
4228 if ($target_disk =~ /^unused\d+$/) {
4229 $target_conf->{$target_disk} = $drive_string;
4230 PVE
::QemuConfig-
>write_config($target_vmid, $target_conf);
4235 vmid
=> $target_vmid,
4236 digest
=> $target_digest,
4237 $target_disk => $drive_string,
4243 # remove possible replication snapshots
4244 if (PVE
::Storage
::volume_has_feature
(
4250 PVE
::Replication
::prepare
(
4260 print "Failed to remove replication snapshots on moved disk " .
4261 "'$target_disk'. Manual cleanup could be necessary.\n";
4268 if ($target_vmid && $storeid) {
4269 my $msg = "either set 'storage' or 'target-vmid', but not both";
4270 raise_param_exc
({ 'target-vmid' => $msg, 'storage' => $msg });
4271 } elsif ($target_vmid) {
4272 $rpcenv->check_vm_perm($authuser, $target_vmid, undef, ['VM.Config.Disk'])
4273 if $authuser ne 'root@pam';
4275 raise_param_exc
({ 'target-vmid' => "must be different than source VMID to reassign disk" })
4276 if $vmid eq $target_vmid;
4278 my (undef, undef, $drive) = &$load_and_check_reassign_configs();
4279 my $storage = PVE
::Storage
::parse_volume_id
($drive->{file
});
4280 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace']);
4282 return $rpcenv->fork_worker(
4284 "${vmid}-${disk}>${target_vmid}-${target_disk}",
4288 } elsif ($storeid) {
4289 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
4291 die "cannot move disk '$disk', only configured disks can be moved to another storage\n"
4292 if $disk =~ m/^unused\d+$/;
4294 $load_and_check_move->(); # early checks before forking/locking
4297 PVE
::QemuConfig-
>lock_config($vmid, $move_updatefn);
4300 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
4302 my $msg = "both 'storage' and 'target-vmid' missing, either needs to be set";
4303 raise_param_exc
({ 'target-vmid' => $msg, 'storage' => $msg });
4307 my $check_vm_disks_local = sub {
4308 my ($storecfg, $vmconf, $vmid) = @_;
4310 my $local_disks = {};
4312 # add some more information to the disks e.g. cdrom
4313 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
4314 my ($volid, $attr) = @_;
4316 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
4318 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
4319 return if $scfg->{shared
};
4321 # The shared attr here is just a special case where the vdisk
4322 # is marked as shared manually
4323 return if $attr->{shared
};
4324 return if $attr->{cdrom
} and $volid eq "none";
4326 if (exists $local_disks->{$volid}) {
4327 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
4329 $local_disks->{$volid} = $attr;
4330 # ensure volid is present in case it's needed
4331 $local_disks->{$volid}->{volid
} = $volid;
4335 return $local_disks;
4338 __PACKAGE__-
>register_method({
4339 name
=> 'migrate_vm_precondition',
4340 path
=> '{vmid}/migrate',
4344 description
=> "Get preconditions for migration.",
4346 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
4349 additionalProperties
=> 0,
4351 node
=> get_standard_option
('pve-node'),
4352 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4353 target
=> get_standard_option
('pve-node', {
4354 description
=> "Target node.",
4355 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
4363 running
=> { type
=> 'boolean' },
4367 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
4369 not_allowed_nodes
=> {
4372 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
4376 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
4378 local_resources
=> {
4380 description
=> "List local resources e.g. pci, usb"
4382 'mapped-resources' => {
4384 description
=> "List of mapped resources e.g. pci, usb"
4391 my $rpcenv = PVE
::RPCEnvironment
::get
();
4393 my $authuser = $rpcenv->get_user();
4395 PVE
::Cluster
::check_cfs_quorum
();
4399 my $vmid = extract_param
($param, 'vmid');
4400 my $target = extract_param
($param, 'target');
4401 my $localnode = PVE
::INotify
::nodename
();
4405 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
4406 my $storecfg = PVE
::Storage
::config
();
4409 # try to detect errors early
4410 PVE
::QemuConfig-
>check_lock($vmconf);
4412 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
4414 my ($local_resources, $mapped_resources, $missing_mappings_by_node) =
4415 PVE
::QemuServer
::check_local_resources
($vmconf, 1);
4416 delete $missing_mappings_by_node->{$localnode};
4418 my $vga = PVE
::QemuServer
::parse_vga
($vmconf->{vga
});
4419 if ($res->{running
} && $vga->{'clipboard'} && $vga->{'clipboard'} eq 'vnc') {
4420 push $local_resources->@*, "clipboard=vnc";
4423 # if vm is not running, return target nodes where local storage/mapped devices are available
4424 # for offline migration
4425 if (!$res->{running
}) {
4426 $res->{allowed_nodes
} = [];
4427 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
4428 delete $checked_nodes->{$localnode};
4430 foreach my $node (keys %$checked_nodes) {
4431 my $missing_mappings = $missing_mappings_by_node->{$node};
4432 if (scalar($missing_mappings->@*)) {
4433 $checked_nodes->{$node}->{'unavailable-resources'} = $missing_mappings;
4437 if (!defined($checked_nodes->{$node}->{unavailable_storages
})) {
4438 push @{$res->{allowed_nodes
}}, $node;
4442 $res->{not_allowed_nodes
} = $checked_nodes;
4445 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
4446 $res->{local_disks
} = [ values %$local_disks ];;
4448 $res->{local_resources
} = $local_resources;
4449 $res->{'mapped-resources'} = $mapped_resources;
4456 __PACKAGE__-
>register_method({
4457 name
=> 'migrate_vm',
4458 path
=> '{vmid}/migrate',
4462 description
=> "Migrate virtual machine. Creates a new migration task.",
4464 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
4467 additionalProperties
=> 0,
4469 node
=> get_standard_option
('pve-node'),
4470 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4471 target
=> get_standard_option
('pve-node', {
4472 description
=> "Target node.",
4473 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
4477 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
4482 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
4487 enum
=> ['secure', 'insecure'],
4488 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
4491 migration_network
=> {
4492 type
=> 'string', format
=> 'CIDR',
4493 description
=> "CIDR of the (sub) network that is used for migration.",
4496 "with-local-disks" => {
4498 description
=> "Enable live storage migration for local disk",
4501 targetstorage
=> get_standard_option
('pve-targetstorage', {
4502 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
4505 description
=> "Override I/O bandwidth limit (in KiB/s).",
4509 default => 'migrate limit from datacenter or storage config',
4515 description
=> "the task ID.",
4520 my $rpcenv = PVE
::RPCEnvironment
::get
();
4521 my $authuser = $rpcenv->get_user();
4523 my $target = extract_param
($param, 'target');
4525 my $localnode = PVE
::INotify
::nodename
();
4526 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
4528 PVE
::Cluster
::check_cfs_quorum
();
4530 PVE
::Cluster
::check_node_exists
($target);
4532 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
4534 my $vmid = extract_param
($param, 'vmid');
4536 raise_param_exc
({ force
=> "Only root may use this option." })
4537 if $param->{force
} && $authuser ne 'root@pam';
4539 raise_param_exc
({ migration_type
=> "Only root may use this option." })
4540 if $param->{migration_type
} && $authuser ne 'root@pam';
4542 # allow root only until better network permissions are available
4543 raise_param_exc
({ migration_network
=> "Only root may use this option." })
4544 if $param->{migration_network
} && $authuser ne 'root@pam';
4547 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4549 # try to detect errors early
4551 PVE
::QemuConfig-
>check_lock($conf);
4553 if (PVE
::QemuServer
::check_running
($vmid)) {
4554 die "can't migrate running VM without --online\n" if !$param->{online
};
4556 my $repl_conf = PVE
::ReplicationConfig-
>new();
4557 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
4558 my $is_replicated_to_target = defined($repl_conf->find_local_replication_job($vmid, $target));
4559 if (!$param->{force
} && $is_replicated && !$is_replicated_to_target) {
4560 die "Cannot live-migrate replicated VM to node '$target' - not a replication " .
4561 "target. Use 'force' to override.\n";
4564 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
4565 $param->{online
} = 0;
4568 my $storecfg = PVE
::Storage
::config
();
4569 if (my $targetstorage = $param->{targetstorage
}) {
4570 my $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
4571 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
4574 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk'])
4575 if !defined($storagemap->{identity
});
4577 foreach my $target_sid (values %{$storagemap->{entries
}}) {
4578 $check_storage_access_migrate->($rpcenv, $authuser, $storecfg, $target_sid, $target);
4581 $check_storage_access_migrate->($rpcenv, $authuser, $storecfg, $storagemap->{default}, $target)
4582 if $storagemap->{default};
4584 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target)
4585 if $storagemap->{identity
};
4587 $param->{storagemap
} = $storagemap;
4589 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
4592 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
4597 print "Requesting HA migration for VM $vmid to node $target\n";
4599 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
4600 PVE
::Tools
::run_command
($cmd);
4604 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
4609 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
4613 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
4616 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
4621 __PACKAGE__-
>register_method({
4622 name
=> 'remote_migrate_vm',
4623 path
=> '{vmid}/remote_migrate',
4627 description
=> "Migrate virtual machine to a remote cluster. Creates a new migration task. EXPERIMENTAL feature!",
4629 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
4632 additionalProperties
=> 0,
4634 node
=> get_standard_option
('pve-node'),
4635 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4636 'target-vmid' => get_standard_option
('pve-vmid', { optional
=> 1 }),
4637 'target-endpoint' => get_standard_option
('proxmox-remote', {
4638 description
=> "Remote target endpoint",
4642 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
4647 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.",
4651 'target-storage' => get_standard_option
('pve-targetstorage', {
4652 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
4655 'target-bridge' => {
4657 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.",
4658 format
=> 'bridge-pair-list',
4661 description
=> "Override I/O bandwidth limit (in KiB/s).",
4665 default => 'migrate limit from datacenter or storage config',
4671 description
=> "the task ID.",
4676 my $rpcenv = PVE
::RPCEnvironment
::get
();
4677 my $authuser = $rpcenv->get_user();
4679 my $source_vmid = extract_param
($param, 'vmid');
4680 my $target_endpoint = extract_param
($param, 'target-endpoint');
4681 my $target_vmid = extract_param
($param, 'target-vmid') // $source_vmid;
4683 my $delete = extract_param
($param, 'delete') // 0;
4685 PVE
::Cluster
::check_cfs_quorum
();
4688 my $conf = PVE
::QemuConfig-
>load_config($source_vmid);
4690 PVE
::QemuConfig-
>check_lock($conf);
4692 raise_param_exc
({ vmid
=> "cannot migrate HA-managed VM to remote cluster" })
4693 if PVE
::HA
::Config
::vm_is_ha_managed
($source_vmid);
4695 my $remote = PVE
::JSONSchema
::parse_property_string
('proxmox-remote', $target_endpoint);
4697 # TODO: move this as helper somewhere appropriate?
4699 protocol
=> 'https',
4700 host
=> $remote->{host
},
4701 port
=> $remote->{port
} // 8006,
4702 apitoken
=> $remote->{apitoken
},
4706 if ($fp = $remote->{fingerprint
}) {
4707 $conn_args->{cached_fingerprints
} = { uc($fp) => 1 };
4710 print "Establishing API connection with remote at '$remote->{host}'\n";
4712 my $api_client = PVE
::APIClient
::LWP-
>new(%$conn_args);
4714 if (!defined($fp)) {
4715 my $cert_info = $api_client->get("/nodes/localhost/certificates/info");
4716 foreach my $cert (@$cert_info) {
4717 my $filename = $cert->{filename
};
4718 next if $filename ne 'pveproxy-ssl.pem' && $filename ne 'pve-ssl.pem';
4719 $fp = $cert->{fingerprint
} if !$fp || $filename eq 'pveproxy-ssl.pem';
4721 $conn_args->{cached_fingerprints
} = { uc($fp) => 1 }
4725 my $repl_conf = PVE
::ReplicationConfig-
>new();
4726 my $is_replicated = $repl_conf->check_for_existing_jobs($source_vmid, 1);
4727 die "cannot remote-migrate replicated VM\n" if $is_replicated;
4729 if (PVE
::QemuServer
::check_running
($source_vmid)) {
4730 die "can't migrate running VM without --online\n" if !$param->{online
};
4733 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
4734 $param->{online
} = 0;
4737 my $storecfg = PVE
::Storage
::config
();
4738 my $target_storage = extract_param
($param, 'target-storage');
4739 my $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($target_storage, 'pve-storage-id') };
4740 raise_param_exc
({ 'target-storage' => "failed to parse storage map: $@" })
4743 my $target_bridge = extract_param
($param, 'target-bridge');
4744 my $bridgemap = eval { PVE
::JSONSchema
::parse_idmap
($target_bridge, 'pve-bridge-id') };
4745 raise_param_exc
({ 'target-bridge' => "failed to parse bridge map: $@" })
4748 die "remote migration requires explicit storage mapping!\n"
4749 if $storagemap->{identity
};
4751 $param->{storagemap
} = $storagemap;
4752 $param->{bridgemap
} = $bridgemap;
4753 $param->{remote
} = {
4754 conn
=> $conn_args, # re-use fingerprint for tunnel
4755 client
=> $api_client,
4756 vmid
=> $target_vmid,
4758 $param->{migration_type
} = 'websocket';
4759 $param->{'with-local-disks'} = 1;
4760 $param->{delete} = $delete if $delete;
4762 my $cluster_status = $api_client->get("/cluster/status");
4764 foreach my $entry (@$cluster_status) {
4765 next if $entry->{type
} ne 'node';
4766 if ($entry->{local}) {
4767 $target_node = $entry->{name
};
4772 die "couldn't determine endpoint's node name\n"
4773 if !defined($target_node);
4776 PVE
::QemuMigrate-
>migrate($target_node, $remote->{host
}, $source_vmid, $param);
4780 return PVE
::GuestHelpers
::guest_migration_lock
($source_vmid, 10, $realcmd);
4783 return $rpcenv->fork_worker('qmigrate', $source_vmid, $authuser, $worker);
4786 __PACKAGE__-
>register_method({
4788 path
=> '{vmid}/monitor',
4792 description
=> "Execute QEMU monitor commands.",
4794 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
4795 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
4798 additionalProperties
=> 0,
4800 node
=> get_standard_option
('pve-node'),
4801 vmid
=> get_standard_option
('pve-vmid'),
4804 description
=> "The monitor command.",
4808 returns
=> { type
=> 'string'},
4812 my $rpcenv = PVE
::RPCEnvironment
::get
();
4813 my $authuser = $rpcenv->get_user();
4816 my $command = shift;
4817 return $command =~ m/^\s*info(\s+|$)/
4818 || $command =~ m/^\s*help\s*$/;
4821 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
4822 if !&$is_ro($param->{command
});
4824 my $vmid = $param->{vmid
};
4826 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
4830 $res = PVE
::QemuServer
::Monitor
::hmp_cmd
($vmid, $param->{command
});
4832 $res = "ERROR: $@" if $@;
4837 __PACKAGE__-
>register_method({
4838 name
=> 'resize_vm',
4839 path
=> '{vmid}/resize',
4843 description
=> "Extend volume size.",
4845 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
4848 additionalProperties
=> 0,
4850 node
=> get_standard_option
('pve-node'),
4851 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4852 skiplock
=> get_standard_option
('skiplock'),
4855 description
=> "The disk you want to resize.",
4856 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
4860 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
4861 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.",
4865 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
4873 description
=> "the task ID.",
4878 my $rpcenv = PVE
::RPCEnvironment
::get
();
4880 my $authuser = $rpcenv->get_user();
4882 my $node = extract_param
($param, 'node');
4884 my $vmid = extract_param
($param, 'vmid');
4886 my $digest = extract_param
($param, 'digest');
4888 my $disk = extract_param
($param, 'disk');
4890 my $sizestr = extract_param
($param, 'size');
4892 my $skiplock = extract_param
($param, 'skiplock');
4893 raise_param_exc
({ skiplock
=> "Only root may use this option." })
4894 if $skiplock && $authuser ne 'root@pam';
4896 my $storecfg = PVE
::Storage
::config
();
4898 my $updatefn = sub {
4900 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4902 die "checksum missmatch (file change by other user?)\n"
4903 if $digest && $digest ne $conf->{digest
};
4904 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
4906 die "disk '$disk' does not exist\n" if !$conf->{$disk};
4908 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
4910 my (undef, undef, undef, undef, undef, undef, $format) =
4911 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
4913 my $volid = $drive->{file
};
4915 die "disk '$disk' has no associated volume\n" if !$volid;
4917 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
4919 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
4921 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
4923 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
4924 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
4926 die "Could not determine current size of volume '$volid'\n" if !defined($size);
4928 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
4929 my ($ext, $newsize, $unit) = ($1, $2, $4);
4932 $newsize = $newsize * 1024;
4933 } elsif ($unit eq 'M') {
4934 $newsize = $newsize * 1024 * 1024;
4935 } elsif ($unit eq 'G') {
4936 $newsize = $newsize * 1024 * 1024 * 1024;
4937 } elsif ($unit eq 'T') {
4938 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
4941 $newsize += $size if $ext;
4942 $newsize = int($newsize);
4944 die "shrinking disks is not supported\n" if $newsize < $size;
4946 return if $size == $newsize;
4948 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
4950 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
4952 $drive->{size
} = $newsize;
4953 $conf->{$disk} = PVE
::QemuServer
::print_drive
($drive);
4955 PVE
::QemuConfig-
>write_config($vmid, $conf);
4959 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4962 return $rpcenv->fork_worker('resize', $vmid, $authuser, $worker);
4965 __PACKAGE__-
>register_method({
4966 name
=> 'snapshot_list',
4967 path
=> '{vmid}/snapshot',
4969 description
=> "List all snapshots.",
4971 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4974 protected
=> 1, # qemu pid files are only readable by root
4976 additionalProperties
=> 0,
4978 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4979 node
=> get_standard_option
('pve-node'),
4988 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
4992 description
=> "Snapshot includes RAM.",
4997 description
=> "Snapshot description.",
5001 description
=> "Snapshot creation time",
5003 renderer
=> 'timestamp',
5007 description
=> "Parent snapshot identifier.",
5013 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
5018 my $vmid = $param->{vmid
};
5020 my $conf = PVE
::QemuConfig-
>load_config($vmid);
5021 my $snaphash = $conf->{snapshots
} || {};
5025 foreach my $name (keys %$snaphash) {
5026 my $d = $snaphash->{$name};
5029 snaptime
=> $d->{snaptime
} || 0,
5030 vmstate
=> $d->{vmstate
} ?
1 : 0,
5031 description
=> $d->{description
} || '',
5033 $item->{parent
} = $d->{parent
} if $d->{parent
};
5034 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
5038 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
5041 digest
=> $conf->{digest
},
5042 running
=> $running,
5043 description
=> "You are here!",
5045 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
5047 push @$res, $current;
5052 __PACKAGE__-
>register_method({
5054 path
=> '{vmid}/snapshot',
5058 description
=> "Snapshot a VM.",
5060 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
5063 additionalProperties
=> 0,
5065 node
=> get_standard_option
('pve-node'),
5066 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
5067 snapname
=> get_standard_option
('pve-snapshot-name'),
5071 description
=> "Save the vmstate",
5076 description
=> "A textual description or comment.",
5082 description
=> "the task ID.",
5087 my $rpcenv = PVE
::RPCEnvironment
::get
();
5089 my $authuser = $rpcenv->get_user();
5091 my $node = extract_param
($param, 'node');
5093 my $vmid = extract_param
($param, 'vmid');
5095 my $snapname = extract_param
($param, 'snapname');
5097 die "unable to use snapshot name 'current' (reserved name)\n"
5098 if $snapname eq 'current';
5100 die "unable to use snapshot name 'pending' (reserved name)\n"
5101 if lc($snapname) eq 'pending';
5104 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
5105 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
5106 $param->{description
});
5109 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
5112 __PACKAGE__-
>register_method({
5113 name
=> 'snapshot_cmd_idx',
5114 path
=> '{vmid}/snapshot/{snapname}',
5121 additionalProperties
=> 0,
5123 vmid
=> get_standard_option
('pve-vmid'),
5124 node
=> get_standard_option
('pve-node'),
5125 snapname
=> get_standard_option
('pve-snapshot-name'),
5134 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
5141 push @$res, { cmd
=> 'rollback' };
5142 push @$res, { cmd
=> 'config' };
5147 __PACKAGE__-
>register_method({
5148 name
=> 'update_snapshot_config',
5149 path
=> '{vmid}/snapshot/{snapname}/config',
5153 description
=> "Update snapshot metadata.",
5155 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
5158 additionalProperties
=> 0,
5160 node
=> get_standard_option
('pve-node'),
5161 vmid
=> get_standard_option
('pve-vmid'),
5162 snapname
=> get_standard_option
('pve-snapshot-name'),
5166 description
=> "A textual description or comment.",
5170 returns
=> { type
=> 'null' },
5174 my $rpcenv = PVE
::RPCEnvironment
::get
();
5176 my $authuser = $rpcenv->get_user();
5178 my $vmid = extract_param
($param, 'vmid');
5180 my $snapname = extract_param
($param, 'snapname');
5182 return if !defined($param->{description
});
5184 my $updatefn = sub {
5186 my $conf = PVE
::QemuConfig-
>load_config($vmid);
5188 PVE
::QemuConfig-
>check_lock($conf);
5190 my $snap = $conf->{snapshots
}->{$snapname};
5192 die "snapshot '$snapname' does not exist\n" if !defined($snap);
5194 $snap->{description
} = $param->{description
} if defined($param->{description
});
5196 PVE
::QemuConfig-
>write_config($vmid, $conf);
5199 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
5204 __PACKAGE__-
>register_method({
5205 name
=> 'get_snapshot_config',
5206 path
=> '{vmid}/snapshot/{snapname}/config',
5209 description
=> "Get snapshot configuration",
5211 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback', 'VM.Audit' ], any
=> 1],
5214 additionalProperties
=> 0,
5216 node
=> get_standard_option
('pve-node'),
5217 vmid
=> get_standard_option
('pve-vmid'),
5218 snapname
=> get_standard_option
('pve-snapshot-name'),
5221 returns
=> { type
=> "object" },
5225 my $rpcenv = PVE
::RPCEnvironment
::get
();
5227 my $authuser = $rpcenv->get_user();
5229 my $vmid = extract_param
($param, 'vmid');
5231 my $snapname = extract_param
($param, 'snapname');
5233 my $conf = PVE
::QemuConfig-
>load_config($vmid);
5235 my $snap = $conf->{snapshots
}->{$snapname};
5237 die "snapshot '$snapname' does not exist\n" if !defined($snap);
5242 __PACKAGE__-
>register_method({
5244 path
=> '{vmid}/snapshot/{snapname}/rollback',
5248 description
=> "Rollback VM state to specified snapshot.",
5250 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
5253 additionalProperties
=> 0,
5255 node
=> get_standard_option
('pve-node'),
5256 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
5257 snapname
=> get_standard_option
('pve-snapshot-name'),
5260 description
=> "Whether the VM should get started after rolling back successfully."
5261 . " (Note: VMs will be automatically started if the snapshot includes RAM.)",
5269 description
=> "the task ID.",
5274 my $rpcenv = PVE
::RPCEnvironment
::get
();
5276 my $authuser = $rpcenv->get_user();
5278 my $node = extract_param
($param, 'node');
5280 my $vmid = extract_param
($param, 'vmid');
5282 my $snapname = extract_param
($param, 'snapname');
5285 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
5286 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
5288 if ($param->{start
} && !PVE
::QemuServer
::Helpers
::vm_running_locally
($vmid)) {
5289 PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node });
5294 # hold migration lock, this makes sure that nobody create replication snapshots
5295 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
5298 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
5301 __PACKAGE__-
>register_method({
5302 name
=> 'delsnapshot',
5303 path
=> '{vmid}/snapshot/{snapname}',
5307 description
=> "Delete a VM snapshot.",
5309 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
5312 additionalProperties
=> 0,
5314 node
=> get_standard_option
('pve-node'),
5315 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
5316 snapname
=> get_standard_option
('pve-snapshot-name'),
5320 description
=> "For removal from config file, even if removing disk snapshots fails.",
5326 description
=> "the task ID.",
5331 my $rpcenv = PVE
::RPCEnvironment
::get
();
5333 my $authuser = $rpcenv->get_user();
5335 my $node = extract_param
($param, 'node');
5337 my $vmid = extract_param
($param, 'vmid');
5339 my $snapname = extract_param
($param, 'snapname');
5342 my $do_delete = sub {
5344 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
5345 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
5349 if ($param->{force
}) {
5352 eval { PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $do_delete); };
5354 die $err if $lock_obtained;
5355 die "Failed to obtain guest migration lock - replication running?\n";
5360 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
5363 __PACKAGE__-
>register_method({
5365 path
=> '{vmid}/template',
5369 description
=> "Create a Template.",
5371 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
5372 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
5375 additionalProperties
=> 0,
5377 node
=> get_standard_option
('pve-node'),
5378 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
5382 description
=> "If you want to convert only 1 disk to base image.",
5383 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
5390 description
=> "the task ID.",
5395 my $rpcenv = PVE
::RPCEnvironment
::get
();
5397 my $authuser = $rpcenv->get_user();
5399 my $node = extract_param
($param, 'node');
5401 my $vmid = extract_param
($param, 'vmid');
5403 my $disk = extract_param
($param, 'disk');
5405 my $load_and_check = sub {
5406 my $conf = PVE
::QemuConfig-
>load_config($vmid);
5408 PVE
::QemuConfig-
>check_lock($conf);
5410 die "unable to create template, because VM contains snapshots\n"
5411 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
5413 die "you can't convert a template to a template\n"
5414 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
5416 die "you can't convert a VM to template if VM is running\n"
5417 if PVE
::QemuServer
::check_running
($vmid);
5422 $load_and_check->();
5425 PVE
::QemuConfig-
>lock_config($vmid, sub {
5426 my $conf = $load_and_check->();
5428 $conf->{template
} = 1;
5429 PVE
::QemuConfig-
>write_config($vmid, $conf);
5431 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
5435 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
5438 __PACKAGE__-
>register_method({
5439 name
=> 'cloudinit_generated_config_dump',
5440 path
=> '{vmid}/cloudinit/dump',
5443 description
=> "Get automatically generated cloudinit config.",
5445 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
5448 additionalProperties
=> 0,
5450 node
=> get_standard_option
('pve-node'),
5451 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
5453 description
=> 'Config type.',
5455 enum
=> ['user', 'network', 'meta'],
5465 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
5467 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});
5470 __PACKAGE__-
>register_method({
5472 path
=> '{vmid}/mtunnel',
5475 description
=> 'Migration tunnel endpoint - only for internal use by VM migration.',
5479 ['perm', '/vms/{vmid}', [ 'VM.Allocate' ]],
5480 ['perm', '/', [ 'Sys.Incoming' ]],
5482 description
=> "You need 'VM.Allocate' permissions on '/vms/{vmid}' and Sys.Incoming" .
5483 " on '/'. Further permission checks happen during the actual migration.",
5486 additionalProperties
=> 0,
5488 node
=> get_standard_option
('pve-node'),
5489 vmid
=> get_standard_option
('pve-vmid'),
5492 format
=> 'pve-storage-id-list',
5494 description
=> 'List of storages to check permission and availability. Will be checked again for all actually used storages during migration.',
5498 format
=> 'pve-bridge-id-list',
5500 description
=> 'List of network bridges to check availability. Will be checked again for actually used bridges during migration.',
5505 additionalProperties
=> 0,
5507 upid
=> { type
=> 'string' },
5508 ticket
=> { type
=> 'string' },
5509 socket => { type
=> 'string' },
5515 my $rpcenv = PVE
::RPCEnvironment
::get
();
5516 my $authuser = $rpcenv->get_user();
5518 my $node = extract_param
($param, 'node');
5519 my $vmid = extract_param
($param, 'vmid');
5521 my $storages = extract_param
($param, 'storages');
5522 my $bridges = extract_param
($param, 'bridges');
5524 my $nodename = PVE
::INotify
::nodename
();
5526 raise_param_exc
({ node
=> "node needs to be 'localhost' or local hostname '$nodename'" })
5527 if $node ne 'localhost' && $node ne $nodename;
5531 my $storecfg = PVE
::Storage
::config
();
5532 foreach my $storeid (PVE
::Tools
::split_list
($storages)) {
5533 $check_storage_access_migrate->($rpcenv, $authuser, $storecfg, $storeid, $node);
5536 foreach my $bridge (PVE
::Tools
::split_list
($bridges)) {
5537 PVE
::Network
::read_bridge_mtu
($bridge);
5540 PVE
::Cluster
::check_cfs_quorum
();
5542 my $lock = 'create';
5543 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, 0, $lock); };
5545 raise_param_exc
({ vmid
=> "unable to create empty VM config - $@"})
5550 storecfg
=> PVE
::Storage
::config
(),
5555 my $run_locked = sub {
5556 my ($code, $params) = @_;
5557 return PVE
::QemuConfig-
>lock_config($state->{vmid
}, sub {
5558 my $conf = PVE
::QemuConfig-
>load_config($state->{vmid
});
5560 $state->{conf
} = $conf;
5562 die "Encountered wrong lock - aborting mtunnel command handling.\n"
5563 if $state->{lock} && !PVE
::QemuConfig-
>has_lock($conf, $state->{lock});
5565 return $code->($params);
5573 description
=> 'Full VM config, adapted for target cluster/node',
5575 'firewall-config' => {
5577 description
=> 'VM firewall config',
5582 format
=> PVE
::JSONSchema
::get_standard_option
('pve-qm-image-format'),
5585 format
=> 'pve-storage-id',
5589 description
=> 'parsed drive information without volid and format',
5595 description
=> 'params passed to vm_start_nolock',
5599 description
=> 'migrate_opts passed to vm_start_nolock',
5605 description
=> 'socket path for which the ticket should be valid. must be known to current mtunnel instance.',
5611 description
=> 'remove VM config and disks, aborting migration',
5615 'disk-import' => $PVE::StorageTunnel
::cmd_schema-
>{'disk-import'},
5616 'query-disk-import' => $PVE::StorageTunnel
::cmd_schema-
>{'query-disk-import'},
5617 bwlimit
=> $PVE::StorageTunnel
::cmd_schema-
>{bwlimit
},
5620 my $cmd_handlers = {
5622 # compared against other end's version
5623 # bump/reset for breaking changes
5624 # bump/bump for opt-in changes
5626 api
=> $PVE::QemuMigrate
::WS_TUNNEL_VERSION
,
5633 # parse and write out VM FW config if given
5634 if (my $fw_conf = $params->{'firewall-config'}) {
5635 my ($path, $fh) = PVE
::Tools
::tempfile_contents
($fw_conf, 700);
5642 ipset_comments
=> {},
5644 my $cluster_fw_conf = PVE
::Firewall
::load_clusterfw_conf
();
5646 # TODO: add flag for strict parsing?
5647 # TODO: add import sub that does all this given raw content?
5648 my $vmfw_conf = PVE
::Firewall
::generic_fw_config_parser
($path, $cluster_fw_conf, $empty_conf, 'vm');
5649 $vmfw_conf->{vmid
} = $state->{vmid
};
5650 PVE
::Firewall
::save_vmfw_conf
($state->{vmid
}, $vmfw_conf);
5652 $state->{cleanup
}->{fw
} = 1;
5655 my $conf_fn = "incoming/qemu-server/$state->{vmid}.conf";
5656 my $new_conf = PVE
::QemuServer
::parse_vm_config
($conf_fn, $params->{conf
}, 1);
5657 delete $new_conf->{lock};
5658 delete $new_conf->{digest
};
5660 # TODO handle properly?
5661 delete $new_conf->{snapshots
};
5662 delete $new_conf->{parent
};
5663 delete $new_conf->{pending
};
5665 # not handled by update_vm_api
5666 my $vmgenid = delete $new_conf->{vmgenid
};
5667 my $meta = delete $new_conf->{meta
};
5668 my $cloudinit = delete $new_conf->{cloudinit
}; # this is informational only
5669 $new_conf->{skip_cloud_init
} = 1; # re-use image from source side
5671 $new_conf->{vmid
} = $state->{vmid
};
5672 $new_conf->{node
} = $node;
5674 PVE
::QemuConfig-
>remove_lock($state->{vmid
}, 'create');
5677 $update_vm_api->($new_conf, 1);
5680 # revert to locked previous config
5681 my $conf = PVE
::QemuConfig-
>load_config($state->{vmid
});
5682 $conf->{lock} = 'create';
5683 PVE
::QemuConfig-
>write_config($state->{vmid
}, $conf);
5688 my $conf = PVE
::QemuConfig-
>load_config($state->{vmid
});
5689 $conf->{lock} = 'migrate';
5690 $conf->{vmgenid
} = $vmgenid if defined($vmgenid);
5691 $conf->{meta
} = $meta if defined($meta);
5692 $conf->{cloudinit
} = $cloudinit if defined($cloudinit);
5693 PVE
::QemuConfig-
>write_config($state->{vmid
}, $conf);
5695 $state->{lock} = 'migrate';
5701 return PVE
::StorageTunnel
::handle_bwlimit
($params);
5706 my $format = $params->{format
};
5707 my $storeid = $params->{storage
};
5708 my $drive = $params->{drive
};
5710 $check_storage_access_migrate->($rpcenv, $authuser, $state->{storecfg
}, $storeid, $node);
5713 default => $storeid,
5716 my $source_volumes = {
5726 my $res = PVE
::QemuServer
::vm_migrate_alloc_nbd_disks
($state->{storecfg
}, $state->{vmid
}, $source_volumes, $storagemap);
5727 if (defined($res->{disk
})) {
5728 $state->{cleanup
}->{volumes
}->{$res->{disk
}->{volid
}} = 1;
5729 return $res->{disk
};
5731 die "failed to allocate NBD disk..\n";
5734 'disk-import' => sub {
5737 $check_storage_access_migrate->(
5745 $params->{unix
} = "/run/qemu-server/$state->{vmid}.storage";
5747 return PVE
::StorageTunnel
::handle_disk_import
($state, $params);
5749 'query-disk-import' => sub {
5752 return PVE
::StorageTunnel
::handle_query_disk_import
($state, $params);
5757 my $info = PVE
::QemuServer
::vm_start_nolock
(
5761 $params->{start_params
},
5762 $params->{migrate_opts
},
5766 if ($info->{migrate
}->{proto
} ne 'unix') {
5767 PVE
::QemuServer
::vm_stop
(undef, $state->{vmid
}, 1, 1);
5768 die "migration over non-UNIX sockets not possible\n";
5771 my $socket = $info->{migrate
}->{addr
};
5772 chown $state->{socket_uid
}, -1, $socket;
5773 $state->{sockets
}->{$socket} = 1;
5775 my $unix_sockets = $info->{migrate
}->{unix_sockets
};
5776 foreach my $socket (@$unix_sockets) {
5777 chown $state->{socket_uid
}, -1, $socket;
5778 $state->{sockets
}->{$socket} = 1;
5783 if (PVE
::QemuServer
::qga_check_running
($state->{vmid
})) {
5784 eval { mon_cmd
($state->{vmid
}, "guest-fstrim") };
5785 warn "fstrim failed: $@\n" if $@;
5790 PVE
::QemuServer
::vm_stop
(undef, $state->{vmid
}, 1, 1);
5794 PVE
::QemuServer
::nbd_stop
($state->{vmid
});
5798 if (PVE
::QemuServer
::Helpers
::vm_running_locally
($state->{vmid
})) {
5799 PVE
::QemuServer
::vm_resume
($state->{vmid
}, 1, 1);
5801 die "VM $state->{vmid} not running\n";
5806 PVE
::QemuConfig-
>remove_lock($state->{vmid
}, $state->{lock});
5807 delete $state->{lock};
5813 my $path = $params->{path
};
5815 die "Not allowed to generate ticket for unknown socket '$path'\n"
5816 if !defined($state->{sockets
}->{$path});
5818 return { ticket
=> PVE
::AccessControl
::assemble_tunnel_ticket
($authuser, "/socket/$path") };
5823 if ($params->{cleanup
}) {
5824 if ($state->{cleanup
}->{fw
}) {
5825 PVE
::Firewall
::remove_vmfw_conf
($state->{vmid
});
5828 for my $volid (keys $state->{cleanup
}->{volumes
}->%*) {
5829 print "freeing volume '$volid' as part of cleanup\n";
5830 eval { PVE
::Storage
::vdisk_free
($state->{storecfg
}, $volid) };
5834 PVE
::QemuServer
::destroy_vm
($state->{storecfg
}, $state->{vmid
}, 1);
5837 print "switching to exit-mode, waiting for client to disconnect\n";
5844 my $socket_addr = "/run/qemu-server/$state->{vmid}.mtunnel";
5845 unlink $socket_addr;
5847 $state->{socket} = IO
::Socket
::UNIX-
>new(
5848 Type
=> SOCK_STREAM
(),
5849 Local
=> $socket_addr,
5853 $state->{socket_uid
} = getpwnam('www-data')
5854 or die "Failed to resolve user 'www-data' to numeric UID\n";
5855 chown $state->{socket_uid
}, -1, $socket_addr;
5858 print "mtunnel started\n";
5860 my $conn = eval { PVE
::Tools
::run_with_timeout
(300, sub { $state->{socket}->accept() }) };
5862 warn "Failed to accept tunnel connection - $@\n";
5864 warn "Removing tunnel socket..\n";
5865 unlink $state->{socket};
5867 warn "Removing temporary VM config..\n";
5869 PVE
::QemuServer
::destroy_vm
($state->{storecfg
}, $state->{vmid
}, 1);
5872 die "Exiting mtunnel\n";
5875 $state->{conn
} = $conn;
5877 my $reply_err = sub {
5880 my $reply = JSON
::encode_json
({
5881 success
=> JSON
::false
,
5884 $conn->print("$reply\n");
5888 my $reply_ok = sub {
5891 $res->{success
} = JSON
::true
;
5892 my $reply = JSON
::encode_json
($res);
5893 $conn->print("$reply\n");
5897 while (my $line = <$conn>) {
5900 # untaint, we validate below if needed
5901 ($line) = $line =~ /^(.*)$/;
5902 my $parsed = eval { JSON
::decode_json
($line) };
5904 $reply_err->("failed to parse command - $@");
5908 my $cmd = delete $parsed->{cmd
};
5909 if (!defined($cmd)) {
5910 $reply_err->("'cmd' missing");
5911 } elsif ($state->{exit}) {
5912 $reply_err->("tunnel is in exit-mode, processing '$cmd' cmd not possible");
5914 } elsif (my $handler = $cmd_handlers->{$cmd}) {
5915 print "received command '$cmd'\n";
5917 if ($cmd_desc->{$cmd}) {
5918 PVE
::JSONSchema
::validate
($parsed, $cmd_desc->{$cmd});
5922 my $res = $run_locked->($handler, $parsed);
5925 $reply_err->("failed to handle '$cmd' command - $@")
5928 $reply_err->("unknown command '$cmd' given");
5932 if ($state->{exit}) {
5933 print "mtunnel exited\n";
5935 die "mtunnel exited unexpectedly\n";
5939 my $socket_addr = "/run/qemu-server/$vmid.mtunnel";
5940 my $ticket = PVE
::AccessControl
::assemble_tunnel_ticket
($authuser, "/socket/$socket_addr");
5941 my $upid = $rpcenv->fork_worker('qmtunnel', $vmid, $authuser, $realcmd);
5946 socket => $socket_addr,
5950 __PACKAGE__-
>register_method({
5951 name
=> 'mtunnelwebsocket',
5952 path
=> '{vmid}/mtunnelwebsocket',
5955 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.",
5956 user
=> 'all', # check inside
5958 description
=> 'Migration tunnel endpoint for websocket upgrade - only for internal use by VM migration.',
5960 additionalProperties
=> 0,
5962 node
=> get_standard_option
('pve-node'),
5963 vmid
=> get_standard_option
('pve-vmid'),
5966 description
=> "unix socket to forward to",
5970 description
=> "ticket return by initial 'mtunnel' API call, or retrieved via 'ticket' tunnel command",
5977 port
=> { type
=> 'string', optional
=> 1 },
5978 socket => { type
=> 'string', optional
=> 1 },
5984 my $rpcenv = PVE
::RPCEnvironment
::get
();
5985 my $authuser = $rpcenv->get_user();
5987 my $nodename = PVE
::INotify
::nodename
();
5988 my $node = extract_param
($param, 'node');
5990 raise_param_exc
({ node
=> "node needs to be 'localhost' or local hostname '$nodename'" })
5991 if $node ne 'localhost' && $node ne $nodename;
5993 my $vmid = $param->{vmid
};
5995 PVE
::QemuConfig-
>load_config($vmid);
5997 my $socket = $param->{socket};
5998 PVE
::AccessControl
::verify_tunnel_ticket
($param->{ticket
}, $authuser, "/socket/$socket");
6000 return { socket => $socket };