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
::PCI
;
36 use PVE
::QemuServer
::USB
;
38 use PVE
::RPCEnvironment
;
39 use PVE
::AccessControl
;
43 use PVE
::API2
::Firewall
::VM
;
44 use PVE
::API2
::Qemu
::Agent
;
45 use PVE
::VZDump
::Plugin
;
46 use PVE
::DataCenterConfig
;
49 use PVE
::StorageTunnel
;
52 if (!$ENV{PVE_GENERATING_DOCS
}) {
53 require PVE
::HA
::Env
::PVE2
;
54 import PVE
::HA
::Env
::PVE2
;
55 require PVE
::HA
::Config
;
56 import PVE
::HA
::Config
;
60 use base
qw(PVE::RESTHandler);
62 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.";
64 my $resolve_cdrom_alias = sub {
67 if (my $value = $param->{cdrom
}) {
68 $value .= ",media=cdrom" if $value !~ m/media=/;
69 $param->{ide2
} = $value;
70 delete $param->{cdrom
};
74 # Used in import-enabled API endpoints. Parses drives using the extended '_with_alloc' schema.
75 my $foreach_volume_with_alloc = sub {
76 my ($param, $func) = @_;
78 for my $opt (sort keys $param->%*) {
79 next if !PVE
::QemuServer
::is_valid_drivename
($opt);
81 my $drive = PVE
::QemuServer
::Drive
::parse_drive
($opt, $param->{$opt}, 1);
84 $func->($opt, $drive);
88 my $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
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
} !~ $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 =~ $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 =~ $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 = {
570 my $check_vm_create_serial_perm = sub {
571 my ($rpcenv, $authuser, $vmid, $pool, $param) = @_;
573 return 1 if $authuser eq 'root@pam';
575 foreach my $opt (keys %{$param}) {
576 next if $opt !~ m/^serial\d+$/;
578 if ($param->{$opt} eq 'socket') {
579 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
581 die "only root can set '$opt' config for real devices\n";
588 my sub check_usb_perm
{
589 my ($rpcenv, $authuser, $vmid, $pool, $opt, $value) = @_;
591 return 1 if $authuser eq 'root@pam';
593 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
595 my $device = PVE
::JSONSchema
::parse_property_string
('pve-qm-usb', $value);
596 if ($device->{host
} && $device->{host
} !~ m/^spice$/i) {
597 die "only root can set '$opt' config for real devices\n";
598 } elsif ($device->{mapping
}) {
599 $rpcenv->check_full($authuser, "/mapping/usb/$device->{mapping}", ['Mapping.Use']);
601 die "either 'host' or 'mapping' must be set.\n";
607 my sub check_vm_create_usb_perm
{
608 my ($rpcenv, $authuser, $vmid, $pool, $param) = @_;
610 return 1 if $authuser eq 'root@pam';
612 foreach my $opt (keys %{$param}) {
613 next if $opt !~ m/^usb\d+$/;
614 check_usb_perm
($rpcenv, $authuser, $vmid, $pool, $opt, $param->{$opt});
620 my sub check_hostpci_perm
{
621 my ($rpcenv, $authuser, $vmid, $pool, $opt, $value) = @_;
623 return 1 if $authuser eq 'root@pam';
625 my $device = PVE
::JSONSchema
::parse_property_string
('pve-qm-hostpci', $value);
626 if ($device->{host
}) {
627 die "only root can set '$opt' config for non-mapped devices\n";
628 } elsif ($device->{mapping
}) {
629 $rpcenv->check_full($authuser, "/mapping/pci/$device->{mapping}", ['Mapping.Use']);
630 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
632 die "either 'host' or 'mapping' must be set.\n";
638 my sub check_vm_create_hostpci_perm
{
639 my ($rpcenv, $authuser, $vmid, $pool, $param) = @_;
641 return 1 if $authuser eq 'root@pam';
643 foreach my $opt (keys %{$param}) {
644 next if $opt !~ m/^hostpci\d+$/;
645 check_hostpci_perm
($rpcenv, $authuser, $vmid, $pool, $opt, $param->{$opt});
651 my $check_vm_modify_config_perm = sub {
652 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
654 return 1 if $authuser eq 'root@pam';
656 foreach my $opt (@$key_list) {
657 # some checks (e.g., disk, serial port, usb) need to be done somewhere
658 # else, as there the permission can be value dependend
659 next if PVE
::QemuServer
::is_valid_drivename
($opt);
660 next if $opt eq 'cdrom';
661 next if $opt =~ m/^(?:unused|serial|usb|hostpci)\d+$/;
662 next if $opt eq 'tags';
665 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
666 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
667 } elsif ($memoryoptions->{$opt}) {
668 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
669 } elsif ($hwtypeoptions->{$opt}) {
670 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
671 } elsif ($generaloptions->{$opt}) {
672 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
673 # special case for startup since it changes host behaviour
674 if ($opt eq 'startup') {
675 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
677 } elsif ($vmpoweroptions->{$opt}) {
678 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
679 } elsif ($diskoptions->{$opt}) {
680 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
681 } elsif ($opt =~ m/^net\d+$/) {
682 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
683 } elsif ($cloudinitoptions->{$opt} || $opt =~ m/^ipconfig\d+$/) {
684 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Cloudinit', 'VM.Config.Network'], 1);
685 } elsif ($opt eq 'vmstate') {
686 # the user needs Disk and PowerMgmt privileges to change the vmstate
687 # also needs privileges on the storage, that will be checked later
688 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk', 'VM.PowerMgmt' ]);
690 # catches args, lock, etc.
691 # new options will be checked here
692 die "only root can set '$opt' config\n";
699 __PACKAGE__-
>register_method({
703 description
=> "Virtual machine index (per node).",
705 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
709 protected
=> 1, # qemu pid files are only readable by root
711 additionalProperties
=> 0,
713 node
=> get_standard_option
('pve-node'),
717 description
=> "Determine the full status of active VMs.",
725 properties
=> $PVE::QemuServer
::vmstatus_return_properties
,
727 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
732 my $rpcenv = PVE
::RPCEnvironment
::get
();
733 my $authuser = $rpcenv->get_user();
735 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
738 foreach my $vmid (keys %$vmstatus) {
739 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
741 my $data = $vmstatus->{$vmid};
748 my $parse_restore_archive = sub {
749 my ($storecfg, $archive) = @_;
751 my ($archive_storeid, $archive_volname) = PVE
::Storage
::parse_volume_id
($archive, 1);
755 if (defined($archive_storeid)) {
756 my $scfg = PVE
::Storage
::storage_config
($storecfg, $archive_storeid);
757 $res->{volid
} = $archive;
758 if ($scfg->{type
} eq 'pbs') {
759 $res->{type
} = 'pbs';
763 my $path = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
764 $res->{type
} = 'file';
765 $res->{path
} = $path;
770 __PACKAGE__-
>register_method({
774 description
=> "Create or restore a virtual machine.",
776 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
777 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
778 "If you create disks you need 'Datastore.AllocateSpace' on any used storage." .
779 "If you use a bridge/vlan, you need 'SDN.Use' on any used bridge/vlan.",
780 user
=> 'all', # check inside
785 additionalProperties
=> 0,
786 properties
=> PVE
::QemuServer
::json_config_properties
(
788 node
=> get_standard_option
('pve-node'),
789 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
791 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.",
795 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
797 storage
=> get_standard_option
('pve-storage-id', {
798 description
=> "Default storage.",
800 completion
=> \
&PVE
::QemuServer
::complete_storage
,
805 description
=> "Allow to overwrite existing VM.",
806 requires
=> 'archive',
811 description
=> "Assign a unique random ethernet address.",
812 requires
=> 'archive',
817 description
=> "Start the VM immediately from the backup and restore in background. PBS only.",
818 requires
=> 'archive',
822 type
=> 'string', format
=> 'pve-poolid',
823 description
=> "Add the VM to the specified pool.",
826 description
=> "Override I/O bandwidth limit (in KiB/s).",
830 default => 'restore limit from datacenter or storage config',
836 description
=> "Start VM after it was created successfully.",
848 my $rpcenv = PVE
::RPCEnvironment
::get
();
849 my $authuser = $rpcenv->get_user();
851 my $node = extract_param
($param, 'node');
852 my $vmid = extract_param
($param, 'vmid');
854 my $archive = extract_param
($param, 'archive');
855 my $is_restore = !!$archive;
857 my $bwlimit = extract_param
($param, 'bwlimit');
858 my $force = extract_param
($param, 'force');
859 my $pool = extract_param
($param, 'pool');
860 my $start_after_create = extract_param
($param, 'start');
861 my $storage = extract_param
($param, 'storage');
862 my $unique = extract_param
($param, 'unique');
863 my $live_restore = extract_param
($param, 'live-restore');
865 if (defined(my $ssh_keys = $param->{sshkeys
})) {
866 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
867 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
870 $param->{cpuunits
} = PVE
::CGroup
::clamp_cpu_shares
($param->{cpuunits
})
871 if defined($param->{cpuunits
}); # clamp value depending on cgroup version
873 PVE
::Cluster
::check_cfs_quorum
();
875 my $filename = PVE
::QemuConfig-
>config_file($vmid);
876 my $storecfg = PVE
::Storage
::config
();
878 if (defined($pool)) {
879 $rpcenv->check_pool_exist($pool);
882 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
883 if defined($storage);
885 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
887 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
889 } elsif ($archive && $force && (-f
$filename) &&
890 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
891 # OK: user has VM.Backup permissions and wants to restore an existing VM
897 for my $opt (sort keys $param->%*) {
898 if (PVE
::QemuServer
::Drive
::is_valid_drivename
($opt)) {
899 raise_param_exc
({ $opt => "option conflicts with option 'archive'" });
903 if ($archive eq '-') {
904 die "pipe requires cli environment\n" if $rpcenv->{type
} ne 'cli';
905 $archive = { type
=> 'pipe' };
907 PVE
::Storage
::check_volume_access
(
916 $archive = $parse_restore_archive->($storecfg, $archive);
920 if (scalar(keys $param->%*) > 0) {
921 &$resolve_cdrom_alias($param);
923 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
925 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
927 &$check_vm_create_serial_perm($rpcenv, $authuser, $vmid, $pool, $param);
928 check_vm_create_usb_perm
($rpcenv, $authuser, $vmid, $pool, $param);
929 check_vm_create_hostpci_perm
($rpcenv, $authuser, $vmid, $pool, $param);
931 PVE
::QemuServer
::check_bridge_access
($rpcenv, $authuser, $param);
932 &$check_cpu_model_access($rpcenv, $authuser, $param);
934 $check_drive_param->($param, $storecfg);
936 PVE
::QemuServer
::add_random_macs
($param);
939 my $emsg = $is_restore ?
"unable to restore VM $vmid -" : "unable to create VM $vmid -";
941 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, $force) };
942 die "$emsg $@" if $@;
944 my $restored_data = 0;
945 my $restorefn = sub {
946 my $conf = PVE
::QemuConfig-
>load_config($vmid);
948 PVE
::QemuConfig-
>check_protection($conf, $emsg);
950 die "$emsg vm is running\n" if PVE
::QemuServer
::check_running
($vmid);
953 my $restore_options = {
958 live
=> $live_restore,
959 override_conf
=> $param,
961 if (my $volid = $archive->{volid
}) {
962 # best effort, real check is after restoring!
964 my $old_conf = PVE
::Storage
::extract_vzdump_config
($storecfg, $volid);
965 PVE
::QemuServer
::restore_merge_config
("backup/qemu-server/$vmid.conf", $old_conf, $param);
968 warn "Could not extract backed up config: $@\n";
969 warn "Skipping early checks!\n";
971 PVE
::QemuServer
::check_restore_permissions
($rpcenv, $authuser, $merged);
974 if ($archive->{type
} eq 'file' || $archive->{type
} eq 'pipe') {
975 die "live-restore is only compatible with backup images from a Proxmox Backup Server\n"
977 PVE
::QemuServer
::restore_file_archive
($archive->{path
} // '-', $vmid, $authuser, $restore_options);
978 } elsif ($archive->{type
} eq 'pbs') {
979 PVE
::QemuServer
::restore_proxmox_backup_archive
($archive->{volid
}, $vmid, $authuser, $restore_options);
981 die "unknown backup archive type\n";
985 my $restored_conf = PVE
::QemuConfig-
>load_config($vmid);
986 # Convert restored VM to template if backup was VM template
987 if (PVE
::QemuConfig-
>is_template($restored_conf)) {
988 warn "Convert to template.\n";
989 eval { PVE
::QemuServer
::template_create
($vmid, $restored_conf) };
994 # ensure no old replication state are exists
995 PVE
::ReplicationState
::delete_guest_states
($vmid);
997 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
999 if ($start_after_create && !$live_restore) {
1000 print "Execute autostart\n";
1001 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
1006 my $createfn = sub {
1007 # ensure no old replication state are exists
1008 PVE
::ReplicationState
::delete_guest_states
($vmid);
1012 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
1014 $conf->{meta
} = PVE
::QemuServer
::new_meta_info_string
();
1018 ($vollist, my $created_opts) = $create_disks->(
1029 $conf->{$_} = $created_opts->{$_} for keys $created_opts->%*;
1031 if (!$conf->{boot
}) {
1032 my $devs = PVE
::QemuServer
::get_default_bootdevices
($conf);
1033 $conf->{boot
} = PVE
::QemuServer
::print_bootorder
($devs);
1036 # auto generate uuid if user did not specify smbios1 option
1037 if (!$conf->{smbios1
}) {
1038 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
1041 if ((!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') && $arch ne 'aarch64') {
1042 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
1045 my $machine = $conf->{machine
};
1046 if (!$machine || $machine =~ m/^(?:pc|q35|virt)$/) {
1047 # always pin Windows' machine version on create, they get to easily confused
1048 if (PVE
::QemuServer
::Helpers
::windows_version
($conf->{ostype
})) {
1049 $conf->{machine
} = PVE
::QemuServer
::windows_get_pinned_machine_version
($machine);
1053 PVE
::QemuConfig-
>write_config($vmid, $conf);
1059 foreach my $volid (@$vollist) {
1060 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
1066 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
1069 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
1071 if ($start_after_create) {
1072 print "Execute autostart\n";
1073 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
1078 my ($code, $worker_name);
1080 $worker_name = 'qmrestore';
1082 eval { $restorefn->() };
1084 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
1086 if ($restored_data) {
1087 warn "error after data was restored, VM disks should be OK but config may "
1088 ."require adaptions. VM $vmid state is NOT cleaned up.\n";
1090 warn "error before or during data restore, some or all disks were not "
1091 ."completely restored. VM $vmid state is NOT cleaned up.\n";
1097 $worker_name = 'qmcreate';
1099 eval { $createfn->() };
1102 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
1103 unlink($conffile) or die "failed to remove config file: $!\n";
1111 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
1114 __PACKAGE__-
>register_method({
1119 description
=> "Directory index",
1124 additionalProperties
=> 0,
1126 node
=> get_standard_option
('pve-node'),
1127 vmid
=> get_standard_option
('pve-vmid'),
1135 subdir
=> { type
=> 'string' },
1138 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1144 { subdir
=> 'config' },
1145 { subdir
=> 'cloudinit' },
1146 { subdir
=> 'pending' },
1147 { subdir
=> 'status' },
1148 { subdir
=> 'unlink' },
1149 { subdir
=> 'vncproxy' },
1150 { subdir
=> 'termproxy' },
1151 { subdir
=> 'migrate' },
1152 { subdir
=> 'resize' },
1153 { subdir
=> 'move' },
1154 { subdir
=> 'rrd' },
1155 { subdir
=> 'rrddata' },
1156 { subdir
=> 'monitor' },
1157 { subdir
=> 'agent' },
1158 { subdir
=> 'snapshot' },
1159 { subdir
=> 'spiceproxy' },
1160 { subdir
=> 'sendkey' },
1161 { subdir
=> 'firewall' },
1162 { subdir
=> 'mtunnel' },
1163 { subdir
=> 'remote_migrate' },
1169 __PACKAGE__-
>register_method ({
1170 subclass
=> "PVE::API2::Firewall::VM",
1171 path
=> '{vmid}/firewall',
1174 __PACKAGE__-
>register_method ({
1175 subclass
=> "PVE::API2::Qemu::Agent",
1176 path
=> '{vmid}/agent',
1179 __PACKAGE__-
>register_method({
1181 path
=> '{vmid}/rrd',
1183 protected
=> 1, # fixme: can we avoid that?
1185 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1187 description
=> "Read VM RRD statistics (returns PNG)",
1189 additionalProperties
=> 0,
1191 node
=> get_standard_option
('pve-node'),
1192 vmid
=> get_standard_option
('pve-vmid'),
1194 description
=> "Specify the time frame you are interested in.",
1196 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
1199 description
=> "The list of datasources you want to display.",
1200 type
=> 'string', format
=> 'pve-configid-list',
1203 description
=> "The RRD consolidation function",
1205 enum
=> [ 'AVERAGE', 'MAX' ],
1213 filename
=> { type
=> 'string' },
1219 return PVE
::RRD
::create_rrd_graph
(
1220 "pve2-vm/$param->{vmid}", $param->{timeframe
},
1221 $param->{ds
}, $param->{cf
});
1225 __PACKAGE__-
>register_method({
1227 path
=> '{vmid}/rrddata',
1229 protected
=> 1, # fixme: can we avoid that?
1231 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1233 description
=> "Read VM RRD statistics",
1235 additionalProperties
=> 0,
1237 node
=> get_standard_option
('pve-node'),
1238 vmid
=> get_standard_option
('pve-vmid'),
1240 description
=> "Specify the time frame you are interested in.",
1242 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
1245 description
=> "The RRD consolidation function",
1247 enum
=> [ 'AVERAGE', 'MAX' ],
1262 return PVE
::RRD
::create_rrd_data
(
1263 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
1267 __PACKAGE__-
>register_method({
1268 name
=> 'vm_config',
1269 path
=> '{vmid}/config',
1272 description
=> "Get the virtual machine configuration with pending configuration " .
1273 "changes applied. Set the 'current' parameter to get the current configuration instead.",
1275 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1278 additionalProperties
=> 0,
1280 node
=> get_standard_option
('pve-node'),
1281 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1283 description
=> "Get current values (instead of pending values).",
1288 snapshot
=> get_standard_option
('pve-snapshot-name', {
1289 description
=> "Fetch config values from given snapshot.",
1292 my ($cmd, $pname, $cur, $args) = @_;
1293 PVE
::QemuConfig-
>snapshot_list($args->[0]);
1299 description
=> "The VM configuration.",
1301 properties
=> PVE
::QemuServer
::json_config_properties
({
1304 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
1311 raise_param_exc
({ snapshot
=> "cannot use 'snapshot' parameter with 'current'",
1312 current
=> "cannot use 'snapshot' parameter with 'current'"})
1313 if ($param->{snapshot
} && $param->{current
});
1316 if ($param->{snapshot
}) {
1317 $conf = PVE
::QemuConfig-
>load_snapshot_config($param->{vmid
}, $param->{snapshot
});
1319 $conf = PVE
::QemuConfig-
>load_current_config($param->{vmid
}, $param->{current
});
1321 $conf->{cipassword
} = '**********' if $conf->{cipassword
};
1326 __PACKAGE__-
>register_method({
1327 name
=> 'vm_pending',
1328 path
=> '{vmid}/pending',
1331 description
=> "Get the virtual machine configuration with both current and pending values.",
1333 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1336 additionalProperties
=> 0,
1338 node
=> get_standard_option
('pve-node'),
1339 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1348 description
=> "Configuration option name.",
1352 description
=> "Current value.",
1357 description
=> "Pending value.",
1362 description
=> "Indicates a pending delete request if present and not 0. " .
1363 "The value 2 indicates a force-delete request.",
1375 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1377 my $pending_delete_hash = PVE
::QemuConfig-
>parse_pending_delete($conf->{pending
}->{delete});
1379 $conf->{cipassword
} = '**********' if defined($conf->{cipassword
});
1380 $conf->{pending
}->{cipassword
} = '********** ' if defined($conf->{pending
}->{cipassword
});
1382 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);
1385 __PACKAGE__-
>register_method({
1386 name
=> 'cloudinit_pending',
1387 path
=> '{vmid}/cloudinit',
1390 description
=> "Get the cloudinit configuration with both current and pending values.",
1392 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1395 additionalProperties
=> 0,
1397 node
=> get_standard_option
('pve-node'),
1398 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1407 description
=> "Configuration option name.",
1411 description
=> "Value as it was used to generate the current cloudinit image.",
1416 description
=> "The new pending value.",
1421 description
=> "Indicates a pending delete request if present and not 0. ",
1433 my $vmid = $param->{vmid
};
1434 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1436 my $ci = $conf->{cloudinit
};
1438 $conf->{cipassword
} = '**********' if exists $conf->{cipassword
};
1439 $ci->{cipassword
} = '**********' if exists $ci->{cipassword
};
1443 # All the values that got added
1444 my $added = delete($ci->{added
}) // '';
1445 for my $key (PVE
::Tools
::split_list
($added)) {
1446 push @$res, { key
=> $key, pending
=> $conf->{$key} };
1449 # All already existing values (+ their new value, if it exists)
1450 for my $opt (keys %$cloudinitoptions) {
1451 next if !$conf->{$opt};
1452 next if $added =~ m/$opt/;
1457 if (my $pending = $ci->{$opt}) {
1458 $item->{value
} = $pending;
1459 $item->{pending
} = $conf->{$opt};
1461 $item->{value
} = $conf->{$opt},
1467 # Now, we'll find the deleted ones
1468 for my $opt (keys %$ci) {
1469 next if $conf->{$opt};
1470 push @$res, { key
=> $opt, delete => 1 };
1476 __PACKAGE__-
>register_method({
1477 name
=> 'cloudinit_update',
1478 path
=> '{vmid}/cloudinit',
1482 description
=> "Regenerate and change cloudinit config drive.",
1484 check
=> ['perm', '/vms/{vmid}', 'VM.Config.Cloudinit'],
1487 additionalProperties
=> 0,
1489 node
=> get_standard_option
('pve-node'),
1490 vmid
=> get_standard_option
('pve-vmid'),
1493 returns
=> { type
=> 'null' },
1497 my $rpcenv = PVE
::RPCEnvironment
::get
();
1498 my $authuser = $rpcenv->get_user();
1500 my $vmid = extract_param
($param, 'vmid');
1502 PVE
::QemuConfig-
>lock_config($vmid, sub {
1503 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1504 PVE
::QemuConfig-
>check_lock($conf);
1506 my $storecfg = PVE
::Storage
::config
();
1507 PVE
::QemuServer
::vmconfig_update_cloudinit_drive
($storecfg, $conf, $vmid);
1512 # POST/PUT {vmid}/config implementation
1514 # The original API used PUT (idempotent) an we assumed that all operations
1515 # are fast. But it turned out that almost any configuration change can
1516 # involve hot-plug actions, or disk alloc/free. Such actions can take long
1517 # time to complete and have side effects (not idempotent).
1519 # The new implementation uses POST and forks a worker process. We added
1520 # a new option 'background_delay'. If specified we wait up to
1521 # 'background_delay' second for the worker task to complete. It returns null
1522 # if the task is finished within that time, else we return the UPID.
1524 my $update_vm_api = sub {
1525 my ($param, $sync) = @_;
1527 my $rpcenv = PVE
::RPCEnvironment
::get
();
1529 my $authuser = $rpcenv->get_user();
1531 my $node = extract_param
($param, 'node');
1533 my $vmid = extract_param
($param, 'vmid');
1535 my $digest = extract_param
($param, 'digest');
1537 my $background_delay = extract_param
($param, 'background_delay');
1539 my $skip_cloud_init = extract_param
($param, 'skip_cloud_init');
1541 if (defined(my $cipassword = $param->{cipassword
})) {
1542 # Same logic as in cloud-init (but with the regex fixed...)
1543 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
1544 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
1547 my @paramarr = (); # used for log message
1548 foreach my $key (sort keys %$param) {
1549 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
1550 push @paramarr, "-$key", $value;
1553 my $skiplock = extract_param
($param, 'skiplock');
1554 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1555 if $skiplock && $authuser ne 'root@pam';
1557 my $delete_str = extract_param
($param, 'delete');
1559 my $revert_str = extract_param
($param, 'revert');
1561 my $force = extract_param
($param, 'force');
1563 if (defined(my $ssh_keys = $param->{sshkeys
})) {
1564 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
1565 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
1568 $param->{cpuunits
} = PVE
::CGroup
::clamp_cpu_shares
($param->{cpuunits
})
1569 if defined($param->{cpuunits
}); # clamp value depending on cgroup version
1571 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
1573 my $storecfg = PVE
::Storage
::config
();
1575 my $defaults = PVE
::QemuServer
::load_defaults
();
1577 &$resolve_cdrom_alias($param);
1579 # now try to verify all parameters
1582 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1583 if (!PVE
::QemuServer
::option_exists
($opt)) {
1584 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1587 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1588 "-revert $opt' at the same time" })
1589 if defined($param->{$opt});
1591 $revert->{$opt} = 1;
1595 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1596 $opt = 'ide2' if $opt eq 'cdrom';
1598 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1599 "-delete $opt' at the same time" })
1600 if defined($param->{$opt});
1602 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1603 "-revert $opt' at the same time" })
1606 if (!PVE
::QemuServer
::option_exists
($opt)) {
1607 raise_param_exc
({ delete => "unknown option '$opt'" });
1613 my $repl_conf = PVE
::ReplicationConfig-
>new();
1614 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1615 my $check_replication = sub {
1617 return if !$is_replicated;
1618 my $volid = $drive->{file
};
1619 return if !$volid || !($drive->{replicate
}//1);
1620 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1622 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1623 die "cannot add non-managed/pass-through volume to a replicated VM\n"
1624 if !defined($storeid);
1626 return if defined($volname) && $volname eq 'cloudinit';
1629 if ($volid =~ $NEW_DISK_RE) {
1631 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1633 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1635 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1636 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1637 return if $scfg->{shared
};
1638 die "cannot add non-replicatable volume to a replicated VM\n";
1641 $check_drive_param->($param, $storecfg, $check_replication);
1643 foreach my $opt (keys %$param) {
1644 if ($opt =~ m/^net(\d+)$/) {
1646 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1647 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1648 } elsif ($opt eq 'vmgenid') {
1649 if ($param->{$opt} eq '1') {
1650 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1652 } elsif ($opt eq 'hookscript') {
1653 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1654 raise_param_exc
({ $opt => $@ }) if $@;
1658 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1660 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1662 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1664 PVE
::QemuServer
::check_bridge_access
($rpcenv, $authuser, $param);
1666 my $updatefn = sub {
1668 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1670 die "checksum missmatch (file change by other user?)\n"
1671 if $digest && $digest ne $conf->{digest
};
1673 &$check_cpu_model_access($rpcenv, $authuser, $param, $conf);
1675 # FIXME: 'suspended' lock should probabyl be a state or "weak" lock?!
1676 if (scalar(@delete) && grep { $_ eq 'vmstate'} @delete) {
1677 if (defined($conf->{lock}) && $conf->{lock} eq 'suspended') {
1678 delete $conf->{lock}; # for check lock check, not written out
1679 push @delete, 'lock'; # this is the real deal to write it out
1681 push @delete, 'runningmachine' if $conf->{runningmachine
};
1682 push @delete, 'runningcpu' if $conf->{runningcpu
};
1685 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1687 foreach my $opt (keys %$revert) {
1688 if (defined($conf->{$opt})) {
1689 $param->{$opt} = $conf->{$opt};
1690 } elsif (defined($conf->{pending
}->{$opt})) {
1695 if ($param->{memory
} || defined($param->{balloon
})) {
1696 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1697 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1699 die "balloon value too large (must be smaller than assigned memory)\n"
1700 if $balloon && $balloon > $maxmem;
1703 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1707 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1709 # write updates to pending section
1711 my $modified = {}; # record what $option we modify
1714 if (my $boot = $conf->{boot
}) {
1715 my $bootcfg = PVE
::JSONSchema
::parse_property_string
('pve-qm-boot', $boot);
1716 @bootorder = PVE
::Tools
::split_list
($bootcfg->{order
}) if $bootcfg && $bootcfg->{order
};
1718 my $bootorder_deleted = grep {$_ eq 'bootorder'} @delete;
1720 my $check_drive_perms = sub {
1721 my ($opt, $val) = @_;
1722 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val, 1);
1723 if (PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
1724 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Cloudinit', 'VM.Config.CDROM']);
1725 } elsif (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) { # CDROM
1726 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1728 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1733 foreach my $opt (@delete) {
1734 $modified->{$opt} = 1;
1735 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1737 # value of what we want to delete, independent if pending or not
1738 my $val = $conf->{$opt} // $conf->{pending
}->{$opt};
1739 if (!defined($val)) {
1740 warn "cannot delete '$opt' - not set in current configuration!\n";
1741 $modified->{$opt} = 0;
1744 my $is_pending_val = defined($conf->{pending
}->{$opt});
1745 delete $conf->{pending
}->{$opt};
1747 # remove from bootorder if necessary
1748 if (!$bootorder_deleted && @bootorder && grep {$_ eq $opt} @bootorder) {
1749 @bootorder = grep {$_ ne $opt} @bootorder;
1750 $conf->{pending
}->{boot
} = PVE
::QemuServer
::print_bootorder
(\
@bootorder);
1751 $modified->{boot
} = 1;
1754 if ($opt =~ m/^unused/) {
1755 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1756 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1757 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1758 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1759 delete $conf->{$opt};
1760 PVE
::QemuConfig-
>write_config($vmid, $conf);
1762 } elsif ($opt eq 'vmstate') {
1763 PVE
::QemuConfig-
>check_protection($conf, "can't remove vmstate '$val'");
1764 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, { file
=> $val }, $rpcenv, $authuser, 1)) {
1765 delete $conf->{$opt};
1766 PVE
::QemuConfig-
>write_config($vmid, $conf);
1768 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1769 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1770 $check_drive_perms->($opt, $val);
1771 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $val))
1773 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1774 PVE
::QemuConfig-
>write_config($vmid, $conf);
1775 } elsif ($opt =~ m/^serial\d+$/) {
1776 if ($val eq 'socket') {
1777 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1778 } elsif ($authuser ne 'root@pam') {
1779 die "only root can delete '$opt' config for real devices\n";
1781 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1782 PVE
::QemuConfig-
>write_config($vmid, $conf);
1783 } elsif ($opt =~ m/^usb\d+$/) {
1784 check_usb_perm
($rpcenv, $authuser, $vmid, undef, $opt, $val);
1785 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1786 PVE
::QemuConfig-
>write_config($vmid, $conf);
1787 } elsif ($opt =~ m/^hostpci\d+$/) {
1788 check_hostpci_perm
($rpcenv, $authuser, $vmid, undef, $opt, $val);
1789 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1790 PVE
::QemuConfig-
>write_config($vmid, $conf);
1791 } elsif ($opt eq 'tags') {
1792 assert_tag_permissions
($vmid, $val, '', $rpcenv, $authuser);
1793 delete $conf->{$opt};
1794 PVE
::QemuConfig-
>write_config($vmid, $conf);
1795 } elsif ($opt =~ m/^net\d+$/) {
1796 if ($conf->{$opt}) {
1797 PVE
::QemuServer
::check_bridge_access
(
1798 $rpcenv, $authuser, { $opt => $conf->{$opt} });
1800 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1801 PVE
::QemuConfig-
>write_config($vmid, $conf);
1803 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1804 PVE
::QemuConfig-
>write_config($vmid, $conf);
1808 foreach my $opt (keys %$param) { # add/change
1809 $modified->{$opt} = 1;
1810 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1811 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1813 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
1815 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1817 if ($conf->{$opt}) {
1818 $check_drive_perms->($opt, $conf->{$opt});
1822 $check_drive_perms->($opt, $param->{$opt});
1823 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1824 if defined($conf->{pending
}->{$opt});
1826 my (undef, $created_opts) = $create_disks->(
1834 {$opt => $param->{$opt}},
1836 $conf->{pending
}->{$_} = $created_opts->{$_} for keys $created_opts->%*;
1838 # default legacy boot order implies all cdroms anyway
1840 # append new CD drives to bootorder to mark them bootable
1841 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt}, 1);
1842 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1) && !grep(/^$opt$/, @bootorder)) {
1843 push @bootorder, $opt;
1844 $conf->{pending
}->{boot
} = PVE
::QemuServer
::print_bootorder
(\
@bootorder);
1845 $modified->{boot
} = 1;
1848 } elsif ($opt =~ m/^serial\d+/) {
1849 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1850 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1851 } elsif ($authuser ne 'root@pam') {
1852 die "only root can modify '$opt' config for real devices\n";
1854 $conf->{pending
}->{$opt} = $param->{$opt};
1855 } elsif ($opt =~ m/^usb\d+/) {
1856 if (my $olddevice = $conf->{$opt}) {
1857 check_usb_perm
($rpcenv, $authuser, $vmid, undef, $opt, $conf->{$opt});
1859 check_usb_perm
($rpcenv, $authuser, $vmid, undef, $opt, $param->{$opt});
1860 $conf->{pending
}->{$opt} = $param->{$opt};
1861 } elsif ($opt =~ m/^hostpci\d+$/) {
1862 if (my $oldvalue = $conf->{$opt}) {
1863 check_hostpci_perm
($rpcenv, $authuser, $vmid, undef, $opt, $oldvalue);
1865 check_hostpci_perm
($rpcenv, $authuser, $vmid, undef, $opt, $param->{$opt});
1866 $conf->{pending
}->{$opt} = $param->{$opt};
1867 } elsif ($opt eq 'tags') {
1868 assert_tag_permissions
($vmid, $conf->{$opt}, $param->{$opt}, $rpcenv, $authuser);
1869 $conf->{pending
}->{$opt} = PVE
::GuestHelpers
::get_unique_tags
($param->{$opt});
1870 } elsif ($opt =~ m/^net\d+$/) {
1871 if ($conf->{$opt}) {
1872 PVE
::QemuServer
::check_bridge_access
(
1873 $rpcenv, $authuser, { $opt => $conf->{$opt} });
1875 $conf->{pending
}->{$opt} = $param->{$opt};
1877 $conf->{pending
}->{$opt} = $param->{$opt};
1879 if ($opt eq 'boot') {
1880 my $new_bootcfg = PVE
::JSONSchema
::parse_property_string
('pve-qm-boot', $param->{$opt});
1881 if ($new_bootcfg->{order
}) {
1882 my @devs = PVE
::Tools
::split_list
($new_bootcfg->{order
});
1883 for my $dev (@devs) {
1884 my $exists = $conf->{$dev} || $conf->{pending
}->{$dev} || $param->{$dev};
1885 my $deleted = grep {$_ eq $dev} @delete;
1886 die "invalid bootorder: device '$dev' does not exist'\n"
1887 if !$exists || $deleted;
1890 # remove legacy boot order settings if new one set
1891 $conf->{pending
}->{$opt} = PVE
::QemuServer
::print_bootorder
(\
@devs);
1892 PVE
::QemuConfig-
>add_to_pending_delete($conf, "bootdisk")
1893 if $conf->{bootdisk
};
1897 PVE
::QemuConfig-
>remove_from_pending_delete($conf, $opt);
1898 PVE
::QemuConfig-
>write_config($vmid, $conf);
1901 # remove pending changes when nothing changed
1902 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1903 my $changes = PVE
::QemuConfig-
>cleanup_pending($conf);
1904 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1906 return if !scalar(keys %{$conf->{pending
}});
1908 my $running = PVE
::QemuServer
::check_running
($vmid);
1910 # apply pending changes
1912 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1916 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1918 # cloud_init must be skipped if we are in an incoming, remote live migration
1919 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $errors, $skip_cloud_init);
1921 raise_param_exc
($errors) if scalar(keys %$errors);
1930 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1932 if ($background_delay) {
1934 # Note: It would be better to do that in the Event based HTTPServer
1935 # to avoid blocking call to sleep.
1937 my $end_time = time() + $background_delay;
1939 my $task = PVE
::Tools
::upid_decode
($upid);
1942 while (time() < $end_time) {
1943 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1945 sleep(1); # this gets interrupted when child process ends
1949 my $status = PVE
::Tools
::upid_read_status
($upid);
1950 return if !PVE
::Tools
::upid_status_is_error
($status);
1951 die "failed to update VM $vmid: $status\n";
1959 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1962 my $vm_config_perm_list = [
1967 'VM.Config.Network',
1969 'VM.Config.Options',
1970 'VM.Config.Cloudinit',
1973 __PACKAGE__-
>register_method({
1974 name
=> 'update_vm_async',
1975 path
=> '{vmid}/config',
1979 description
=> "Set virtual machine options (asynchrounous API).",
1981 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1984 additionalProperties
=> 0,
1985 properties
=> PVE
::QemuServer
::json_config_properties
(
1987 node
=> get_standard_option
('pve-node'),
1988 vmid
=> get_standard_option
('pve-vmid'),
1989 skiplock
=> get_standard_option
('skiplock'),
1991 type
=> 'string', format
=> 'pve-configid-list',
1992 description
=> "A list of settings you want to delete.",
1996 type
=> 'string', format
=> 'pve-configid-list',
1997 description
=> "Revert a pending change.",
2002 description
=> $opt_force_description,
2004 requires
=> 'delete',
2008 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2012 background_delay
=> {
2014 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
2020 1, # with_disk_alloc
2027 code
=> $update_vm_api,
2030 __PACKAGE__-
>register_method({
2031 name
=> 'update_vm',
2032 path
=> '{vmid}/config',
2036 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
2038 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
2041 additionalProperties
=> 0,
2042 properties
=> PVE
::QemuServer
::json_config_properties
(
2044 node
=> get_standard_option
('pve-node'),
2045 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2046 skiplock
=> get_standard_option
('skiplock'),
2048 type
=> 'string', format
=> 'pve-configid-list',
2049 description
=> "A list of settings you want to delete.",
2053 type
=> 'string', format
=> 'pve-configid-list',
2054 description
=> "Revert a pending change.",
2059 description
=> $opt_force_description,
2061 requires
=> 'delete',
2065 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2070 1, # with_disk_alloc
2073 returns
=> { type
=> 'null' },
2076 &$update_vm_api($param, 1);
2081 __PACKAGE__-
>register_method({
2082 name
=> 'destroy_vm',
2087 description
=> "Destroy the VM and all used/owned volumes. Removes any VM specific permissions"
2088 ." and firewall rules",
2090 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
2093 additionalProperties
=> 0,
2095 node
=> get_standard_option
('pve-node'),
2096 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
2097 skiplock
=> get_standard_option
('skiplock'),
2100 description
=> "Remove VMID from configurations, like backup & replication jobs and HA.",
2103 'destroy-unreferenced-disks' => {
2105 description
=> "If set, destroy additionally all disks not referenced in the config"
2106 ." but with a matching VMID from all enabled storages.",
2118 my $rpcenv = PVE
::RPCEnvironment
::get
();
2119 my $authuser = $rpcenv->get_user();
2120 my $vmid = $param->{vmid
};
2122 my $skiplock = $param->{skiplock
};
2123 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2124 if $skiplock && $authuser ne 'root@pam';
2126 my $early_checks = sub {
2128 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2129 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
2131 my $ha_managed = PVE
::HA
::Config
::service_is_configured
("vm:$vmid");
2133 if (!$param->{purge
}) {
2134 die "unable to remove VM $vmid - used in HA resources and purge parameter not set.\n"
2136 # don't allow destroy if with replication jobs but no purge param
2137 my $repl_conf = PVE
::ReplicationConfig-
>new();
2138 $repl_conf->check_for_existing_jobs($vmid);
2141 die "VM $vmid is running - destroy failed\n"
2142 if PVE
::QemuServer
::check_running
($vmid);
2152 my $storecfg = PVE
::Storage
::config
();
2154 syslog
('info', "destroy VM $vmid: $upid\n");
2155 PVE
::QemuConfig-
>lock_config($vmid, sub {
2156 # repeat, config might have changed
2157 my $ha_managed = $early_checks->();
2159 my $purge_unreferenced = $param->{'destroy-unreferenced-disks'};
2161 PVE
::QemuServer
::destroy_vm
(
2164 $skiplock, { lock => 'destroyed' },
2165 $purge_unreferenced,
2168 PVE
::AccessControl
::remove_vm_access
($vmid);
2169 PVE
::Firewall
::remove_vmfw_conf
($vmid);
2170 if ($param->{purge
}) {
2171 print "purging VM $vmid from related configurations..\n";
2172 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
2173 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
2176 PVE
::HA
::Config
::delete_service_from_config
("vm:$vmid");
2177 print "NOTE: removed VM $vmid from HA resource configuration.\n";
2181 # only now remove the zombie config, else we can have reuse race
2182 PVE
::QemuConfig-
>destroy_config($vmid);
2186 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
2189 __PACKAGE__-
>register_method({
2191 path
=> '{vmid}/unlink',
2195 description
=> "Unlink/delete disk images.",
2197 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
2200 additionalProperties
=> 0,
2202 node
=> get_standard_option
('pve-node'),
2203 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2205 type
=> 'string', format
=> 'pve-configid-list',
2206 description
=> "A list of disk IDs you want to delete.",
2210 description
=> $opt_force_description,
2215 returns
=> { type
=> 'null'},
2219 $param->{delete} = extract_param
($param, 'idlist');
2221 __PACKAGE__-
>update_vm($param);
2226 # uses good entropy, each char is limited to 6 bit to get printable chars simply
2227 my $gen_rand_chars = sub {
2230 die "invalid length $length" if $length < 1;
2232 my $min = ord('!'); # first printable ascii
2234 my $rand_bytes = Crypt
::OpenSSL
::Random
::random_bytes
($length);
2235 die "failed to generate random bytes!\n"
2238 my $str = join('', map { chr((ord($_) & 0x3F) + $min) } split('', $rand_bytes));
2245 __PACKAGE__-
>register_method({
2247 path
=> '{vmid}/vncproxy',
2251 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2253 description
=> "Creates a TCP VNC proxy connections.",
2255 additionalProperties
=> 0,
2257 node
=> get_standard_option
('pve-node'),
2258 vmid
=> get_standard_option
('pve-vmid'),
2262 description
=> "starts websockify instead of vncproxy",
2264 'generate-password' => {
2268 description
=> "Generates a random password to be used as ticket instead of the API ticket.",
2273 additionalProperties
=> 0,
2275 user
=> { type
=> 'string' },
2276 ticket
=> { type
=> 'string' },
2279 description
=> "Returned if requested with 'generate-password' param."
2280 ." Consists of printable ASCII characters ('!' .. '~').",
2283 cert
=> { type
=> 'string' },
2284 port
=> { type
=> 'integer' },
2285 upid
=> { type
=> 'string' },
2291 my $rpcenv = PVE
::RPCEnvironment
::get
();
2293 my $authuser = $rpcenv->get_user();
2295 my $vmid = $param->{vmid
};
2296 my $node = $param->{node
};
2297 my $websocket = $param->{websocket
};
2299 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
2303 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
2304 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
2307 my $authpath = "/vms/$vmid";
2309 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
2310 my $password = $ticket;
2311 if ($param->{'generate-password'}) {
2312 $password = $gen_rand_chars->(8);
2315 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
2321 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
2322 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
2323 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
2324 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
2325 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, defined($serial) ?
'-t' : '-T');
2327 $family = PVE
::Tools
::get_host_address_family
($node);
2330 my $port = PVE
::Tools
::next_vnc_port
($family);
2337 syslog
('info', "starting vnc proxy $upid\n");
2341 if (defined($serial)) {
2343 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $serial, '-escape', '0' ];
2345 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
2346 '-timeout', $timeout, '-authpath', $authpath,
2347 '-perm', 'Sys.Console'];
2349 if ($param->{websocket
}) {
2350 $ENV{PVE_VNC_TICKET
} = $password; # pass ticket to vncterm
2351 push @$cmd, '-notls', '-listen', 'localhost';
2354 push @$cmd, '-c', @$remcmd, @$termcmd;
2356 PVE
::Tools
::run_command
($cmd);
2360 $ENV{LC_PVE_TICKET
} = $password if $websocket; # set ticket with "qm vncproxy"
2362 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
2364 my $sock = IO
::Socket
::IP-
>new(
2369 GetAddrInfoFlags
=> 0,
2370 ) or die "failed to create socket: $!\n";
2371 # Inside the worker we shouldn't have any previous alarms
2372 # running anyway...:
2374 local $SIG{ALRM
} = sub { die "connection timed out\n" };
2376 accept(my $cli, $sock) or die "connection failed: $!\n";
2379 if (PVE
::Tools
::run_command
($cmd,
2380 output
=> '>&'.fileno($cli),
2381 input
=> '<&'.fileno($cli),
2384 die "Failed to run vncproxy.\n";
2391 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
2393 PVE
::Tools
::wait_for_vnc_port
($port);
2402 $res->{password
} = $password if $param->{'generate-password'};
2407 __PACKAGE__-
>register_method({
2408 name
=> 'termproxy',
2409 path
=> '{vmid}/termproxy',
2413 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2415 description
=> "Creates a TCP proxy connections.",
2417 additionalProperties
=> 0,
2419 node
=> get_standard_option
('pve-node'),
2420 vmid
=> get_standard_option
('pve-vmid'),
2424 enum
=> [qw(serial0 serial1 serial2 serial3)],
2425 description
=> "opens a serial terminal (defaults to display)",
2430 additionalProperties
=> 0,
2432 user
=> { type
=> 'string' },
2433 ticket
=> { type
=> 'string' },
2434 port
=> { type
=> 'integer' },
2435 upid
=> { type
=> 'string' },
2441 my $rpcenv = PVE
::RPCEnvironment
::get
();
2443 my $authuser = $rpcenv->get_user();
2445 my $vmid = $param->{vmid
};
2446 my $node = $param->{node
};
2447 my $serial = $param->{serial
};
2449 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
2451 if (!defined($serial)) {
2453 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
2454 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
2458 my $authpath = "/vms/$vmid";
2460 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
2465 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
2466 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
2467 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
2468 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, '-t');
2469 push @$remcmd, '--';
2471 $family = PVE
::Tools
::get_host_address_family
($node);
2474 my $port = PVE
::Tools
::next_vnc_port
($family);
2476 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
2477 push @$termcmd, '-iface', $serial if $serial;
2482 syslog
('info', "starting qemu termproxy $upid\n");
2484 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
2485 '--perm', 'VM.Console', '--'];
2486 push @$cmd, @$remcmd, @$termcmd;
2488 PVE
::Tools
::run_command
($cmd);
2491 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
2493 PVE
::Tools
::wait_for_vnc_port
($port);
2503 __PACKAGE__-
>register_method({
2504 name
=> 'vncwebsocket',
2505 path
=> '{vmid}/vncwebsocket',
2508 description
=> "You also need to pass a valid ticket (vncticket).",
2509 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2511 description
=> "Opens a weksocket for VNC traffic.",
2513 additionalProperties
=> 0,
2515 node
=> get_standard_option
('pve-node'),
2516 vmid
=> get_standard_option
('pve-vmid'),
2518 description
=> "Ticket from previous call to vncproxy.",
2523 description
=> "Port number returned by previous vncproxy call.",
2533 port
=> { type
=> 'string' },
2539 my $rpcenv = PVE
::RPCEnvironment
::get
();
2541 my $authuser = $rpcenv->get_user();
2543 my $vmid = $param->{vmid
};
2544 my $node = $param->{node
};
2546 my $authpath = "/vms/$vmid";
2548 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
2550 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
2552 # Note: VNC ports are acessible from outside, so we do not gain any
2553 # security if we verify that $param->{port} belongs to VM $vmid. This
2554 # check is done by verifying the VNC ticket (inside VNC protocol).
2556 my $port = $param->{port
};
2558 return { port
=> $port };
2561 __PACKAGE__-
>register_method({
2562 name
=> 'spiceproxy',
2563 path
=> '{vmid}/spiceproxy',
2568 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2570 description
=> "Returns a SPICE configuration to connect to the VM.",
2572 additionalProperties
=> 0,
2574 node
=> get_standard_option
('pve-node'),
2575 vmid
=> get_standard_option
('pve-vmid'),
2576 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
2579 returns
=> get_standard_option
('remote-viewer-config'),
2583 my $rpcenv = PVE
::RPCEnvironment
::get
();
2585 my $authuser = $rpcenv->get_user();
2587 my $vmid = $param->{vmid
};
2588 my $node = $param->{node
};
2589 my $proxy = $param->{proxy
};
2591 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
2592 my $title = "VM $vmid";
2593 $title .= " - ". $conf->{name
} if $conf->{name
};
2595 my $port = PVE
::QemuServer
::spice_port
($vmid);
2597 my ($ticket, undef, $remote_viewer_config) =
2598 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
2600 mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
2601 mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
2603 return $remote_viewer_config;
2606 __PACKAGE__-
>register_method({
2608 path
=> '{vmid}/status',
2611 description
=> "Directory index",
2616 additionalProperties
=> 0,
2618 node
=> get_standard_option
('pve-node'),
2619 vmid
=> get_standard_option
('pve-vmid'),
2627 subdir
=> { type
=> 'string' },
2630 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
2636 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2639 { subdir
=> 'current' },
2640 { subdir
=> 'start' },
2641 { subdir
=> 'stop' },
2642 { subdir
=> 'reset' },
2643 { subdir
=> 'shutdown' },
2644 { subdir
=> 'suspend' },
2645 { subdir
=> 'reboot' },
2651 __PACKAGE__-
>register_method({
2652 name
=> 'vm_status',
2653 path
=> '{vmid}/status/current',
2656 protected
=> 1, # qemu pid files are only readable by root
2657 description
=> "Get virtual machine status.",
2659 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2662 additionalProperties
=> 0,
2664 node
=> get_standard_option
('pve-node'),
2665 vmid
=> get_standard_option
('pve-vmid'),
2671 %$PVE::QemuServer
::vmstatus_return_properties
,
2673 description
=> "HA manager service status.",
2677 description
=> "QEMU VGA configuration supports spice.",
2682 description
=> "QEMU Guest Agent is enabled in config.",
2692 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2694 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
2695 my $status = $vmstatus->{$param->{vmid
}};
2697 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
2700 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
2701 my $spice = defined($vga->{type
}) && $vga->{type
} =~ /^virtio/;
2702 $spice ||= PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
2703 $status->{spice
} = 1 if $spice;
2705 $status->{agent
} = 1 if PVE
::QemuServer
::get_qga_key
($conf, 'enabled');
2710 __PACKAGE__-
>register_method({
2712 path
=> '{vmid}/status/start',
2716 description
=> "Start virtual machine.",
2718 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2721 additionalProperties
=> 0,
2723 node
=> get_standard_option
('pve-node'),
2724 vmid
=> get_standard_option
('pve-vmid',
2725 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
2726 skiplock
=> get_standard_option
('skiplock'),
2727 stateuri
=> get_standard_option
('pve-qm-stateuri'),
2728 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
2731 enum
=> ['secure', 'insecure'],
2732 description
=> "Migration traffic is encrypted using an SSH " .
2733 "tunnel by default. On secure, completely private networks " .
2734 "this can be disabled to increase performance.",
2737 migration_network
=> {
2738 type
=> 'string', format
=> 'CIDR',
2739 description
=> "CIDR of the (sub) network that is used for migration.",
2742 machine
=> get_standard_option
('pve-qemu-machine'),
2744 description
=> "Override QEMU's -cpu argument with the given string.",
2748 targetstorage
=> get_standard_option
('pve-targetstorage'),
2750 description
=> "Wait maximal timeout seconds.",
2753 default => 'max(30, vm memory in GiB)',
2764 my $rpcenv = PVE
::RPCEnvironment
::get
();
2765 my $authuser = $rpcenv->get_user();
2767 my $node = extract_param
($param, 'node');
2768 my $vmid = extract_param
($param, 'vmid');
2769 my $timeout = extract_param
($param, 'timeout');
2770 my $machine = extract_param
($param, 'machine');
2772 my $get_root_param = sub {
2773 my $value = extract_param
($param, $_[0]);
2774 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2775 if $value && $authuser ne 'root@pam';
2779 my $stateuri = $get_root_param->('stateuri');
2780 my $skiplock = $get_root_param->('skiplock');
2781 my $migratedfrom = $get_root_param->('migratedfrom');
2782 my $migration_type = $get_root_param->('migration_type');
2783 my $migration_network = $get_root_param->('migration_network');
2784 my $targetstorage = $get_root_param->('targetstorage');
2785 my $force_cpu = $get_root_param->('force-cpu');
2789 if ($targetstorage) {
2790 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2792 $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
2793 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
2797 # read spice ticket from STDIN
2799 my $nbd_protocol_version = 0;
2800 my $replicated_volumes = {};
2801 my $offline_volumes = {};
2802 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2803 while (defined(my $line = <STDIN
>)) {
2805 if ($line =~ m/^spice_ticket: (.+)$/) {
2807 } elsif ($line =~ m/^nbd_protocol_version: (\d+)$/) {
2808 $nbd_protocol_version = $1;
2809 } elsif ($line =~ m/^replicated_volume: (.*)$/) {
2810 $replicated_volumes->{$1} = 1;
2811 } elsif ($line =~ m/^tpmstate0: (.*)$/) { # Deprecated, use offline_volume instead
2812 $offline_volumes->{tpmstate0
} = $1;
2813 } elsif ($line =~ m/^offline_volume: ([^:]+): (.*)$/) {
2814 $offline_volumes->{$1} = $2;
2815 } elsif (!$spice_ticket) {
2816 # fallback for old source node
2817 $spice_ticket = $line;
2819 warn "unknown 'start' parameter on STDIN: '$line'\n";
2824 PVE
::Cluster
::check_cfs_quorum
();
2826 my $storecfg = PVE
::Storage
::config
();
2828 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2832 print "Requesting HA start for VM $vmid\n";
2834 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2835 PVE
::Tools
::run_command
($cmd);
2839 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2846 syslog
('info', "start VM $vmid: $upid\n");
2848 my $migrate_opts = {
2849 migratedfrom
=> $migratedfrom,
2850 spice_ticket
=> $spice_ticket,
2851 network
=> $migration_network,
2852 type
=> $migration_type,
2853 storagemap
=> $storagemap,
2854 nbd_proto_version
=> $nbd_protocol_version,
2855 replicated_volumes
=> $replicated_volumes,
2856 offline_volumes
=> $offline_volumes,
2860 statefile
=> $stateuri,
2861 skiplock
=> $skiplock,
2862 forcemachine
=> $machine,
2863 timeout
=> $timeout,
2864 forcecpu
=> $force_cpu,
2867 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $params, $migrate_opts);
2871 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2875 __PACKAGE__-
>register_method({
2877 path
=> '{vmid}/status/stop',
2881 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2882 "is akin to pulling the power plug of a running computer and may damage the VM data",
2884 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2887 additionalProperties
=> 0,
2889 node
=> get_standard_option
('pve-node'),
2890 vmid
=> get_standard_option
('pve-vmid',
2891 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2892 skiplock
=> get_standard_option
('skiplock'),
2893 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2895 description
=> "Wait maximal timeout seconds.",
2901 description
=> "Do not deactivate storage volumes.",
2914 my $rpcenv = PVE
::RPCEnvironment
::get
();
2915 my $authuser = $rpcenv->get_user();
2917 my $node = extract_param
($param, 'node');
2918 my $vmid = extract_param
($param, 'vmid');
2920 my $skiplock = extract_param
($param, 'skiplock');
2921 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2922 if $skiplock && $authuser ne 'root@pam';
2924 my $keepActive = extract_param
($param, 'keepActive');
2925 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2926 if $keepActive && $authuser ne 'root@pam';
2928 my $migratedfrom = extract_param
($param, 'migratedfrom');
2929 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2930 if $migratedfrom && $authuser ne 'root@pam';
2933 my $storecfg = PVE
::Storage
::config
();
2935 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2940 print "Requesting HA stop for VM $vmid\n";
2942 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
2943 PVE
::Tools
::run_command
($cmd);
2947 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2953 syslog
('info', "stop VM $vmid: $upid\n");
2955 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2956 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2960 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2964 __PACKAGE__-
>register_method({
2966 path
=> '{vmid}/status/reset',
2970 description
=> "Reset virtual machine.",
2972 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2975 additionalProperties
=> 0,
2977 node
=> get_standard_option
('pve-node'),
2978 vmid
=> get_standard_option
('pve-vmid',
2979 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2980 skiplock
=> get_standard_option
('skiplock'),
2989 my $rpcenv = PVE
::RPCEnvironment
::get
();
2991 my $authuser = $rpcenv->get_user();
2993 my $node = extract_param
($param, 'node');
2995 my $vmid = extract_param
($param, 'vmid');
2997 my $skiplock = extract_param
($param, 'skiplock');
2998 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2999 if $skiplock && $authuser ne 'root@pam';
3001 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
3006 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
3011 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
3014 __PACKAGE__-
>register_method({
3015 name
=> 'vm_shutdown',
3016 path
=> '{vmid}/status/shutdown',
3020 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
3021 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
3023 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
3026 additionalProperties
=> 0,
3028 node
=> get_standard_option
('pve-node'),
3029 vmid
=> get_standard_option
('pve-vmid',
3030 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
3031 skiplock
=> get_standard_option
('skiplock'),
3033 description
=> "Wait maximal timeout seconds.",
3039 description
=> "Make sure the VM stops.",
3045 description
=> "Do not deactivate storage volumes.",
3058 my $rpcenv = PVE
::RPCEnvironment
::get
();
3059 my $authuser = $rpcenv->get_user();
3061 my $node = extract_param
($param, 'node');
3062 my $vmid = extract_param
($param, 'vmid');
3064 my $skiplock = extract_param
($param, 'skiplock');
3065 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3066 if $skiplock && $authuser ne 'root@pam';
3068 my $keepActive = extract_param
($param, 'keepActive');
3069 raise_param_exc
({ keepActive
=> "Only root may use this option." })
3070 if $keepActive && $authuser ne 'root@pam';
3072 my $storecfg = PVE
::Storage
::config
();
3076 # if vm is paused, do not shutdown (but stop if forceStop = 1)
3077 # otherwise, we will infer a shutdown command, but run into the timeout,
3078 # then when the vm is resumed, it will instantly shutdown
3080 # checking the qmp status here to get feedback to the gui/cli/api
3081 # and the status query should not take too long
3082 if (PVE
::QemuServer
::vm_is_paused
($vmid)) {
3083 if ($param->{forceStop
}) {
3084 warn "VM is paused - stop instead of shutdown\n";
3087 die "VM is paused - cannot shutdown\n";
3091 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3093 my $timeout = $param->{timeout
} // 60;
3097 print "Requesting HA stop for VM $vmid\n";
3099 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
3100 PVE
::Tools
::run_command
($cmd);
3104 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
3111 syslog
('info', "shutdown VM $vmid: $upid\n");
3113 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
3114 $shutdown, $param->{forceStop
}, $keepActive);
3118 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
3122 __PACKAGE__-
>register_method({
3123 name
=> 'vm_reboot',
3124 path
=> '{vmid}/status/reboot',
3128 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
3130 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
3133 additionalProperties
=> 0,
3135 node
=> get_standard_option
('pve-node'),
3136 vmid
=> get_standard_option
('pve-vmid',
3137 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
3139 description
=> "Wait maximal timeout seconds for the shutdown.",
3152 my $rpcenv = PVE
::RPCEnvironment
::get
();
3153 my $authuser = $rpcenv->get_user();
3155 my $node = extract_param
($param, 'node');
3156 my $vmid = extract_param
($param, 'vmid');
3158 die "VM is paused - cannot shutdown\n" if PVE
::QemuServer
::vm_is_paused
($vmid);
3160 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
3165 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
3166 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
3170 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
3173 __PACKAGE__-
>register_method({
3174 name
=> 'vm_suspend',
3175 path
=> '{vmid}/status/suspend',
3179 description
=> "Suspend virtual machine.",
3181 description
=> "You need 'VM.PowerMgmt' on /vms/{vmid}, and if you have set 'todisk',".
3182 " you need also 'VM.Config.Disk' on /vms/{vmid} and 'Datastore.AllocateSpace'".
3183 " on the storage for the vmstate.",
3184 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
3187 additionalProperties
=> 0,
3189 node
=> get_standard_option
('pve-node'),
3190 vmid
=> get_standard_option
('pve-vmid',
3191 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
3192 skiplock
=> get_standard_option
('skiplock'),
3197 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
3199 statestorage
=> get_standard_option
('pve-storage-id', {
3200 description
=> "The storage for the VM state",
3201 requires
=> 'todisk',
3203 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
3213 my $rpcenv = PVE
::RPCEnvironment
::get
();
3214 my $authuser = $rpcenv->get_user();
3216 my $node = extract_param
($param, 'node');
3217 my $vmid = extract_param
($param, 'vmid');
3219 my $todisk = extract_param
($param, 'todisk') // 0;
3221 my $statestorage = extract_param
($param, 'statestorage');
3223 my $skiplock = extract_param
($param, 'skiplock');
3224 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3225 if $skiplock && $authuser ne 'root@pam';
3227 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
3229 die "Cannot suspend HA managed VM to disk\n"
3230 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
3232 # early check for storage permission, for better user feedback
3234 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
3235 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3237 # cannot save the state of a non-virtualized PCIe device, so resume cannot really work
3238 for my $key (keys %$conf) {
3239 next if $key !~ /^hostpci\d+/;
3240 die "cannot suspend VM to disk due to passed-through PCI device(s), which lack the"
3241 ." possibility to save/restore their internal state\n";
3244 if (!$statestorage) {
3245 # get statestorage from config if none is given
3246 my $storecfg = PVE
::Storage
::config
();
3247 $statestorage = PVE
::QemuServer
::find_vmstate_storage
($conf, $storecfg);
3250 $rpcenv->check($authuser, "/storage/$statestorage", ['Datastore.AllocateSpace']);
3256 syslog
('info', "suspend VM $vmid: $upid\n");
3258 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
3263 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
3264 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
3267 __PACKAGE__-
>register_method({
3268 name
=> 'vm_resume',
3269 path
=> '{vmid}/status/resume',
3273 description
=> "Resume virtual machine.",
3275 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
3278 additionalProperties
=> 0,
3280 node
=> get_standard_option
('pve-node'),
3281 vmid
=> get_standard_option
('pve-vmid',
3282 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
3283 skiplock
=> get_standard_option
('skiplock'),
3284 nocheck
=> { type
=> 'boolean', optional
=> 1 },
3294 my $rpcenv = PVE
::RPCEnvironment
::get
();
3296 my $authuser = $rpcenv->get_user();
3298 my $node = extract_param
($param, 'node');
3300 my $vmid = extract_param
($param, 'vmid');
3302 my $skiplock = extract_param
($param, 'skiplock');
3303 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3304 if $skiplock && $authuser ne 'root@pam';
3306 # nocheck is used as part of migration when config file might be still
3308 my $nocheck = extract_param
($param, 'nocheck');
3309 raise_param_exc
({ nocheck
=> "Only root may use this option." })
3310 if $nocheck && $authuser ne 'root@pam';
3312 my $to_disk_suspended;
3314 PVE
::QemuConfig-
>lock_config($vmid, sub {
3315 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3316 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
3320 die "VM $vmid not running\n"
3321 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
3326 syslog
('info', "resume VM $vmid: $upid\n");
3328 if (!$to_disk_suspended) {
3329 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
3331 my $storecfg = PVE
::Storage
::config
();
3332 PVE
::QemuServer
::vm_start
($storecfg, $vmid, { skiplock
=> $skiplock });
3338 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
3341 __PACKAGE__-
>register_method({
3342 name
=> 'vm_sendkey',
3343 path
=> '{vmid}/sendkey',
3347 description
=> "Send key event to virtual machine.",
3349 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
3352 additionalProperties
=> 0,
3354 node
=> get_standard_option
('pve-node'),
3355 vmid
=> get_standard_option
('pve-vmid',
3356 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
3357 skiplock
=> get_standard_option
('skiplock'),
3359 description
=> "The key (qemu monitor encoding).",
3364 returns
=> { type
=> 'null'},
3368 my $rpcenv = PVE
::RPCEnvironment
::get
();
3370 my $authuser = $rpcenv->get_user();
3372 my $node = extract_param
($param, 'node');
3374 my $vmid = extract_param
($param, 'vmid');
3376 my $skiplock = extract_param
($param, 'skiplock');
3377 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3378 if $skiplock && $authuser ne 'root@pam';
3380 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
3385 __PACKAGE__-
>register_method({
3386 name
=> 'vm_feature',
3387 path
=> '{vmid}/feature',
3391 description
=> "Check if feature for virtual machine is available.",
3393 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3396 additionalProperties
=> 0,
3398 node
=> get_standard_option
('pve-node'),
3399 vmid
=> get_standard_option
('pve-vmid'),
3401 description
=> "Feature to check.",
3403 enum
=> [ 'snapshot', 'clone', 'copy' ],
3405 snapname
=> get_standard_option
('pve-snapshot-name', {
3413 hasFeature
=> { type
=> 'boolean' },
3416 items
=> { type
=> 'string' },
3423 my $node = extract_param
($param, 'node');
3425 my $vmid = extract_param
($param, 'vmid');
3427 my $snapname = extract_param
($param, 'snapname');
3429 my $feature = extract_param
($param, 'feature');
3431 my $running = PVE
::QemuServer
::check_running
($vmid);
3433 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3436 my $snap = $conf->{snapshots
}->{$snapname};
3437 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3440 my $storecfg = PVE
::Storage
::config
();
3442 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
3443 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
3446 hasFeature
=> $hasFeature,
3447 nodes
=> [ keys %$nodelist ],
3451 __PACKAGE__-
>register_method({
3453 path
=> '{vmid}/clone',
3457 description
=> "Create a copy of virtual machine/template.",
3459 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
3460 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
3461 "'Datastore.AllocateSpace' on any used storage and 'SDN.Use' on any used bridge/vnet",
3464 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
3466 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
3467 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
3472 additionalProperties
=> 0,
3474 node
=> get_standard_option
('pve-node'),
3475 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3476 newid
=> get_standard_option
('pve-vmid', {
3477 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
3478 description
=> 'VMID for the clone.' }),
3481 type
=> 'string', format
=> 'dns-name',
3482 description
=> "Set a name for the new VM.",
3487 description
=> "Description for the new VM.",
3491 type
=> 'string', format
=> 'pve-poolid',
3492 description
=> "Add the new VM to the specified pool.",
3494 snapname
=> get_standard_option
('pve-snapshot-name', {
3497 storage
=> get_standard_option
('pve-storage-id', {
3498 description
=> "Target storage for full clone.",
3502 description
=> "Target format for file storage. Only valid for full clone.",
3505 enum
=> [ 'raw', 'qcow2', 'vmdk'],
3510 description
=> "Create a full copy of all disks. This is always done when " .
3511 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
3513 target
=> get_standard_option
('pve-node', {
3514 description
=> "Target node. Only allowed if the original VM is on shared storage.",
3518 description
=> "Override I/O bandwidth limit (in KiB/s).",
3522 default => 'clone limit from datacenter or storage config',
3532 my $rpcenv = PVE
::RPCEnvironment
::get
();
3533 my $authuser = $rpcenv->get_user();
3535 my $node = extract_param
($param, 'node');
3536 my $vmid = extract_param
($param, 'vmid');
3537 my $newid = extract_param
($param, 'newid');
3538 my $pool = extract_param
($param, 'pool');
3540 my $snapname = extract_param
($param, 'snapname');
3541 my $storage = extract_param
($param, 'storage');
3542 my $format = extract_param
($param, 'format');
3543 my $target = extract_param
($param, 'target');
3545 my $localnode = PVE
::INotify
::nodename
();
3547 if ($target && ($target eq $localnode || $target eq 'localhost')) {
3551 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
3553 my $load_and_check = sub {
3554 $rpcenv->check_pool_exist($pool) if defined($pool);
3555 PVE
::Cluster
::check_node_exists
($target) if $target;
3557 my $storecfg = PVE
::Storage
::config
();
3560 # check if storage is enabled on local node
3561 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
3563 # check if storage is available on target node
3564 PVE
::Storage
::storage_check_enabled
($storecfg, $storage, $target);
3565 # clone only works if target storage is shared
3566 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
3567 die "can't clone to non-shared storage '$storage'\n"
3568 if !$scfg->{shared
};
3572 PVE
::Cluster
::check_cfs_quorum
();
3574 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3575 PVE
::QemuConfig-
>check_lock($conf);
3577 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
3578 die "unexpected state change\n" if $verify_running != $running;
3580 die "snapshot '$snapname' does not exist\n"
3581 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
3583 my $full = $param->{full
} // !PVE
::QemuConfig-
>is_template($conf);
3585 die "parameter 'storage' not allowed for linked clones\n"
3586 if defined($storage) && !$full;
3588 die "parameter 'format' not allowed for linked clones\n"
3589 if defined($format) && !$full;
3591 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
3593 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
3594 PVE
::QemuServer
::check_mapping_access
($rpcenv, $authuser, $oldconf);
3596 PVE
::QemuServer
::check_bridge_access
($rpcenv, $authuser, $oldconf);
3598 die "can't clone VM to node '$target' (VM uses local storage)\n"
3599 if $target && !$sharedvm;
3601 my $conffile = PVE
::QemuConfig-
>config_file($newid);
3602 die "unable to create VM $newid: config file already exists\n"
3605 my $newconf = { lock => 'clone' };
3610 foreach my $opt (keys %$oldconf) {
3611 my $value = $oldconf->{$opt};
3613 # do not copy snapshot related info
3614 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
3615 $opt eq 'vmstate' || $opt eq 'snapstate';
3617 # no need to copy unused images, because VMID(owner) changes anyways
3618 next if $opt =~ m/^unused\d+$/;
3620 die "cannot clone TPM state while VM is running\n"
3621 if $full && $running && !$snapname && $opt eq 'tpmstate0';
3623 # always change MAC! address
3624 if ($opt =~ m/^net(\d+)$/) {
3625 my $net = PVE
::QemuServer
::parse_net
($value);
3626 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
3627 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
3628 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
3629 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
3630 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
3631 die "unable to parse drive options for '$opt'\n" if !$drive;
3632 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
3633 $newconf->{$opt} = $value; # simply copy configuration
3635 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
3636 die "Full clone feature is not supported for drive '$opt'\n"
3637 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
3638 $fullclone->{$opt} = 1;
3640 # not full means clone instead of copy
3641 die "Linked clone feature is not supported for drive '$opt'\n"
3642 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
3644 $drives->{$opt} = $drive;
3645 next if PVE
::QemuServer
::drive_is_cloudinit
($drive);
3646 push @$vollist, $drive->{file
};
3649 # copy everything else
3650 $newconf->{$opt} = $value;
3654 return ($conffile, $newconf, $oldconf, $vollist, $drives, $fullclone);
3658 my ($conffile, $newconf, $oldconf, $vollist, $drives, $fullclone) = $load_and_check->();
3659 my $storecfg = PVE
::Storage
::config
();
3661 # auto generate a new uuid
3662 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
3663 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
3664 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
3665 # auto generate a new vmgenid only if the option was set for template
3666 if ($newconf->{vmgenid
}) {
3667 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
3670 delete $newconf->{template
};
3672 if ($param->{name
}) {
3673 $newconf->{name
} = $param->{name
};
3675 $newconf->{name
} = "Copy-of-VM-" . ($oldconf->{name
} // $vmid);
3678 if ($param->{description
}) {
3679 $newconf->{description
} = $param->{description
};
3682 # create empty/temp config - this fails if VM already exists on other node
3683 # FIXME use PVE::QemuConfig->create_and_lock_config and adapt code
3684 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
3686 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
3688 my $newvollist = [];
3695 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3697 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
3699 my $bwlimit = extract_param
($param, 'bwlimit');
3701 my $total_jobs = scalar(keys %{$drives});
3704 foreach my $opt (sort keys %$drives) {
3705 my $drive = $drives->{$opt};
3706 my $skipcomplete = ($total_jobs != $i); # finish after last drive
3707 my $completion = $skipcomplete ?
'skip' : 'complete';
3709 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
3710 my $storage_list = [ $src_sid ];
3711 push @$storage_list, $storage if defined($storage);
3712 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
3716 running
=> $running,
3719 snapname
=> $snapname,
3725 storage
=> $storage,
3729 $dest_info->{efisize
} = PVE
::QemuServer
::get_efivars_size
($oldconf)
3730 if $opt eq 'efidisk0';
3732 my $newdrive = PVE
::QemuServer
::clone_disk
(
3744 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($newdrive);
3746 PVE
::QemuConfig-
>write_config($newid, $newconf);
3750 delete $newconf->{lock};
3752 # do not write pending changes
3753 if (my @changes = keys %{$newconf->{pending
}}) {
3754 my $pending = join(',', @changes);
3755 warn "found pending changes for '$pending', discarding for clone\n";
3756 delete $newconf->{pending
};
3759 PVE
::QemuConfig-
>write_config($newid, $newconf);
3762 # always deactivate volumes - avoid lvm LVs to be active on several nodes
3763 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
3764 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
3766 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
3767 die "Failed to move config to node '$target' - rename failed: $!\n"
3768 if !rename($conffile, $newconffile);
3771 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
3774 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
3775 sleep 1; # some storage like rbd need to wait before release volume - really?
3777 foreach my $volid (@$newvollist) {
3778 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3782 PVE
::Firewall
::remove_vmfw_conf
($newid);
3784 unlink $conffile; # avoid races -> last thing before die
3786 die "clone failed: $err";
3792 # Aquire exclusive lock lock for $newid
3793 my $lock_target_vm = sub {
3794 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
3797 my $lock_source_vm = sub {
3798 # exclusive lock if VM is running - else shared lock is enough;
3800 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $lock_target_vm);
3802 return PVE
::QemuConfig-
>lock_config_shared($vmid, 1, $lock_target_vm);
3806 $load_and_check->(); # early checks before forking/locking
3808 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $lock_source_vm);
3811 __PACKAGE__-
>register_method({
3812 name
=> 'move_vm_disk',
3813 path
=> '{vmid}/move_disk',
3817 description
=> "Move volume to different storage or to a different VM.",
3819 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
3820 "and 'Datastore.AllocateSpace' permissions on the storage. To move ".
3821 "a disk to another VM, you need the permissions on the target VM as well.",
3822 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3825 additionalProperties
=> 0,
3827 node
=> get_standard_option
('pve-node'),
3828 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3829 'target-vmid' => get_standard_option
('pve-vmid', {
3830 completion
=> \
&PVE
::QemuServer
::complete_vmid
,
3835 description
=> "The disk you want to move.",
3836 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names_with_unused
()],
3838 storage
=> get_standard_option
('pve-storage-id', {
3839 description
=> "Target storage.",
3840 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3845 description
=> "Target Format.",
3846 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
3851 description
=> "Delete the original disk after successful copy. By default the"
3852 ." original disk is kept as unused disk.",
3858 description
=> 'Prevent changes if current configuration file has different SHA1"
3859 ." digest. This can be used to prevent concurrent modifications.',
3864 description
=> "Override I/O bandwidth limit (in KiB/s).",
3868 default => 'move limit from datacenter or storage config',
3872 description
=> "The config key the disk will be moved to on the target VM"
3873 ." (for example, ide0 or scsi1). Default is the source disk key.",
3874 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names_with_unused
()],
3877 'target-digest' => {
3879 description
=> 'Prevent changes if the current config file of the target VM has a"
3880 ." different SHA1 digest. This can be used to detect concurrent modifications.',
3888 description
=> "the task ID.",
3893 my $rpcenv = PVE
::RPCEnvironment
::get
();
3894 my $authuser = $rpcenv->get_user();
3896 my $node = extract_param
($param, 'node');
3897 my $vmid = extract_param
($param, 'vmid');
3898 my $target_vmid = extract_param
($param, 'target-vmid');
3899 my $digest = extract_param
($param, 'digest');
3900 my $target_digest = extract_param
($param, 'target-digest');
3901 my $disk = extract_param
($param, 'disk');
3902 my $target_disk = extract_param
($param, 'target-disk') // $disk;
3903 my $storeid = extract_param
($param, 'storage');
3904 my $format = extract_param
($param, 'format');
3906 my $storecfg = PVE
::Storage
::config
();
3908 my $load_and_check_move = sub {
3909 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3910 PVE
::QemuConfig-
>check_lock($conf);
3912 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
3914 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3916 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3918 die "disk '$disk' has no associated volume\n" if !$drive->{file
};
3919 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3921 my $old_volid = $drive->{file
};
3923 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3924 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3928 die "you can't move to the same storage with same format\n"
3929 if $oldstoreid eq $storeid && (!$format || !$oldfmt || $oldfmt eq $format);
3931 # this only checks snapshots because $disk is passed!
3932 my $snapshotted = PVE
::QemuServer
::Drive
::is_volume_in_use
(
3938 die "you can't move a disk with snapshots and delete the source\n"
3939 if $snapshotted && $param->{delete};
3941 return ($conf, $drive, $oldstoreid, $snapshotted);
3944 my $move_updatefn = sub {
3945 my ($conf, $drive, $oldstoreid, $snapshotted) = $load_and_check_move->();
3946 my $old_volid = $drive->{file
};
3948 PVE
::Cluster
::log_msg
(
3951 "move disk VM $vmid: move --disk $disk --storage $storeid"
3954 my $running = PVE
::QemuServer
::check_running
($vmid);
3956 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3958 my $newvollist = [];
3964 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3966 warn "moving disk with snapshots, snapshots will not be moved!\n"
3969 my $bwlimit = extract_param
($param, 'bwlimit');
3970 my $movelimit = PVE
::Storage
::get_bandwidth_limit
(
3972 [$oldstoreid, $storeid],
3978 running
=> $running,
3987 storage
=> $storeid,
3991 $dest_info->{efisize
} = PVE
::QemuServer
::get_efivars_size
($conf)
3992 if $disk eq 'efidisk0';
3994 my $newdrive = PVE
::QemuServer
::clone_disk
(
4005 $conf->{$disk} = PVE
::QemuServer
::print_drive
($newdrive);
4007 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
4009 # convert moved disk to base if part of template
4010 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
4011 if PVE
::QemuConfig-
>is_template($conf);
4013 PVE
::QemuConfig-
>write_config($vmid, $conf);
4015 my $do_trim = PVE
::QemuServer
::get_qga_key
($conf, 'fstrim_cloned_disks');
4016 if ($running && $do_trim && PVE
::QemuServer
::qga_check_running
($vmid)) {
4017 eval { mon_cmd
($vmid, "guest-fstrim") };
4021 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
4022 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
4028 foreach my $volid (@$newvollist) {
4029 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid) };
4032 die "storage migration failed: $err";
4035 if ($param->{delete}) {
4037 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
4038 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
4044 my $load_and_check_reassign_configs = sub {
4045 my $vmlist = PVE
::Cluster
::get_vmlist
()->{ids
};
4047 die "could not find VM ${vmid}\n" if !exists($vmlist->{$vmid});
4048 die "could not find target VM ${target_vmid}\n" if !exists($vmlist->{$target_vmid});
4050 my $source_node = $vmlist->{$vmid}->{node
};
4051 my $target_node = $vmlist->{$target_vmid}->{node
};
4053 die "Both VMs need to be on the same node ($source_node != $target_node)\n"
4054 if $source_node ne $target_node;
4056 my $source_conf = PVE
::QemuConfig-
>load_config($vmid);
4057 PVE
::QemuConfig-
>check_lock($source_conf);
4058 my $target_conf = PVE
::QemuConfig-
>load_config($target_vmid);
4059 PVE
::QemuConfig-
>check_lock($target_conf);
4061 die "Can't move disks from or to template VMs\n"
4062 if ($source_conf->{template
} || $target_conf->{template
});
4065 eval { PVE
::Tools
::assert_if_modified
($digest, $source_conf->{digest
}) };
4066 die "VM ${vmid}: $@" if $@;
4069 if ($target_digest) {
4070 eval { PVE
::Tools
::assert_if_modified
($target_digest, $target_conf->{digest
}) };
4071 die "VM ${target_vmid}: $@" if $@;
4074 die "Disk '${disk}' for VM '$vmid' does not exist\n" if !defined($source_conf->{$disk});
4076 die "Target disk key '${target_disk}' is already in use for VM '$target_vmid'\n"
4077 if $target_conf->{$target_disk};
4079 my $drive = PVE
::QemuServer
::parse_drive
(
4081 $source_conf->{$disk},
4083 die "failed to parse source disk - $@\n" if !$drive;
4085 my $source_volid = $drive->{file
};
4087 die "disk '${disk}' has no associated volume\n" if !$source_volid;
4088 die "CD drive contents can't be moved to another VM\n"
4089 if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
4091 my $storeid = PVE
::Storage
::parse_volume_id
($source_volid, 1);
4092 die "Volume '$source_volid' not managed by PVE\n" if !defined($storeid);
4094 die "Can't move disk used by a snapshot to another VM\n"
4095 if PVE
::QemuServer
::Drive
::is_volume_in_use
($storecfg, $source_conf, $disk, $source_volid);
4096 die "Storage does not support moving of this disk to another VM\n"
4097 if (!PVE
::Storage
::volume_has_feature
($storecfg, 'rename', $source_volid));
4098 die "Cannot move disk to another VM while the source VM is running - detach first\n"
4099 if PVE
::QemuServer
::check_running
($vmid) && $disk !~ m/^unused\d+$/;
4101 # now re-parse using target disk slot format
4102 if ($target_disk =~ /^unused\d+$/) {
4103 $drive = PVE
::QemuServer
::parse_drive
(
4108 $drive = PVE
::QemuServer
::parse_drive
(
4110 $source_conf->{$disk},
4113 die "failed to parse source disk for target disk format - $@\n" if !$drive;
4115 my $repl_conf = PVE
::ReplicationConfig-
>new();
4116 if ($repl_conf->check_for_existing_jobs($target_vmid, 1)) {
4117 my $format = (PVE
::Storage
::parse_volname
($storecfg, $source_volid))[6];
4118 die "Cannot move disk to a replicated VM. Storage does not support replication!\n"
4119 if !PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
4122 return ($source_conf, $target_conf, $drive);
4127 print STDERR
"$msg\n";
4130 my $disk_reassignfn = sub {
4131 return PVE
::QemuConfig-
>lock_config($vmid, sub {
4132 return PVE
::QemuConfig-
>lock_config($target_vmid, sub {
4133 my ($source_conf, $target_conf, $drive) = &$load_and_check_reassign_configs();
4135 my $source_volid = $drive->{file
};
4137 print "moving disk '$disk' from VM '$vmid' to '$target_vmid'\n";
4138 my ($storeid, $source_volname) = PVE
::Storage
::parse_volume_id
($source_volid);
4140 my $fmt = (PVE
::Storage
::parse_volname
($storecfg, $source_volid))[6];
4142 my $new_volid = PVE
::Storage
::rename_volume
(
4148 $drive->{file
} = $new_volid;
4150 my $boot_order = PVE
::QemuServer
::device_bootorder
($source_conf);
4151 if (defined(delete $boot_order->{$disk})) {
4152 print "removing disk '$disk' from boot order config\n";
4153 my $boot_devs = [ sort { $boot_order->{$a} <=> $boot_order->{$b} } keys %$boot_order ];
4154 $source_conf->{boot
} = PVE
::QemuServer
::print_bootorder
($boot_devs);
4157 delete $source_conf->{$disk};
4158 print "removing disk '${disk}' from VM '${vmid}' config\n";
4159 PVE
::QemuConfig-
>write_config($vmid, $source_conf);
4161 my $drive_string = PVE
::QemuServer
::print_drive
($drive);
4163 if ($target_disk =~ /^unused\d+$/) {
4164 $target_conf->{$target_disk} = $drive_string;
4165 PVE
::QemuConfig-
>write_config($target_vmid, $target_conf);
4170 vmid
=> $target_vmid,
4171 digest
=> $target_digest,
4172 $target_disk => $drive_string,
4178 # remove possible replication snapshots
4179 if (PVE
::Storage
::volume_has_feature
(
4185 PVE
::Replication
::prepare
(
4195 print "Failed to remove replication snapshots on moved disk " .
4196 "'$target_disk'. Manual cleanup could be necessary.\n";
4203 if ($target_vmid && $storeid) {
4204 my $msg = "either set 'storage' or 'target-vmid', but not both";
4205 raise_param_exc
({ 'target-vmid' => $msg, 'storage' => $msg });
4206 } elsif ($target_vmid) {
4207 $rpcenv->check_vm_perm($authuser, $target_vmid, undef, ['VM.Config.Disk'])
4208 if $authuser ne 'root@pam';
4210 raise_param_exc
({ 'target-vmid' => "must be different than source VMID to reassign disk" })
4211 if $vmid eq $target_vmid;
4213 my (undef, undef, $drive) = &$load_and_check_reassign_configs();
4214 my $storage = PVE
::Storage
::parse_volume_id
($drive->{file
});
4215 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace']);
4217 return $rpcenv->fork_worker(
4219 "${vmid}-${disk}>${target_vmid}-${target_disk}",
4223 } elsif ($storeid) {
4224 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
4226 die "cannot move disk '$disk', only configured disks can be moved to another storage\n"
4227 if $disk =~ m/^unused\d+$/;
4229 $load_and_check_move->(); # early checks before forking/locking
4232 PVE
::QemuConfig-
>lock_config($vmid, $move_updatefn);
4235 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
4237 my $msg = "both 'storage' and 'target-vmid' missing, either needs to be set";
4238 raise_param_exc
({ 'target-vmid' => $msg, 'storage' => $msg });
4242 my $check_vm_disks_local = sub {
4243 my ($storecfg, $vmconf, $vmid) = @_;
4245 my $local_disks = {};
4247 # add some more information to the disks e.g. cdrom
4248 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
4249 my ($volid, $attr) = @_;
4251 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
4253 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
4254 return if $scfg->{shared
};
4256 # The shared attr here is just a special case where the vdisk
4257 # is marked as shared manually
4258 return if $attr->{shared
};
4259 return if $attr->{cdrom
} and $volid eq "none";
4261 if (exists $local_disks->{$volid}) {
4262 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
4264 $local_disks->{$volid} = $attr;
4265 # ensure volid is present in case it's needed
4266 $local_disks->{$volid}->{volid
} = $volid;
4270 return $local_disks;
4273 __PACKAGE__-
>register_method({
4274 name
=> 'migrate_vm_precondition',
4275 path
=> '{vmid}/migrate',
4279 description
=> "Get preconditions for migration.",
4281 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
4284 additionalProperties
=> 0,
4286 node
=> get_standard_option
('pve-node'),
4287 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4288 target
=> get_standard_option
('pve-node', {
4289 description
=> "Target node.",
4290 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
4298 running
=> { type
=> 'boolean' },
4302 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
4304 not_allowed_nodes
=> {
4307 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
4311 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
4313 local_resources
=> {
4315 description
=> "List local resources e.g. pci, usb"
4317 'mapped-resources' => {
4319 description
=> "List of mapped resources e.g. pci, usb"
4326 my $rpcenv = PVE
::RPCEnvironment
::get
();
4328 my $authuser = $rpcenv->get_user();
4330 PVE
::Cluster
::check_cfs_quorum
();
4334 my $vmid = extract_param
($param, 'vmid');
4335 my $target = extract_param
($param, 'target');
4336 my $localnode = PVE
::INotify
::nodename
();
4340 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
4341 my $storecfg = PVE
::Storage
::config
();
4344 # try to detect errors early
4345 PVE
::QemuConfig-
>check_lock($vmconf);
4347 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
4349 my ($local_resources, $mapped_resources, $missing_mappings_by_node) =
4350 PVE
::QemuServer
::check_local_resources
($vmconf, 1);
4351 delete $missing_mappings_by_node->{$localnode};
4353 # if vm is not running, return target nodes where local storage/mapped devices are available
4354 # for offline migration
4355 if (!$res->{running
}) {
4356 $res->{allowed_nodes
} = [];
4357 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
4358 delete $checked_nodes->{$localnode};
4360 foreach my $node (keys %$checked_nodes) {
4361 my $missing_mappings = $missing_mappings_by_node->{$node};
4362 if (scalar($missing_mappings->@*)) {
4363 $checked_nodes->{$node}->{'unavailable-resources'} = $missing_mappings;
4367 if (!defined($checked_nodes->{$node}->{unavailable_storages
})) {
4368 push @{$res->{allowed_nodes
}}, $node;
4372 $res->{not_allowed_nodes
} = $checked_nodes;
4375 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
4376 $res->{local_disks
} = [ values %$local_disks ];;
4378 $res->{local_resources
} = $local_resources;
4379 $res->{'mapped-resources'} = $mapped_resources;
4386 __PACKAGE__-
>register_method({
4387 name
=> 'migrate_vm',
4388 path
=> '{vmid}/migrate',
4392 description
=> "Migrate virtual machine. Creates a new migration task.",
4394 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
4397 additionalProperties
=> 0,
4399 node
=> get_standard_option
('pve-node'),
4400 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4401 target
=> get_standard_option
('pve-node', {
4402 description
=> "Target node.",
4403 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
4407 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
4412 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
4417 enum
=> ['secure', 'insecure'],
4418 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
4421 migration_network
=> {
4422 type
=> 'string', format
=> 'CIDR',
4423 description
=> "CIDR of the (sub) network that is used for migration.",
4426 "with-local-disks" => {
4428 description
=> "Enable live storage migration for local disk",
4431 targetstorage
=> get_standard_option
('pve-targetstorage', {
4432 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
4435 description
=> "Override I/O bandwidth limit (in KiB/s).",
4439 default => 'migrate limit from datacenter or storage config',
4445 description
=> "the task ID.",
4450 my $rpcenv = PVE
::RPCEnvironment
::get
();
4451 my $authuser = $rpcenv->get_user();
4453 my $target = extract_param
($param, 'target');
4455 my $localnode = PVE
::INotify
::nodename
();
4456 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
4458 PVE
::Cluster
::check_cfs_quorum
();
4460 PVE
::Cluster
::check_node_exists
($target);
4462 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
4464 my $vmid = extract_param
($param, 'vmid');
4466 raise_param_exc
({ force
=> "Only root may use this option." })
4467 if $param->{force
} && $authuser ne 'root@pam';
4469 raise_param_exc
({ migration_type
=> "Only root may use this option." })
4470 if $param->{migration_type
} && $authuser ne 'root@pam';
4472 # allow root only until better network permissions are available
4473 raise_param_exc
({ migration_network
=> "Only root may use this option." })
4474 if $param->{migration_network
} && $authuser ne 'root@pam';
4477 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4479 # try to detect errors early
4481 PVE
::QemuConfig-
>check_lock($conf);
4483 if (PVE
::QemuServer
::check_running
($vmid)) {
4484 die "can't migrate running VM without --online\n" if !$param->{online
};
4486 my $repl_conf = PVE
::ReplicationConfig-
>new();
4487 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
4488 my $is_replicated_to_target = defined($repl_conf->find_local_replication_job($vmid, $target));
4489 if (!$param->{force
} && $is_replicated && !$is_replicated_to_target) {
4490 die "Cannot live-migrate replicated VM to node '$target' - not a replication " .
4491 "target. Use 'force' to override.\n";
4494 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
4495 $param->{online
} = 0;
4498 my $storecfg = PVE
::Storage
::config
();
4499 if (my $targetstorage = $param->{targetstorage
}) {
4500 my $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
4501 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
4504 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk'])
4505 if !defined($storagemap->{identity
});
4507 foreach my $target_sid (values %{$storagemap->{entries
}}) {
4508 $check_storage_access_migrate->($rpcenv, $authuser, $storecfg, $target_sid, $target);
4511 $check_storage_access_migrate->($rpcenv, $authuser, $storecfg, $storagemap->{default}, $target)
4512 if $storagemap->{default};
4514 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target)
4515 if $storagemap->{identity
};
4517 $param->{storagemap
} = $storagemap;
4519 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
4522 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
4527 print "Requesting HA migration for VM $vmid to node $target\n";
4529 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
4530 PVE
::Tools
::run_command
($cmd);
4534 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
4539 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
4543 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
4546 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
4551 __PACKAGE__-
>register_method({
4552 name
=> 'remote_migrate_vm',
4553 path
=> '{vmid}/remote_migrate',
4557 description
=> "Migrate virtual machine to a remote cluster. Creates a new migration task. EXPERIMENTAL feature!",
4559 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
4562 additionalProperties
=> 0,
4564 node
=> get_standard_option
('pve-node'),
4565 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4566 'target-vmid' => get_standard_option
('pve-vmid', { optional
=> 1 }),
4567 'target-endpoint' => get_standard_option
('proxmox-remote', {
4568 description
=> "Remote target endpoint",
4572 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
4577 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.",
4581 'target-storage' => get_standard_option
('pve-targetstorage', {
4582 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
4585 'target-bridge' => {
4587 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.",
4588 format
=> 'bridge-pair-list',
4591 description
=> "Override I/O bandwidth limit (in KiB/s).",
4595 default => 'migrate limit from datacenter or storage config',
4601 description
=> "the task ID.",
4606 my $rpcenv = PVE
::RPCEnvironment
::get
();
4607 my $authuser = $rpcenv->get_user();
4609 my $source_vmid = extract_param
($param, 'vmid');
4610 my $target_endpoint = extract_param
($param, 'target-endpoint');
4611 my $target_vmid = extract_param
($param, 'target-vmid') // $source_vmid;
4613 my $delete = extract_param
($param, 'delete') // 0;
4615 PVE
::Cluster
::check_cfs_quorum
();
4618 my $conf = PVE
::QemuConfig-
>load_config($source_vmid);
4620 PVE
::QemuConfig-
>check_lock($conf);
4622 raise_param_exc
({ vmid
=> "cannot migrate HA-managed VM to remote cluster" })
4623 if PVE
::HA
::Config
::vm_is_ha_managed
($source_vmid);
4625 my $remote = PVE
::JSONSchema
::parse_property_string
('proxmox-remote', $target_endpoint);
4627 # TODO: move this as helper somewhere appropriate?
4629 protocol
=> 'https',
4630 host
=> $remote->{host
},
4631 port
=> $remote->{port
} // 8006,
4632 apitoken
=> $remote->{apitoken
},
4636 if ($fp = $remote->{fingerprint
}) {
4637 $conn_args->{cached_fingerprints
} = { uc($fp) => 1 };
4640 print "Establishing API connection with remote at '$remote->{host}'\n";
4642 my $api_client = PVE
::APIClient
::LWP-
>new(%$conn_args);
4644 if (!defined($fp)) {
4645 my $cert_info = $api_client->get("/nodes/localhost/certificates/info");
4646 foreach my $cert (@$cert_info) {
4647 my $filename = $cert->{filename
};
4648 next if $filename ne 'pveproxy-ssl.pem' && $filename ne 'pve-ssl.pem';
4649 $fp = $cert->{fingerprint
} if !$fp || $filename eq 'pveproxy-ssl.pem';
4651 $conn_args->{cached_fingerprints
} = { uc($fp) => 1 }
4655 my $repl_conf = PVE
::ReplicationConfig-
>new();
4656 my $is_replicated = $repl_conf->check_for_existing_jobs($source_vmid, 1);
4657 die "cannot remote-migrate replicated VM\n" if $is_replicated;
4659 if (PVE
::QemuServer
::check_running
($source_vmid)) {
4660 die "can't migrate running VM without --online\n" if !$param->{online
};
4663 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
4664 $param->{online
} = 0;
4667 my $storecfg = PVE
::Storage
::config
();
4668 my $target_storage = extract_param
($param, 'target-storage');
4669 my $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($target_storage, 'pve-storage-id') };
4670 raise_param_exc
({ 'target-storage' => "failed to parse storage map: $@" })
4673 my $target_bridge = extract_param
($param, 'target-bridge');
4674 my $bridgemap = eval { PVE
::JSONSchema
::parse_idmap
($target_bridge, 'pve-bridge-id') };
4675 raise_param_exc
({ 'target-bridge' => "failed to parse bridge map: $@" })
4678 die "remote migration requires explicit storage mapping!\n"
4679 if $storagemap->{identity
};
4681 $param->{storagemap
} = $storagemap;
4682 $param->{bridgemap
} = $bridgemap;
4683 $param->{remote
} = {
4684 conn
=> $conn_args, # re-use fingerprint for tunnel
4685 client
=> $api_client,
4686 vmid
=> $target_vmid,
4688 $param->{migration_type
} = 'websocket';
4689 $param->{'with-local-disks'} = 1;
4690 $param->{delete} = $delete if $delete;
4692 my $cluster_status = $api_client->get("/cluster/status");
4694 foreach my $entry (@$cluster_status) {
4695 next if $entry->{type
} ne 'node';
4696 if ($entry->{local}) {
4697 $target_node = $entry->{name
};
4702 die "couldn't determine endpoint's node name\n"
4703 if !defined($target_node);
4706 PVE
::QemuMigrate-
>migrate($target_node, $remote->{host
}, $source_vmid, $param);
4710 return PVE
::GuestHelpers
::guest_migration_lock
($source_vmid, 10, $realcmd);
4713 return $rpcenv->fork_worker('qmigrate', $source_vmid, $authuser, $worker);
4716 __PACKAGE__-
>register_method({
4718 path
=> '{vmid}/monitor',
4722 description
=> "Execute QEMU monitor commands.",
4724 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
4725 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
4728 additionalProperties
=> 0,
4730 node
=> get_standard_option
('pve-node'),
4731 vmid
=> get_standard_option
('pve-vmid'),
4734 description
=> "The monitor command.",
4738 returns
=> { type
=> 'string'},
4742 my $rpcenv = PVE
::RPCEnvironment
::get
();
4743 my $authuser = $rpcenv->get_user();
4746 my $command = shift;
4747 return $command =~ m/^\s*info(\s+|$)/
4748 || $command =~ m/^\s*help\s*$/;
4751 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
4752 if !&$is_ro($param->{command
});
4754 my $vmid = $param->{vmid
};
4756 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
4760 $res = PVE
::QemuServer
::Monitor
::hmp_cmd
($vmid, $param->{command
});
4762 $res = "ERROR: $@" if $@;
4767 __PACKAGE__-
>register_method({
4768 name
=> 'resize_vm',
4769 path
=> '{vmid}/resize',
4773 description
=> "Extend volume size.",
4775 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
4778 additionalProperties
=> 0,
4780 node
=> get_standard_option
('pve-node'),
4781 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4782 skiplock
=> get_standard_option
('skiplock'),
4785 description
=> "The disk you want to resize.",
4786 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
4790 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
4791 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.",
4795 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
4803 description
=> "the task ID.",
4808 my $rpcenv = PVE
::RPCEnvironment
::get
();
4810 my $authuser = $rpcenv->get_user();
4812 my $node = extract_param
($param, 'node');
4814 my $vmid = extract_param
($param, 'vmid');
4816 my $digest = extract_param
($param, 'digest');
4818 my $disk = extract_param
($param, 'disk');
4820 my $sizestr = extract_param
($param, 'size');
4822 my $skiplock = extract_param
($param, 'skiplock');
4823 raise_param_exc
({ skiplock
=> "Only root may use this option." })
4824 if $skiplock && $authuser ne 'root@pam';
4826 my $storecfg = PVE
::Storage
::config
();
4828 my $updatefn = sub {
4830 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4832 die "checksum missmatch (file change by other user?)\n"
4833 if $digest && $digest ne $conf->{digest
};
4834 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
4836 die "disk '$disk' does not exist\n" if !$conf->{$disk};
4838 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
4840 my (undef, undef, undef, undef, undef, undef, $format) =
4841 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
4843 my $volid = $drive->{file
};
4845 die "disk '$disk' has no associated volume\n" if !$volid;
4847 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
4849 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
4851 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
4853 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
4854 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
4856 die "Could not determine current size of volume '$volid'\n" if !defined($size);
4858 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
4859 my ($ext, $newsize, $unit) = ($1, $2, $4);
4862 $newsize = $newsize * 1024;
4863 } elsif ($unit eq 'M') {
4864 $newsize = $newsize * 1024 * 1024;
4865 } elsif ($unit eq 'G') {
4866 $newsize = $newsize * 1024 * 1024 * 1024;
4867 } elsif ($unit eq 'T') {
4868 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
4871 $newsize += $size if $ext;
4872 $newsize = int($newsize);
4874 die "shrinking disks is not supported\n" if $newsize < $size;
4876 return if $size == $newsize;
4878 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
4880 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
4882 $drive->{size
} = $newsize;
4883 $conf->{$disk} = PVE
::QemuServer
::print_drive
($drive);
4885 PVE
::QemuConfig-
>write_config($vmid, $conf);
4889 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4892 return $rpcenv->fork_worker('resize', $vmid, $authuser, $worker);
4895 __PACKAGE__-
>register_method({
4896 name
=> 'snapshot_list',
4897 path
=> '{vmid}/snapshot',
4899 description
=> "List all snapshots.",
4901 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4904 protected
=> 1, # qemu pid files are only readable by root
4906 additionalProperties
=> 0,
4908 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4909 node
=> get_standard_option
('pve-node'),
4918 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
4922 description
=> "Snapshot includes RAM.",
4927 description
=> "Snapshot description.",
4931 description
=> "Snapshot creation time",
4933 renderer
=> 'timestamp',
4937 description
=> "Parent snapshot identifier.",
4943 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
4948 my $vmid = $param->{vmid
};
4950 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4951 my $snaphash = $conf->{snapshots
} || {};
4955 foreach my $name (keys %$snaphash) {
4956 my $d = $snaphash->{$name};
4959 snaptime
=> $d->{snaptime
} || 0,
4960 vmstate
=> $d->{vmstate
} ?
1 : 0,
4961 description
=> $d->{description
} || '',
4963 $item->{parent
} = $d->{parent
} if $d->{parent
};
4964 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
4968 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
4971 digest
=> $conf->{digest
},
4972 running
=> $running,
4973 description
=> "You are here!",
4975 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
4977 push @$res, $current;
4982 __PACKAGE__-
>register_method({
4984 path
=> '{vmid}/snapshot',
4988 description
=> "Snapshot a VM.",
4990 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4993 additionalProperties
=> 0,
4995 node
=> get_standard_option
('pve-node'),
4996 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4997 snapname
=> get_standard_option
('pve-snapshot-name'),
5001 description
=> "Save the vmstate",
5006 description
=> "A textual description or comment.",
5012 description
=> "the task ID.",
5017 my $rpcenv = PVE
::RPCEnvironment
::get
();
5019 my $authuser = $rpcenv->get_user();
5021 my $node = extract_param
($param, 'node');
5023 my $vmid = extract_param
($param, 'vmid');
5025 my $snapname = extract_param
($param, 'snapname');
5027 die "unable to use snapshot name 'current' (reserved name)\n"
5028 if $snapname eq 'current';
5030 die "unable to use snapshot name 'pending' (reserved name)\n"
5031 if lc($snapname) eq 'pending';
5034 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
5035 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
5036 $param->{description
});
5039 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
5042 __PACKAGE__-
>register_method({
5043 name
=> 'snapshot_cmd_idx',
5044 path
=> '{vmid}/snapshot/{snapname}',
5051 additionalProperties
=> 0,
5053 vmid
=> get_standard_option
('pve-vmid'),
5054 node
=> get_standard_option
('pve-node'),
5055 snapname
=> get_standard_option
('pve-snapshot-name'),
5064 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
5071 push @$res, { cmd
=> 'rollback' };
5072 push @$res, { cmd
=> 'config' };
5077 __PACKAGE__-
>register_method({
5078 name
=> 'update_snapshot_config',
5079 path
=> '{vmid}/snapshot/{snapname}/config',
5083 description
=> "Update snapshot metadata.",
5085 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
5088 additionalProperties
=> 0,
5090 node
=> get_standard_option
('pve-node'),
5091 vmid
=> get_standard_option
('pve-vmid'),
5092 snapname
=> get_standard_option
('pve-snapshot-name'),
5096 description
=> "A textual description or comment.",
5100 returns
=> { type
=> 'null' },
5104 my $rpcenv = PVE
::RPCEnvironment
::get
();
5106 my $authuser = $rpcenv->get_user();
5108 my $vmid = extract_param
($param, 'vmid');
5110 my $snapname = extract_param
($param, 'snapname');
5112 return if !defined($param->{description
});
5114 my $updatefn = sub {
5116 my $conf = PVE
::QemuConfig-
>load_config($vmid);
5118 PVE
::QemuConfig-
>check_lock($conf);
5120 my $snap = $conf->{snapshots
}->{$snapname};
5122 die "snapshot '$snapname' does not exist\n" if !defined($snap);
5124 $snap->{description
} = $param->{description
} if defined($param->{description
});
5126 PVE
::QemuConfig-
>write_config($vmid, $conf);
5129 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
5134 __PACKAGE__-
>register_method({
5135 name
=> 'get_snapshot_config',
5136 path
=> '{vmid}/snapshot/{snapname}/config',
5139 description
=> "Get snapshot configuration",
5141 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback', 'VM.Audit' ], any
=> 1],
5144 additionalProperties
=> 0,
5146 node
=> get_standard_option
('pve-node'),
5147 vmid
=> get_standard_option
('pve-vmid'),
5148 snapname
=> get_standard_option
('pve-snapshot-name'),
5151 returns
=> { type
=> "object" },
5155 my $rpcenv = PVE
::RPCEnvironment
::get
();
5157 my $authuser = $rpcenv->get_user();
5159 my $vmid = extract_param
($param, 'vmid');
5161 my $snapname = extract_param
($param, 'snapname');
5163 my $conf = PVE
::QemuConfig-
>load_config($vmid);
5165 my $snap = $conf->{snapshots
}->{$snapname};
5167 die "snapshot '$snapname' does not exist\n" if !defined($snap);
5172 __PACKAGE__-
>register_method({
5174 path
=> '{vmid}/snapshot/{snapname}/rollback',
5178 description
=> "Rollback VM state to specified snapshot.",
5180 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
5183 additionalProperties
=> 0,
5185 node
=> get_standard_option
('pve-node'),
5186 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
5187 snapname
=> get_standard_option
('pve-snapshot-name'),
5190 description
=> "Whether the VM should get started after rolling back successfully."
5191 . " (Note: VMs will be automatically started if the snapshot includes RAM.)",
5199 description
=> "the task ID.",
5204 my $rpcenv = PVE
::RPCEnvironment
::get
();
5206 my $authuser = $rpcenv->get_user();
5208 my $node = extract_param
($param, 'node');
5210 my $vmid = extract_param
($param, 'vmid');
5212 my $snapname = extract_param
($param, 'snapname');
5215 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
5216 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
5218 if ($param->{start
} && !PVE
::QemuServer
::Helpers
::vm_running_locally
($vmid)) {
5219 PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node });
5224 # hold migration lock, this makes sure that nobody create replication snapshots
5225 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
5228 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
5231 __PACKAGE__-
>register_method({
5232 name
=> 'delsnapshot',
5233 path
=> '{vmid}/snapshot/{snapname}',
5237 description
=> "Delete a VM snapshot.",
5239 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
5242 additionalProperties
=> 0,
5244 node
=> get_standard_option
('pve-node'),
5245 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
5246 snapname
=> get_standard_option
('pve-snapshot-name'),
5250 description
=> "For removal from config file, even if removing disk snapshots fails.",
5256 description
=> "the task ID.",
5261 my $rpcenv = PVE
::RPCEnvironment
::get
();
5263 my $authuser = $rpcenv->get_user();
5265 my $node = extract_param
($param, 'node');
5267 my $vmid = extract_param
($param, 'vmid');
5269 my $snapname = extract_param
($param, 'snapname');
5272 my $do_delete = sub {
5274 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
5275 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
5279 if ($param->{force
}) {
5282 eval { PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $do_delete); };
5284 die $err if $lock_obtained;
5285 die "Failed to obtain guest migration lock - replication running?\n";
5290 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
5293 __PACKAGE__-
>register_method({
5295 path
=> '{vmid}/template',
5299 description
=> "Create a Template.",
5301 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
5302 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
5305 additionalProperties
=> 0,
5307 node
=> get_standard_option
('pve-node'),
5308 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
5312 description
=> "If you want to convert only 1 disk to base image.",
5313 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
5320 description
=> "the task ID.",
5325 my $rpcenv = PVE
::RPCEnvironment
::get
();
5327 my $authuser = $rpcenv->get_user();
5329 my $node = extract_param
($param, 'node');
5331 my $vmid = extract_param
($param, 'vmid');
5333 my $disk = extract_param
($param, 'disk');
5335 my $load_and_check = sub {
5336 my $conf = PVE
::QemuConfig-
>load_config($vmid);
5338 PVE
::QemuConfig-
>check_lock($conf);
5340 die "unable to create template, because VM contains snapshots\n"
5341 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
5343 die "you can't convert a template to a template\n"
5344 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
5346 die "you can't convert a VM to template if VM is running\n"
5347 if PVE
::QemuServer
::check_running
($vmid);
5352 $load_and_check->();
5355 PVE
::QemuConfig-
>lock_config($vmid, sub {
5356 my $conf = $load_and_check->();
5358 $conf->{template
} = 1;
5359 PVE
::QemuConfig-
>write_config($vmid, $conf);
5361 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
5365 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
5368 __PACKAGE__-
>register_method({
5369 name
=> 'cloudinit_generated_config_dump',
5370 path
=> '{vmid}/cloudinit/dump',
5373 description
=> "Get automatically generated cloudinit config.",
5375 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
5378 additionalProperties
=> 0,
5380 node
=> get_standard_option
('pve-node'),
5381 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
5383 description
=> 'Config type.',
5385 enum
=> ['user', 'network', 'meta'],
5395 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
5397 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});
5400 __PACKAGE__-
>register_method({
5402 path
=> '{vmid}/mtunnel',
5405 description
=> 'Migration tunnel endpoint - only for internal use by VM migration.',
5409 ['perm', '/vms/{vmid}', [ 'VM.Allocate' ]],
5410 ['perm', '/', [ 'Sys.Incoming' ]],
5412 description
=> "You need 'VM.Allocate' permissions on '/vms/{vmid}' and Sys.Incoming" .
5413 " on '/'. Further permission checks happen during the actual migration.",
5416 additionalProperties
=> 0,
5418 node
=> get_standard_option
('pve-node'),
5419 vmid
=> get_standard_option
('pve-vmid'),
5422 format
=> 'pve-storage-id-list',
5424 description
=> 'List of storages to check permission and availability. Will be checked again for all actually used storages during migration.',
5428 format
=> 'pve-bridge-id-list',
5430 description
=> 'List of network bridges to check availability. Will be checked again for actually used bridges during migration.',
5435 additionalProperties
=> 0,
5437 upid
=> { type
=> 'string' },
5438 ticket
=> { type
=> 'string' },
5439 socket => { type
=> 'string' },
5445 my $rpcenv = PVE
::RPCEnvironment
::get
();
5446 my $authuser = $rpcenv->get_user();
5448 my $node = extract_param
($param, 'node');
5449 my $vmid = extract_param
($param, 'vmid');
5451 my $storages = extract_param
($param, 'storages');
5452 my $bridges = extract_param
($param, 'bridges');
5454 my $nodename = PVE
::INotify
::nodename
();
5456 raise_param_exc
({ node
=> "node needs to be 'localhost' or local hostname '$nodename'" })
5457 if $node ne 'localhost' && $node ne $nodename;
5461 my $storecfg = PVE
::Storage
::config
();
5462 foreach my $storeid (PVE
::Tools
::split_list
($storages)) {
5463 $check_storage_access_migrate->($rpcenv, $authuser, $storecfg, $storeid, $node);
5466 foreach my $bridge (PVE
::Tools
::split_list
($bridges)) {
5467 PVE
::Network
::read_bridge_mtu
($bridge);
5470 PVE
::Cluster
::check_cfs_quorum
();
5472 my $lock = 'create';
5473 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, 0, $lock); };
5475 raise_param_exc
({ vmid
=> "unable to create empty VM config - $@"})
5480 storecfg
=> PVE
::Storage
::config
(),
5485 my $run_locked = sub {
5486 my ($code, $params) = @_;
5487 return PVE
::QemuConfig-
>lock_config($state->{vmid
}, sub {
5488 my $conf = PVE
::QemuConfig-
>load_config($state->{vmid
});
5490 $state->{conf
} = $conf;
5492 die "Encountered wrong lock - aborting mtunnel command handling.\n"
5493 if $state->{lock} && !PVE
::QemuConfig-
>has_lock($conf, $state->{lock});
5495 return $code->($params);
5503 description
=> 'Full VM config, adapted for target cluster/node',
5505 'firewall-config' => {
5507 description
=> 'VM firewall config',
5512 format
=> PVE
::JSONSchema
::get_standard_option
('pve-qm-image-format'),
5515 format
=> 'pve-storage-id',
5519 description
=> 'parsed drive information without volid and format',
5525 description
=> 'params passed to vm_start_nolock',
5529 description
=> 'migrate_opts passed to vm_start_nolock',
5535 description
=> 'socket path for which the ticket should be valid. must be known to current mtunnel instance.',
5541 description
=> 'remove VM config and disks, aborting migration',
5545 'disk-import' => $PVE::StorageTunnel
::cmd_schema-
>{'disk-import'},
5546 'query-disk-import' => $PVE::StorageTunnel
::cmd_schema-
>{'query-disk-import'},
5547 bwlimit
=> $PVE::StorageTunnel
::cmd_schema-
>{bwlimit
},
5550 my $cmd_handlers = {
5552 # compared against other end's version
5553 # bump/reset for breaking changes
5554 # bump/bump for opt-in changes
5556 api
=> $PVE::QemuMigrate
::WS_TUNNEL_VERSION
,
5563 # parse and write out VM FW config if given
5564 if (my $fw_conf = $params->{'firewall-config'}) {
5565 my ($path, $fh) = PVE
::Tools
::tempfile_contents
($fw_conf, 700);
5572 ipset_comments
=> {},
5574 my $cluster_fw_conf = PVE
::Firewall
::load_clusterfw_conf
();
5576 # TODO: add flag for strict parsing?
5577 # TODO: add import sub that does all this given raw content?
5578 my $vmfw_conf = PVE
::Firewall
::generic_fw_config_parser
($path, $cluster_fw_conf, $empty_conf, 'vm');
5579 $vmfw_conf->{vmid
} = $state->{vmid
};
5580 PVE
::Firewall
::save_vmfw_conf
($state->{vmid
}, $vmfw_conf);
5582 $state->{cleanup
}->{fw
} = 1;
5585 my $conf_fn = "incoming/qemu-server/$state->{vmid}.conf";
5586 my $new_conf = PVE
::QemuServer
::parse_vm_config
($conf_fn, $params->{conf
}, 1);
5587 delete $new_conf->{lock};
5588 delete $new_conf->{digest
};
5590 # TODO handle properly?
5591 delete $new_conf->{snapshots
};
5592 delete $new_conf->{parent
};
5593 delete $new_conf->{pending
};
5595 # not handled by update_vm_api
5596 my $vmgenid = delete $new_conf->{vmgenid
};
5597 my $meta = delete $new_conf->{meta
};
5598 my $cloudinit = delete $new_conf->{cloudinit
}; # this is informational only
5599 $new_conf->{skip_cloud_init
} = 1; # re-use image from source side
5601 $new_conf->{vmid
} = $state->{vmid
};
5602 $new_conf->{node
} = $node;
5604 PVE
::QemuConfig-
>remove_lock($state->{vmid
}, 'create');
5607 $update_vm_api->($new_conf, 1);
5610 # revert to locked previous config
5611 my $conf = PVE
::QemuConfig-
>load_config($state->{vmid
});
5612 $conf->{lock} = 'create';
5613 PVE
::QemuConfig-
>write_config($state->{vmid
}, $conf);
5618 my $conf = PVE
::QemuConfig-
>load_config($state->{vmid
});
5619 $conf->{lock} = 'migrate';
5620 $conf->{vmgenid
} = $vmgenid if defined($vmgenid);
5621 $conf->{meta
} = $meta if defined($meta);
5622 $conf->{cloudinit
} = $cloudinit if defined($cloudinit);
5623 PVE
::QemuConfig-
>write_config($state->{vmid
}, $conf);
5625 $state->{lock} = 'migrate';
5631 return PVE
::StorageTunnel
::handle_bwlimit
($params);
5636 my $format = $params->{format
};
5637 my $storeid = $params->{storage
};
5638 my $drive = $params->{drive
};
5640 $check_storage_access_migrate->($rpcenv, $authuser, $state->{storecfg
}, $storeid, $node);
5643 default => $storeid,
5646 my $source_volumes = {
5657 my $res = PVE
::QemuServer
::vm_migrate_alloc_nbd_disks
($state->{storecfg
}, $state->{vmid
}, $source_volumes, $storagemap);
5658 if (defined($res->{disk
})) {
5659 $state->{cleanup
}->{volumes
}->{$res->{disk
}->{volid
}} = 1;
5660 return $res->{disk
};
5662 die "failed to allocate NBD disk..\n";
5665 'disk-import' => sub {
5668 $check_storage_access_migrate->(
5676 $params->{unix
} = "/run/qemu-server/$state->{vmid}.storage";
5678 return PVE
::StorageTunnel
::handle_disk_import
($state, $params);
5680 'query-disk-import' => sub {
5683 return PVE
::StorageTunnel
::handle_query_disk_import
($state, $params);
5688 my $info = PVE
::QemuServer
::vm_start_nolock
(
5692 $params->{start_params
},
5693 $params->{migrate_opts
},
5697 if ($info->{migrate
}->{proto
} ne 'unix') {
5698 PVE
::QemuServer
::vm_stop
(undef, $state->{vmid
}, 1, 1);
5699 die "migration over non-UNIX sockets not possible\n";
5702 my $socket = $info->{migrate
}->{addr
};
5703 chown $state->{socket_uid
}, -1, $socket;
5704 $state->{sockets
}->{$socket} = 1;
5706 my $unix_sockets = $info->{migrate
}->{unix_sockets
};
5707 foreach my $socket (@$unix_sockets) {
5708 chown $state->{socket_uid
}, -1, $socket;
5709 $state->{sockets
}->{$socket} = 1;
5714 if (PVE
::QemuServer
::qga_check_running
($state->{vmid
})) {
5715 eval { mon_cmd
($state->{vmid
}, "guest-fstrim") };
5716 warn "fstrim failed: $@\n" if $@;
5721 PVE
::QemuServer
::vm_stop
(undef, $state->{vmid
}, 1, 1);
5725 PVE
::QemuServer
::nbd_stop
($state->{vmid
});
5729 if (PVE
::QemuServer
::Helpers
::vm_running_locally
($state->{vmid
})) {
5730 PVE
::QemuServer
::vm_resume
($state->{vmid
}, 1, 1);
5732 die "VM $state->{vmid} not running\n";
5737 PVE
::QemuConfig-
>remove_lock($state->{vmid
}, $state->{lock});
5738 delete $state->{lock};
5744 my $path = $params->{path
};
5746 die "Not allowed to generate ticket for unknown socket '$path'\n"
5747 if !defined($state->{sockets
}->{$path});
5749 return { ticket
=> PVE
::AccessControl
::assemble_tunnel_ticket
($authuser, "/socket/$path") };
5754 if ($params->{cleanup
}) {
5755 if ($state->{cleanup
}->{fw
}) {
5756 PVE
::Firewall
::remove_vmfw_conf
($state->{vmid
});
5759 for my $volid (keys $state->{cleanup
}->{volumes
}->%*) {
5760 print "freeing volume '$volid' as part of cleanup\n";
5761 eval { PVE
::Storage
::vdisk_free
($state->{storecfg
}, $volid) };
5765 PVE
::QemuServer
::destroy_vm
($state->{storecfg
}, $state->{vmid
}, 1);
5768 print "switching to exit-mode, waiting for client to disconnect\n";
5775 my $socket_addr = "/run/qemu-server/$state->{vmid}.mtunnel";
5776 unlink $socket_addr;
5778 $state->{socket} = IO
::Socket
::UNIX-
>new(
5779 Type
=> SOCK_STREAM
(),
5780 Local
=> $socket_addr,
5784 $state->{socket_uid
} = getpwnam('www-data')
5785 or die "Failed to resolve user 'www-data' to numeric UID\n";
5786 chown $state->{socket_uid
}, -1, $socket_addr;
5789 print "mtunnel started\n";
5791 my $conn = eval { PVE
::Tools
::run_with_timeout
(300, sub { $state->{socket}->accept() }) };
5793 warn "Failed to accept tunnel connection - $@\n";
5795 warn "Removing tunnel socket..\n";
5796 unlink $state->{socket};
5798 warn "Removing temporary VM config..\n";
5800 PVE
::QemuServer
::destroy_vm
($state->{storecfg
}, $state->{vmid
}, 1);
5803 die "Exiting mtunnel\n";
5806 $state->{conn
} = $conn;
5808 my $reply_err = sub {
5811 my $reply = JSON
::encode_json
({
5812 success
=> JSON
::false
,
5815 $conn->print("$reply\n");
5819 my $reply_ok = sub {
5822 $res->{success
} = JSON
::true
;
5823 my $reply = JSON
::encode_json
($res);
5824 $conn->print("$reply\n");
5828 while (my $line = <$conn>) {
5831 # untaint, we validate below if needed
5832 ($line) = $line =~ /^(.*)$/;
5833 my $parsed = eval { JSON
::decode_json
($line) };
5835 $reply_err->("failed to parse command - $@");
5839 my $cmd = delete $parsed->{cmd
};
5840 if (!defined($cmd)) {
5841 $reply_err->("'cmd' missing");
5842 } elsif ($state->{exit}) {
5843 $reply_err->("tunnel is in exit-mode, processing '$cmd' cmd not possible");
5845 } elsif (my $handler = $cmd_handlers->{$cmd}) {
5846 print "received command '$cmd'\n";
5848 if ($cmd_desc->{$cmd}) {
5849 PVE
::JSONSchema
::validate
($parsed, $cmd_desc->{$cmd});
5853 my $res = $run_locked->($handler, $parsed);
5856 $reply_err->("failed to handle '$cmd' command - $@")
5859 $reply_err->("unknown command '$cmd' given");
5863 if ($state->{exit}) {
5864 print "mtunnel exited\n";
5866 die "mtunnel exited unexpectedly\n";
5870 my $socket_addr = "/run/qemu-server/$vmid.mtunnel";
5871 my $ticket = PVE
::AccessControl
::assemble_tunnel_ticket
($authuser, "/socket/$socket_addr");
5872 my $upid = $rpcenv->fork_worker('qmtunnel', $vmid, $authuser, $realcmd);
5877 socket => $socket_addr,
5881 __PACKAGE__-
>register_method({
5882 name
=> 'mtunnelwebsocket',
5883 path
=> '{vmid}/mtunnelwebsocket',
5886 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.",
5887 user
=> 'all', # check inside
5889 description
=> 'Migration tunnel endpoint for websocket upgrade - only for internal use by VM migration.',
5891 additionalProperties
=> 0,
5893 node
=> get_standard_option
('pve-node'),
5894 vmid
=> get_standard_option
('pve-vmid'),
5897 description
=> "unix socket to forward to",
5901 description
=> "ticket return by initial 'mtunnel' API call, or retrieved via 'ticket' tunnel command",
5908 port
=> { type
=> 'string', optional
=> 1 },
5909 socket => { type
=> 'string', optional
=> 1 },
5915 my $rpcenv = PVE
::RPCEnvironment
::get
();
5916 my $authuser = $rpcenv->get_user();
5918 my $nodename = PVE
::INotify
::nodename
();
5919 my $node = extract_param
($param, 'node');
5921 raise_param_exc
({ node
=> "node needs to be 'localhost' or local hostname '$nodename'" })
5922 if $node ne 'localhost' && $node ne $nodename;
5924 my $vmid = $param->{vmid
};
5926 PVE
::QemuConfig-
>load_config($vmid);
5928 my $socket = $param->{socket};
5929 PVE
::AccessControl
::verify_tunnel_ticket
($param->{ticket
}, $authuser, "/socket/$socket");
5931 return { socket => $socket };